150
|
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"
|
207
|
10 #include "clang/AST/ASTConsumer.h"
|
|
11 #include "clang/AST/ParentMapContext.h"
|
150
|
12 #include "clang/Frontend/CompilerInstance.h"
|
|
13 #include "clang/Frontend/FrontendAction.h"
|
|
14 #include "clang/Tooling/Tooling.h"
|
207
|
15 #include "llvm/ADT/Optional.h"
|
150
|
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
|
207
|
32 void HandleTranslationUnit(ASTContext &Ctx) override {
|
150
|
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
|
207
|
48 const DynTypedNodeList ParentsBeforeImport =
|
|
49 Ctx.getParentMapContext().getParents<Decl>(*FD);
|
|
50 ASSERT_FALSE(ParentsBeforeImport.empty());
|
|
51
|
150
|
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() << "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) {
|
207
|
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());
|
150
|
102 }
|
|
103 }
|
|
104 }
|
|
105
|
207
|
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
|
150
|
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;
|
207
|
130 CI.getAnalyzerOpts()->CTUImportCppThreshold = OverrideLimit;
|
150
|
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 =
|
207
|
171 parseCrossTUIndex(IndexFileName);
|
150
|
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
|
207
|
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 }
|
150
|
224
|
207
|
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");
|
150
|
274 }
|
|
275
|
|
276 } // end namespace cross_tu
|
|
277 } // end namespace clang
|