1 //===- unittest/Tooling/CrossTranslationUnitTest.cpp - Tooling unit tests -===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/CrossTU/CrossTranslationUnit.h" 10 #include "clang/Frontend/CompilerInstance.h" 11 #include "clang/AST/ASTConsumer.h" 12 #include "clang/Frontend/FrontendAction.h" 13 #include "clang/Tooling/Tooling.h" 14 #include "llvm/Support/FileSystem.h" 15 #include "llvm/Support/Path.h" 16 #include "llvm/Support/ToolOutputFile.h" 17 #include "gtest/gtest.h" 18 #include <cassert> 19 20 namespace clang { 21 namespace cross_tu { 22 23 namespace { 24 25 class CTUASTConsumer : public clang::ASTConsumer { 26 public: 27 explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success) 28 : CTU(CI), Success(Success) {} 29 30 void HandleTranslationUnit(ASTContext &Ctx) { 31 auto FindFInTU = [](const TranslationUnitDecl *TU) { 32 const FunctionDecl *FD = nullptr; 33 for (const Decl *D : TU->decls()) { 34 FD = dyn_cast<FunctionDecl>(D); 35 if (FD && FD->getName() == "f") 36 break; 37 } 38 return FD; 39 }; 40 41 const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl(); 42 const FunctionDecl *FD = FindFInTU(TU); 43 assert(FD && FD->getName() == "f"); 44 bool OrigFDHasBody = FD->hasBody(); 45 46 // Prepare the index file and the AST file. 47 int ASTFD; 48 llvm::SmallString<256> ASTFileName; 49 ASSERT_FALSE( 50 llvm::sys::fs::createTemporaryFile("f_ast", "ast", ASTFD, ASTFileName)); 51 llvm::ToolOutputFile ASTFile(ASTFileName, ASTFD); 52 53 int IndexFD; 54 llvm::SmallString<256> IndexFileName; 55 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD, 56 IndexFileName)); 57 llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD); 58 IndexFile.os() << "c:@F@f#I# " << ASTFileName << "\n"; 59 IndexFile.os().flush(); 60 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName)); 61 62 StringRef SourceText = "int f(int) { return 0; }\n"; 63 // This file must exist since the saved ASTFile will reference it. 64 int SourceFD; 65 llvm::SmallString<256> SourceFileName; 66 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("input", "cpp", SourceFD, 67 SourceFileName)); 68 llvm::ToolOutputFile SourceFile(SourceFileName, SourceFD); 69 SourceFile.os() << SourceText; 70 SourceFile.os().flush(); 71 EXPECT_TRUE(llvm::sys::fs::exists(SourceFileName)); 72 73 std::unique_ptr<ASTUnit> ASTWithDefinition = 74 tooling::buildASTFromCode(SourceText, SourceFileName); 75 ASTWithDefinition->Save(ASTFileName.str()); 76 EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName)); 77 78 // Load the definition from the AST file. 79 llvm::Expected<const FunctionDecl *> NewFDorError = handleExpected( 80 CTU.getCrossTUDefinition(FD, "", IndexFileName, false), 81 []() { return nullptr; }, [](IndexError &) {}); 82 83 if (NewFDorError) { 84 const FunctionDecl *NewFD = *NewFDorError; 85 *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody; 86 87 if (NewFD) { 88 // Check GetImportedFromSourceLocation. 89 llvm::Optional<std::pair<SourceLocation, ASTUnit *>> SLocResult = 90 CTU.getImportedFromSourceLocation(NewFD->getLocation()); 91 EXPECT_TRUE(SLocResult); 92 if (SLocResult) { 93 SourceLocation OrigSLoc = (*SLocResult).first; 94 ASTUnit *OrigUnit = (*SLocResult).second; 95 // OrigUnit is created internally by CTU (is not the 96 // ASTWithDefinition). 97 TranslationUnitDecl *OrigTU = 98 OrigUnit->getASTContext().getTranslationUnitDecl(); 99 const FunctionDecl *FDWithDefinition = FindFInTU(OrigTU); 100 EXPECT_TRUE(FDWithDefinition); 101 if (FDWithDefinition) { 102 EXPECT_EQ(FDWithDefinition->getName(), "f"); 103 EXPECT_TRUE(FDWithDefinition->isThisDeclarationADefinition()); 104 EXPECT_EQ(OrigSLoc, FDWithDefinition->getLocation()); 105 } 106 } 107 } 108 } 109 } 110 111 private: 112 CrossTranslationUnitContext CTU; 113 bool *Success; 114 }; 115 116 class CTUAction : public clang::ASTFrontendAction { 117 public: 118 CTUAction(bool *Success, unsigned OverrideLimit) 119 : Success(Success), OverrideLimit(OverrideLimit) {} 120 121 protected: 122 std::unique_ptr<clang::ASTConsumer> 123 CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override { 124 CI.getAnalyzerOpts()->CTUImportThreshold = OverrideLimit; 125 return std::make_unique<CTUASTConsumer>(CI, Success); 126 } 127 128 private: 129 bool *Success; 130 const unsigned OverrideLimit; 131 }; 132 133 } // end namespace 134 135 TEST(CrossTranslationUnit, CanLoadFunctionDefinition) { 136 bool Success = false; 137 EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 1u), 138 "int f(int);")); 139 EXPECT_TRUE(Success); 140 } 141 142 TEST(CrossTranslationUnit, RespectsLoadThreshold) { 143 bool Success = false; 144 EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 0u), 145 "int f(int);")); 146 EXPECT_FALSE(Success); 147 } 148 149 TEST(CrossTranslationUnit, IndexFormatCanBeParsed) { 150 llvm::StringMap<std::string> Index; 151 Index["a"] = "/b/f1"; 152 Index["c"] = "/d/f2"; 153 Index["e"] = "/f/f3"; 154 std::string IndexText = createCrossTUIndexString(Index); 155 156 int IndexFD; 157 llvm::SmallString<256> IndexFileName; 158 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD, 159 IndexFileName)); 160 llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD); 161 IndexFile.os() << IndexText; 162 IndexFile.os().flush(); 163 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName)); 164 llvm::Expected<llvm::StringMap<std::string>> IndexOrErr = 165 parseCrossTUIndex(IndexFileName, ""); 166 EXPECT_TRUE((bool)IndexOrErr); 167 llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get(); 168 for (const auto &E : Index) { 169 EXPECT_TRUE(ParsedIndex.count(E.getKey())); 170 EXPECT_EQ(ParsedIndex[E.getKey()], E.getValue()); 171 } 172 for (const auto &E : ParsedIndex) 173 EXPECT_TRUE(Index.count(E.getKey())); 174 } 175 176 TEST(CrossTranslationUnit, CTUDirIsHandledCorrectly) { 177 llvm::StringMap<std::string> Index; 178 Index["a"] = "/b/c/d"; 179 std::string IndexText = createCrossTUIndexString(Index); 180 181 int IndexFD; 182 llvm::SmallString<256> IndexFileName; 183 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD, 184 IndexFileName)); 185 llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD); 186 IndexFile.os() << IndexText; 187 IndexFile.os().flush(); 188 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName)); 189 llvm::Expected<llvm::StringMap<std::string>> IndexOrErr = 190 parseCrossTUIndex(IndexFileName, "/ctudir"); 191 EXPECT_TRUE((bool)IndexOrErr); 192 llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get(); 193 EXPECT_EQ(ParsedIndex["a"], "/ctudir/b/c/d"); 194 } 195 196 } // end namespace cross_tu 197 } // end namespace clang 198