forked from OSchip/llvm-project
270 lines
10 KiB
C++
270 lines
10 KiB
C++
//===-- FindAllSymbols.cpp - find all symbols--------------------*- 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 "FindAllSymbols.h"
|
|
#include "HeaderMapCollector.h"
|
|
#include "PathConfig.h"
|
|
#include "SymbolInfo.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/ADT/Optional.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace find_all_symbols {
|
|
namespace {
|
|
|
|
AST_MATCHER(EnumConstantDecl, isInScopedEnum) {
|
|
if (const auto *ED = dyn_cast<EnumDecl>(Node.getDeclContext()))
|
|
return ED->isScoped();
|
|
return false;
|
|
}
|
|
|
|
AST_POLYMORPHIC_MATCHER(isFullySpecialized,
|
|
AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl,
|
|
CXXRecordDecl)) {
|
|
if (Node.getTemplateSpecializationKind() == TSK_ExplicitSpecialization) {
|
|
bool IsPartialSpecialization =
|
|
llvm::isa<VarTemplatePartialSpecializationDecl>(Node) ||
|
|
llvm::isa<ClassTemplatePartialSpecializationDecl>(Node);
|
|
return !IsPartialSpecialization;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<SymbolInfo::Context> GetContexts(const NamedDecl *ND) {
|
|
std::vector<SymbolInfo::Context> Contexts;
|
|
for (const auto *Context = ND->getDeclContext(); Context;
|
|
Context = Context->getParent()) {
|
|
if (llvm::isa<TranslationUnitDecl>(Context) ||
|
|
llvm::isa<LinkageSpecDecl>(Context))
|
|
break;
|
|
|
|
assert(llvm::isa<NamedDecl>(Context) &&
|
|
"Expect Context to be a NamedDecl");
|
|
if (const auto *NSD = dyn_cast<NamespaceDecl>(Context)) {
|
|
if (!NSD->isInlineNamespace())
|
|
Contexts.emplace_back(SymbolInfo::ContextType::Namespace,
|
|
NSD->getName().str());
|
|
} else if (const auto *ED = dyn_cast<EnumDecl>(Context)) {
|
|
Contexts.emplace_back(SymbolInfo::ContextType::EnumDecl,
|
|
ED->getName().str());
|
|
} else {
|
|
const auto *RD = cast<RecordDecl>(Context);
|
|
Contexts.emplace_back(SymbolInfo::ContextType::Record,
|
|
RD->getName().str());
|
|
}
|
|
}
|
|
return Contexts;
|
|
}
|
|
|
|
llvm::Optional<SymbolInfo>
|
|
CreateSymbolInfo(const NamedDecl *ND, const SourceManager &SM,
|
|
const HeaderMapCollector *Collector) {
|
|
SymbolInfo::SymbolKind Type;
|
|
if (llvm::isa<VarDecl>(ND)) {
|
|
Type = SymbolInfo::SymbolKind::Variable;
|
|
} else if (llvm::isa<FunctionDecl>(ND)) {
|
|
Type = SymbolInfo::SymbolKind::Function;
|
|
} else if (llvm::isa<TypedefNameDecl>(ND)) {
|
|
Type = SymbolInfo::SymbolKind::TypedefName;
|
|
} else if (llvm::isa<EnumConstantDecl>(ND)) {
|
|
Type = SymbolInfo::SymbolKind::EnumConstantDecl;
|
|
} else if (llvm::isa<EnumDecl>(ND)) {
|
|
Type = SymbolInfo::SymbolKind::EnumDecl;
|
|
// Ignore anonymous enum declarations.
|
|
if (ND->getName().empty())
|
|
return llvm::None;
|
|
} else {
|
|
assert(llvm::isa<RecordDecl>(ND) &&
|
|
"Matched decl must be one of VarDecl, "
|
|
"FunctionDecl, TypedefNameDecl, EnumConstantDecl, "
|
|
"EnumDecl and RecordDecl!");
|
|
// C-style record decl can have empty name, e.g "struct { ... } var;".
|
|
if (ND->getName().empty())
|
|
return llvm::None;
|
|
Type = SymbolInfo::SymbolKind::Class;
|
|
}
|
|
|
|
SourceLocation Loc = SM.getExpansionLoc(ND->getLocation());
|
|
if (!Loc.isValid()) {
|
|
llvm::errs() << "Declaration " << ND->getDeclName() << "("
|
|
<< ND->getDeclKindName()
|
|
<< ") has invalid declaration location.";
|
|
return llvm::None;
|
|
}
|
|
|
|
std::string FilePath = getIncludePath(SM, Loc, Collector);
|
|
if (FilePath.empty()) return llvm::None;
|
|
|
|
return SymbolInfo(ND->getNameAsString(), Type, FilePath, GetContexts(ND));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void FindAllSymbols::registerMatchers(MatchFinder *MatchFinder) {
|
|
// FIXME: Handle specialization.
|
|
auto IsInSpecialization = hasAncestor(
|
|
decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()),
|
|
functionDecl(isExplicitTemplateSpecialization()))));
|
|
|
|
// Matchers for both C and C++.
|
|
// We only match symbols from header files, i.e. not from main files (see
|
|
// function's comment for detailed explanation).
|
|
auto CommonFilter =
|
|
allOf(unless(isImplicit()), unless(isExpansionInMainFile()));
|
|
|
|
auto HasNSOrTUCtxMatcher =
|
|
hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl()));
|
|
|
|
// We need separate rules for C record types and C++ record types since some
|
|
// template related matchers are inapplicable on C record declarations.
|
|
//
|
|
// Matchers specific to C++ code.
|
|
// All declarations should be in namespace or translation unit.
|
|
auto CCMatcher =
|
|
allOf(HasNSOrTUCtxMatcher, unless(IsInSpecialization),
|
|
unless(ast_matchers::isTemplateInstantiation()),
|
|
unless(isInstantiated()), unless(isFullySpecialized()));
|
|
|
|
// Matchers specific to code in extern "C" {...}.
|
|
auto ExternCMatcher = hasDeclContext(linkageSpecDecl());
|
|
|
|
// Matchers for variable declarations.
|
|
//
|
|
// In most cases, `ParmVarDecl` is filtered out by hasDeclContext(...)
|
|
// matcher since the declaration context is usually `MethodDecl`. However,
|
|
// this assumption does not hold for parameters of a function pointer
|
|
// parameter.
|
|
// For example, consider a function declaration:
|
|
// void Func(void (*)(float), int);
|
|
// The float parameter of the function pointer has an empty name, and its
|
|
// declaration context is an anonymous namespace; therefore, it won't be
|
|
// filtered out by our matchers above.
|
|
auto Vars = varDecl(CommonFilter, anyOf(ExternCMatcher, CCMatcher),
|
|
unless(parmVarDecl()));
|
|
|
|
// Matchers for C-style record declarations in extern "C" {...}.
|
|
auto CRecords = recordDecl(CommonFilter, ExternCMatcher, isDefinition());
|
|
// Matchers for C++ record declarations.
|
|
auto CXXRecords = cxxRecordDecl(CommonFilter, CCMatcher, isDefinition());
|
|
|
|
// Matchers for function declarations.
|
|
// We want to exclude friend declaration, but the `DeclContext` of a friend
|
|
// function declaration is not the class in which it is declared, so we need
|
|
// to explicitly check if the parent is a `friendDecl`.
|
|
auto Functions = functionDecl(CommonFilter, unless(hasParent(friendDecl())),
|
|
anyOf(ExternCMatcher, CCMatcher));
|
|
|
|
// Matcher for typedef and type alias declarations.
|
|
//
|
|
// typedef and type alias can come from C-style headers and C++ headers.
|
|
// For C-style headers, `DeclContxet` can be either `TranslationUnitDecl`
|
|
// or `LinkageSpecDecl`.
|
|
// For C++ headers, `DeclContext ` can be either `TranslationUnitDecl`
|
|
// or `NamespaceDecl`.
|
|
// With the following context matcher, we can match `typedefNameDecl` from
|
|
// both C-style headers and C++ headers (except for those in classes).
|
|
// "cc_matchers" are not included since template-related matchers are not
|
|
// applicable on `TypedefNameDecl`.
|
|
auto Typedefs =
|
|
typedefNameDecl(CommonFilter, anyOf(HasNSOrTUCtxMatcher,
|
|
hasDeclContext(linkageSpecDecl())));
|
|
|
|
// Matchers for enum declarations.
|
|
auto Enums = enumDecl(CommonFilter, isDefinition(),
|
|
anyOf(HasNSOrTUCtxMatcher, ExternCMatcher));
|
|
|
|
// Matchers for enum constant declarations.
|
|
// We only match the enum constants in non-scoped enum declarations which are
|
|
// inside toplevel translation unit or a namespace.
|
|
auto EnumConstants = enumConstantDecl(
|
|
CommonFilter, unless(isInScopedEnum()),
|
|
anyOf(hasDeclContext(enumDecl(HasNSOrTUCtxMatcher)), ExternCMatcher));
|
|
|
|
// Most of the time we care about all matchable decls, or all types.
|
|
auto Types = namedDecl(anyOf(CRecords, CXXRecords, Enums));
|
|
auto Decls = namedDecl(anyOf(CRecords, CXXRecords, Enums, Typedefs, Vars,
|
|
EnumConstants, Functions));
|
|
|
|
// We want eligible decls bound to "decl"...
|
|
MatchFinder->addMatcher(Decls.bind("decl"), this);
|
|
|
|
// ... and all uses of them bound to "use". These have many cases:
|
|
// Uses of values/functions: these generate a declRefExpr.
|
|
MatchFinder->addMatcher(
|
|
declRefExpr(isExpansionInMainFile(), to(Decls.bind("use"))), this);
|
|
// Uses of function templates:
|
|
MatchFinder->addMatcher(
|
|
declRefExpr(isExpansionInMainFile(),
|
|
to(functionDecl(hasParent(
|
|
functionTemplateDecl(has(Functions.bind("use"))))))),
|
|
this);
|
|
|
|
// Uses of most types: just look at what the typeLoc refers to.
|
|
MatchFinder->addMatcher(
|
|
typeLoc(isExpansionInMainFile(),
|
|
loc(qualType(hasDeclaration(Types.bind("use"))))),
|
|
this);
|
|
// Uses of typedefs: these are often transparent to hasDeclaration, so we need
|
|
// to handle them explicitly.
|
|
MatchFinder->addMatcher(
|
|
typeLoc(isExpansionInMainFile(),
|
|
loc(typedefType(hasDeclaration(Typedefs.bind("use"))))),
|
|
this);
|
|
// Uses of class templates:
|
|
// The typeLoc names the templateSpecializationType. Its declaration is the
|
|
// ClassTemplateDecl, which contains the CXXRecordDecl we want.
|
|
MatchFinder->addMatcher(
|
|
typeLoc(isExpansionInMainFile(),
|
|
loc(templateSpecializationType(hasDeclaration(
|
|
classTemplateSpecializationDecl(hasSpecializedTemplate(
|
|
classTemplateDecl(has(CXXRecords.bind("use"))))))))),
|
|
this);
|
|
}
|
|
|
|
void FindAllSymbols::run(const MatchFinder::MatchResult &Result) {
|
|
// Ignore Results in failing TUs.
|
|
if (Result.Context->getDiagnostics().hasErrorOccurred()) {
|
|
return;
|
|
}
|
|
|
|
SymbolInfo::Signals Signals;
|
|
const NamedDecl *ND;
|
|
if ((ND = Result.Nodes.getNodeAs<NamedDecl>("use")))
|
|
Signals.Used = 1;
|
|
else if ((ND = Result.Nodes.getNodeAs<NamedDecl>("decl")))
|
|
Signals.Seen = 1;
|
|
else
|
|
assert(false && "Must match a NamedDecl!");
|
|
|
|
const SourceManager *SM = Result.SourceManager;
|
|
if (auto Symbol = CreateSymbolInfo(ND, *SM, Collector)) {
|
|
Filename =
|
|
std::string(SM->getFileEntryForID(SM->getMainFileID())->getName());
|
|
FileSymbols[*Symbol] += Signals;
|
|
}
|
|
}
|
|
|
|
void FindAllSymbols::onEndOfTranslationUnit() {
|
|
if (Filename != "") {
|
|
Reporter->reportSymbols(Filename, FileSymbols);
|
|
FileSymbols.clear();
|
|
Filename = "";
|
|
}
|
|
}
|
|
|
|
} // namespace find_all_symbols
|
|
} // namespace clang
|