1 //===-- FindAllSymbols.cpp - find all symbols--------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "FindAllSymbols.h"
10 #include "HeaderMapCollector.h"
11 #include "PathConfig.h"
12 #include "SymbolInfo.h"
13 #include "clang/AST/Decl.h"
14 #include "clang/AST/DeclCXX.h"
15 #include "clang/AST/Type.h"
16 #include "clang/ASTMatchers/ASTMatchFinder.h"
17 #include "clang/ASTMatchers/ASTMatchers.h"
18 #include "clang/Tooling/Tooling.h"
19 #include "llvm/ADT/Optional.h"
20 #include "llvm/Support/FileSystem.h"
22 using namespace clang::ast_matchers
;
25 namespace find_all_symbols
{
28 AST_MATCHER(EnumConstantDecl
, isInScopedEnum
) {
29 if (const auto *ED
= dyn_cast
<EnumDecl
>(Node
.getDeclContext()))
30 return ED
->isScoped();
34 AST_POLYMORPHIC_MATCHER(isFullySpecialized
,
35 AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl
, VarDecl
,
37 if (Node
.getTemplateSpecializationKind() == TSK_ExplicitSpecialization
) {
38 bool IsPartialSpecialization
=
39 llvm::isa
<VarTemplatePartialSpecializationDecl
>(Node
) ||
40 llvm::isa
<ClassTemplatePartialSpecializationDecl
>(Node
);
41 return !IsPartialSpecialization
;
46 std::vector
<SymbolInfo::Context
> GetContexts(const NamedDecl
*ND
) {
47 std::vector
<SymbolInfo::Context
> Contexts
;
48 for (const auto *Context
= ND
->getDeclContext(); Context
;
49 Context
= Context
->getParent()) {
50 if (llvm::isa
<TranslationUnitDecl
>(Context
) ||
51 llvm::isa
<LinkageSpecDecl
>(Context
))
54 assert(llvm::isa
<NamedDecl
>(Context
) &&
55 "Expect Context to be a NamedDecl");
56 if (const auto *NSD
= dyn_cast
<NamespaceDecl
>(Context
)) {
57 if (!NSD
->isInlineNamespace())
58 Contexts
.emplace_back(SymbolInfo::ContextType::Namespace
,
59 NSD
->getName().str());
60 } else if (const auto *ED
= dyn_cast
<EnumDecl
>(Context
)) {
61 Contexts
.emplace_back(SymbolInfo::ContextType::EnumDecl
,
64 const auto *RD
= cast
<RecordDecl
>(Context
);
65 Contexts
.emplace_back(SymbolInfo::ContextType::Record
,
72 llvm::Optional
<SymbolInfo
>
73 CreateSymbolInfo(const NamedDecl
*ND
, const SourceManager
&SM
,
74 const HeaderMapCollector
*Collector
) {
75 SymbolInfo::SymbolKind Type
;
76 if (llvm::isa
<VarDecl
>(ND
)) {
77 Type
= SymbolInfo::SymbolKind::Variable
;
78 } else if (llvm::isa
<FunctionDecl
>(ND
)) {
79 Type
= SymbolInfo::SymbolKind::Function
;
80 } else if (llvm::isa
<TypedefNameDecl
>(ND
)) {
81 Type
= SymbolInfo::SymbolKind::TypedefName
;
82 } else if (llvm::isa
<EnumConstantDecl
>(ND
)) {
83 Type
= SymbolInfo::SymbolKind::EnumConstantDecl
;
84 } else if (llvm::isa
<EnumDecl
>(ND
)) {
85 Type
= SymbolInfo::SymbolKind::EnumDecl
;
86 // Ignore anonymous enum declarations.
87 if (ND
->getName().empty())
90 assert(llvm::isa
<RecordDecl
>(ND
) &&
91 "Matched decl must be one of VarDecl, "
92 "FunctionDecl, TypedefNameDecl, EnumConstantDecl, "
93 "EnumDecl and RecordDecl!");
94 // C-style record decl can have empty name, e.g "struct { ... } var;".
95 if (ND
->getName().empty())
97 Type
= SymbolInfo::SymbolKind::Class
;
100 SourceLocation Loc
= SM
.getExpansionLoc(ND
->getLocation());
101 if (!Loc
.isValid()) {
102 llvm::errs() << "Declaration " << ND
->getDeclName() << "("
103 << ND
->getDeclKindName()
104 << ") has invalid declaration location.";
108 std::string FilePath
= getIncludePath(SM
, Loc
, Collector
);
109 if (FilePath
.empty()) return llvm::None
;
111 return SymbolInfo(ND
->getNameAsString(), Type
, FilePath
, GetContexts(ND
));
116 void FindAllSymbols::registerMatchers(MatchFinder
*MatchFinder
) {
117 // FIXME: Handle specialization.
118 auto IsInSpecialization
= hasAncestor(
119 decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()),
120 functionDecl(isExplicitTemplateSpecialization()))));
122 // Matchers for both C and C++.
123 // We only match symbols from header files, i.e. not from main files (see
124 // function's comment for detailed explanation).
126 allOf(unless(isImplicit()), unless(isExpansionInMainFile()));
128 auto HasNSOrTUCtxMatcher
=
129 hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl()));
131 // We need separate rules for C record types and C++ record types since some
132 // template related matchers are inapplicable on C record declarations.
134 // Matchers specific to C++ code.
135 // All declarations should be in namespace or translation unit.
137 allOf(HasNSOrTUCtxMatcher
, unless(IsInSpecialization
),
138 unless(ast_matchers::isTemplateInstantiation()),
139 unless(isInstantiated()), unless(isFullySpecialized()));
141 // Matchers specific to code in extern "C" {...}.
142 auto ExternCMatcher
= hasDeclContext(linkageSpecDecl());
144 // Matchers for variable declarations.
146 // In most cases, `ParmVarDecl` is filtered out by hasDeclContext(...)
147 // matcher since the declaration context is usually `MethodDecl`. However,
148 // this assumption does not hold for parameters of a function pointer
150 // For example, consider a function declaration:
151 // void Func(void (*)(float), int);
152 // The float parameter of the function pointer has an empty name, and its
153 // declaration context is an anonymous namespace; therefore, it won't be
154 // filtered out by our matchers above.
155 auto Vars
= varDecl(CommonFilter
, anyOf(ExternCMatcher
, CCMatcher
),
156 unless(parmVarDecl()));
158 // Matchers for C-style record declarations in extern "C" {...}.
159 auto CRecords
= recordDecl(CommonFilter
, ExternCMatcher
, isDefinition());
160 // Matchers for C++ record declarations.
161 auto CXXRecords
= cxxRecordDecl(CommonFilter
, CCMatcher
, isDefinition());
163 // Matchers for function declarations.
164 // We want to exclude friend declaration, but the `DeclContext` of a friend
165 // function declaration is not the class in which it is declared, so we need
166 // to explicitly check if the parent is a `friendDecl`.
167 auto Functions
= functionDecl(CommonFilter
, unless(hasParent(friendDecl())),
168 anyOf(ExternCMatcher
, CCMatcher
));
170 // Matcher for typedef and type alias declarations.
172 // typedef and type alias can come from C-style headers and C++ headers.
173 // For C-style headers, `DeclContxet` can be either `TranslationUnitDecl`
174 // or `LinkageSpecDecl`.
175 // For C++ headers, `DeclContext ` can be either `TranslationUnitDecl`
176 // or `NamespaceDecl`.
177 // With the following context matcher, we can match `typedefNameDecl` from
178 // both C-style headers and C++ headers (except for those in classes).
179 // "cc_matchers" are not included since template-related matchers are not
180 // applicable on `TypedefNameDecl`.
182 typedefNameDecl(CommonFilter
, anyOf(HasNSOrTUCtxMatcher
,
183 hasDeclContext(linkageSpecDecl())));
185 // Matchers for enum declarations.
186 auto Enums
= enumDecl(CommonFilter
, isDefinition(),
187 anyOf(HasNSOrTUCtxMatcher
, ExternCMatcher
));
189 // Matchers for enum constant declarations.
190 // We only match the enum constants in non-scoped enum declarations which are
191 // inside toplevel translation unit or a namespace.
192 auto EnumConstants
= enumConstantDecl(
193 CommonFilter
, unless(isInScopedEnum()),
194 anyOf(hasDeclContext(enumDecl(HasNSOrTUCtxMatcher
)), ExternCMatcher
));
196 // Most of the time we care about all matchable decls, or all types.
197 auto Types
= namedDecl(anyOf(CRecords
, CXXRecords
, Enums
));
198 auto Decls
= namedDecl(anyOf(CRecords
, CXXRecords
, Enums
, Typedefs
, Vars
,
199 EnumConstants
, Functions
));
201 // We want eligible decls bound to "decl"...
202 MatchFinder
->addMatcher(Decls
.bind("decl"), this);
204 // ... and all uses of them bound to "use". These have many cases:
205 // Uses of values/functions: these generate a declRefExpr.
206 MatchFinder
->addMatcher(
207 declRefExpr(isExpansionInMainFile(), to(Decls
.bind("use"))), this);
208 // Uses of function templates:
209 MatchFinder
->addMatcher(
210 declRefExpr(isExpansionInMainFile(),
211 to(functionDecl(hasParent(
212 functionTemplateDecl(has(Functions
.bind("use"))))))),
215 // Uses of most types: just look at what the typeLoc refers to.
216 MatchFinder
->addMatcher(
217 typeLoc(isExpansionInMainFile(),
218 loc(qualType(allOf(unless(elaboratedType()),
219 hasDeclaration(Types
.bind("use")))))),
221 // Uses of typedefs: these are often transparent to hasDeclaration, so we need
222 // to handle them explicitly.
223 MatchFinder
->addMatcher(
224 typeLoc(isExpansionInMainFile(),
225 loc(typedefType(hasDeclaration(Typedefs
.bind("use"))))),
227 // Uses of class templates:
228 // The typeLoc names the templateSpecializationType. Its declaration is the
229 // ClassTemplateDecl, which contains the CXXRecordDecl we want.
230 MatchFinder
->addMatcher(
231 typeLoc(isExpansionInMainFile(),
232 loc(templateSpecializationType(hasDeclaration(
233 classTemplateSpecializationDecl(hasSpecializedTemplate(
234 classTemplateDecl(has(CXXRecords
.bind("use"))))))))),
238 void FindAllSymbols::run(const MatchFinder::MatchResult
&Result
) {
239 // Ignore Results in failing TUs.
240 if (Result
.Context
->getDiagnostics().hasErrorOccurred()) {
244 SymbolInfo::Signals Signals
;
246 if ((ND
= Result
.Nodes
.getNodeAs
<NamedDecl
>("use")))
248 else if ((ND
= Result
.Nodes
.getNodeAs
<NamedDecl
>("decl")))
251 assert(false && "Must match a NamedDecl!");
253 const SourceManager
*SM
= Result
.SourceManager
;
254 if (auto Symbol
= CreateSymbolInfo(ND
, *SM
, Collector
)) {
256 std::string(SM
->getFileEntryForID(SM
->getMainFileID())->getName());
257 FileSymbols
[*Symbol
] += Signals
;
261 void FindAllSymbols::onEndOfTranslationUnit() {
262 if (Filename
!= "") {
263 Reporter
->reportSymbols(Filename
, FileSymbols
);
269 } // namespace find_all_symbols