[clang][modules] Don't prevent translation of FW_Private includes when explicitly...
[llvm-project.git] / clang-tools-extra / include-cleaner / unittests / AnalysisTest.cpp
blob6558b68087684644515bb6f62566c419f08d774e
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 //===----------------------------------------------------------------------===//
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>
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;
44 std::string guard(llvm::StringRef Code) {
45 return "#pragma once\n" + Code.str();
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;
62 PragmaIncludes *Out;
64 return std::make_unique<Hook>(&PI);
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);
78 std::multimap<size_t, std::vector<Header>> OffsetToProviders;
79 walkUsed(TopLevelDecls, MacroRefs, &PI, AST.preprocessor(),
80 [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
81 auto [FID, Offset] = SM.getDecomposedLoc(Ref.RefLocation);
82 if (FID != SM.getMainFileID())
83 ADD_FAILURE() << "Reference outside of the main file!";
84 OffsetToProviders.emplace(Offset, Providers.vec());
85 });
86 return OffsetToProviders;
90 TEST_F(WalkUsedTest, Basic) {
91 llvm::Annotations Code(R"cpp(
92 #include "header.h"
93 #include "private.h"
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);
102 )cpp");
103 Inputs.Code = Code.code();
104 Inputs.ExtraFiles["header.h"] = guard(R"cpp(
105 void foo();
106 namespace std { class vector {}; int&& move(int&&); }
107 )cpp");
108 Inputs.ExtraFiles["private.h"] = guard(R"cpp(
109 // IWYU pragma: private, include "path/public.h"
110 class Private {};
111 )cpp");
113 TestAST AST(Inputs);
114 auto &SM = AST.sourceManager();
115 auto HeaderFile = Header(*AST.fileManager().getOptionalFileRef("header.h"));
116 auto PrivateFile = Header(*AST.fileManager().getOptionalFileRef("private.h"));
117 auto PublicFile = Header("\"path/public.h\"");
118 auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
119 auto VectorSTL = Header(*tooling::stdlib::Header::named("<vector>"));
120 auto UtilitySTL = Header(*tooling::stdlib::Header::named("<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))));
135 TEST_F(WalkUsedTest, MultipleProviders) {
136 llvm::Annotations Code(R"cpp(
137 #include "header1.h"
138 #include "header2.h"
139 void foo();
141 void bar() {
142 $foo^foo();
144 )cpp");
145 Inputs.Code = Code.code();
146 Inputs.ExtraFiles["header1.h"] = guard(R"cpp(
147 void foo();
148 )cpp");
149 Inputs.ExtraFiles["header2.h"] = guard(R"cpp(
150 void foo();
151 )cpp");
153 TestAST AST(Inputs);
154 auto &SM = AST.sourceManager();
155 auto HeaderFile1 = Header(*AST.fileManager().getOptionalFileRef("header1.h"));
156 auto HeaderFile2 = Header(*AST.fileManager().getOptionalFileRef("header2.h"));
157 auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
158 EXPECT_THAT(
159 offsetToProviders(AST),
160 Contains(Pair(Code.point("foo"),
161 UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile))));
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("#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("hdr.h");
177 auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
179 auto HdrID = SM.translateFile(HdrFile);
181 Symbol Answer1 = Macro{PP.getIdentifierInfo("ANSWER"),
182 SM.getComposedLoc(HdrID, Hdr.point())};
183 Symbol Answer2 = Macro{PP.getIdentifierInfo("ANSWER"),
184 SM.getComposedLoc(HdrID, 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))));
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(PP.record(CI.getPreprocessor()));
213 PI.record(CI);
214 return true;
217 RecordedPP &PP;
218 PragmaIncludes &PI;
220 return std::make_unique<Hook>(PP, PI);
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
231 int x = a + c;
232 )cpp";
233 Inputs.ExtraFiles["a.h"] = guard("int a;");
234 Inputs.ExtraFiles["b.h"] = guard(R"cpp(
235 #include "c.h"
236 int b;
237 )cpp");
238 Inputs.ExtraFiles["c.h"] = guard("int c;");
239 Inputs.ExtraFiles["keep.h"] = guard("");
240 TestAST AST(Inputs);
241 auto Decls = AST.context().getTranslationUnitDecl()->decls();
242 auto Results =
243 analyze(std::vector<Decl *>{Decls.begin(), Decls.end()},
244 PP.MacroReferences, PP.Includes, &PI, AST.preprocessor());
246 const Include *B = PP.Includes.atLine(3);
247 ASSERT_EQ(B->Spelled, "b.h");
248 EXPECT_THAT(Results.Missing, ElementsAre("\"c.h\""));
249 EXPECT_THAT(Results.Unused, ElementsAre(B));
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("// 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({}, {}, PP.Includes, &PI, AST.preprocessor());
261 EXPECT_THAT(Results.Unused, testing::IsEmpty());
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({}, {}, PP.Includes, &PI, AST.preprocessor());
271 EXPECT_THAT(Results.Unused, testing::IsEmpty());
274 TEST_F(AnalyzeTest, ResourceDirIsIgnored) {
275 Inputs.ExtraArgs.push_back("-resource-dir");
276 Inputs.ExtraArgs.push_back("resources");
277 Inputs.ExtraArgs.push_back("-internal-isystem");
278 Inputs.ExtraArgs.push_back("resources/include");
279 Inputs.Code = R"cpp(
280 #include <amintrin.h>
281 #include <imintrin.h>
282 void baz() {
283 bar();
285 )cpp";
286 Inputs.ExtraFiles["resources/include/amintrin.h"] = guard("");
287 Inputs.ExtraFiles["resources/include/emintrin.h"] = guard(R"cpp(
288 void bar();
289 )cpp");
290 Inputs.ExtraFiles["resources/include/imintrin.h"] = guard(R"cpp(
291 #include <emintrin.h>
292 )cpp");
293 TestAST AST(Inputs);
294 auto Results = analyze({}, {}, PP.Includes, &PI, AST.preprocessor());
295 EXPECT_THAT(Results.Unused, testing::IsEmpty());
296 EXPECT_THAT(Results.Missing, testing::IsEmpty());
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";
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);
319 AnalysisResults Results;
320 Results.Missing.push_back("\"aa.h\"");
321 Results.Missing.push_back("\"ab.h\"");
322 Results.Missing.push_back("<e.h>");
323 Results.Unused.push_back(Inc.atLine(3));
324 Results.Unused.push_back(Inc.atLine(4));
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");
334 Results = {};
335 Results.Missing.push_back("\"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");
342 MATCHER_P3(expandedAt, FileID, Offset, SM, "") {
343 auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg);
344 return ExpanedFileID == FileID && ExpandedOffset == Offset;
346 MATCHER_P3(spelledAt, FileID, Offset, SM, "") {
347 auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg);
348 return SpelledFileID == FileID && SpelledOffset == Offset;
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 Header;
357 llvm::StringRef Main;
358 } TestCases[] = {
359 // Tests for decl references.
361 /*Header=*/"int target();",
362 R"cpp(
363 #define CALL_FUNC $spell^target()
365 int b = $expand^CALL_FUNC;
366 )cpp",
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 "int a = CALL_FUNC;"},
375 /*Header=*/R"cpp(
376 int target();
377 #define PLUS_ONE(X) X() + 1
378 )cpp",
379 R"cpp(
380 int a = $expand^PLUS_ONE($spell^target);
381 )cpp",
384 /*Header=*/R"cpp(
385 int target();
386 #define PLUS_ONE(X) X() + 1
387 )cpp",
388 R"cpp(
389 int a = $expand^PLUS_ONE($spell^target);
390 )cpp",
392 // Tests for macro references
393 {/*Header=*/"#define target 1",
394 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 R"cpp(
404 int a = USE_target;
405 )cpp"},
408 for (const auto &T : TestCases) {
409 llvm::Annotations Main(T.Main);
410 TestInputs Inputs(Main.code());
411 Inputs.ExtraFiles["header.h"] = guard(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(Out.record(PP));
420 return true;
423 return std::make_unique<RecordAction>(Recorded);
425 Inputs.ExtraArgs.push_back("-include");
426 Inputs.ExtraArgs.push_back("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();
433 SourceLocation RefLoc;
434 walkUsed(TopLevelDecls, Recorded.MacroReferences,
435 /*PragmaIncludes=*/nullptr, AST.preprocessor(),
436 [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) {
437 if (!Ref.RefLocation.isMacroID())
438 return;
439 if (llvm::to_string(Ref.Target) == "target") {
440 ASSERT_TRUE(RefLoc.isInvalid())
441 << "Expected only one 'target' ref loc per testcase";
442 RefLoc = Ref.RefLocation;
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());
456 struct Tag {
457 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) {
458 return OS << "Anon Tag";
461 TEST(Hints, Ordering) {
462 auto Hinted = [](Hints Hints) {
463 return clang::include_cleaner::Hinted<Tag>({}, Hints);
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));
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("template<typename> struct Foo;");
488 Inputs.ExtraFiles["def.h"] = guard("template<typename> struct Foo {};");
489 Inputs.ExtraFiles["partial.h"] =
490 guard("template<typename T> struct Foo<T*> {};");
491 TestAST AST(Inputs);
492 auto &SM = AST.sourceManager();
493 auto Fwd = *SM.getFileManager().getOptionalFileRef("fwd.h");
494 auto Def = *SM.getFileManager().getOptionalFileRef("def.h");
495 auto Partial = *SM.getFileManager().getOptionalFileRef("partial.h");
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)))));
509 TEST_F(WalkUsedTest, IgnoresIdentityMacros) {
510 llvm::Annotations Code(R"cpp(
511 #include "header.h"
512 void $bar^bar() {
513 $stdin^stdin();
515 )cpp");
516 Inputs.Code = Code.code();
517 Inputs.ExtraFiles["header.h"] = guard(R"cpp(
518 #include "inner.h"
519 void stdin();
520 )cpp");
521 Inputs.ExtraFiles["inner.h"] = guard(R"cpp(
522 #define stdin stdin
523 )cpp");
525 TestAST AST(Inputs);
526 auto &SM = AST.sourceManager();
527 auto MainFile = Header(*SM.getFileEntryRefForID(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))));
533 } // namespace
534 } // namespace clang::include_cleaner