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