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 @@ -24,7 +24,10 @@ #include "clang-include-cleaner/Record.h" #include "clang-include-cleaner/Types.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/STLFunctionalExtras.h" +#include +#include namespace clang { class ASTContext; @@ -66,7 +69,6 @@ bool operator==(const SymbolLocation &RHS) const { return Storage == RHS.Storage; } - SourceLocation physical() const { return std::get(Storage); } tooling::stdlib::Symbol standard() const { return std::get(Storage); @@ -91,6 +93,9 @@ HeaderSearch &HS, PragmaIncludes *PI, llvm::raw_ostream &OS); +/// A set of locations that provides the declaration. +std::vector locateSymbol(const Symbol &S); + } // namespace include_cleaner } // namespace clang diff --git a/clang-tools-extra/include-cleaner/lib/LocateSymbol.cpp b/clang-tools-extra/include-cleaner/lib/LocateSymbol.cpp --- a/clang-tools-extra/include-cleaner/lib/LocateSymbol.cpp +++ b/clang-tools-extra/include-cleaner/lib/LocateSymbol.cpp @@ -1,4 +1,4 @@ -//===--- LocateSymbol.cpp -------------------------------------------------===// +//===--- LocateSymbol.cpp - Find locations providing a symbol -------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,10 +7,28 @@ //===----------------------------------------------------------------------===// #include "AnalysisInternal.h" +#include "clang/AST/DeclBase.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" +#include +#include namespace clang::include_cleaner { +namespace { + +std::vector locateDecl(const Decl &D) { + std::vector Result; + // FIXME: Should we also provide physical locations? + if (auto SS = tooling::stdlib::Recognizer()(&D)) + return {SymbolLocation(*SS)}; + for (auto *Redecl : D.redecls()) + Result.push_back(Redecl->getLocation()); + return Result; +} + +} // namespace llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolLocation &S) { switch (S.kind()) { @@ -28,4 +46,13 @@ llvm_unreachable("Unhandled Symbol kind"); } -} // namespace clang::include_cleaner \ No newline at end of file +std::vector locateSymbol(const Symbol &S) { + switch (S.kind()) { + case Symbol::Declaration: + return locateDecl(S.declaration()); + case Symbol::Macro: + return {SymbolLocation(S.macro().Definition)}; + } +} + +} // namespace clang::include_cleaner 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 @@ -7,6 +7,7 @@ add_unittest(ClangIncludeCleanerUnitTests ClangIncludeCleanerTests AnalysisTest.cpp FindHeadersTest.cpp + LocateSymbolTest.cpp RecordTest.cpp TypesTest.cpp WalkASTTest.cpp diff --git a/clang-tools-extra/include-cleaner/unittests/LocateSymbolTest.cpp b/clang-tools-extra/include-cleaner/unittests/LocateSymbolTest.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/include-cleaner/unittests/LocateSymbolTest.cpp @@ -0,0 +1,133 @@ +//===--- LocateSymbolTest.cpp -------------------------------------- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "AnalysisInternal.h" +#include "clang-include-cleaner/Types.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Testing/TestAST.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Testing/Support/Annotations.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include + +namespace clang::include_cleaner { +namespace { +using testing::ElementsAre; +using testing::ElementsAreArray; +using testing::Pair; +using testing::UnorderedElementsAre; + +// A helper for building ASTs and getting decls out of it by name. Example usage +// looks like: +// LocateExample X("void ^foo();"); +// Decl &Foo = X.findDecl("foo"); +// X.points(); // returns all the points in annotated test input. +struct LocateExample { +private: + llvm::Annotations Target; + TestAST AST; + +public: + LocateExample(llvm::StringRef AnnotatedCode) + : Target(AnnotatedCode), AST([this] { + TestInputs Inputs(Target.code()); + Inputs.ExtraArgs.push_back("-std=c++17"); + return Inputs; + }()) {} + + const Decl &findDecl(llvm::StringRef SymbolName) { + const NamedDecl *DeclToLocate; + 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(DeclToLocate); + ast_matchers::MatchFinder Finder; + Finder.addMatcher(ast_matchers::namedDecl( + ast_matchers::unless(ast_matchers::isImplicit()), + ast_matchers::hasName(SymbolName)) + .bind("id"), + &CB); + Finder.matchAST(AST.context()); + if (!DeclToLocate) + ADD_FAILURE() << "Couldn't find any decls with name: " << SymbolName; + assert(DeclToLocate); + return *DeclToLocate; + } + + Macro findMacro(llvm::StringRef Name) { + auto &PP = AST.preprocessor(); + auto *II = PP.getIdentifierInfo(Name); + if (!II || !II->hasMacroDefinition()) { + ADD_FAILURE() << "Couldn't find any macros with name: " << Name; + return {}; + } + auto MD = PP.getMacroDefinition(II); + assert(MD.getMacroInfo()); + return {II, MD.getMacroInfo()->getDefinitionLoc()}; + } + + std::vector points() { + auto &SM = AST.sourceManager(); + auto FID = SM.getMainFileID(); + auto Offsets = Target.points(); + std::vector Results; + for (auto &Offset : Offsets) + Results.emplace_back(SM.getComposedLoc(FID, Offset)); + return Results; + } +}; + +TEST(LocateSymbol, Decl) { + // Looks for decl with name 'foo' and performs locateSymbol on it. + // Expects all the locations in the case to be returned as a location. + const llvm::StringLiteral Cases[] = { + "struct ^foo; struct ^foo {};", + "namespace ns { void ^foo(); void ^foo() {} }", + "enum class ^foo; enum class ^foo {};", + }; + + for (auto &Case : Cases) { + SCOPED_TRACE(Case); + LocateExample Test(Case); + EXPECT_THAT(locateSymbol(Test.findDecl("foo")), + ElementsAreArray(Test.points())); + } +} + +TEST(LocateSymbol, Stdlib) { + LocateExample Test("namespace std { struct vector; }"); + EXPECT_THAT(locateSymbol(Test.findDecl("vector")), + ElementsAre(*tooling::stdlib::Symbol::named("std::", "vector"))); +} + +TEST(LocateSymbol, Macros) { + // Make sure we preserve the last one. + LocateExample Test("#define FOO\n#undef FOO\n#define ^FOO"); + EXPECT_THAT(locateSymbol(Test.findMacro("FOO")), + ElementsAreArray(Test.points())); +} + +} // namespace +} // namespace clang::include_cleaner