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/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"
36 namespace clang::include_cleaner
{
39 using testing::Contains
;
40 using testing::ElementsAre
;
42 using testing::UnorderedElementsAre
;
44 std::string
guard(llvm::StringRef Code
) {
45 return "#pragma once\n" + Code
.str();
48 class WalkUsedTest
: public testing::Test
{
53 Inputs
.MakeAction
= [this] {
54 struct Hook
: public SyntaxOnlyAction
{
56 Hook(PragmaIncludes
*Out
) : Out(Out
) {}
57 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
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())))
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());
86 return OffsetToProviders
;
90 TEST_F(WalkUsedTest
, Basic
) {
91 llvm::Annotations
Code(R
"cpp(
95 // No reference reported for the Parameter "p
".
96 void $bar^bar($private^Private p) {
98 std::$vector^vector $vconstructor^$v^v;
99 $builtin^__builtin_popcount(1);
103 Inputs
.Code
= Code
.code();
104 Inputs
.ExtraFiles
["header.h"] = guard(R
"cpp(
106 namespace std { class vector {}; int&& move(int&&); }
108 Inputs
.ExtraFiles
["private.h"] = guard(R
"cpp(
109 // IWYU pragma: private, include "path
/public.h
"
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>"));
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(
145 Inputs
.Code
= Code
.code();
146 Inputs
.ExtraFiles
["header1.h"] = guard(R
"cpp(
149 Inputs
.ExtraFiles
["header2.h"] = guard(R
"cpp(
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()));
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(
167 int $3^x = $1^ANSWER;
168 int $4^y = $2^ANSWER;
170 llvm::Annotations
Hdr(guard("#define ^ANSWER 42"));
171 Inputs
.Code
= Code
.code();
172 Inputs
.ExtraFiles
["hdr.h"] = Hdr
.code();
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())};
189 Answer1
, SM
.getComposedLoc(SM
.getMainFileID(), Code
.point("1")),
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
{
207 Inputs
.MakeAction
= [this] {
208 struct Hook
: public SyntaxOnlyAction
{
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()));
220 return std::make_unique
<Hook
>(PP
, PI
);
225 TEST_F(AnalyzeTest
, Basic
) {
229 #include "keep
.h
" // IWYU pragma: keep
233 Inputs
.ExtraFiles
["a.h"] = guard("int a;");
234 Inputs
.ExtraFiles
["b.h"] = guard(R
"cpp(
238 Inputs
.ExtraFiles
["c.h"] = guard("int c;");
239 Inputs
.ExtraFiles
["keep.h"] = guard("");
241 auto Decls
= AST
.context().getTranslationUnitDecl()->decls();
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";
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;
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");
280 #include <amintrin.h>
281 #include <imintrin.h>
286 Inputs
.ExtraFiles
["resources/include/amintrin.h"] = guard("");
287 Inputs
.ExtraFiles
["resources/include/emintrin.h"] = guard(R
"cpp(
290 Inputs
.ExtraFiles
["resources/include/imintrin.h"] = guard(R
"cpp(
291 #include <emintrin.h>
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
"
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()),
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()),
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
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.
356 llvm::StringRef Header
;
357 llvm::StringRef Main
;
359 // Tests for decl references.
361 /*Header=*/"int target();",
363 #define CALL_FUNC $spell^target()
365 int b = $expand^CALL_FUNC;
370 #define CALL_FUNC target()
372 // No ref of `target` being reported, as it is not spelled in main file.
373 "int a = CALL_FUNC;"},
377 #define PLUS_ONE(X) X() + 1
380 int a = $expand^PLUS_ONE($spell^target);
386 #define PLUS_ONE(X) X() + 1
389 int a = $expand^PLUS_ONE($spell^target);
392 // Tests for macro references
393 {/*Header=*/"#define target 1",
395 #define USE_target $spell^target
396 int b = $expand^USE_target;
400 #define USE_target target
402 // No ref of `target` being reported, it is not spelled in main file.
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
);
413 Inputs
.MakeAction
= [&]() {
414 struct RecordAction
: public SyntaxOnlyAction
{
416 RecordAction(RecordedPP
&Out
) : Out(Out
) {}
417 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
418 auto &PP
= CI
.getPreprocessor();
419 PP
.addPPCallbacks(Out
.record(PP
));
423 return std::make_unique
<RecordAction
>(Recorded
);
425 Inputs
.ExtraArgs
.push_back("-include");
426 Inputs
.ExtraArgs
.push_back("header.h");
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())
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
)))
451 EXPECT_THAT(Main
.points(), testing::IsEmpty());
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(
480 template <> struct $exp_spec^Foo<char> {};
481 template struct $exp^Foo<int>;
483 $implicit^Foo<bool> y;
484 $partial^Foo<int*> z;
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*> {};");
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");
498 offsetToProviders(AST
),
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
))),
504 Pair(Code
.point("implicit"), UnorderedElementsAre(Fwd
, Def
))),
506 Pair(Code
.point("partial"), UnorderedElementsAre(Partial
)))));
509 TEST_F(WalkUsedTest
, IgnoresIdentityMacros
) {
510 llvm::Annotations
Code(R
"cpp(
516 Inputs
.Code
= Code
.code();
517 Inputs
.ExtraFiles
["header.h"] = guard(R
"cpp(
521 Inputs
.ExtraFiles
["inner.h"] = guard(R
"cpp(
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
))));
534 } // namespace clang::include_cleaner