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/AST/ASTConsumer.h" 11 #include "clang/AST/ParentMapContext.h" 12 #include "clang/Frontend/CompilerInstance.h" 13 #include "clang/Frontend/FrontendAction.h" 14 #include "clang/Tooling/Tooling.h" 15 #include "llvm/ADT/Optional.h" 16 #include "llvm/Support/FileSystem.h" 17 #include "llvm/Support/Path.h" 18 #include "llvm/Support/ToolOutputFile.h" 19 #include "gtest/gtest.h" 20 #include <cassert> 21 22 namespace clang { 23 namespace cross_tu { 24 25 namespace { 26 27 class CTUASTConsumer : public clang::ASTConsumer { 28 public: 29 explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success) 30 : CTU(CI), Success(Success) {} 31 32 void HandleTranslationUnit(ASTContext &Ctx) override { 33 auto FindFInTU = [](const TranslationUnitDecl *TU) { 34 const FunctionDecl *FD = nullptr; 35 for (const Decl *D : TU->decls()) { 36 FD = dyn_cast<FunctionDecl>(D); 37 if (FD && FD->getName() == "f") 38 break; 39 } 40 return FD; 41 }; 42 43 const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl(); 44 const FunctionDecl *FD = FindFInTU(TU); 45 assert(FD && FD->getName() == "f"); 46 bool OrigFDHasBody = FD->hasBody(); 47 48 const DynTypedNodeList ParentsBeforeImport = 49 Ctx.getParentMapContext().getParents<Decl>(*FD); 50 ASSERT_FALSE(ParentsBeforeImport.empty()); 51 52 // Prepare the index file and the AST file. 53 int ASTFD; 54 llvm::SmallString<256> ASTFileName; 55 ASSERT_FALSE( 56 llvm::sys::fs::createTemporaryFile("f_ast", "ast", ASTFD, ASTFileName)); 57 llvm::ToolOutputFile ASTFile(ASTFileName, ASTFD); 58 59 int IndexFD; 60 llvm::SmallString<256> IndexFileName; 61 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD, 62 IndexFileName)); 63 llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD); 64 IndexFile.os() << "9:c:@F@f#I# " << ASTFileName << "\n"; 65 IndexFile.os().flush(); 66 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName)); 67 68 StringRef SourceText = "int f(int) { return 0; }\n"; 69 // This file must exist since the saved ASTFile will reference it. 70 int SourceFD; 71 llvm::SmallString<256> SourceFileName; 72 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("input", "cpp", SourceFD, 73 SourceFileName)); 74 llvm::ToolOutputFile SourceFile(SourceFileName, SourceFD); 75 SourceFile.os() << SourceText; 76 SourceFile.os().flush(); 77 EXPECT_TRUE(llvm::sys::fs::exists(SourceFileName)); 78 79 std::unique_ptr<ASTUnit> ASTWithDefinition = 80 tooling::buildASTFromCode(SourceText, SourceFileName); 81 ASTWithDefinition->Save(ASTFileName.str()); 82 EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName)); 83 84 // Load the definition from the AST file. 85 llvm::Expected<const FunctionDecl *> NewFDorError = handleExpected( 86 CTU.getCrossTUDefinition(FD, "", IndexFileName, false), 87 []() { return nullptr; }, [](IndexError &) {}); 88 89 if (NewFDorError) { 90 const FunctionDecl *NewFD = *NewFDorError; 91 *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody; 92 93 if (NewFD) { 94 // Check parent map. 95 const DynTypedNodeList ParentsAfterImport = 96 Ctx.getParentMapContext().getParents<Decl>(*FD); 97 const DynTypedNodeList ParentsOfImported = 98 Ctx.getParentMapContext().getParents<Decl>(*NewFD); 99 EXPECT_TRUE( 100 checkParentListsEq(ParentsBeforeImport, ParentsAfterImport)); 101 EXPECT_FALSE(ParentsOfImported.empty()); 102 } 103 } 104 } 105 106 static bool checkParentListsEq(const DynTypedNodeList &L1, 107 const DynTypedNodeList &L2) { 108 if (L1.size() != L2.size()) 109 return false; 110 for (unsigned int I = 0; I < L1.size(); ++I) 111 if (L1[I] != L2[I]) 112 return false; 113 return true; 114 } 115 116 private: 117 CrossTranslationUnitContext CTU; 118 bool *Success; 119 }; 120 121 class CTUAction : public clang::ASTFrontendAction { 122 public: 123 CTUAction(bool *Success, unsigned OverrideLimit) 124 : Success(Success), OverrideLimit(OverrideLimit) {} 125 126 protected: 127 std::unique_ptr<clang::ASTConsumer> 128 CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override { 129 CI.getAnalyzerOpts()->CTUImportThreshold = OverrideLimit; 130 CI.getAnalyzerOpts()->CTUImportCppThreshold = OverrideLimit; 131 return std::make_unique<CTUASTConsumer>(CI, Success); 132 } 133 134 private: 135 bool *Success; 136 const unsigned OverrideLimit; 137 }; 138 139 } // end namespace 140 141 TEST(CrossTranslationUnit, CanLoadFunctionDefinition) { 142 bool Success = false; 143 EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 1u), 144 "int f(int);")); 145 EXPECT_TRUE(Success); 146 } 147 148 TEST(CrossTranslationUnit, RespectsLoadThreshold) { 149 bool Success = false; 150 EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 0u), 151 "int f(int);")); 152 EXPECT_FALSE(Success); 153 } 154 155 TEST(CrossTranslationUnit, IndexFormatCanBeParsed) { 156 llvm::StringMap<std::string> Index; 157 Index["a"] = "/b/f1"; 158 Index["c"] = "/d/f2"; 159 Index["e"] = "/f/f3"; 160 std::string IndexText = createCrossTUIndexString(Index); 161 162 int IndexFD; 163 llvm::SmallString<256> IndexFileName; 164 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD, 165 IndexFileName)); 166 llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD); 167 IndexFile.os() << IndexText; 168 IndexFile.os().flush(); 169 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName)); 170 llvm::Expected<llvm::StringMap<std::string>> IndexOrErr = 171 parseCrossTUIndex(IndexFileName); 172 EXPECT_TRUE((bool)IndexOrErr); 173 llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get(); 174 for (const auto &E : Index) { 175 EXPECT_TRUE(ParsedIndex.count(E.getKey())); 176 EXPECT_EQ(ParsedIndex[E.getKey()], E.getValue()); 177 } 178 for (const auto &E : ParsedIndex) 179 EXPECT_TRUE(Index.count(E.getKey())); 180 } 181 182 TEST(CrossTranslationUnit, EmptyInvocationListIsNotValid) { 183 auto Input = ""; 184 185 llvm::Expected<InvocationListTy> Result = parseInvocationList(Input); 186 EXPECT_FALSE(static_cast<bool>(Result)); 187 bool IsWrongFromatError = false; 188 llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) { 189 IsWrongFromatError = 190 Err.getCode() == index_error_code::invocation_list_wrong_format; 191 }); 192 EXPECT_TRUE(IsWrongFromatError); 193 } 194 195 TEST(CrossTranslationUnit, AmbiguousInvocationListIsDetected) { 196 // The same source file occurs twice (for two different architecture) in 197 // this test case. The disambiguation is the responsibility of the user. 198 auto Input = R"( 199 /tmp/main.cpp: 200 - clang++ 201 - -c 202 - -m32 203 - -o 204 - main32.o 205 - /tmp/main.cpp 206 /tmp/main.cpp: 207 - clang++ 208 - -c 209 - -m64 210 - -o 211 - main64.o 212 - /tmp/main.cpp 213 )"; 214 215 llvm::Expected<InvocationListTy> Result = parseInvocationList(Input); 216 EXPECT_FALSE(static_cast<bool>(Result)); 217 bool IsAmbiguousError = false; 218 llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) { 219 IsAmbiguousError = 220 Err.getCode() == index_error_code::invocation_list_ambiguous; 221 }); 222 EXPECT_TRUE(IsAmbiguousError); 223 } 224 225 TEST(CrossTranslationUnit, SingleInvocationCanBeParsed) { 226 auto Input = R"( 227 /tmp/main.cpp: 228 - clang++ 229 - /tmp/main.cpp 230 )"; 231 llvm::Expected<InvocationListTy> Result = parseInvocationList(Input); 232 EXPECT_TRUE(static_cast<bool>(Result)); 233 234 EXPECT_EQ(Result->size(), 1u); 235 236 auto It = Result->find("/tmp/main.cpp"); 237 EXPECT_TRUE(It != Result->end()); 238 EXPECT_EQ(It->getValue()[0], "clang++"); 239 EXPECT_EQ(It->getValue()[1], "/tmp/main.cpp"); 240 } 241 242 TEST(CrossTranslationUnit, MultipleInvocationsCanBeParsed) { 243 auto Input = R"( 244 /tmp/main.cpp: 245 - clang++ 246 - /tmp/other.o 247 - /tmp/main.cpp 248 /tmp/other.cpp: 249 - g++ 250 - -c 251 - -o 252 - /tmp/other.o 253 - /tmp/other.cpp 254 )"; 255 llvm::Expected<InvocationListTy> Result = parseInvocationList(Input); 256 EXPECT_TRUE(static_cast<bool>(Result)); 257 258 EXPECT_EQ(Result->size(), 2u); 259 260 auto It = Result->find("/tmp/main.cpp"); 261 EXPECT_TRUE(It != Result->end()); 262 EXPECT_EQ(It->getKey(), "/tmp/main.cpp"); 263 EXPECT_EQ(It->getValue()[0], "clang++"); 264 EXPECT_EQ(It->getValue()[1], "/tmp/other.o"); 265 EXPECT_EQ(It->getValue()[2], "/tmp/main.cpp"); 266 267 It = Result->find("/tmp/other.cpp"); 268 EXPECT_TRUE(It != Result->end()); 269 EXPECT_EQ(It->getValue()[0], "g++"); 270 EXPECT_EQ(It->getValue()[1], "-c"); 271 EXPECT_EQ(It->getValue()[2], "-o"); 272 EXPECT_EQ(It->getValue()[3], "/tmp/other.o"); 273 EXPECT_EQ(It->getValue()[4], "/tmp/other.cpp"); 274 } 275 276 } // end namespace cross_tu 277 } // end namespace clang 278