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/Basic/FileManager.h" |
16 | #include "clang/Basic/IdentifierTable.h" |
17 | #include "clang/Basic/SourceLocation.h" |
18 | #include "clang/Basic/SourceManager.h" |
19 | #include "clang/Format/Format.h" |
20 | #include "clang/Frontend/FrontendActions.h" |
21 | #include "clang/Testing/TestAST.h" |
22 | #include "clang/Tooling/Inclusions/StandardLibrary.h" |
23 | #include "llvm/ADT/ArrayRef.h" |
24 | #include "llvm/ADT/SmallVector.h" |
25 | #include "llvm/ADT/StringRef.h" |
26 | #include "llvm/Support/ScopedPrinter.h" |
27 | #include "llvm/Testing/Annotations/Annotations.h" |
28 | #include "gmock/gmock.h" |
29 | #include "gtest/gtest.h" |
30 | #include <cstddef> |
31 | #include <map> |
32 | #include <memory> |
33 | #include <string> |
34 | #include <vector> |
35 | |
36 | namespace clang::include_cleaner { |
37 | namespace { |
38 | using testing::AllOf; |
39 | using testing::Contains; |
40 | using testing::ElementsAre; |
41 | using testing::Pair; |
42 | using testing::UnorderedElementsAre; |
43 | |
44 | std::string guard(llvm::StringRef Code) { |
45 | return "#pragma once\n" + Code.str(); |
46 | } |
47 | |
48 | class WalkUsedTest : public testing::Test { |
49 | protected: |
50 | TestInputs Inputs; |
51 | PragmaIncludes PI; |
52 | WalkUsedTest() { |
53 | Inputs.MakeAction = [this] { |
54 | struct Hook : public SyntaxOnlyAction { |
55 | public: |
56 | Hook(PragmaIncludes *Out) : Out(Out) {} |
57 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
58 | Out->record(CI); |
59 | return true; |
60 | } |
61 | |
62 | PragmaIncludes *Out; |
63 | }; |
64 | return std::make_unique<Hook>(args: &PI); |
65 | }; |
66 | } |
67 | |
68 | std::multimap<size_t, std::vector<Header>> |
69 | offsetToProviders(TestAST &AST, |
70 | llvm::ArrayRef<SymbolReference> MacroRefs = {}) { |
71 | const auto &SM = AST.sourceManager(); |
72 | llvm::SmallVector<Decl *> TopLevelDecls; |
73 | for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) { |
74 | if (!SM.isWrittenInMainFile(SM.getExpansionLoc(D->getLocation()))) |
75 | continue; |
76 | TopLevelDecls.emplace_back(D); |
77 | } |
78 | std::multimap<size_t, std::vector<Header>> OffsetToProviders; |
79 | walkUsed(ASTRoots: TopLevelDecls, MacroRefs, PI: &PI, PP: AST.preprocessor(), |
80 | CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) { |
81 | auto [FID, Offset] = SM.getDecomposedLoc(Loc: Ref.RefLocation); |
82 | if (FID != SM.getMainFileID()) |
83 | ADD_FAILURE() << "Reference outside of the main file!" ; |
84 | OffsetToProviders.emplace(args&: Offset, args: Providers.vec()); |
85 | }); |
86 | return OffsetToProviders; |
87 | } |
88 | }; |
89 | |
90 | TEST_F(WalkUsedTest, Basic) { |
91 | llvm::Annotations Code(R"cpp( |
92 | #include "header.h" |
93 | #include "private.h" |
94 | |
95 | // No reference reported for the Parameter "p". |
96 | void $bar^bar($private^Private p) { |
97 | $foo^foo(); |
98 | std::$vector^vector $vconstructor^$v^v; |
99 | $builtin^__builtin_popcount(1); |
100 | std::$move^move(3); |
101 | } |
102 | )cpp" ); |
103 | Inputs.Code = Code.code(); |
104 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
105 | void foo(); |
106 | namespace std { class vector {}; int&& move(int&&); } |
107 | )cpp" ); |
108 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
109 | // IWYU pragma: private, include "path/public.h" |
110 | class Private {}; |
111 | )cpp" ); |
112 | |
113 | TestAST AST(Inputs); |
114 | auto &SM = AST.sourceManager(); |
115 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header.h" )); |
116 | auto PrivateFile = Header(*AST.fileManager().getOptionalFileRef(Filename: "private.h" )); |
117 | auto PublicFile = Header("\"path/public.h\"" ); |
118 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
119 | auto VectorSTL = Header(*tooling::stdlib::Header::named(Name: "<vector>" )); |
120 | auto UtilitySTL = Header(*tooling::stdlib::Header::named(Name: "<utility>" )); |
121 | EXPECT_THAT( |
122 | offsetToProviders(AST), |
123 | UnorderedElementsAre( |
124 | Pair(Code.point("bar" ), UnorderedElementsAre(MainFile)), |
125 | Pair(Code.point("private" ), |
126 | UnorderedElementsAre(PublicFile, PrivateFile)), |
127 | Pair(Code.point("foo" ), UnorderedElementsAre(HeaderFile)), |
128 | Pair(Code.point("vector" ), UnorderedElementsAre(VectorSTL)), |
129 | Pair(Code.point("vconstructor" ), UnorderedElementsAre(VectorSTL)), |
130 | Pair(Code.point("v" ), UnorderedElementsAre(MainFile)), |
131 | Pair(Code.point("builtin" ), testing::IsEmpty()), |
132 | Pair(Code.point("move" ), UnorderedElementsAre(UtilitySTL)))); |
133 | } |
134 | |
135 | TEST_F(WalkUsedTest, MultipleProviders) { |
136 | llvm::Annotations Code(R"cpp( |
137 | #include "header1.h" |
138 | #include "header2.h" |
139 | void foo(); |
140 | |
141 | void bar() { |
142 | $foo^foo(); |
143 | } |
144 | )cpp" ); |
145 | Inputs.Code = Code.code(); |
146 | Inputs.ExtraFiles["header1.h" ] = guard(Code: R"cpp( |
147 | void foo(); |
148 | )cpp" ); |
149 | Inputs.ExtraFiles["header2.h" ] = guard(Code: R"cpp( |
150 | void foo(); |
151 | )cpp" ); |
152 | |
153 | TestAST AST(Inputs); |
154 | auto &SM = AST.sourceManager(); |
155 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header1.h" )); |
156 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header2.h" )); |
157 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
158 | EXPECT_THAT( |
159 | offsetToProviders(AST), |
160 | Contains(Pair(Code.point("foo" ), |
161 | UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile)))); |
162 | } |
163 | |
164 | TEST_F(WalkUsedTest, MacroRefs) { |
165 | llvm::Annotations Code(R"cpp( |
166 | #include "hdr.h" |
167 | int $3^x = $1^ANSWER; |
168 | int $4^y = $2^ANSWER; |
169 | )cpp" ); |
170 | llvm::Annotations Hdr(guard(Code: "#define ^ANSWER 42" )); |
171 | Inputs.Code = Code.code(); |
172 | Inputs.ExtraFiles["hdr.h" ] = Hdr.code(); |
173 | TestAST AST(Inputs); |
174 | auto &SM = AST.sourceManager(); |
175 | auto &PP = AST.preprocessor(); |
176 | auto HdrFile = *SM.getFileManager().getOptionalFileRef(Filename: "hdr.h" ); |
177 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
178 | |
179 | auto HdrID = SM.translateFile(SourceFile: HdrFile); |
180 | |
181 | Symbol Answer1 = Macro{.Name: PP.getIdentifierInfo(Name: "ANSWER" ), |
182 | .Definition: SM.getComposedLoc(FID: HdrID, Offset: Hdr.point())}; |
183 | Symbol Answer2 = Macro{.Name: PP.getIdentifierInfo(Name: "ANSWER" ), |
184 | .Definition: SM.getComposedLoc(FID: HdrID, Offset: Hdr.point())}; |
185 | EXPECT_THAT( |
186 | offsetToProviders( |
187 | AST, |
188 | {SymbolReference{ |
189 | Answer1, SM.getComposedLoc(SM.getMainFileID(), Code.point("1" )), |
190 | RefType::Explicit}, |
191 | SymbolReference{ |
192 | Answer2, SM.getComposedLoc(SM.getMainFileID(), Code.point("2" )), |
193 | RefType::Explicit}}), |
194 | UnorderedElementsAre( |
195 | Pair(Code.point("1" ), UnorderedElementsAre(HdrFile)), |
196 | Pair(Code.point("2" ), UnorderedElementsAre(HdrFile)), |
197 | Pair(Code.point("3" ), UnorderedElementsAre(MainFile)), |
198 | Pair(Code.point("4" ), UnorderedElementsAre(MainFile)))); |
199 | } |
200 | |
201 | class AnalyzeTest : public testing::Test { |
202 | protected: |
203 | TestInputs Inputs; |
204 | PragmaIncludes PI; |
205 | RecordedPP PP; |
206 | AnalyzeTest() { |
207 | Inputs.MakeAction = [this] { |
208 | struct Hook : public SyntaxOnlyAction { |
209 | public: |
210 | Hook(RecordedPP &PP, PragmaIncludes &PI) : PP(PP), PI(PI) {} |
211 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
212 | CI.getPreprocessor().addPPCallbacks(C: PP.record(PP: CI.getPreprocessor())); |
213 | PI.record(CI); |
214 | return true; |
215 | } |
216 | |
217 | RecordedPP &PP; |
218 | PragmaIncludes &PI; |
219 | }; |
220 | return std::make_unique<Hook>(args&: PP, args&: PI); |
221 | }; |
222 | } |
223 | }; |
224 | |
225 | TEST_F(AnalyzeTest, Basic) { |
226 | Inputs.Code = R"cpp( |
227 | #include "a.h" |
228 | #include "b.h" |
229 | #include "keep.h" // IWYU pragma: keep |
230 | |
231 | int x = a + c; |
232 | )cpp" ; |
233 | Inputs.ExtraFiles["a.h" ] = guard(Code: "int a;" ); |
234 | Inputs.ExtraFiles["b.h" ] = guard(Code: R"cpp( |
235 | #include "c.h" |
236 | int b; |
237 | )cpp" ); |
238 | Inputs.ExtraFiles["c.h" ] = guard(Code: "int c;" ); |
239 | Inputs.ExtraFiles["keep.h" ] = guard(Code: "" ); |
240 | TestAST AST(Inputs); |
241 | auto Decls = AST.context().getTranslationUnitDecl()->decls(); |
242 | auto Results = |
243 | analyze(ASTRoots: std::vector<Decl *>{Decls.begin(), Decls.end()}, |
244 | MacroRefs: PP.MacroReferences, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
245 | |
246 | const Include *B = PP.Includes.atLine(OneBasedIndex: 3); |
247 | ASSERT_EQ(B->Spelled, "b.h" ); |
248 | EXPECT_THAT(Results.Missing, ElementsAre("\"c.h\"" )); |
249 | EXPECT_THAT(Results.Unused, ElementsAre(B)); |
250 | } |
251 | |
252 | TEST_F(AnalyzeTest, PrivateUsedInPublic) { |
253 | // Check that umbrella header uses private include. |
254 | Inputs.Code = R"cpp(#include "private.h")cpp" ; |
255 | Inputs.ExtraFiles["private.h" ] = |
256 | guard(Code: "// IWYU pragma: private, include \"public.h\"" ); |
257 | Inputs.FileName = "public.h" ; |
258 | TestAST AST(Inputs); |
259 | EXPECT_FALSE(PP.Includes.all().empty()); |
260 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
261 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
262 | } |
263 | |
264 | TEST_F(AnalyzeTest, NoCrashWhenUnresolved) { |
265 | // Check that umbrella header uses private include. |
266 | Inputs.Code = R"cpp(#include "not_found.h")cpp" ; |
267 | Inputs.ErrorOK = true; |
268 | TestAST AST(Inputs); |
269 | EXPECT_FALSE(PP.Includes.all().empty()); |
270 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
271 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
272 | } |
273 | |
274 | TEST_F(AnalyzeTest, ResourceDirIsIgnored) { |
275 | Inputs.ExtraArgs.push_back(x: "-resource-dir" ); |
276 | Inputs.ExtraArgs.push_back(x: "resources" ); |
277 | Inputs.ExtraArgs.push_back(x: "-internal-isystem" ); |
278 | Inputs.ExtraArgs.push_back(x: "resources/include" ); |
279 | Inputs.Code = R"cpp( |
280 | #include <amintrin.h> |
281 | #include <imintrin.h> |
282 | void baz() { |
283 | bar(); |
284 | } |
285 | )cpp" ; |
286 | Inputs.ExtraFiles["resources/include/amintrin.h" ] = guard(Code: "" ); |
287 | Inputs.ExtraFiles["resources/include/emintrin.h" ] = guard(Code: R"cpp( |
288 | void bar(); |
289 | )cpp" ); |
290 | Inputs.ExtraFiles["resources/include/imintrin.h" ] = guard(Code: R"cpp( |
291 | #include <emintrin.h> |
292 | )cpp" ); |
293 | TestAST AST(Inputs); |
294 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
295 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
296 | EXPECT_THAT(Results.Missing, testing::IsEmpty()); |
297 | } |
298 | |
299 | TEST(FixIncludes, Basic) { |
300 | llvm::StringRef Code = R"cpp(#include "d.h" |
301 | #include "a.h" |
302 | #include "b.h" |
303 | #include <c.h> |
304 | )cpp" ; |
305 | |
306 | Includes Inc; |
307 | Include I; |
308 | I.Spelled = "a.h" ; |
309 | I.Line = 2; |
310 | Inc.add(I); |
311 | I.Spelled = "b.h" ; |
312 | I.Line = 3; |
313 | Inc.add(I); |
314 | I.Spelled = "c.h" ; |
315 | I.Line = 4; |
316 | I.Angled = true; |
317 | Inc.add(I); |
318 | |
319 | AnalysisResults Results; |
320 | Results.Missing.push_back(x: "\"aa.h\"" ); |
321 | Results.Missing.push_back(x: "\"ab.h\"" ); |
322 | Results.Missing.push_back(x: "<e.h>" ); |
323 | Results.Unused.push_back(x: Inc.atLine(OneBasedIndex: 3)); |
324 | Results.Unused.push_back(x: Inc.atLine(OneBasedIndex: 4)); |
325 | |
326 | EXPECT_EQ(fixIncludes(Results, "d.cc" , Code, format::getLLVMStyle()), |
327 | R"cpp(#include "d.h" |
328 | #include "a.h" |
329 | #include "aa.h" |
330 | #include "ab.h" |
331 | #include <e.h> |
332 | )cpp" ); |
333 | |
334 | Results = {}; |
335 | Results.Missing.push_back(x: "\"d.h\"" ); |
336 | Code = R"cpp(#include "a.h")cpp" ; |
337 | EXPECT_EQ(fixIncludes(Results, "d.cc" , Code, format::getLLVMStyle()), |
338 | R"cpp(#include "d.h" |
339 | #include "a.h")cpp" ); |
340 | } |
341 | |
342 | MATCHER_P3(expandedAt, FileID, Offset, SM, "" ) { |
343 | auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg); |
344 | return ExpanedFileID == FileID && ExpandedOffset == Offset; |
345 | } |
346 | MATCHER_P3(spelledAt, FileID, Offset, SM, "" ) { |
347 | auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg); |
348 | return SpelledFileID == FileID && SpelledOffset == Offset; |
349 | } |
350 | TEST(WalkUsed, FilterRefsNotSpelledInMainFile) { |
351 | // Each test is expected to have a single expected ref of `target` symbol |
352 | // (or have none). |
353 | // The location in the reported ref is a macro location. $expand points to |
354 | // the macro location, and $spell points to the spelled location. |
355 | struct { |
356 | llvm::StringRef ; |
357 | llvm::StringRef Main; |
358 | } TestCases[] = { |
359 | // Tests for decl references. |
360 | { |
361 | /*Header=*/"int target();" , |
362 | .Main: R"cpp( |
363 | #define CALL_FUNC $spell^target() |
364 | |
365 | int b = $expand^CALL_FUNC; |
366 | )cpp" , |
367 | }, |
368 | {/*Header=*/R"cpp( |
369 | int target(); |
370 | #define CALL_FUNC target() |
371 | )cpp" , |
372 | // No ref of `target` being reported, as it is not spelled in main file. |
373 | .Main: "int a = CALL_FUNC;" }, |
374 | { |
375 | /*Header=*/R"cpp( |
376 | int target(); |
377 | #define PLUS_ONE(X) X() + 1 |
378 | )cpp" , |
379 | .Main: R"cpp( |
380 | int a = $expand^PLUS_ONE($spell^target); |
381 | )cpp" , |
382 | }, |
383 | { |
384 | /*Header=*/R"cpp( |
385 | int target(); |
386 | #define PLUS_ONE(X) X() + 1 |
387 | )cpp" , |
388 | .Main: R"cpp( |
389 | int a = $expand^PLUS_ONE($spell^target); |
390 | )cpp" , |
391 | }, |
392 | // Tests for macro references |
393 | {/*Header=*/"#define target 1" , |
394 | .Main: R"cpp( |
395 | #define USE_target $spell^target |
396 | int b = $expand^USE_target; |
397 | )cpp" }, |
398 | {/*Header=*/R"cpp( |
399 | #define target 1 |
400 | #define USE_target target |
401 | )cpp" , |
402 | // No ref of `target` being reported, it is not spelled in main file. |
403 | .Main: R"cpp( |
404 | int a = USE_target; |
405 | )cpp" }, |
406 | }; |
407 | |
408 | for (const auto &T : TestCases) { |
409 | llvm::Annotations Main(T.Main); |
410 | TestInputs Inputs(Main.code()); |
411 | Inputs.ExtraFiles["header.h" ] = guard(Code: T.Header); |
412 | RecordedPP Recorded; |
413 | Inputs.MakeAction = [&]() { |
414 | struct RecordAction : public SyntaxOnlyAction { |
415 | RecordedPP &Out; |
416 | RecordAction(RecordedPP &Out) : Out(Out) {} |
417 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
418 | auto &PP = CI.getPreprocessor(); |
419 | PP.addPPCallbacks(C: Out.record(PP)); |
420 | return true; |
421 | } |
422 | }; |
423 | return std::make_unique<RecordAction>(args&: Recorded); |
424 | }; |
425 | Inputs.ExtraArgs.push_back(x: "-include" ); |
426 | Inputs.ExtraArgs.push_back(x: "header.h" ); |
427 | TestAST AST(Inputs); |
428 | llvm::SmallVector<Decl *> TopLevelDecls; |
429 | for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) |
430 | TopLevelDecls.emplace_back(D); |
431 | auto &SM = AST.sourceManager(); |
432 | |
433 | SourceLocation RefLoc; |
434 | walkUsed(ASTRoots: TopLevelDecls, MacroRefs: Recorded.MacroReferences, |
435 | /*PragmaIncludes=*/PI: nullptr, PP: AST.preprocessor(), |
436 | CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) { |
437 | if (!Ref.RefLocation.isMacroID()) |
438 | return; |
439 | if (llvm::to_string(Value: Ref.Target) == "target" ) { |
440 | ASSERT_TRUE(RefLoc.isInvalid()) |
441 | << "Expected only one 'target' ref loc per testcase" ; |
442 | RefLoc = Ref.RefLocation; |
443 | } |
444 | }); |
445 | FileID MainFID = SM.getMainFileID(); |
446 | if (RefLoc.isValid()) { |
447 | EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand" ), &SM), |
448 | spelledAt(MainFID, Main.point("spell" ), &SM))) |
449 | << T.Main.str(); |
450 | } else { |
451 | EXPECT_THAT(Main.points(), testing::IsEmpty()); |
452 | } |
453 | } |
454 | } |
455 | |
456 | struct Tag { |
457 | friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) { |
458 | return OS << "Anon Tag" ; |
459 | } |
460 | }; |
461 | TEST(Hints, Ordering) { |
462 | auto Hinted = [](Hints Hints) { |
463 | return clang::include_cleaner::Hinted<Tag>({}, Hints); |
464 | }; |
465 | EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol)); |
466 | EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader)); |
467 | EXPECT_LT(Hinted(Hints::PreferredHeader), Hinted(Hints::PublicHeader)); |
468 | EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PreferredHeader), |
469 | Hinted(Hints::PublicHeader)); |
470 | } |
471 | |
472 | // Test ast traversal & redecl selection end-to-end for templates, as explicit |
473 | // instantiations/specializations are not redecls of the primary template. We |
474 | // need to make sure we're selecting the right ones. |
475 | TEST_F(WalkUsedTest, TemplateDecls) { |
476 | llvm::Annotations Code(R"cpp( |
477 | #include "fwd.h" |
478 | #include "def.h" |
479 | #include "partial.h" |
480 | template <> struct $exp_spec^Foo<char> {}; |
481 | template struct $exp^Foo<int>; |
482 | $full^Foo<int> x; |
483 | $implicit^Foo<bool> y; |
484 | $partial^Foo<int*> z; |
485 | )cpp" ); |
486 | Inputs.Code = Code.code(); |
487 | Inputs.ExtraFiles["fwd.h" ] = guard(Code: "template<typename> struct Foo;" ); |
488 | Inputs.ExtraFiles["def.h" ] = guard(Code: "template<typename> struct Foo {};" ); |
489 | Inputs.ExtraFiles["partial.h" ] = |
490 | guard(Code: "template<typename T> struct Foo<T*> {};" ); |
491 | TestAST AST(Inputs); |
492 | auto &SM = AST.sourceManager(); |
493 | auto Fwd = *SM.getFileManager().getOptionalFileRef(Filename: "fwd.h" ); |
494 | auto Def = *SM.getFileManager().getOptionalFileRef(Filename: "def.h" ); |
495 | auto Partial = *SM.getFileManager().getOptionalFileRef(Filename: "partial.h" ); |
496 | |
497 | EXPECT_THAT( |
498 | offsetToProviders(AST), |
499 | AllOf(Contains( |
500 | Pair(Code.point("exp_spec" ), UnorderedElementsAre(Fwd, Def))), |
501 | Contains(Pair(Code.point("exp" ), UnorderedElementsAre(Fwd, Def))), |
502 | Contains(Pair(Code.point("full" ), UnorderedElementsAre(Fwd, Def))), |
503 | Contains( |
504 | Pair(Code.point("implicit" ), UnorderedElementsAre(Fwd, Def))), |
505 | Contains( |
506 | Pair(Code.point("partial" ), UnorderedElementsAre(Partial))))); |
507 | } |
508 | |
509 | TEST_F(WalkUsedTest, IgnoresIdentityMacros) { |
510 | llvm::Annotations Code(R"cpp( |
511 | #include "header.h" |
512 | void $bar^bar() { |
513 | $stdin^stdin(); |
514 | } |
515 | )cpp" ); |
516 | Inputs.Code = Code.code(); |
517 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
518 | #include "inner.h" |
519 | void stdin(); |
520 | )cpp" ); |
521 | Inputs.ExtraFiles["inner.h" ] = guard(Code: R"cpp( |
522 | #define stdin stdin |
523 | )cpp" ); |
524 | |
525 | TestAST AST(Inputs); |
526 | auto &SM = AST.sourceManager(); |
527 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
528 | EXPECT_THAT(offsetToProviders(AST), |
529 | UnorderedElementsAre( |
530 | // FIXME: we should have a reference from stdin to header.h |
531 | Pair(Code.point("bar" ), UnorderedElementsAre(MainFile)))); |
532 | } |
533 | } // namespace |
534 | } // namespace clang::include_cleaner |
535 | |