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:
CTUASTConsumer(clang::CompilerInstance & CI,bool * Success)29 explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success)
30 : CTU(CI), Success(Success) {}
31
HandleTranslationUnit(ASTContext & Ctx)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
checkParentListsEq(const DynTypedNodeList & L1,const DynTypedNodeList & L2)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:
CTUAction(bool * Success,unsigned OverrideLimit)123 CTUAction(bool *Success, unsigned OverrideLimit)
124 : Success(Success), OverrideLimit(OverrideLimit) {}
125
126 protected:
127 std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance & CI,StringRef)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
TEST(CrossTranslationUnit,CanLoadFunctionDefinition)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
TEST(CrossTranslationUnit,RespectsLoadThreshold)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
TEST(CrossTranslationUnit,IndexFormatCanBeParsed)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
TEST(CrossTranslationUnit,EmptyInvocationListIsNotValid)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
TEST(CrossTranslationUnit,AmbiguousInvocationListIsDetected)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
TEST(CrossTranslationUnit,SingleInvocationCanBeParsed)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
TEST(CrossTranslationUnit,MultipleInvocationsCanBeParsed)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