diff --git a/clang-tools-extra/include-cleaner/lib/Analysis.cpp b/clang-tools-extra/include-cleaner/lib/Analysis.cpp --- a/clang-tools-extra/include-cleaner/lib/Analysis.cpp +++ b/clang-tools-extra/include-cleaner/lib/Analysis.cpp @@ -26,6 +26,8 @@ for (auto *Root : ASTRoots) { auto &SM = Root->getASTContext().getSourceManager(); walkAST(*Root, [&](SourceLocation Loc, NamedDecl &ND, RefType RT) { + if (!isSpelledInMainFile(Loc, SM)) + return; SymbolReference SymRef{Loc, ND, RT}; if (auto SS = Recognizer(&ND)) { // FIXME: Also report forward decls from main-file, so that the caller @@ -38,6 +40,8 @@ } for (const SymbolReference &MacroRef : MacroRefs) { assert(MacroRef.Target.kind() == Symbol::Macro); + if (!isSpelledInMainFile(MacroRef.RefLocation, SM)) + continue; CB(MacroRef, findHeaders(MacroRef.Target.macro().Definition, SM, PI)); } } diff --git a/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h b/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h --- a/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h +++ b/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h @@ -84,6 +84,9 @@ const SourceManager &SM, const PragmaIncludes *PI); +/// Returns true if the given source location is spelled in the main file. +bool isSpelledInMainFile(SourceLocation Loc, const SourceManager &SM); + /// Write an HTML summary of the analysis to the given stream. void writeHTMLReport(FileID File, const RecordedPP::RecordedIncludes &Includes, llvm::ArrayRef Roots, diff --git a/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp b/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp --- a/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp +++ b/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp @@ -54,4 +54,8 @@ llvm_unreachable("unhandled SymbolLocation kind!"); } +bool isSpelledInMainFile(SourceLocation Loc, const SourceManager &SM) { + return SM.isWrittenInMainFile(Loc.isMacroID() ? SM.getSpellingLoc(Loc) : Loc); +} + } // namespace clang::include_cleaner \ No newline at end of file diff --git a/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp b/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp --- a/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp +++ b/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp @@ -529,10 +529,15 @@ Reporter R(OS, Ctx, HS, Includes, PI, File); for (Decl *Root : Roots) walkAST(*Root, [&](SourceLocation Loc, const NamedDecl &D, RefType T) { + if (!isSpelledInMainFile(Loc, Ctx.getSourceManager())) + return; R.addRef(SymbolReference{Loc, D, T}); }); - for (const SymbolReference &Ref : MacroRefs) + for (const SymbolReference &Ref : MacroRefs) { + if (!isSpelledInMainFile(Ref.RefLocation, Ctx.getSourceManager())) + continue; R.addRef(Ref); + } R.write(); } diff --git a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp --- a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp @@ -25,6 +25,8 @@ namespace clang::include_cleaner { namespace { +using testing::AllOf; +using testing::Not; using testing::Pair; using testing::UnorderedElementsAre; @@ -134,6 +136,118 @@ UnorderedElementsAre(Pair(Main.point(), UnorderedElementsAre(HdrFile)))); } +MATCHER(isMacroLoc, "") { return arg.isMacroID(); } +MATCHER_P3(expandedAt, FileID, Offset, SM, "") { + auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg); + return ExpanedFileID == FileID && ExpandedOffset == Offset; +} +MATCHER_P3(spelledAt, FileID, Offset, SM, "") { + auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg); + return SpelledFileID == FileID && SpelledOffset == Offset; +} +TEST(WalkUsed, FilterRefsNotSpelledInMainFile) { + llvm::Annotations Main(R"cpp( + #include "header.h" + + // Macros defined in main file. + #define PLUS_ONE(X) X+1; + #define CALL_FUN $func_spelled^func() + + #define M 1 + #define USE_M $M1^$M2_spelled^M + + void test(int a) { + // Tests for decl refs. + // The ref of `func()` is spelled in the main-file macro body. + $func_expanded^CALL_FUN; + + // Refs of `a` are spelled in the main file (via macro arg). + $a1_expanded^PLUS_ONE($a1_spelled^a); + $a2_expanded^HDR_PLUS_ONE($a2_spelled^a); + + // Refs of `func()` and `a` are from macro body which are spelled outside + // the main file, thus filtered out. + HDR_CALL_FUN; + HDR_A_PLUS_ONE; + + // Tests for macro refs. + // 2 macro refs of `M`: + // - M1 is a file loc, spelled in the macro body of USE_M + // - M2 is a macro loc, expanded by the macro USE_M usage. + $M2_expanded^USE_M; + // Macro refs of `HDR_M` not spelled in main file, thus filtered out. + HDR_USE_M; + } + )cpp"); + TestInputs Inputs(Main.code()); + Inputs.ExtraFiles["header.h"] = guard(R"cpp( + #define HDR_PLUS_ONE(X) X + 1 + #define HDR_A_PLUS_ONE a + 1 + #define HDR_CALL_FUN func() + + void func(); + + #define HDR_M 1 + #define HDR_USE_M HDR_M + )cpp"); + RecordedPP Recorded; + Inputs.MakeAction = [&]() { + struct RecordAction : public SyntaxOnlyAction { + RecordedPP &Out; + RecordAction(RecordedPP &Out) : Out(Out) {} + bool BeginSourceFileAction(clang::CompilerInstance &CI) override { + auto &PP = CI.getPreprocessor(); + PP.addPPCallbacks(Out.record(PP)); + return true; + } + }; + return std::make_unique(Recorded); + }; + TestAST AST(Inputs); + + llvm::SmallVector TopLevelDecls; + for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) + TopLevelDecls.emplace_back(D); + auto &SM = AST.sourceManager(); + FileID MainFID = SM.getMainFileID(); + llvm::StringMap> SymbolRefs; + walkUsed( + TopLevelDecls, Recorded.MacroReferences, /*PragmaIncludes=*/nullptr, SM, + [&](const SymbolReference &Ref, llvm::ArrayRef
) { + switch (Ref.Target.kind()) { + case Symbol::Declaration: + SymbolRefs[dyn_cast(&Ref.Target.declaration())->getName()] + .push_back(Ref.RefLocation); + break; + case Symbol::Macro: + SymbolRefs[Ref.Target.macro().Name->getName()].push_back( + Ref.RefLocation); + break; + } + }); + EXPECT_THAT(SymbolRefs.lookup("func"), + UnorderedElementsAre( + AllOf(isMacroLoc(), + expandedAt(MainFID, Main.point("func_expanded"), &SM), + spelledAt(MainFID, Main.point("func_spelled"), &SM)))); + EXPECT_THAT(SymbolRefs.lookup("a"), + UnorderedElementsAre( + AllOf(isMacroLoc(), + expandedAt(MainFID, Main.point("a1_expanded"), &SM), + spelledAt(MainFID, Main.point("a1_spelled"), &SM)), + AllOf(isMacroLoc(), + expandedAt(MainFID, Main.point("a2_expanded"), &SM), + spelledAt(MainFID, Main.point("a2_spelled"), &SM)))); + + EXPECT_THAT( + SymbolRefs.lookup("M"), + UnorderedElementsAre( + AllOf(Not(isMacroLoc()), spelledAt(MainFID, Main.point("M1"), &SM)), + AllOf(isMacroLoc(), + expandedAt(MainFID, Main.point("M2_expanded"), &SM), + spelledAt(MainFID, Main.point("M2_spelled"), &SM)))); + EXPECT_THAT(SymbolRefs.lookup("HDR_M"), testing::IsEmpty()); +} } // namespace } // namespace clang::include_cleaner