1 //===--- AnalysisTest.cpp -------------------------------------------------===//
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
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/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"
41 namespace clang::include_cleaner
{
45 using testing::Contains
;
46 using testing::ElementsAre
;
48 using testing::UnorderedElementsAre
;
50 std::string
guard(llvm::StringRef Code
) {
51 return "#pragma once\n" + Code
.str();
54 class WalkUsedTest
: public testing::Test
{
59 Inputs
.MakeAction
= [this] {
60 struct Hook
: public SyntaxOnlyAction
{
62 Hook(PragmaIncludes
*Out
) : Out(Out
) {}
63 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
70 return std::make_unique
<Hook
>(&PI
);
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())))
82 TopLevelDecls
.emplace_back(D
);
84 std::multimap
<size_t, std::vector
<Header
>> OffsetToProviders
;
85 walkUsed(TopLevelDecls
, MacroRefs
, &PI
, AST
.preprocessor(),
86 [&](const SymbolReference
&Ref
, llvm::ArrayRef
<Header
> Providers
) {
87 auto [FID
, Offset
] = SM
.getDecomposedLoc(Ref
.RefLocation
);
88 if (FID
!= SM
.getMainFileID())
89 ADD_FAILURE() << "Reference outside of the main file!";
90 OffsetToProviders
.emplace(Offset
, Providers
.vec());
92 return OffsetToProviders
;
96 TEST_F(WalkUsedTest
, Basic
) {
97 llvm::Annotations
Code(R
"cpp(
101 // No reference reported for the Parameter "p
".
102 void $bar^bar($private^Private p) {
104 std::$vector^vector $vconstructor^$v^v;
105 $builtin^__builtin_popcount(1);
109 Inputs
.Code
= Code
.code();
110 Inputs
.ExtraFiles
["header.h"] = guard(R
"cpp(
112 namespace std { class vector {}; int&& move(int&&); }
114 Inputs
.ExtraFiles
["private.h"] = guard(R
"cpp(
115 // IWYU pragma: private, include "path
/public.h
"
120 auto &SM
= AST
.sourceManager();
121 auto HeaderFile
= Header(*AST
.fileManager().getOptionalFileRef("header.h"));
122 auto PrivateFile
= Header(*AST
.fileManager().getOptionalFileRef("private.h"));
123 auto PublicFile
= Header("\"path/public.h\"");
124 auto MainFile
= Header(*SM
.getFileEntryRefForID(SM
.getMainFileID()));
125 auto VectorSTL
= Header(*tooling::stdlib::Header::named("<vector>"));
126 auto UtilitySTL
= Header(*tooling::stdlib::Header::named("<utility>"));
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
))));
141 TEST_F(WalkUsedTest
, MultipleProviders
) {
142 llvm::Annotations
Code(R
"cpp(
151 Inputs
.Code
= Code
.code();
152 Inputs
.ExtraFiles
["header1.h"] = guard(R
"cpp(
155 Inputs
.ExtraFiles
["header2.h"] = guard(R
"cpp(
160 auto &SM
= AST
.sourceManager();
161 auto HeaderFile1
= Header(*AST
.fileManager().getOptionalFileRef("header1.h"));
162 auto HeaderFile2
= Header(*AST
.fileManager().getOptionalFileRef("header2.h"));
163 auto MainFile
= Header(*SM
.getFileEntryRefForID(SM
.getMainFileID()));
165 offsetToProviders(AST
),
166 Contains(Pair(Code
.point("foo"),
167 UnorderedElementsAre(HeaderFile1
, HeaderFile2
, MainFile
))));
170 TEST_F(WalkUsedTest
, MacroRefs
) {
171 llvm::Annotations
Code(R
"cpp(
173 int $3^x = $1^ANSWER;
174 int $4^y = $2^ANSWER;
176 llvm::Annotations
Hdr(guard("#define ^ANSWER 42"));
177 Inputs
.Code
= Code
.code();
178 Inputs
.ExtraFiles
["hdr.h"] = Hdr
.code();
180 auto &SM
= AST
.sourceManager();
181 auto &PP
= AST
.preprocessor();
182 auto HdrFile
= *SM
.getFileManager().getOptionalFileRef("hdr.h");
183 auto MainFile
= Header(*SM
.getFileEntryRefForID(SM
.getMainFileID()));
185 auto HdrID
= SM
.translateFile(HdrFile
);
187 Symbol Answer1
= Macro
{PP
.getIdentifierInfo("ANSWER"),
188 SM
.getComposedLoc(HdrID
, Hdr
.point())};
189 Symbol Answer2
= Macro
{PP
.getIdentifierInfo("ANSWER"),
190 SM
.getComposedLoc(HdrID
, Hdr
.point())};
195 Answer1
, SM
.getComposedLoc(SM
.getMainFileID(), Code
.point("1")),
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
))));
207 class AnalyzeTest
: public testing::Test
{
212 llvm::IntrusiveRefCntPtr
<llvm::vfs::InMemoryFileSystem
> ExtraFS
= nullptr;
215 Inputs
.MakeAction
= [this] {
216 struct Hook
: public SyntaxOnlyAction
{
218 Hook(RecordedPP
&PP
, PragmaIncludes
&PI
,
219 llvm::IntrusiveRefCntPtr
<llvm::vfs::InMemoryFileSystem
> ExtraFS
)
220 : PP(PP
), PI(PI
), ExtraFS(std::move(ExtraFS
)) {}
221 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
222 CI
.getPreprocessor().addPPCallbacks(PP
.record(CI
.getPreprocessor()));
227 bool BeginInvocation(CompilerInstance
&CI
) override
{
231 llvm::makeIntrusiveRefCnt
<llvm::vfs::OverlayFileSystem
>(
232 CI
.getFileManager().getVirtualFileSystemPtr());
233 OverlayFS
->pushOverlay(ExtraFS
);
234 CI
.getFileManager().setVirtualFileSystem(std::move(OverlayFS
));
240 llvm::IntrusiveRefCntPtr
<llvm::vfs::InMemoryFileSystem
> ExtraFS
;
242 return std::make_unique
<Hook
>(PP
, PI
, ExtraFS
);
247 TEST_F(AnalyzeTest
, Basic
) {
251 #include "keep
.h
" // IWYU pragma: keep
255 Inputs
.ExtraFiles
["a.h"] = guard("int a;");
256 Inputs
.ExtraFiles
["b.h"] = guard(R
"cpp(
260 Inputs
.ExtraFiles
["c.h"] = guard("int c;");
261 Inputs
.ExtraFiles
["keep.h"] = guard("");
263 auto Decls
= AST
.context().getTranslationUnitDecl()->decls();
265 analyze(std::vector
<Decl
*>{Decls
.begin(), Decls
.end()},
266 PP
.MacroReferences
, PP
.Includes
, &PI
, AST
.preprocessor());
267 auto CHeader
= llvm::cantFail(
268 AST
.context().getSourceManager().getFileManager().getFileRef("c.h"));
270 const Include
*B
= PP
.Includes
.atLine(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
));
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("// IWYU pragma: private, include \"public.h\"");
281 Inputs
.FileName
= "public.h";
283 EXPECT_FALSE(PP
.Includes
.all().empty());
284 auto Results
= analyze({}, {}, PP
.Includes
, &PI
, AST
.preprocessor());
285 EXPECT_THAT(Results
.Unused
, testing::IsEmpty());
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;
293 EXPECT_FALSE(PP
.Includes
.all().empty());
294 auto Results
= analyze({}, {}, PP
.Includes
, &PI
, AST
.preprocessor());
295 EXPECT_THAT(Results
.Unused
, testing::IsEmpty());
298 TEST_F(AnalyzeTest
, ResourceDirIsIgnored
) {
299 Inputs
.ExtraArgs
.push_back("-resource-dir");
300 Inputs
.ExtraArgs
.push_back("resources");
301 Inputs
.ExtraArgs
.push_back("-internal-isystem");
302 Inputs
.ExtraArgs
.push_back("resources/include");
304 #include <amintrin.h>
305 #include <imintrin.h>
310 Inputs
.ExtraFiles
["resources/include/amintrin.h"] = guard("");
311 Inputs
.ExtraFiles
["resources/include/emintrin.h"] = guard(R
"cpp(
314 Inputs
.ExtraFiles
["resources/include/imintrin.h"] = guard(R
"cpp(
315 #include <emintrin.h>
318 auto Results
= analyze({}, {}, PP
.Includes
, &PI
, AST
.preprocessor());
319 EXPECT_THAT(Results
.Unused
, testing::IsEmpty());
320 EXPECT_THAT(Results
.Missing
, testing::IsEmpty());
323 TEST_F(AnalyzeTest
, DifferentHeaderSameSpelling
) {
324 Inputs
.ExtraArgs
.push_back("-Ifoo");
325 Inputs
.ExtraArgs
.push_back("-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
335 Inputs
.ExtraFiles
["foo/foo.h"] = guard("#include_next <foo.h>");
336 Inputs
.ExtraFiles
["foo_inner/foo.h"] = guard(R
"cpp(
340 std::vector
<Decl
*> DeclsInTU
;
341 for (auto *D
: AST
.context().getTranslationUnitDecl()->decls())
342 DeclsInTU
.push_back(D
);
343 auto Results
= analyze(DeclsInTU
, {}, PP
.Includes
, &PI
, AST
.preprocessor());
344 EXPECT_THAT(Results
.Unused
, testing::IsEmpty());
345 EXPECT_THAT(Results
.Missing
, testing::IsEmpty());
348 TEST_F(AnalyzeTest
, SpellingIncludesWithSymlinks
) {
349 llvm::Annotations
Code(R
"cpp(
355 Inputs
.Code
= Code
.code();
356 ExtraFS
= llvm::makeIntrusiveRefCnt
<llvm::vfs::InMemoryFileSystem
>();
357 ExtraFS
->addFile("content_for/0", /*ModificationTime=*/{},
358 llvm::MemoryBuffer::getMemBufferCopy(guard(R
"cpp(
361 ExtraFS
->addSymbolicLink("header.h", "content_for/0",
362 /*ModificationTime=*/{});
363 ExtraFS
->addFile("content_for/1", /*ModificationTime=*/{},
364 llvm::MemoryBuffer::getMemBufferCopy(guard(R
"cpp(
367 ExtraFS
->addSymbolicLink("inner.h", "content_for/1",
368 /*ModificationTime=*/{});
371 std::vector
<Decl
*> DeclsInTU
;
372 for (auto *D
: AST
.context().getTranslationUnitDecl()->decls())
373 DeclsInTU
.push_back(D
);
374 auto Results
= analyze(DeclsInTU
, {}, PP
.Includes
, &PI
, AST
.preprocessor());
375 // Check that we're spelling header using the symlink, and not underlying
377 EXPECT_THAT(Results
.Missing
, testing::ElementsAre(Pair("\"inner.h\"", _
)));
378 // header.h should be unused.
379 EXPECT_THAT(Results
.Unused
, Not(testing::IsEmpty()));
382 // Make sure filtering is also applied to symlink, not underlying file.
383 auto HeaderFilter
= [](llvm::StringRef Path
) { return Path
== "inner.h"; };
384 Results
= analyze(DeclsInTU
, {}, PP
.Includes
, &PI
, AST
.preprocessor(),
386 EXPECT_THAT(Results
.Missing
, testing::ElementsAre(Pair("\"inner.h\"", _
)));
387 // header.h should be unused.
388 EXPECT_THAT(Results
.Unused
, Not(testing::IsEmpty()));
391 auto HeaderFilter
= [](llvm::StringRef Path
) { return Path
== "header.h"; };
392 Results
= analyze(DeclsInTU
, {}, PP
.Includes
, &PI
, AST
.preprocessor(),
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\"", _
)));
400 TEST(FixIncludes
, Basic
) {
401 llvm::StringRef Code
= R
"cpp(#include "d
.h
"
420 AnalysisResults Results
;
421 Results
.Missing
.emplace_back("\"aa.h\"", Header(""));
422 Results
.Missing
.emplace_back("\"ab.h\"", Header(""));
423 Results
.Missing
.emplace_back("<e.h>", Header(""));
424 Results
.Unused
.push_back(Inc
.atLine(3));
425 Results
.Unused
.push_back(Inc
.atLine(4));
427 EXPECT_EQ(fixIncludes(Results
, "d.cc", Code
, format::getLLVMStyle()),
436 Results
.Missing
.emplace_back("\"d.h\"", Header(""));
437 Code
= R
"cpp(#include "a
.h
")cpp";
438 EXPECT_EQ(fixIncludes(Results
, "d.cc", Code
, format::getLLVMStyle()),
440 #include "a
.h
")cpp");
443 MATCHER_P3(expandedAt
, FileID
, Offset
, SM
, "") {
444 auto [ExpanedFileID
, ExpandedOffset
] = SM
->getDecomposedExpansionLoc(arg
);
445 return ExpanedFileID
== FileID
&& ExpandedOffset
== Offset
;
447 MATCHER_P3(spelledAt
, FileID
, Offset
, SM
, "") {
448 auto [SpelledFileID
, SpelledOffset
] = SM
->getDecomposedSpellingLoc(arg
);
449 return SpelledFileID
== FileID
&& SpelledOffset
== Offset
;
451 TEST(WalkUsed
, FilterRefsNotSpelledInMainFile
) {
452 // Each test is expected to have a single expected ref of `target` symbol
454 // The location in the reported ref is a macro location. $expand points to
455 // the macro location, and $spell points to the spelled location.
457 llvm::StringRef Header
;
458 llvm::StringRef Main
;
460 // Tests for decl references.
462 /*Header=*/"int target();",
464 #define CALL_FUNC $spell^target()
466 int b = $expand^CALL_FUNC;
471 #define CALL_FUNC target()
473 // No ref of `target` being reported, as it is not spelled in main file.
474 "int a = CALL_FUNC;"},
478 #define PLUS_ONE(X) X() + 1
481 int a = $expand^PLUS_ONE($spell^target);
487 #define PLUS_ONE(X) X() + 1
490 int a = $expand^PLUS_ONE($spell^target);
493 // Tests for macro references
494 {/*Header=*/"#define target 1",
496 #define USE_target $spell^target
497 int b = $expand^USE_target;
501 #define USE_target target
503 // No ref of `target` being reported, it is not spelled in main file.
509 for (const auto &T
: TestCases
) {
510 llvm::Annotations
Main(T
.Main
);
511 TestInputs
Inputs(Main
.code());
512 Inputs
.ExtraFiles
["header.h"] = guard(T
.Header
);
514 Inputs
.MakeAction
= [&]() {
515 struct RecordAction
: public SyntaxOnlyAction
{
517 RecordAction(RecordedPP
&Out
) : Out(Out
) {}
518 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
519 auto &PP
= CI
.getPreprocessor();
520 PP
.addPPCallbacks(Out
.record(PP
));
524 return std::make_unique
<RecordAction
>(Recorded
);
526 Inputs
.ExtraArgs
.push_back("-include");
527 Inputs
.ExtraArgs
.push_back("header.h");
529 llvm::SmallVector
<Decl
*> TopLevelDecls
;
530 for (Decl
*D
: AST
.context().getTranslationUnitDecl()->decls())
531 TopLevelDecls
.emplace_back(D
);
532 auto &SM
= AST
.sourceManager();
534 SourceLocation RefLoc
;
535 walkUsed(TopLevelDecls
, Recorded
.MacroReferences
,
536 /*PragmaIncludes=*/nullptr, AST
.preprocessor(),
537 [&](const SymbolReference
&Ref
, llvm::ArrayRef
<Header
>) {
538 if (!Ref
.RefLocation
.isMacroID())
540 if (llvm::to_string(Ref
.Target
) == "target") {
541 ASSERT_TRUE(RefLoc
.isInvalid())
542 << "Expected only one 'target' ref loc per testcase";
543 RefLoc
= Ref
.RefLocation
;
546 FileID MainFID
= SM
.getMainFileID();
547 if (RefLoc
.isValid()) {
548 EXPECT_THAT(RefLoc
, AllOf(expandedAt(MainFID
, Main
.point("expand"), &SM
),
549 spelledAt(MainFID
, Main
.point("spell"), &SM
)))
552 EXPECT_THAT(Main
.points(), testing::IsEmpty());
558 friend llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const Tag
&T
) {
559 return OS
<< "Anon Tag";
562 TEST(Hints
, Ordering
) {
563 auto Hinted
= [](Hints Hints
) {
564 return clang::include_cleaner::Hinted
<Tag
>({}, Hints
);
566 EXPECT_LT(Hinted(Hints::None
), Hinted(Hints::CompleteSymbol
));
567 EXPECT_LT(Hinted(Hints::CompleteSymbol
), Hinted(Hints::PublicHeader
));
568 EXPECT_LT(Hinted(Hints::PreferredHeader
), Hinted(Hints::PublicHeader
));
569 EXPECT_LT(Hinted(Hints::CompleteSymbol
| Hints::PreferredHeader
),
570 Hinted(Hints::PublicHeader
));
573 // Test ast traversal & redecl selection end-to-end for templates, as explicit
574 // instantiations/specializations are not redecls of the primary template. We
575 // need to make sure we're selecting the right ones.
576 TEST_F(WalkUsedTest
, TemplateDecls
) {
577 llvm::Annotations
Code(R
"cpp(
581 template <> struct $exp_spec^Foo<char> {};
582 template struct $exp^Foo<int>;
584 $implicit^Foo<bool> y;
585 $partial^Foo<int*> z;
587 Inputs
.Code
= Code
.code();
588 Inputs
.ExtraFiles
["fwd.h"] = guard("template<typename> struct Foo;");
589 Inputs
.ExtraFiles
["def.h"] = guard("template<typename> struct Foo {};");
590 Inputs
.ExtraFiles
["partial.h"] =
591 guard("template<typename T> struct Foo<T*> {};");
593 auto &SM
= AST
.sourceManager();
594 auto Fwd
= *SM
.getFileManager().getOptionalFileRef("fwd.h");
595 auto Def
= *SM
.getFileManager().getOptionalFileRef("def.h");
596 auto Partial
= *SM
.getFileManager().getOptionalFileRef("partial.h");
599 offsetToProviders(AST
),
601 Pair(Code
.point("exp_spec"), UnorderedElementsAre(Fwd
, Def
))),
602 Contains(Pair(Code
.point("exp"), UnorderedElementsAre(Fwd
, Def
))),
603 Contains(Pair(Code
.point("full"), UnorderedElementsAre(Fwd
, Def
))),
605 Pair(Code
.point("implicit"), UnorderedElementsAre(Fwd
, Def
))),
607 Pair(Code
.point("partial"), UnorderedElementsAre(Partial
)))));
610 TEST_F(WalkUsedTest
, IgnoresIdentityMacros
) {
611 llvm::Annotations
Code(R
"cpp(
617 Inputs
.Code
= Code
.code();
618 Inputs
.ExtraFiles
["header.h"] = guard(R
"cpp(
622 Inputs
.ExtraFiles
["inner.h"] = guard(R
"cpp(
627 auto &SM
= AST
.sourceManager();
628 auto MainFile
= Header(*SM
.getFileEntryRefForID(SM
.getMainFileID()));
629 EXPECT_THAT(offsetToProviders(AST
),
630 UnorderedElementsAre(
631 // FIXME: we should have a reference from stdin to header.h
632 Pair(Code
.point("bar"), UnorderedElementsAre(MainFile
))));
635 } // namespace clang::include_cleaner