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
, SourceManager
&SM
,
70 llvm::ArrayRef
<SymbolReference
> MacroRefs
= {}) {
71 llvm::SmallVector
<Decl
*> TopLevelDecls
;
72 for (Decl
*D
: AST
.context().getTranslationUnitDecl()->decls()) {
73 if (!SM
.isWrittenInMainFile(SM
.getExpansionLoc(D
->getLocation())))
75 TopLevelDecls
.emplace_back(D
);
77 std::multimap
<size_t, std::vector
<Header
>> OffsetToProviders
;
78 walkUsed(TopLevelDecls
, MacroRefs
, &PI
, SM
,
79 [&](const SymbolReference
&Ref
, llvm::ArrayRef
<Header
> Providers
) {
80 auto [FID
, Offset
] = SM
.getDecomposedLoc(Ref
.RefLocation
);
81 if (FID
!= SM
.getMainFileID())
82 ADD_FAILURE() << "Reference outside of the main file!";
83 OffsetToProviders
.emplace(Offset
, Providers
.vec());
85 return OffsetToProviders
;
89 TEST_F(WalkUsedTest
, Basic
) {
90 llvm::Annotations
Code(R
"cpp(
94 // No reference reported for the Parameter "p
".
95 void $bar^bar($private^Private p) {
97 std::$vector^vector $vconstructor^$v^v;
98 $builtin^__builtin_popcount(1);
102 Inputs
.Code
= Code
.code();
103 Inputs
.ExtraFiles
["header.h"] = guard(R
"cpp(
105 namespace std { class vector {}; int&& move(int&&); }
107 Inputs
.ExtraFiles
["private.h"] = guard(R
"cpp(
108 // IWYU pragma: private, include "path
/public.h
"
113 auto &SM
= AST
.sourceManager();
114 auto HeaderFile
= Header(AST
.fileManager().getFile("header.h").get());
115 auto PrivateFile
= Header(AST
.fileManager().getFile("private.h").get());
116 auto PublicFile
= Header("\"path/public.h\"");
117 auto MainFile
= Header(SM
.getFileEntryForID(SM
.getMainFileID()));
118 auto VectorSTL
= Header(*tooling::stdlib::Header::named("<vector>"));
119 auto UtilitySTL
= Header(*tooling::stdlib::Header::named("<utility>"));
121 offsetToProviders(AST
, SM
),
122 UnorderedElementsAre(
123 Pair(Code
.point("bar"), UnorderedElementsAre(MainFile
)),
124 Pair(Code
.point("private"),
125 UnorderedElementsAre(PublicFile
, PrivateFile
)),
126 Pair(Code
.point("foo"), UnorderedElementsAre(HeaderFile
)),
127 Pair(Code
.point("vector"), UnorderedElementsAre(VectorSTL
)),
128 Pair(Code
.point("vconstructor"), UnorderedElementsAre(VectorSTL
)),
129 Pair(Code
.point("v"), UnorderedElementsAre(MainFile
)),
130 Pair(Code
.point("builtin"), testing::IsEmpty()),
131 Pair(Code
.point("move"), UnorderedElementsAre(UtilitySTL
))));
134 TEST_F(WalkUsedTest
, MultipleProviders
) {
135 llvm::Annotations
Code(R
"cpp(
144 Inputs
.Code
= Code
.code();
145 Inputs
.ExtraFiles
["header1.h"] = guard(R
"cpp(
148 Inputs
.ExtraFiles
["header2.h"] = guard(R
"cpp(
153 auto &SM
= AST
.sourceManager();
154 auto HeaderFile1
= Header(AST
.fileManager().getFile("header1.h").get());
155 auto HeaderFile2
= Header(AST
.fileManager().getFile("header2.h").get());
156 auto MainFile
= Header(SM
.getFileEntryForID(SM
.getMainFileID()));
158 offsetToProviders(AST
, SM
),
159 Contains(Pair(Code
.point("foo"),
160 UnorderedElementsAre(HeaderFile1
, HeaderFile2
, MainFile
))));
163 TEST_F(WalkUsedTest
, MacroRefs
) {
164 llvm::Annotations
Code(R
"cpp(
166 int $3^x = $1^ANSWER;
167 int $4^y = $2^ANSWER;
169 llvm::Annotations
Hdr(guard("#define ^ANSWER 42"));
170 Inputs
.Code
= Code
.code();
171 Inputs
.ExtraFiles
["hdr.h"] = Hdr
.code();
173 auto &SM
= AST
.sourceManager();
174 const auto *HdrFile
= SM
.getFileManager().getFile("hdr.h").get();
175 auto MainFile
= Header(SM
.getFileEntryForID(SM
.getMainFileID()));
177 auto HdrID
= SM
.translateFile(HdrFile
);
179 IdentifierTable Idents
;
181 Macro
{&Idents
.get("ANSWER"), SM
.getComposedLoc(HdrID
, Hdr
.point())};
183 Macro
{&Idents
.get("ANSWER"), SM
.getComposedLoc(HdrID
, Hdr
.point())};
188 Answer1
, SM
.getComposedLoc(SM
.getMainFileID(), Code
.point("1")),
191 Answer2
, SM
.getComposedLoc(SM
.getMainFileID(), Code
.point("2")),
192 RefType::Explicit
}}),
193 UnorderedElementsAre(
194 Pair(Code
.point("1"), UnorderedElementsAre(HdrFile
)),
195 Pair(Code
.point("2"), UnorderedElementsAre(HdrFile
)),
196 Pair(Code
.point("3"), UnorderedElementsAre(MainFile
)),
197 Pair(Code
.point("4"), UnorderedElementsAre(MainFile
))));
200 class AnalyzeTest
: public testing::Test
{
206 Inputs
.MakeAction
= [this] {
207 struct Hook
: public SyntaxOnlyAction
{
209 Hook(RecordedPP
&PP
, PragmaIncludes
&PI
) : PP(PP
), PI(PI
) {}
210 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
211 CI
.getPreprocessor().addPPCallbacks(PP
.record(CI
.getPreprocessor()));
219 return std::make_unique
<Hook
>(PP
, PI
);
224 TEST_F(AnalyzeTest
, Basic
) {
228 #include "keep
.h
" // IWYU pragma: keep
232 Inputs
.ExtraFiles
["a.h"] = guard("int a;");
233 Inputs
.ExtraFiles
["b.h"] = guard(R
"cpp(
237 Inputs
.ExtraFiles
["c.h"] = guard("int c;");
238 Inputs
.ExtraFiles
["keep.h"] = guard("");
240 auto Decls
= AST
.context().getTranslationUnitDecl()->decls();
242 analyze(std::vector
<Decl
*>{Decls
.begin(), Decls
.end()},
243 PP
.MacroReferences
, PP
.Includes
, &PI
, AST
.sourceManager(),
244 AST
.preprocessor().getHeaderSearchInfo());
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
.sourceManager(),
261 AST
.preprocessor().getHeaderSearchInfo());
262 EXPECT_THAT(Results
.Unused
, testing::IsEmpty());
265 TEST_F(AnalyzeTest
, NoCrashWhenUnresolved
) {
266 // Check that umbrella header uses private include.
267 Inputs
.Code
= R
"cpp(#include "not_found
.h
")cpp";
268 Inputs
.ErrorOK
= true;
270 EXPECT_FALSE(PP
.Includes
.all().empty());
271 auto Results
= analyze({}, {}, PP
.Includes
, &PI
, AST
.sourceManager(),
272 AST
.preprocessor().getHeaderSearchInfo());
273 EXPECT_THAT(Results
.Unused
, testing::IsEmpty());
276 TEST(FixIncludes
, Basic
) {
277 llvm::StringRef Code
= R
"cpp(#include "d
.h
"
296 AnalysisResults Results
;
297 Results
.Missing
.push_back("\"aa.h\"");
298 Results
.Missing
.push_back("\"ab.h\"");
299 Results
.Missing
.push_back("<e.h>");
300 Results
.Unused
.push_back(Inc
.atLine(3));
301 Results
.Unused
.push_back(Inc
.atLine(4));
303 EXPECT_EQ(fixIncludes(Results
, "d.cc", Code
, format::getLLVMStyle()),
312 Results
.Missing
.push_back("\"d.h\"");
313 Code
= R
"cpp(#include "a
.h
")cpp";
314 EXPECT_EQ(fixIncludes(Results
, "d.cc", Code
, format::getLLVMStyle()),
316 #include "a
.h
")cpp");
319 MATCHER_P3(expandedAt
, FileID
, Offset
, SM
, "") {
320 auto [ExpanedFileID
, ExpandedOffset
] = SM
->getDecomposedExpansionLoc(arg
);
321 return ExpanedFileID
== FileID
&& ExpandedOffset
== Offset
;
323 MATCHER_P3(spelledAt
, FileID
, Offset
, SM
, "") {
324 auto [SpelledFileID
, SpelledOffset
] = SM
->getDecomposedSpellingLoc(arg
);
325 return SpelledFileID
== FileID
&& SpelledOffset
== Offset
;
327 TEST(WalkUsed
, FilterRefsNotSpelledInMainFile
) {
328 // Each test is expected to have a single expected ref of `target` symbol
330 // The location in the reported ref is a macro location. $expand points to
331 // the macro location, and $spell points to the spelled location.
333 llvm::StringRef Header
;
334 llvm::StringRef Main
;
336 // Tests for decl references.
338 /*Header=*/"int target();",
340 #define CALL_FUNC $spell^target()
342 int b = $expand^CALL_FUNC;
347 #define CALL_FUNC target()
349 // No ref of `target` being reported, as it is not spelled in main file.
350 "int a = CALL_FUNC;"},
354 #define PLUS_ONE(X) X() + 1
357 int a = $expand^PLUS_ONE($spell^target);
363 #define PLUS_ONE(X) X() + 1
366 int a = $expand^PLUS_ONE($spell^target);
369 // Tests for macro references
370 {/*Header=*/"#define target 1",
372 #define USE_target $spell^target
373 int b = $expand^USE_target;
377 #define USE_target target
379 // No ref of `target` being reported, it is not spelled in main file.
385 for (const auto &T
: TestCases
) {
386 llvm::Annotations
Main(T
.Main
);
387 TestInputs
Inputs(Main
.code());
388 Inputs
.ExtraFiles
["header.h"] = guard(T
.Header
);
390 Inputs
.MakeAction
= [&]() {
391 struct RecordAction
: public SyntaxOnlyAction
{
393 RecordAction(RecordedPP
&Out
) : Out(Out
) {}
394 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
395 auto &PP
= CI
.getPreprocessor();
396 PP
.addPPCallbacks(Out
.record(PP
));
400 return std::make_unique
<RecordAction
>(Recorded
);
402 Inputs
.ExtraArgs
.push_back("-include");
403 Inputs
.ExtraArgs
.push_back("header.h");
405 llvm::SmallVector
<Decl
*> TopLevelDecls
;
406 for (Decl
*D
: AST
.context().getTranslationUnitDecl()->decls())
407 TopLevelDecls
.emplace_back(D
);
408 auto &SM
= AST
.sourceManager();
410 SourceLocation RefLoc
;
411 walkUsed(TopLevelDecls
, Recorded
.MacroReferences
,
412 /*PragmaIncludes=*/nullptr, SM
,
413 [&](const SymbolReference
&Ref
, llvm::ArrayRef
<Header
>) {
414 if (!Ref
.RefLocation
.isMacroID())
416 if (llvm::to_string(Ref
.Target
) == "target") {
417 ASSERT_TRUE(RefLoc
.isInvalid())
418 << "Expected only one 'target' ref loc per testcase";
419 RefLoc
= Ref
.RefLocation
;
422 FileID MainFID
= SM
.getMainFileID();
423 if (RefLoc
.isValid()) {
424 EXPECT_THAT(RefLoc
, AllOf(expandedAt(MainFID
, Main
.point("expand"), &SM
),
425 spelledAt(MainFID
, Main
.point("spell"), &SM
)))
428 EXPECT_THAT(Main
.points(), testing::IsEmpty());
434 friend llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const Tag
&T
) {
435 return OS
<< "Anon Tag";
438 TEST(Hints
, Ordering
) {
439 auto Hinted
= [](Hints Hints
) {
440 return clang::include_cleaner::Hinted
<Tag
>({}, Hints
);
442 EXPECT_LT(Hinted(Hints::None
), Hinted(Hints::CompleteSymbol
));
443 EXPECT_LT(Hinted(Hints::CompleteSymbol
), Hinted(Hints::PublicHeader
));
444 EXPECT_LT(Hinted(Hints::PublicHeader
), Hinted(Hints::PreferredHeader
));
445 EXPECT_LT(Hinted(Hints::CompleteSymbol
| Hints::PublicHeader
),
446 Hinted(Hints::PreferredHeader
));
449 // Test ast traversal & redecl selection end-to-end for templates, as explicit
450 // instantiations/specializations are not redecls of the primary template. We
451 // need to make sure we're selecting the right ones.
452 TEST_F(WalkUsedTest
, TemplateDecls
) {
453 llvm::Annotations
Code(R
"cpp(
457 template <> struct $exp_spec^Foo<char> {};
458 template struct $exp^Foo<int>;
460 $implicit^Foo<bool> y;
461 $partial^Foo<int*> z;
463 Inputs
.Code
= Code
.code();
464 Inputs
.ExtraFiles
["fwd.h"] = guard("template<typename> struct Foo;");
465 Inputs
.ExtraFiles
["def.h"] = guard("template<typename> struct Foo {};");
466 Inputs
.ExtraFiles
["partial.h"] =
467 guard("template<typename T> struct Foo<T*> {};");
469 auto &SM
= AST
.sourceManager();
470 const auto *Fwd
= SM
.getFileManager().getFile("fwd.h").get();
471 const auto *Def
= SM
.getFileManager().getFile("def.h").get();
472 const auto *Partial
= SM
.getFileManager().getFile("partial.h").get();
475 offsetToProviders(AST
, SM
),
477 Pair(Code
.point("exp_spec"), UnorderedElementsAre(Fwd
, Def
))),
478 Contains(Pair(Code
.point("exp"), UnorderedElementsAre(Fwd
, Def
))),
479 Contains(Pair(Code
.point("full"), UnorderedElementsAre(Fwd
, Def
))),
481 Pair(Code
.point("implicit"), UnorderedElementsAre(Fwd
, Def
))),
483 Pair(Code
.point("partial"), UnorderedElementsAre(Partial
)))));
487 } // namespace clang::include_cleaner