diff --git a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h @@ -0,0 +1,75 @@ +//===--- Analysis.h - Analyze symbol references in AST ------------- 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 +// +//===----------------------------------------------------------------------===// +/// A library that provides usage analysis for symbols based on AST analysis. +//===----------------------------------------------------------------------===// + +#ifndef CLANG_INCLUDE_CLEANER_ANALYSIS_H +#define CLANG_INCLUDE_CLEANER_ANALYSIS_H + +#include "clang/Tooling/Inclusions/StandardLibrary.h" +#include "llvm/ADT/STLFunctionalExtras.h" +#include + +namespace clang { +class SourceLocation; +class Decl; +class FileEntry; +namespace include_cleaner { + +/// An entity that can be referenced in the code. +struct Symbol { + Symbol(Decl &D) : Storage(&D) {} + Symbol(tooling::stdlib::Symbol S) : Storage(S) {} + +private: + // FIXME: Add support for macros. + std::variant Storage; +}; + +/// Represents a file that provides some symbol. Might not be includeable, e.g. +/// built-in or main-file itself. +struct Header { + /// A physical (or logical, in case of a builtin) file. + Header(const FileEntry *FE) : Storage(FE) {} + /// A logical file representing a stdlib header. + Header(tooling::stdlib::Header H) : Storage(H) {} + + bool operator==(const Header &RHS) const { return Storage == RHS.Storage; } + +private: + // FIXME: Handle verbatim spellings. + std::variant Storage; +}; +/// A UsedSymbolCB is a callback invoked for each symbol reference seen. +/// +/// References occur at a particular location, refer to a single symbol, and +/// that symbol may be provided by several headers. +/// FIXME: Provide signals about the reference type and providing headers so the +/// caller can filter and rank the results. +using UsedSymbolCB = llvm::function_ref Providers)>; + +/// Find and report all references to symbols in a region of code. +/// +/// The AST traversal is rooted at ASTRoots - typically top-level declarations +/// of a single source file. +/// FIXME: Handle macro uses. +/// +/// This is the main entrypoint of the include-cleaner library, and can be used: +/// - to diagnose missing includes: a referenced symbol is provided by +/// headers which don't match any #include in the main file +/// - to diagnose unused includes: an #include in the main file does not match +/// the headers for any referenced symbol +/// FIXME: Take in an include structure to improve location to header mappings +/// (e.g. IWYU pragmas). +void walkUsed(llvm::ArrayRef ASTRoots, UsedSymbolCB CB); + +} // namespace include_cleaner +} // namespace clang + +#endif diff --git a/clang-tools-extra/include-cleaner/lib/Analysis.cpp b/clang-tools-extra/include-cleaner/lib/Analysis.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/include-cleaner/lib/Analysis.cpp @@ -0,0 +1,50 @@ +//===--- Analysis.cpp -----------------------------------------------------===// +// +// 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 "clang-include-cleaner/Analysis.h" +#include "AnalysisInternal.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" + +namespace clang::include_cleaner { +namespace { +llvm::SmallVector
+toHeader(llvm::ArrayRef Headers) { + llvm::SmallVector
Result; + llvm::for_each(Headers, [&](tooling::stdlib::Header H) { + Result.emplace_back(Header(H)); + }); + return Result; +} + +} // namespace +void walkUsed(llvm::ArrayRef ASTRoots, UsedSymbolCB CB) { + tooling::stdlib::Recognizer Recognizer; + for (auto *Root : ASTRoots) { + auto &SM = Root->getASTContext().getSourceManager(); + walkAST(*Root, [&](SourceLocation Loc, NamedDecl &ND) { + if (auto SS = Recognizer(&ND)) { + // FIXME: Also report forward decls from main-file, so that the caller + // can decide to insert/ignore a header. + return CB(Loc, Symbol(*SS), toHeader(SS->headers())); + } + // FIXME: Extract locations from redecls. + // FIXME: Handle IWYU pragmas, non self-contained files. + // FIXME: Handle macro locations. + if (auto *FE = SM.getFileEntryForID(SM.getFileID(ND.getLocation()))) + return CB(Loc, Symbol(ND), {Header(FE)}); + }); + } + // FIXME: Handle references of macros. +} + +} // namespace clang::include_cleaner 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 @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS Support) add_clang_library(clangIncludeCleaner + Analysis.cpp HTMLReport.cpp Record.cpp WalkAST.cpp @@ -9,5 +10,6 @@ clangAST clangBasic clangLex + 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 @@ -9,8 +9,7 @@ #include "AnalysisInternal.h" #include "clang/AST/RecursiveASTVisitor.h" -namespace clang { -namespace include_cleaner { +namespace clang::include_cleaner { namespace { using DeclCallback = llvm::function_ref; @@ -43,5 +42,4 @@ ASTWalker(Callback).TraverseDecl(&Root); } -} // namespace include_cleaner -} // namespace clang +} // namespace clang::include_cleaner diff --git a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp @@ -0,0 +1,69 @@ +//===--- AnalysisTest.cpp -------------------------------------------------===// +// +// 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 "clang-include-cleaner/Analysis.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Testing/TestAST.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Testing/Support/Annotations.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +namespace clang::include_cleaner { +namespace { +using testing::Pair; +using testing::UnorderedElementsAre; + +TEST(WalkUsed, Basic) { + // FIXME: Have a fixture for setting up tests. + llvm::Annotations HeaderCode(R"cpp( + void foo(); + namespace std { class vector {}; })cpp"); + llvm::Annotations Code(R"cpp( + void bar() { + $foo^foo(); + std::$vector^vector v; + } + )cpp"); + TestInputs Inputs(Code.code()); + Inputs.ExtraFiles["header.h"] = HeaderCode.code().str(); + Inputs.ExtraArgs.push_back("-include"); + Inputs.ExtraArgs.push_back("header.h"); + TestAST AST(Inputs); + + llvm::SmallVector TopLevelDecls; + for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) { + TopLevelDecls.emplace_back(D); + } + + auto &SM = AST.sourceManager(); + llvm::DenseMap> OffsetToProviders; + walkUsed(TopLevelDecls, [&](SourceLocation RefLoc, Symbol S, + llvm::ArrayRef
Providers) { + auto [FID, Offset] = SM.getDecomposedLoc(RefLoc); + EXPECT_EQ(FID, SM.getMainFileID()); + OffsetToProviders.try_emplace(Offset, Providers.vec()); + }); + auto HeaderFile = AST.fileManager().getFile("header.h").get(); + EXPECT_THAT( + OffsetToProviders, + UnorderedElementsAre( + Pair(Code.point("foo"), UnorderedElementsAre(Header(HeaderFile))), + Pair(Code.point("vector"), + UnorderedElementsAre(Header( + tooling::stdlib::Header::named("").value()))))); +} + +} // namespace +} // 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 @@ -5,6 +5,7 @@ add_custom_target(ClangIncludeCleanerUnitTests) add_unittest(ClangIncludeCleanerUnitTests ClangIncludeCleanerTests + AnalysisTest.cpp RecordTest.cpp WalkASTTest.cpp ) @@ -18,6 +19,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,15 +1,28 @@ +//===--- WalkASTTest.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/AST/ASTContext.h" #include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Frontend/TextDiagnostic.h" #include "clang/Testing/TestAST.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/Support/Error.h" #include "llvm/Testing/Support/Annotations.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include +#include -namespace clang { -namespace include_cleaner { +namespace clang::include_cleaner { namespace { +using testing::Pair; +using testing::UnorderedElementsAre; // Specifies a test of which symbols are referenced by a piece of code. // @@ -106,5 +119,4 @@ } } // namespace -} // namespace include_cleaner -} // namespace clang +} // namespace clang::include_cleaner