[clang-tools-extra] d1ec581 - [clangd] IncludeCleaner as a library: Find all references to symbols in the file
Kirill Bobyrev via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 18 01:10:15 PDT 2021
Author: Kirill Bobyrev
Date: 2021-08-18T10:08:35+02:00
New Revision: d1ec581ebfca9bd455ae1a467b2d4b52fc59092b
URL: https://github.com/llvm/llvm-project/commit/d1ec581ebfca9bd455ae1a467b2d4b52fc59092b
DIFF: https://github.com/llvm/llvm-project/commit/d1ec581ebfca9bd455ae1a467b2d4b52fc59092b.diff
LOG: [clangd] IncludeCleaner as a library: Find all references to symbols in the file
This is the first patch in an ongoing attempt of Include Cleaner: unused/missing
headere diagnostics, an IWYU-like functionality implementation for clangd. The
work is split into (mostly) distinct and parallelizable pieces:
- Finding all referenced locations (this patch).
- Finding all referenced locations of macros.
- Building IncludeGraph and marking headers as unused, used and directly used.
- Making use of the introduced library and add an option to use in clangd.
---
* Adding support for standard library headers (possibly through mapping
genfiles).
Based on https://reviews.llvm.org/D100540.
Reviewed By: sammccall
Differential Revision: https://reviews.llvm.org/D105426
Added:
clang-tools-extra/clangd/IncludeCleaner.cpp
clang-tools-extra/clangd/IncludeCleaner.h
clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
Modified:
clang-tools-extra/clangd/CMakeLists.txt
clang-tools-extra/clangd/unittests/CMakeLists.txt
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index 476de70654cf9..056a3272ebd11 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -83,6 +83,7 @@ add_clang_library(clangDaemon
HeaderSourceSwitch.cpp
HeuristicResolver.cpp
Hover.cpp
+ IncludeCleaner.cpp
IncludeFixer.cpp
InlayHints.cpp
JSONTransport.cpp
diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp
new file mode 100644
index 0000000000000..dbf19d4e08f95
--- /dev/null
+++ b/clang-tools-extra/clangd/IncludeCleaner.cpp
@@ -0,0 +1,112 @@
+//===--- IncludeCleaner.cpp - Unused/Missing Headers Analysis ---*- 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 "IncludeCleaner.h"
+#include "support/Logger.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/SourceLocation.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+/// Crawler traverses the AST and feeds in the locations of (sometimes
+/// implicitly) used symbols into \p Result.
+class ReferencedLocationCrawler
+ : public RecursiveASTVisitor<ReferencedLocationCrawler> {
+public:
+ ReferencedLocationCrawler(ReferencedLocations &Result) : Result(Result) {}
+
+ bool VisitDeclRefExpr(DeclRefExpr *DRE) {
+ add(DRE->getDecl());
+ add(DRE->getFoundDecl());
+ return true;
+ }
+
+ bool VisitMemberExpr(MemberExpr *ME) {
+ add(ME->getMemberDecl());
+ add(ME->getFoundDecl().getDecl());
+ return true;
+ }
+
+ bool VisitTagType(TagType *TT) {
+ add(TT->getDecl());
+ return true;
+ }
+
+ bool VisitCXXConstructExpr(CXXConstructExpr *CCE) {
+ add(CCE->getConstructor());
+ return true;
+ }
+
+ bool VisitTemplateSpecializationType(TemplateSpecializationType *TST) {
+ if (isNew(TST)) {
+ add(TST->getTemplateName().getAsTemplateDecl()); // Primary template.
+ add(TST->getAsCXXRecordDecl()); // Specialization
+ }
+ return true;
+ }
+
+ bool VisitTypedefType(TypedefType *TT) {
+ add(TT->getDecl());
+ return true;
+ }
+
+ // Consider types of any subexpression used, even if the type is not named.
+ // This is helpful in getFoo().bar(), where Foo must be complete.
+ // FIXME(kirillbobyrev): Should we tweak this? It may not be desirable to
+ // consider types "used" when they are not directly spelled in code.
+ bool VisitExpr(Expr *E) {
+ TraverseType(E->getType());
+ return true;
+ }
+
+ bool TraverseType(QualType T) {
+ if (isNew(T.getTypePtrOrNull())) { // don't care about quals
+ Base::TraverseType(T);
+ }
+ return true;
+ }
+
+ bool VisitUsingDecl(UsingDecl *D) {
+ for (const auto *Shadow : D->shadows()) {
+ add(Shadow->getTargetDecl());
+ }
+ return true;
+ }
+
+private:
+ using Base = RecursiveASTVisitor<ReferencedLocationCrawler>;
+
+ void add(const Decl *D) {
+ if (!D || !isNew(D->getCanonicalDecl())) {
+ return;
+ }
+ for (const Decl *Redecl : D->redecls()) {
+ Result.insert(Redecl->getLocation());
+ }
+ }
+
+ bool isNew(const void *P) { return P && Visited.insert(P).second; }
+
+ ReferencedLocations &Result;
+ llvm::DenseSet<const void *> Visited;
+};
+
+} // namespace
+
+ReferencedLocations findReferencedLocations(ParsedAST &AST) {
+ ReferencedLocations Result;
+ ReferencedLocationCrawler Crawler(Result);
+ Crawler.TraverseAST(AST.getASTContext());
+ // FIXME(kirillbobyrev): Handle macros.
+ return Result;
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/IncludeCleaner.h b/clang-tools-extra/clangd/IncludeCleaner.h
new file mode 100644
index 0000000000000..ca9f79c0f3b03
--- /dev/null
+++ b/clang-tools-extra/clangd/IncludeCleaner.h
@@ -0,0 +1,52 @@
+//===--- IncludeCleaner.h - Unused/Missing Headers Analysis -----*- 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Include Cleaner is clangd functionality for providing diagnostics for misuse
+/// of transitive headers and unused includes. It is inspired by
+/// Include-What-You-Use tool (https://include-what-you-use.org/). Our goal is
+/// to provide useful warnings in most popular scenarios but not 1:1 exact
+/// feature compatibility.
+///
+/// FIXME(kirillbobyrev): Add support for IWYU pragmas.
+/// FIXME(kirillbobyrev): Add support for standard library headers.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INCLUDE_CLEANER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INCLUDE_CLEANER_H
+
+#include "Headers.h"
+#include "ParsedAST.h"
+#include "clang/Basic/SourceLocation.h"
+#include "llvm/ADT/DenseSet.h"
+
+namespace clang {
+namespace clangd {
+
+using ReferencedLocations = llvm::DenseSet<SourceLocation>;
+/// Finds locations of all symbols used in the main file.
+///
+/// Uses RecursiveASTVisitor to go through main file AST and computes all the
+/// locations used symbols are coming from. Returned locations may be macro
+/// expansions, and are not resolved to their spelling/expansion location. These
+/// locations are later used to determine which headers should be marked as
+/// "used" and "directly used".
+///
+/// We use this to compute unused headers, so we:
+///
+/// - cover the whole file in a single traversal for efficiency
+/// - don't attempt to describe where symbols were referenced from in
+/// ambiguous cases (e.g. implicitly used symbols, multiple declarations)
+/// - err on the side of reporting all possible locations
+ReferencedLocations findReferencedLocations(ParsedAST &AST);
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INCLUDE_CLEANER_H
diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt
index 2f5a754f882ae..8fc4cf414bdb1 100644
--- a/clang-tools-extra/clangd/unittests/CMakeLists.txt
+++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt
@@ -58,6 +58,7 @@ add_unittest(ClangdUnitTests ClangdTests
HeadersTests.cpp
HeaderSourceSwitchTests.cpp
HoverTests.cpp
+ IncludeCleanerTests.cpp
IndexActionTests.cpp
IndexTests.cpp
InlayHintTests.cpp
diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
new file mode 100644
index 0000000000000..718076adc762a
--- /dev/null
+++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
@@ -0,0 +1,136 @@
+//===--- IncludeCleanerTests.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 "Annotations.h"
+#include "IncludeCleaner.h"
+#include "TestTU.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+TEST(IncludeCleaner, ReferencedLocations) {
+ struct TestCase {
+ std::string HeaderCode;
+ std::string MainCode;
+ };
+ TestCase Cases[] = {
+ // DeclRefExpr
+ {
+ "int ^x();",
+ "int y = x();",
+ },
+ // RecordDecl
+ {
+ "class ^X;",
+ "X *y;",
+ },
+ // TypedefType and UsingDecls
+ {
+ "using ^Integer = int;",
+ "Integer x;",
+ },
+ {
+ "namespace ns { struct ^X; struct ^X {}; }",
+ "using ns::X;",
+ },
+ {
+ "namespace ns { struct X; struct X {}; }",
+ "using namespace ns;",
+ },
+ {
+ "struct ^A {}; using B = A; using ^C = B;",
+ "C a;",
+ },
+ {
+ "typedef bool ^Y; template <typename T> struct ^X {};",
+ "X<Y> x;",
+ },
+ {
+ "struct Foo; struct ^Foo{}; typedef Foo ^Bar;",
+ "Bar b;",
+ },
+ // MemberExpr
+ {
+ "struct ^X{int ^a;}; X ^foo();",
+ "int y = foo().a;",
+ },
+ // Expr (type is traversed)
+ {
+ "class ^X{}; X ^foo();",
+ "auto bar() { return foo(); }",
+ },
+ // Redecls
+ {
+ "class ^X; class ^X{}; class ^X;",
+ "X *y;",
+ },
+ // Constructor
+ {
+ "struct ^X { ^X(int) {} int ^foo(); };",
+ "auto x = X(42); auto y = x.foo();",
+ },
+ // Static function
+ {
+ "struct ^X { static bool ^foo(); }; bool X::^foo() {}",
+ "auto b = X::foo();",
+ },
+ // TemplateRecordDecl
+ {
+ "template <typename> class ^X;",
+ "X<int> *y;",
+ },
+ // Type name not spelled out in code
+ {
+ "class ^X{}; X ^getX();",
+ "auto x = getX();",
+ },
+ // Enums
+ {
+ "enum ^Color { ^Red = 42, Green = 9000};",
+ "int MyColor = Red;",
+ },
+ {
+ "struct ^X { enum ^Language { ^CXX = 42, Python = 9000}; };",
+ "int Lang = X::CXX;",
+ },
+ {
+ // When a type is resolved via a using declaration, the
+ // UsingShadowDecl is not referenced in the AST.
+ // Compare to TypedefType, or DeclRefExpr::getFoundDecl().
+ // ^
+ "namespace ns { class ^X; }; using ns::X;",
+ "X *y;",
+ }};
+ for (const TestCase &T : Cases) {
+ TestTU TU;
+ TU.Code = T.MainCode;
+ Annotations Header(T.HeaderCode);
+ TU.HeaderCode = Header.code().str();
+ auto AST = TU.build();
+
+ std::vector<Position> Points;
+ for (const auto &Loc : findReferencedLocations(AST)) {
+ if (AST.getSourceManager().getBufferName(Loc).endswith(
+ TU.HeaderFilename)) {
+ Points.push_back(offsetToPosition(
+ TU.HeaderCode, AST.getSourceManager().getFileOffset(Loc)));
+ }
+ }
+ llvm::sort(Points);
+
+ EXPECT_EQ(Points, Header.points()) << T.HeaderCode << "\n---\n"
+ << T.MainCode;
+ }
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
More information about the cfe-commits
mailing list