//===- unittest/Tooling/CrossTranslationUnitTest.cpp - Tooling unit tests -===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ParentMapContext.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/ToolOutputFile.h" #include "gtest/gtest.h" #include namespace clang { namespace cross_tu { namespace { class CTUASTConsumer : public clang::ASTConsumer { public: explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success) : CTU(CI), Success(Success) {} void HandleTranslationUnit(ASTContext &Ctx) override { auto FindFInTU = [](const TranslationUnitDecl *TU) { const FunctionDecl *FD = nullptr; for (const Decl *D : TU->decls()) { FD = dyn_cast(D); if (FD && FD->getName() == "f") break; } return FD; }; const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl(); const FunctionDecl *FD = FindFInTU(TU); assert(FD && FD->getName() == "f"); bool OrigFDHasBody = FD->hasBody(); const DynTypedNodeList ParentsBeforeImport = Ctx.getParentMapContext().getParents(*FD); ASSERT_FALSE(ParentsBeforeImport.empty()); // Prepare the index file and the AST file. int ASTFD; llvm::SmallString<256> ASTFileName; ASSERT_FALSE( llvm::sys::fs::createTemporaryFile("f_ast", "ast", ASTFD, ASTFileName)); llvm::ToolOutputFile ASTFile(ASTFileName, ASTFD); int IndexFD; llvm::SmallString<256> IndexFileName; ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD, IndexFileName)); llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD); IndexFile.os() << "c:@F@f#I# " << ASTFileName << "\n"; IndexFile.os().flush(); EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName)); StringRef SourceText = "int f(int) { return 0; }\n"; // This file must exist since the saved ASTFile will reference it. int SourceFD; llvm::SmallString<256> SourceFileName; ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("input", "cpp", SourceFD, SourceFileName)); llvm::ToolOutputFile SourceFile(SourceFileName, SourceFD); SourceFile.os() << SourceText; SourceFile.os().flush(); EXPECT_TRUE(llvm::sys::fs::exists(SourceFileName)); std::unique_ptr ASTWithDefinition = tooling::buildASTFromCode(SourceText, SourceFileName); ASTWithDefinition->Save(ASTFileName.str()); EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName)); // Load the definition from the AST file. llvm::Expected NewFDorError = handleExpected( CTU.getCrossTUDefinition(FD, "", IndexFileName, false), []() { return nullptr; }, [](IndexError &) {}); if (NewFDorError) { const FunctionDecl *NewFD = *NewFDorError; *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody; if (NewFD) { // Check GetImportedFromSourceLocation. llvm::Optional> SLocResult = CTU.getImportedFromSourceLocation(NewFD->getLocation()); EXPECT_TRUE(SLocResult); if (SLocResult) { SourceLocation OrigSLoc = (*SLocResult).first; ASTUnit *OrigUnit = (*SLocResult).second; // OrigUnit is created internally by CTU (is not the // ASTWithDefinition). TranslationUnitDecl *OrigTU = OrigUnit->getASTContext().getTranslationUnitDecl(); const FunctionDecl *FDWithDefinition = FindFInTU(OrigTU); EXPECT_TRUE(FDWithDefinition); if (FDWithDefinition) { EXPECT_EQ(FDWithDefinition->getName(), "f"); EXPECT_TRUE(FDWithDefinition->isThisDeclarationADefinition()); EXPECT_EQ(OrigSLoc, FDWithDefinition->getLocation()); } } // Check parent map. const DynTypedNodeList ParentsAfterImport = Ctx.getParentMapContext().getParents(*FD); const DynTypedNodeList ParentsOfImported = Ctx.getParentMapContext().getParents(*NewFD); EXPECT_TRUE( checkParentListsEq(ParentsBeforeImport, ParentsAfterImport)); EXPECT_FALSE(ParentsOfImported.empty()); } } } static bool checkParentListsEq(const DynTypedNodeList &L1, const DynTypedNodeList &L2) { if (L1.size() != L2.size()) return false; for (unsigned int I = 0; I < L1.size(); ++I) if (L1[I] != L2[I]) return false; return true; } private: CrossTranslationUnitContext CTU; bool *Success; }; class CTUAction : public clang::ASTFrontendAction { public: CTUAction(bool *Success, unsigned OverrideLimit) : Success(Success), OverrideLimit(OverrideLimit) {} protected: std::unique_ptr CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override { CI.getAnalyzerOpts()->CTUImportThreshold = OverrideLimit; CI.getAnalyzerOpts()->CTUImportCppThreshold = OverrideLimit; return std::make_unique(CI, Success); } private: bool *Success; const unsigned OverrideLimit; }; } // end namespace TEST(CrossTranslationUnit, CanLoadFunctionDefinition) { bool Success = false; EXPECT_TRUE(tooling::runToolOnCode(std::make_unique(&Success, 1u), "int f(int);")); EXPECT_TRUE(Success); } TEST(CrossTranslationUnit, RespectsLoadThreshold) { bool Success = false; EXPECT_TRUE(tooling::runToolOnCode(std::make_unique(&Success, 0u), "int f(int);")); EXPECT_FALSE(Success); } TEST(CrossTranslationUnit, IndexFormatCanBeParsed) { llvm::StringMap Index; Index["a"] = "/b/f1"; Index["c"] = "/d/f2"; Index["e"] = "/f/f3"; std::string IndexText = createCrossTUIndexString(Index); int IndexFD; llvm::SmallString<256> IndexFileName; ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD, IndexFileName)); llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD); IndexFile.os() << IndexText; IndexFile.os().flush(); EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName)); llvm::Expected> IndexOrErr = parseCrossTUIndex(IndexFileName); EXPECT_TRUE((bool)IndexOrErr); llvm::StringMap ParsedIndex = IndexOrErr.get(); for (const auto &E : Index) { EXPECT_TRUE(ParsedIndex.count(E.getKey())); EXPECT_EQ(ParsedIndex[E.getKey()], E.getValue()); } for (const auto &E : ParsedIndex) EXPECT_TRUE(Index.count(E.getKey())); } TEST(CrossTranslationUnit, EmptyInvocationListIsNotValid) { auto Input = ""; llvm::Expected Result = parseInvocationList(Input); EXPECT_FALSE(static_cast(Result)); bool IsWrongFromatError = false; llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) { IsWrongFromatError = Err.getCode() == index_error_code::invocation_list_wrong_format; }); EXPECT_TRUE(IsWrongFromatError); } TEST(CrossTranslationUnit, AmbiguousInvocationListIsDetected) { // The same source file occurs twice (for two different architecture) in // this test case. The disambiguation is the responsibility of the user. auto Input = R"( /tmp/main.cpp: - clang++ - -c - -m32 - -o - main32.o - /tmp/main.cpp /tmp/main.cpp: - clang++ - -c - -m64 - -o - main64.o - /tmp/main.cpp )"; llvm::Expected Result = parseInvocationList(Input); EXPECT_FALSE(static_cast(Result)); bool IsAmbiguousError = false; llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) { IsAmbiguousError = Err.getCode() == index_error_code::invocation_list_ambiguous; }); EXPECT_TRUE(IsAmbiguousError); } TEST(CrossTranslationUnit, SingleInvocationCanBeParsed) { auto Input = R"( /tmp/main.cpp: - clang++ - /tmp/main.cpp )"; llvm::Expected Result = parseInvocationList(Input); EXPECT_TRUE(static_cast(Result)); EXPECT_EQ(Result->size(), 1u); auto It = Result->find("/tmp/main.cpp"); EXPECT_TRUE(It != Result->end()); EXPECT_EQ(It->getValue()[0], "clang++"); EXPECT_EQ(It->getValue()[1], "/tmp/main.cpp"); } TEST(CrossTranslationUnit, MultipleInvocationsCanBeParsed) { auto Input = R"( /tmp/main.cpp: - clang++ - /tmp/other.o - /tmp/main.cpp /tmp/other.cpp: - g++ - -c - -o - /tmp/other.o - /tmp/other.cpp )"; llvm::Expected Result = parseInvocationList(Input); EXPECT_TRUE(static_cast(Result)); EXPECT_EQ(Result->size(), 2u); auto It = Result->find("/tmp/main.cpp"); EXPECT_TRUE(It != Result->end()); EXPECT_EQ(It->getKey(), "/tmp/main.cpp"); EXPECT_EQ(It->getValue()[0], "clang++"); EXPECT_EQ(It->getValue()[1], "/tmp/other.o"); EXPECT_EQ(It->getValue()[2], "/tmp/main.cpp"); It = Result->find("/tmp/other.cpp"); EXPECT_TRUE(It != Result->end()); EXPECT_EQ(It->getValue()[0], "g++"); EXPECT_EQ(It->getValue()[1], "-c"); EXPECT_EQ(It->getValue()[2], "-o"); EXPECT_EQ(It->getValue()[3], "/tmp/other.o"); EXPECT_EQ(It->getValue()[4], "/tmp/other.cpp"); } } // end namespace cross_tu } // end namespace clang