1 | //===--- AnalysisTest.cpp -------------------------------------------------===// |
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-include-cleaner/Analysis.h" |
10 | #include "AnalysisInternal.h" |
11 | #include "TypesInternal.h" |
12 | #include "clang-include-cleaner/Record.h" |
13 | #include "clang-include-cleaner/Types.h" |
14 | #include "clang/AST/ASTContext.h" |
15 | #include "clang/AST/DeclBase.h" |
16 | #include "clang/Basic/FileManager.h" |
17 | #include "clang/Basic/IdentifierTable.h" |
18 | #include "clang/Basic/SourceLocation.h" |
19 | #include "clang/Basic/SourceManager.h" |
20 | #include "clang/Format/Format.h" |
21 | #include "clang/Frontend/FrontendActions.h" |
22 | #include "clang/Testing/TestAST.h" |
23 | #include "clang/Tooling/Inclusions/StandardLibrary.h" |
24 | #include "llvm/ADT/ArrayRef.h" |
25 | #include "llvm/ADT/IntrusiveRefCntPtr.h" |
26 | #include "llvm/ADT/SmallVector.h" |
27 | #include "llvm/ADT/StringRef.h" |
28 | #include "llvm/Support/Error.h" |
29 | #include "llvm/Support/MemoryBuffer.h" |
30 | #include "llvm/Support/ScopedPrinter.h" |
31 | #include "llvm/Support/VirtualFileSystem.h" |
32 | #include "llvm/Testing/Annotations/Annotations.h" |
33 | #include "gmock/gmock.h" |
34 | #include "gtest/gtest.h" |
35 | #include <cstddef> |
36 | #include <map> |
37 | #include <memory> |
38 | #include <string> |
39 | #include <vector> |
40 | |
41 | namespace clang::include_cleaner { |
42 | namespace { |
43 | using testing::_; |
44 | using testing::AllOf; |
45 | using testing::Contains; |
46 | using testing::ElementsAre; |
47 | using testing::Pair; |
48 | using testing::UnorderedElementsAre; |
49 | |
50 | std::string guard(llvm::StringRef Code) { |
51 | return "#pragma once\n" + Code.str(); |
52 | } |
53 | |
54 | class WalkUsedTest : public testing::Test { |
55 | protected: |
56 | TestInputs Inputs; |
57 | PragmaIncludes PI; |
58 | WalkUsedTest() { |
59 | Inputs.MakeAction = [this] { |
60 | struct Hook : public SyntaxOnlyAction { |
61 | public: |
62 | Hook(PragmaIncludes *Out) : Out(Out) {} |
63 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
64 | Out->record(CI); |
65 | return true; |
66 | } |
67 | |
68 | PragmaIncludes *Out; |
69 | }; |
70 | return std::make_unique<Hook>(args: &PI); |
71 | }; |
72 | } |
73 | |
74 | std::multimap<size_t, std::vector<Header>> |
75 | offsetToProviders(TestAST &AST, |
76 | llvm::ArrayRef<SymbolReference> MacroRefs = {}) { |
77 | const auto &SM = AST.sourceManager(); |
78 | llvm::SmallVector<Decl *> TopLevelDecls; |
79 | for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) { |
80 | if (!SM.isWrittenInMainFile(SM.getExpansionLoc(D->getLocation()))) |
81 | continue; |
82 | TopLevelDecls.emplace_back(D); |
83 | } |
84 | std::multimap<size_t, std::vector<Header>> OffsetToProviders; |
85 | walkUsed(ASTRoots: TopLevelDecls, MacroRefs, PI: &PI, PP: AST.preprocessor(), |
86 | CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) { |
87 | auto [FID, Offset] = SM.getDecomposedLoc(Loc: Ref.RefLocation); |
88 | if (FID != SM.getMainFileID()) |
89 | ADD_FAILURE() << "Reference outside of the main file!" ; |
90 | OffsetToProviders.emplace(args&: Offset, args: Providers.vec()); |
91 | }); |
92 | return OffsetToProviders; |
93 | } |
94 | }; |
95 | |
96 | TEST_F(WalkUsedTest, Basic) { |
97 | llvm::Annotations Code(R"cpp( |
98 | #include "header.h" |
99 | #include "private.h" |
100 | |
101 | // No reference reported for the Parameter "p". |
102 | void $bar^bar($private^Private p) { |
103 | $foo^foo(); |
104 | std::$vector^vector $vconstructor^$v^v; |
105 | $builtin^__builtin_popcount(1); |
106 | std::$move^move(3); |
107 | } |
108 | )cpp" ); |
109 | Inputs.Code = Code.code(); |
110 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
111 | void foo(); |
112 | namespace std { class vector {}; int&& move(int&&); } |
113 | )cpp" ); |
114 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
115 | // IWYU pragma: private, include "path/public.h" |
116 | class Private {}; |
117 | )cpp" ); |
118 | |
119 | TestAST AST(Inputs); |
120 | auto &SM = AST.sourceManager(); |
121 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header.h" )); |
122 | auto PrivateFile = Header(*AST.fileManager().getOptionalFileRef(Filename: "private.h" )); |
123 | auto PublicFile = Header("\"path/public.h\"" ); |
124 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
125 | auto VectorSTL = Header(*tooling::stdlib::Header::named(Name: "<vector>" )); |
126 | auto UtilitySTL = Header(*tooling::stdlib::Header::named(Name: "<utility>" )); |
127 | EXPECT_THAT( |
128 | offsetToProviders(AST), |
129 | UnorderedElementsAre( |
130 | Pair(Code.point("bar" ), UnorderedElementsAre(MainFile)), |
131 | Pair(Code.point("private" ), |
132 | UnorderedElementsAre(PublicFile, PrivateFile)), |
133 | Pair(Code.point("foo" ), UnorderedElementsAre(HeaderFile)), |
134 | Pair(Code.point("vector" ), UnorderedElementsAre(VectorSTL)), |
135 | Pair(Code.point("vconstructor" ), UnorderedElementsAre(VectorSTL)), |
136 | Pair(Code.point("v" ), UnorderedElementsAre(MainFile)), |
137 | Pair(Code.point("builtin" ), testing::IsEmpty()), |
138 | Pair(Code.point("move" ), UnorderedElementsAre(UtilitySTL)))); |
139 | } |
140 | |
141 | TEST_F(WalkUsedTest, MultipleProviders) { |
142 | llvm::Annotations Code(R"cpp( |
143 | #include "header1.h" |
144 | #include "header2.h" |
145 | void foo(); |
146 | |
147 | void bar() { |
148 | $foo^foo(); |
149 | } |
150 | )cpp" ); |
151 | Inputs.Code = Code.code(); |
152 | Inputs.ExtraFiles["header1.h" ] = guard(Code: R"cpp( |
153 | void foo(); |
154 | )cpp" ); |
155 | Inputs.ExtraFiles["header2.h" ] = guard(Code: R"cpp( |
156 | void foo(); |
157 | )cpp" ); |
158 | |
159 | TestAST AST(Inputs); |
160 | auto &SM = AST.sourceManager(); |
161 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header1.h" )); |
162 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header2.h" )); |
163 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
164 | EXPECT_THAT( |
165 | offsetToProviders(AST), |
166 | Contains(Pair(Code.point("foo" ), |
167 | UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile)))); |
168 | } |
169 | |
170 | TEST_F(WalkUsedTest, MacroRefs) { |
171 | llvm::Annotations Code(R"cpp( |
172 | #include "hdr.h" |
173 | int $3^x = $1^ANSWER; |
174 | int $4^y = $2^ANSWER; |
175 | )cpp" ); |
176 | llvm::Annotations Hdr(guard(Code: "#define ^ANSWER 42" )); |
177 | Inputs.Code = Code.code(); |
178 | Inputs.ExtraFiles["hdr.h" ] = Hdr.code(); |
179 | TestAST AST(Inputs); |
180 | auto &SM = AST.sourceManager(); |
181 | auto &PP = AST.preprocessor(); |
182 | auto HdrFile = *SM.getFileManager().getOptionalFileRef(Filename: "hdr.h" ); |
183 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
184 | |
185 | auto HdrID = SM.translateFile(SourceFile: HdrFile); |
186 | |
187 | Symbol Answer1 = Macro{.Name: PP.getIdentifierInfo(Name: "ANSWER" ), |
188 | .Definition: SM.getComposedLoc(FID: HdrID, Offset: Hdr.point())}; |
189 | Symbol Answer2 = Macro{.Name: PP.getIdentifierInfo(Name: "ANSWER" ), |
190 | .Definition: SM.getComposedLoc(FID: HdrID, Offset: Hdr.point())}; |
191 | EXPECT_THAT( |
192 | offsetToProviders( |
193 | AST, |
194 | {SymbolReference{ |
195 | Answer1, SM.getComposedLoc(SM.getMainFileID(), Code.point("1" )), |
196 | RefType::Explicit}, |
197 | SymbolReference{ |
198 | Answer2, SM.getComposedLoc(SM.getMainFileID(), Code.point("2" )), |
199 | RefType::Explicit}}), |
200 | UnorderedElementsAre( |
201 | Pair(Code.point("1" ), UnorderedElementsAre(HdrFile)), |
202 | Pair(Code.point("2" ), UnorderedElementsAre(HdrFile)), |
203 | Pair(Code.point("3" ), UnorderedElementsAre(MainFile)), |
204 | Pair(Code.point("4" ), UnorderedElementsAre(MainFile)))); |
205 | } |
206 | |
207 | class AnalyzeTest : public testing::Test { |
208 | protected: |
209 | TestInputs Inputs; |
210 | PragmaIncludes PI; |
211 | RecordedPP PP; |
212 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> = nullptr; |
213 | |
214 | AnalyzeTest() { |
215 | Inputs.MakeAction = [this] { |
216 | struct Hook : public SyntaxOnlyAction { |
217 | public: |
218 | Hook(RecordedPP &PP, PragmaIncludes &PI, |
219 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ) |
220 | : PP(PP), PI(PI), ExtraFS(std::move(ExtraFS)) {} |
221 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
222 | CI.getPreprocessor().addPPCallbacks(C: PP.record(PP: CI.getPreprocessor())); |
223 | PI.record(CI); |
224 | return true; |
225 | } |
226 | |
227 | bool BeginInvocation(CompilerInstance &CI) override { |
228 | if (!ExtraFS) |
229 | return true; |
230 | auto OverlayFS = |
231 | llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>( |
232 | A: CI.getFileManager().getVirtualFileSystemPtr()); |
233 | OverlayFS->pushOverlay(FS: ExtraFS); |
234 | CI.getFileManager().setVirtualFileSystem(std::move(OverlayFS)); |
235 | return true; |
236 | } |
237 | |
238 | RecordedPP &PP; |
239 | PragmaIncludes &PI; |
240 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ; |
241 | }; |
242 | return std::make_unique<Hook>(args&: PP, args&: PI, args&: ExtraFS); |
243 | }; |
244 | } |
245 | }; |
246 | |
247 | TEST_F(AnalyzeTest, Basic) { |
248 | Inputs.Code = R"cpp( |
249 | #include "a.h" |
250 | #include "b.h" |
251 | #include "keep.h" // IWYU pragma: keep |
252 | |
253 | int x = a + c; |
254 | )cpp" ; |
255 | Inputs.ExtraFiles["a.h" ] = guard(Code: "int a;" ); |
256 | Inputs.ExtraFiles["b.h" ] = guard(Code: R"cpp( |
257 | #include "c.h" |
258 | int b; |
259 | )cpp" ); |
260 | Inputs.ExtraFiles["c.h" ] = guard(Code: "int c;" ); |
261 | Inputs.ExtraFiles["keep.h" ] = guard(Code: "" ); |
262 | TestAST AST(Inputs); |
263 | auto Decls = AST.context().getTranslationUnitDecl()->decls(); |
264 | auto Results = |
265 | analyze(ASTRoots: std::vector<Decl *>{Decls.begin(), Decls.end()}, |
266 | MacroRefs: PP.MacroReferences, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
267 | auto = llvm::cantFail( |
268 | ValOrErr: AST.context().getSourceManager().getFileManager().getFileRef(Filename: "c.h" )); |
269 | |
270 | const Include *B = PP.Includes.atLine(OneBasedIndex: 3); |
271 | ASSERT_EQ(B->Spelled, "b.h" ); |
272 | EXPECT_THAT(Results.Missing, ElementsAre(Pair("\"c.h\"" , Header(CHeader)))); |
273 | EXPECT_THAT(Results.Unused, ElementsAre(B)); |
274 | } |
275 | |
276 | TEST_F(AnalyzeTest, PrivateUsedInPublic) { |
277 | // Check that umbrella header uses private include. |
278 | Inputs.Code = R"cpp(#include "private.h")cpp" ; |
279 | Inputs.ExtraFiles["private.h" ] = |
280 | guard(Code: "// IWYU pragma: private, include \"public.h\"" ); |
281 | Inputs.FileName = "public.h" ; |
282 | TestAST AST(Inputs); |
283 | EXPECT_FALSE(PP.Includes.all().empty()); |
284 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
285 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
286 | } |
287 | |
288 | TEST_F(AnalyzeTest, NoCrashWhenUnresolved) { |
289 | // Check that umbrella header uses private include. |
290 | Inputs.Code = R"cpp(#include "not_found.h")cpp" ; |
291 | Inputs.ErrorOK = true; |
292 | TestAST AST(Inputs); |
293 | EXPECT_FALSE(PP.Includes.all().empty()); |
294 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
295 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
296 | } |
297 | |
298 | TEST_F(AnalyzeTest, ResourceDirIsIgnored) { |
299 | Inputs.ExtraArgs.push_back(x: "-resource-dir" ); |
300 | Inputs.ExtraArgs.push_back(x: "resources" ); |
301 | Inputs.ExtraArgs.push_back(x: "-internal-isystem" ); |
302 | Inputs.ExtraArgs.push_back(x: "resources/include" ); |
303 | Inputs.Code = R"cpp( |
304 | #include <amintrin.h> |
305 | #include <imintrin.h> |
306 | void baz() { |
307 | bar(); |
308 | } |
309 | )cpp" ; |
310 | Inputs.ExtraFiles["resources/include/amintrin.h" ] = guard(Code: "" ); |
311 | Inputs.ExtraFiles["resources/include/emintrin.h" ] = guard(Code: R"cpp( |
312 | void bar(); |
313 | )cpp" ); |
314 | Inputs.ExtraFiles["resources/include/imintrin.h" ] = guard(Code: R"cpp( |
315 | #include <emintrin.h> |
316 | )cpp" ); |
317 | TestAST AST(Inputs); |
318 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
319 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
320 | EXPECT_THAT(Results.Missing, testing::IsEmpty()); |
321 | } |
322 | |
323 | TEST_F(AnalyzeTest, DifferentHeaderSameSpelling) { |
324 | Inputs.ExtraArgs.push_back(x: "-Ifoo" ); |
325 | Inputs.ExtraArgs.push_back(x: "-Ifoo_inner" ); |
326 | // `foo` is declared in foo_inner/foo.h, but there's no way to spell it |
327 | // directly. Make sure we don't generate unusued/missing include findings in |
328 | // such cases. |
329 | Inputs.Code = R"cpp( |
330 | #include <foo.h> |
331 | void baz() { |
332 | foo(); |
333 | } |
334 | )cpp" ; |
335 | Inputs.ExtraFiles["foo/foo.h" ] = guard(Code: "#include_next <foo.h>" ); |
336 | Inputs.ExtraFiles["foo_inner/foo.h" ] = guard(Code: R"cpp( |
337 | void foo(); |
338 | )cpp" ); |
339 | TestAST AST(Inputs); |
340 | std::vector<Decl *> DeclsInTU; |
341 | for (auto *D : AST.context().getTranslationUnitDecl()->decls()) |
342 | DeclsInTU.push_back(D); |
343 | auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
344 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
345 | EXPECT_THAT(Results.Missing, testing::IsEmpty()); |
346 | } |
347 | |
348 | TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) { |
349 | llvm::Annotations Code(R"cpp( |
350 | #include "header.h" |
351 | void $bar^bar() { |
352 | $foo^foo(); |
353 | } |
354 | )cpp" ); |
355 | Inputs.Code = Code.code(); |
356 | ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
357 | ExtraFS->addFile(Path: "content_for/0" , /*ModificationTime=*/{}, |
358 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
359 | #include "inner.h" |
360 | )cpp" ))); |
361 | ExtraFS->addSymbolicLink(NewLink: "header.h" , Target: "content_for/0" , |
362 | /*ModificationTime=*/{}); |
363 | ExtraFS->addFile(Path: "content_for/1" , /*ModificationTime=*/{}, |
364 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
365 | void foo(); |
366 | )cpp" ))); |
367 | ExtraFS->addSymbolicLink(NewLink: "inner.h" , Target: "content_for/1" , |
368 | /*ModificationTime=*/{}); |
369 | |
370 | TestAST AST(Inputs); |
371 | std::vector<Decl *> DeclsInTU; |
372 | for (auto *D : AST.context().getTranslationUnitDecl()->decls()) |
373 | DeclsInTU.push_back(D); |
374 | auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
375 | // Check that we're spelling header using the symlink, and not underlying |
376 | // path. |
377 | EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"" , _))); |
378 | // header.h should be unused. |
379 | EXPECT_THAT(Results.Unused, Not(testing::IsEmpty())); |
380 | |
381 | { |
382 | // Make sure filtering is also applied to symlink, not underlying file. |
383 | auto = [](llvm::StringRef Path) { return Path == "inner.h" ; }; |
384 | Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor(), |
385 | HeaderFilter); |
386 | EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"" , _))); |
387 | // header.h should be unused. |
388 | EXPECT_THAT(Results.Unused, Not(testing::IsEmpty())); |
389 | } |
390 | { |
391 | auto = [](llvm::StringRef Path) { return Path == "header.h" ; }; |
392 | Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor(), |
393 | HeaderFilter); |
394 | // header.h should be ignored now. |
395 | EXPECT_THAT(Results.Unused, Not(testing::IsEmpty())); |
396 | EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"" , _))); |
397 | } |
398 | } |
399 | |
400 | // Make sure that the references to implicit operator new/delete are reported as |
401 | // ambigious. |
402 | TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotMissing) { |
403 | ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
404 | ExtraFS->addFile(Path: "header.h" , |
405 | /*ModificationTime=*/{}, |
406 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
407 | void* operator new(decltype(sizeof(int))); |
408 | )cpp" ))); |
409 | ExtraFS->addFile(Path: "wrapper.h" , |
410 | /*ModificationTime=*/{}, |
411 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
412 | #include "header.h" |
413 | )cpp" ))); |
414 | |
415 | Inputs.Code = R"cpp( |
416 | #include "wrapper.h" |
417 | void bar() { |
418 | operator new(3); |
419 | })cpp" ; |
420 | TestAST AST(Inputs); |
421 | std::vector<Decl *> DeclsInTU; |
422 | for (auto *D : AST.context().getTranslationUnitDecl()->decls()) |
423 | DeclsInTU.push_back(D); |
424 | auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
425 | EXPECT_THAT(Results.Missing, testing::IsEmpty()); |
426 | } |
427 | |
428 | TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotUnused) { |
429 | ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
430 | ExtraFS->addFile(Path: "header.h" , |
431 | /*ModificationTime=*/{}, |
432 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
433 | void* operator new(decltype(sizeof(int))); |
434 | )cpp" ))); |
435 | |
436 | Inputs.Code = R"cpp( |
437 | #include "header.h" |
438 | void bar() { |
439 | operator new(3); |
440 | })cpp" ; |
441 | TestAST AST(Inputs); |
442 | std::vector<Decl *> DeclsInTU; |
443 | for (auto *D : AST.context().getTranslationUnitDecl()->decls()) |
444 | DeclsInTU.push_back(D); |
445 | auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
446 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
447 | } |
448 | |
449 | TEST(FixIncludes, Basic) { |
450 | llvm::StringRef Code = R"cpp(#include "d.h" |
451 | #include "a.h" |
452 | #include "b.h" |
453 | #include <c.h> |
454 | )cpp" ; |
455 | |
456 | Includes Inc; |
457 | Include I; |
458 | I.Spelled = "a.h" ; |
459 | I.Line = 2; |
460 | Inc.add(I); |
461 | I.Spelled = "b.h" ; |
462 | I.Line = 3; |
463 | Inc.add(I); |
464 | I.Spelled = "c.h" ; |
465 | I.Line = 4; |
466 | I.Angled = true; |
467 | Inc.add(I); |
468 | |
469 | AnalysisResults Results; |
470 | Results.Missing.emplace_back(args: "\"aa.h\"" , args: Header("" )); |
471 | Results.Missing.emplace_back(args: "\"ab.h\"" , args: Header("" )); |
472 | Results.Missing.emplace_back(args: "<e.h>" , args: Header("" )); |
473 | Results.Unused.push_back(x: Inc.atLine(OneBasedIndex: 3)); |
474 | Results.Unused.push_back(x: Inc.atLine(OneBasedIndex: 4)); |
475 | |
476 | EXPECT_EQ(fixIncludes(Results, "d.cc" , Code, format::getLLVMStyle()), |
477 | R"cpp(#include "d.h" |
478 | #include "a.h" |
479 | #include "aa.h" |
480 | #include "ab.h" |
481 | #include <e.h> |
482 | )cpp" ); |
483 | |
484 | Results = {}; |
485 | Results.Missing.emplace_back(args: "\"d.h\"" , args: Header("" )); |
486 | Code = R"cpp(#include "a.h")cpp" ; |
487 | EXPECT_EQ(fixIncludes(Results, "d.cc" , Code, format::getLLVMStyle()), |
488 | R"cpp(#include "d.h" |
489 | #include "a.h")cpp" ); |
490 | } |
491 | |
492 | MATCHER_P3(expandedAt, FileID, Offset, SM, "" ) { |
493 | auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg); |
494 | return ExpanedFileID == FileID && ExpandedOffset == Offset; |
495 | } |
496 | MATCHER_P3(spelledAt, FileID, Offset, SM, "" ) { |
497 | auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg); |
498 | return SpelledFileID == FileID && SpelledOffset == Offset; |
499 | } |
500 | TEST(WalkUsed, FilterRefsNotSpelledInMainFile) { |
501 | // Each test is expected to have a single expected ref of `target` symbol |
502 | // (or have none). |
503 | // The location in the reported ref is a macro location. $expand points to |
504 | // the macro location, and $spell points to the spelled location. |
505 | struct { |
506 | llvm::StringRef ; |
507 | llvm::StringRef Main; |
508 | } TestCases[] = { |
509 | // Tests for decl references. |
510 | { |
511 | /*Header=*/"int target();" , |
512 | .Main: R"cpp( |
513 | #define CALL_FUNC $spell^target() |
514 | |
515 | int b = $expand^CALL_FUNC; |
516 | )cpp" , |
517 | }, |
518 | {/*Header=*/R"cpp( |
519 | int target(); |
520 | #define CALL_FUNC target() |
521 | )cpp" , |
522 | // No ref of `target` being reported, as it is not spelled in main file. |
523 | .Main: "int a = CALL_FUNC;" }, |
524 | { |
525 | /*Header=*/R"cpp( |
526 | int target(); |
527 | #define PLUS_ONE(X) X() + 1 |
528 | )cpp" , |
529 | .Main: R"cpp( |
530 | int a = $expand^PLUS_ONE($spell^target); |
531 | )cpp" , |
532 | }, |
533 | { |
534 | /*Header=*/R"cpp( |
535 | int target(); |
536 | #define PLUS_ONE(X) X() + 1 |
537 | )cpp" , |
538 | .Main: R"cpp( |
539 | int a = $expand^PLUS_ONE($spell^target); |
540 | )cpp" , |
541 | }, |
542 | // Tests for macro references |
543 | {/*Header=*/"#define target 1" , |
544 | .Main: R"cpp( |
545 | #define USE_target $spell^target |
546 | int b = $expand^USE_target; |
547 | )cpp" }, |
548 | {/*Header=*/R"cpp( |
549 | #define target 1 |
550 | #define USE_target target |
551 | )cpp" , |
552 | // No ref of `target` being reported, it is not spelled in main file. |
553 | .Main: R"cpp( |
554 | int a = USE_target; |
555 | )cpp" }, |
556 | }; |
557 | |
558 | for (const auto &T : TestCases) { |
559 | llvm::Annotations Main(T.Main); |
560 | TestInputs Inputs(Main.code()); |
561 | Inputs.ExtraFiles["header.h" ] = guard(Code: T.Header); |
562 | RecordedPP Recorded; |
563 | Inputs.MakeAction = [&]() { |
564 | struct RecordAction : public SyntaxOnlyAction { |
565 | RecordedPP &Out; |
566 | RecordAction(RecordedPP &Out) : Out(Out) {} |
567 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
568 | auto &PP = CI.getPreprocessor(); |
569 | PP.addPPCallbacks(C: Out.record(PP)); |
570 | return true; |
571 | } |
572 | }; |
573 | return std::make_unique<RecordAction>(args&: Recorded); |
574 | }; |
575 | Inputs.ExtraArgs.push_back(x: "-include" ); |
576 | Inputs.ExtraArgs.push_back(x: "header.h" ); |
577 | TestAST AST(Inputs); |
578 | llvm::SmallVector<Decl *> TopLevelDecls; |
579 | for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) |
580 | TopLevelDecls.emplace_back(D); |
581 | auto &SM = AST.sourceManager(); |
582 | |
583 | SourceLocation RefLoc; |
584 | walkUsed(ASTRoots: TopLevelDecls, MacroRefs: Recorded.MacroReferences, |
585 | /*PragmaIncludes=*/PI: nullptr, PP: AST.preprocessor(), |
586 | CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) { |
587 | if (!Ref.RefLocation.isMacroID()) |
588 | return; |
589 | if (llvm::to_string(Value: Ref.Target) == "target" ) { |
590 | ASSERT_TRUE(RefLoc.isInvalid()) |
591 | << "Expected only one 'target' ref loc per testcase" ; |
592 | RefLoc = Ref.RefLocation; |
593 | } |
594 | }); |
595 | FileID MainFID = SM.getMainFileID(); |
596 | if (RefLoc.isValid()) { |
597 | EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand" ), &SM), |
598 | spelledAt(MainFID, Main.point("spell" ), &SM))) |
599 | << T.Main.str(); |
600 | } else { |
601 | EXPECT_THAT(Main.points(), testing::IsEmpty()); |
602 | } |
603 | } |
604 | } |
605 | |
606 | struct Tag { |
607 | friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) { |
608 | return OS << "Anon Tag" ; |
609 | } |
610 | }; |
611 | TEST(Hints, Ordering) { |
612 | auto Hinted = [](Hints Hints) { |
613 | return clang::include_cleaner::Hinted<Tag>({}, Hints); |
614 | }; |
615 | EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol)); |
616 | EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader)); |
617 | EXPECT_LT(Hinted(Hints::PreferredHeader), Hinted(Hints::PublicHeader)); |
618 | EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PreferredHeader), |
619 | Hinted(Hints::PublicHeader)); |
620 | } |
621 | |
622 | // Test ast traversal & redecl selection end-to-end for templates, as explicit |
623 | // instantiations/specializations are not redecls of the primary template. We |
624 | // need to make sure we're selecting the right ones. |
625 | TEST_F(WalkUsedTest, TemplateDecls) { |
626 | llvm::Annotations Code(R"cpp( |
627 | #include "fwd.h" |
628 | #include "def.h" |
629 | #include "partial.h" |
630 | template <> struct $exp_spec^Foo<char> {}; |
631 | template struct $exp^Foo<int>; |
632 | $full^Foo<int> x; |
633 | $implicit^Foo<bool> y; |
634 | $partial^Foo<int*> z; |
635 | )cpp" ); |
636 | Inputs.Code = Code.code(); |
637 | Inputs.ExtraFiles["fwd.h" ] = guard(Code: "template<typename> struct Foo;" ); |
638 | Inputs.ExtraFiles["def.h" ] = guard(Code: "template<typename> struct Foo {};" ); |
639 | Inputs.ExtraFiles["partial.h" ] = |
640 | guard(Code: "template<typename T> struct Foo<T*> {};" ); |
641 | TestAST AST(Inputs); |
642 | auto &SM = AST.sourceManager(); |
643 | auto Fwd = *SM.getFileManager().getOptionalFileRef(Filename: "fwd.h" ); |
644 | auto Def = *SM.getFileManager().getOptionalFileRef(Filename: "def.h" ); |
645 | auto Partial = *SM.getFileManager().getOptionalFileRef(Filename: "partial.h" ); |
646 | |
647 | EXPECT_THAT( |
648 | offsetToProviders(AST), |
649 | AllOf(Contains( |
650 | Pair(Code.point("exp_spec" ), UnorderedElementsAre(Fwd, Def))), |
651 | Contains(Pair(Code.point("exp" ), UnorderedElementsAre(Fwd, Def))), |
652 | Contains(Pair(Code.point("full" ), UnorderedElementsAre(Fwd, Def))), |
653 | Contains( |
654 | Pair(Code.point("implicit" ), UnorderedElementsAre(Fwd, Def))), |
655 | Contains( |
656 | Pair(Code.point("partial" ), UnorderedElementsAre(Partial))))); |
657 | } |
658 | |
659 | TEST_F(WalkUsedTest, IgnoresIdentityMacros) { |
660 | llvm::Annotations Code(R"cpp( |
661 | #include "header.h" |
662 | void $bar^bar() { |
663 | $stdin^stdin(); |
664 | } |
665 | )cpp" ); |
666 | Inputs.Code = Code.code(); |
667 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
668 | #include "inner.h" |
669 | void stdin(); |
670 | )cpp" ); |
671 | Inputs.ExtraFiles["inner.h" ] = guard(Code: R"cpp( |
672 | #define stdin stdin |
673 | )cpp" ); |
674 | |
675 | TestAST AST(Inputs); |
676 | auto &SM = AST.sourceManager(); |
677 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
678 | EXPECT_THAT(offsetToProviders(AST), |
679 | UnorderedElementsAre( |
680 | // FIXME: we should have a reference from stdin to header.h |
681 | Pair(Code.point("bar" ), UnorderedElementsAre(MainFile)))); |
682 | } |
683 | } // namespace |
684 | } // namespace clang::include_cleaner |
685 | |