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
36namespace clang::include_cleaner {
37namespace {
38using testing::AllOf;
39using testing::Contains;
40using testing::ElementsAre;
41using testing::Pair;
42using testing::UnorderedElementsAre;
43
44std::string guard(llvm::StringRef Code) {
45 return "#pragma once\n" + Code.str();
46}
47
48class WalkUsedTest : public testing::Test {
49protected:
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
90TEST_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 HeaderFile = 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
135TEST_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 HeaderFile1 = Header(*AST.fileManager().getOptionalFileRef(Filename: "header1.h"));
156 auto HeaderFile2 = 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
164TEST_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
201class AnalyzeTest : public testing::Test {
202protected:
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
225TEST_F(AnalyzeTest, Basic) {
226 Inputs.Code = R"cpp(
227#include "a.h"
228#include "b.h"
229#include "keep.h" // IWYU pragma: keep
230
231int 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
252TEST_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
264TEST_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
274TEST_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
299TEST(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()),
327R"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()),
338R"cpp(#include "d.h"
339#include "a.h")cpp");
340}
341
342MATCHER_P3(expandedAt, FileID, Offset, SM, "") {
343 auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg);
344 return ExpanedFileID == FileID && ExpandedOffset == Offset;
345}
346MATCHER_P3(spelledAt, FileID, Offset, SM, "") {
347 auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg);
348 return SpelledFileID == FileID && SpelledOffset == Offset;
349}
350TEST(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 Header;
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
456struct Tag {
457 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) {
458 return OS << "Anon Tag";
459 }
460};
461TEST(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.
475TEST_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
509TEST_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

source code of clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp