mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-02-17 16:31:02 +00:00
[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:
parent
0104cc85b1
commit
d1ec581ebf
@ -83,6 +83,7 @@ add_clang_library(clangDaemon
|
||||
HeaderSourceSwitch.cpp
|
||||
HeuristicResolver.cpp
|
||||
Hover.cpp
|
||||
IncludeCleaner.cpp
|
||||
IncludeFixer.cpp
|
||||
InlayHints.cpp
|
||||
JSONTransport.cpp
|
||||
|
112
clang-tools-extra/clangd/IncludeCleaner.cpp
Normal file
112
clang-tools-extra/clangd/IncludeCleaner.cpp
Normal 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
|
52
clang-tools-extra/clangd/IncludeCleaner.h
Normal file
52
clang-tools-extra/clangd/IncludeCleaner.h
Normal 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
|
@ -58,6 +58,7 @@ add_unittest(ClangdUnitTests ClangdTests
|
||||
HeadersTests.cpp
|
||||
HeaderSourceSwitchTests.cpp
|
||||
HoverTests.cpp
|
||||
IncludeCleanerTests.cpp
|
||||
IndexActionTests.cpp
|
||||
IndexTests.cpp
|
||||
InlayHintTests.cpp
|
||||
|
136
clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
Normal file
136
clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
Normal 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
|
Loading…
x
Reference in New Issue
Block a user