[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
This commit is contained in:
Kirill Bobyrev 2021-08-18 10:08:08 +02:00
parent 0104cc85b1
commit d1ec581ebf
No known key found for this signature in database
GPG key ID: 2307C055C8384FA0
5 changed files with 302 additions and 0 deletions

View file

@ -83,6 +83,7 @@ add_clang_library(clangDaemon
HeaderSourceSwitch.cpp
HeuristicResolver.cpp
Hover.cpp
IncludeCleaner.cpp
IncludeFixer.cpp
InlayHints.cpp
JSONTransport.cpp

View file

@ -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

View file

@ -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

View file

@ -58,6 +58,7 @@ add_unittest(ClangdUnitTests ClangdTests
HeadersTests.cpp
HeaderSourceSwitchTests.cpp
HoverTests.cpp
IncludeCleanerTests.cpp
IndexActionTests.cpp
IndexTests.cpp
InlayHintTests.cpp

View file

@ -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