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 @@ -22,7 +22,11 @@ #define CLANG_INCLUDE_CLEANER_ANALYSISINTERNAL_H #include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/STLFunctionalExtras.h" +#include +#include +#include namespace clang { class Decl; @@ -57,6 +61,12 @@ void walkAST(Decl &Root, llvm::function_ref); +using SymbolLocation = std::variant; +/// A set of locations that provides the declaration, while indicating if +/// location provides the definition. +std::vector> +locateDecl(const NamedDecl &ND); + } // namespace include_cleaner } // namespace clang diff --git a/clang-tools-extra/include-cleaner/lib/CMakeLists.txt b/clang-tools-extra/include-cleaner/lib/CMakeLists.txt --- a/clang-tools-extra/include-cleaner/lib/CMakeLists.txt +++ b/clang-tools-extra/include-cleaner/lib/CMakeLists.txt @@ -6,5 +6,6 @@ LINK_LIBS clangBasic clangAST + clangToolingInclusionsStdlib ) diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -16,8 +16,11 @@ #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Casting.h" +#include +#include namespace clang { namespace include_cleaner { @@ -137,11 +140,45 @@ } }; +// Looks for a visible definition of \p D. Returns nullptr if none is avilable, +// or there are possibly multiple definitions (e.g. namespaces). +const NamedDecl *getDefinition(const NamedDecl *D) { + if (const auto *TD = dyn_cast(D)) + return TD->getDefinition(); + if (const auto *VD = dyn_cast(D)) + return VD->getDefinition(); + if (const auto *FD = dyn_cast(D)) + return FD->getDefinition(); + if (const auto *CTD = dyn_cast(D)) + if (const auto *RD = CTD->getTemplatedDecl()) + return RD->getDefinition(); + if (isa(D) || isa(D) || + isa(D)) + return D; + return nullptr; +} + } // namespace void walkAST(Decl &Root, DeclCallback Callback) { ASTWalker(Callback).TraverseDecl(&Root); } +std::vector> +locateDecl(const NamedDecl &ND) { + if (auto Symbol = tooling::stdlib::Recognizer()(&ND)) + return {{*Symbol, true}}; + std::vector> Result; + SourceLocation DefLoc; + if (auto *Def = getDefinition(&ND)) { + DefLoc = Def->getLocation(); + Result.push_back({DefLoc, true}); + } + for (auto *Redecl : ND.redecls()) + if (Redecl->getLocation() != DefLoc) + Result.push_back({Redecl->getLocation(), false}); + return Result; +} + } // namespace include_cleaner } // namespace clang diff --git a/clang-tools-extra/include-cleaner/unittests/CMakeLists.txt b/clang-tools-extra/include-cleaner/unittests/CMakeLists.txt --- a/clang-tools-extra/include-cleaner/unittests/CMakeLists.txt +++ b/clang-tools-extra/include-cleaner/unittests/CMakeLists.txt @@ -17,6 +17,7 @@ clangAST clangBasic clangFrontend + clangToolingInclusionsStdlib ) target_link_libraries(ClangIncludeCleanerTests diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp --- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp @@ -1,10 +1,15 @@ #include "AnalysisInternal.h" #include "clang/AST/ASTContext.h" -#include "clang/AST/Expr.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Frontend/TextDiagnostic.h" #include "clang/Testing/TestAST.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Testing/Support/Annotations.h" @@ -12,6 +17,7 @@ #include #include #include +#include #include namespace clang { @@ -194,6 +200,58 @@ testWalk("enum class E : int {};", "enum class ^E : int ;"); } +// Looks for a decl named `foo` and performs locateDecl on it. Expects all the +// locations marked in `Code` with the right annotation to be generated. +void testLocate(llvm::StringRef Code) { + llvm::Annotations Target(Code); + + TestInputs Inputs(Target.code()); + Inputs.ExtraArgs.push_back("-std=c++17"); + TestAST AST(Inputs); + const auto &SM = AST.sourceManager(); + + const NamedDecl *Foo; + struct MatchCB : public ast_matchers::MatchFinder::MatchCallback { + MatchCB(const NamedDecl *&Out) : Out(Out) {} + void run(const ast_matchers::MatchFinder::MatchResult &Result) override { + Out = Result.Nodes.getNodeAs("id"); + assert(Out); + Out = llvm::cast(Out->getCanonicalDecl()); + } + const NamedDecl *&Out; + } CB(Foo); + ast_matchers::MatchFinder Finder; + Finder.addMatcher( + ast_matchers::namedDecl(ast_matchers::unless(ast_matchers::isImplicit()), + ast_matchers::hasName("foo")) + .bind("id"), + &CB); + Finder.matchAST(AST.context()); + ASSERT_TRUE(Foo); + std::vector> ReferencedOffsets; + for (auto Loc : locateDecl(*Foo)) { + if (auto *SL = std::get_if(&Loc.first)) { + auto [FID, Offset] = SM.getDecomposedLoc(SM.getFileLoc(*SL)); + ASSERT_EQ(FID, SM.getMainFileID()); + ReferencedOffsets.push_back( + {Offset, llvm::StringRef(Loc.second ? "$def" : "")}); + } else { + ADD_FAILURE() << "Got stdlib symbol: " << Foo->getNameAsString(); + } + } + llvm::sort(ReferencedOffsets); + auto AnnotatedCode = Target.code().str(); + for (auto [Offset, Annotation] : llvm::reverse(ReferencedOffsets)) + AnnotatedCode.insert(Offset, (Annotation + "^").str()); + EXPECT_EQ(Code, AnnotatedCode); +} + +TEST(LocateDecl, General) { + testLocate("struct ^foo; struct $def^foo {};"); + testLocate("namespace ns { void ^foo(); void $def^foo() {} }"); + testLocate("enum class ^foo; enum class $def^foo {};"); +} + } // namespace } // namespace include_cleaner } // namespace clang