1//===-- FindAllSymbols.cpp - find all symbols--------------------*- C++ -*-===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
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/Support/FileSystem.h"
20#include <optional>
21
22using namespace clang::ast_matchers;
23
24namespace clang {
25namespace find_all_symbols {
26namespace {
27
28AST_MATCHER(EnumConstantDecl, isInScopedEnum) {
29 if (const auto *ED = dyn_cast<EnumDecl>(Node.getDeclContext()))
30 return ED->isScoped();
31 return false;
32}
33
34AST_POLYMORPHIC_MATCHER(isFullySpecialized,
35 AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl,
36 CXXRecordDecl)) {
37 if (Node.getTemplateSpecializationKind() == TSK_ExplicitSpecialization) {
38 bool IsPartialSpecialization =
39 llvm::isa<VarTemplatePartialSpecializationDecl>(Node) ||
40 llvm::isa<ClassTemplatePartialSpecializationDecl>(Node);
41 return !IsPartialSpecialization;
42 }
43 return false;
44}
45
46std::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))
52 break;
53
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,
62 ED->getName().str());
63 } else {
64 const auto *RD = cast<RecordDecl>(Context);
65 Contexts.emplace_back(SymbolInfo::ContextType::Record,
66 RD->getName().str());
67 }
68 }
69 return Contexts;
70}
71
72std::optional<SymbolInfo>
73CreateSymbolInfo(const NamedDecl *ND, const SourceManager &SM,
74 const HeaderMapCollector *Collector) {
75 SymbolInfo::SymbolKind Type;
76 if (llvm::isa<VarDecl>(Val: ND)) {
77 Type = SymbolInfo::SymbolKind::Variable;
78 } else if (llvm::isa<FunctionDecl>(Val: ND)) {
79 Type = SymbolInfo::SymbolKind::Function;
80 } else if (llvm::isa<TypedefNameDecl>(Val: ND)) {
81 Type = SymbolInfo::SymbolKind::TypedefName;
82 } else if (llvm::isa<EnumConstantDecl>(Val: ND)) {
83 Type = SymbolInfo::SymbolKind::EnumConstantDecl;
84 } else if (llvm::isa<EnumDecl>(Val: ND)) {
85 Type = SymbolInfo::SymbolKind::EnumDecl;
86 // Ignore anonymous enum declarations.
87 if (ND->getName().empty())
88 return std::nullopt;
89 } else {
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())
96 return std::nullopt;
97 Type = SymbolInfo::SymbolKind::Class;
98 }
99
100 SourceLocation Loc = SM.getExpansionLoc(Loc: ND->getLocation());
101 if (!Loc.isValid()) {
102 llvm::errs() << "Declaration " << ND->getDeclName() << "("
103 << ND->getDeclKindName()
104 << ") has invalid declaration location.";
105 return std::nullopt;
106 }
107
108 std::string FilePath = getIncludePath(SM, Loc, Collector);
109 if (FilePath.empty())
110 return std::nullopt;
111
112 return SymbolInfo(ND->getNameAsString(), Type, FilePath, GetContexts(ND));
113}
114
115} // namespace
116
117void FindAllSymbols::registerMatchers(MatchFinder *MatchFinder) {
118 // FIXME: Handle specialization.
119 auto IsInSpecialization = hasAncestor(
120 decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()),
121 functionDecl(isExplicitTemplateSpecialization()))));
122
123 // Matchers for both C and C++.
124 // We only match symbols from header files, i.e. not from main files (see
125 // function's comment for detailed explanation).
126 auto CommonFilter =
127 allOf(unless(isImplicit()), unless(isExpansionInMainFile()));
128
129 auto HasNSOrTUCtxMatcher =
130 hasDeclContext(InnerMatcher: anyOf(namespaceDecl(), translationUnitDecl()));
131
132 // We need separate rules for C record types and C++ record types since some
133 // template related matchers are inapplicable on C record declarations.
134 //
135 // Matchers specific to C++ code.
136 // All declarations should be in namespace or translation unit.
137 auto CCMatcher =
138 allOf(HasNSOrTUCtxMatcher, unless(IsInSpecialization),
139 unless(ast_matchers::isTemplateInstantiation()),
140 unless(isInstantiated()), unless(isFullySpecialized()));
141
142 // Matchers specific to code in extern "C" {...}.
143 auto ExternCMatcher = hasDeclContext(InnerMatcher: linkageSpecDecl());
144
145 // Matchers for variable declarations.
146 //
147 // In most cases, `ParmVarDecl` is filtered out by hasDeclContext(...)
148 // matcher since the declaration context is usually `MethodDecl`. However,
149 // this assumption does not hold for parameters of a function pointer
150 // parameter.
151 // For example, consider a function declaration:
152 // void Func(void (*)(float), int);
153 // The float parameter of the function pointer has an empty name, and its
154 // declaration context is an anonymous namespace; therefore, it won't be
155 // filtered out by our matchers above.
156 auto Vars = varDecl(CommonFilter, anyOf(ExternCMatcher, CCMatcher),
157 unless(parmVarDecl()));
158
159 // Matchers for C-style record declarations in extern "C" {...}.
160 auto CRecords = recordDecl(CommonFilter, ExternCMatcher, isDefinition());
161 // Matchers for C++ record declarations.
162 auto CXXRecords = cxxRecordDecl(CommonFilter, CCMatcher, isDefinition());
163
164 // Matchers for function declarations.
165 // We want to exclude friend declaration, but the `DeclContext` of a friend
166 // function declaration is not the class in which it is declared, so we need
167 // to explicitly check if the parent is a `friendDecl`.
168 auto Functions = functionDecl(CommonFilter, unless(hasParent(friendDecl())),
169 anyOf(ExternCMatcher, CCMatcher));
170
171 // Matcher for typedef and type alias declarations.
172 //
173 // typedef and type alias can come from C-style headers and C++ headers.
174 // For C-style headers, `DeclContxet` can be either `TranslationUnitDecl`
175 // or `LinkageSpecDecl`.
176 // For C++ headers, `DeclContext ` can be either `TranslationUnitDecl`
177 // or `NamespaceDecl`.
178 // With the following context matcher, we can match `typedefNameDecl` from
179 // both C-style headers and C++ headers (except for those in classes).
180 // "cc_matchers" are not included since template-related matchers are not
181 // applicable on `TypedefNameDecl`.
182 auto Typedefs =
183 typedefNameDecl(CommonFilter, anyOf(HasNSOrTUCtxMatcher,
184 hasDeclContext(InnerMatcher: linkageSpecDecl())));
185
186 // Matchers for enum declarations.
187 auto Enums = enumDecl(CommonFilter, isDefinition(),
188 anyOf(HasNSOrTUCtxMatcher, ExternCMatcher));
189
190 // Matchers for enum constant declarations.
191 // We only match the enum constants in non-scoped enum declarations which are
192 // inside toplevel translation unit or a namespace.
193 auto EnumConstants = enumConstantDecl(
194 CommonFilter, unless(isInScopedEnum()),
195 anyOf(hasDeclContext(InnerMatcher: enumDecl(HasNSOrTUCtxMatcher)), ExternCMatcher));
196
197 // Most of the time we care about all matchable decls, or all types.
198 auto Types = namedDecl(anyOf(CRecords, CXXRecords, Enums));
199 auto Decls = namedDecl(anyOf(CRecords, CXXRecords, Enums, Typedefs, Vars,
200 EnumConstants, Functions));
201
202 // We want eligible decls bound to "decl"...
203 MatchFinder->addMatcher(NodeMatch: Decls.bind(ID: "decl"), Action: this);
204
205 // ... and all uses of them bound to "use". These have many cases:
206 // Uses of values/functions: these generate a declRefExpr.
207 MatchFinder->addMatcher(
208 NodeMatch: declRefExpr(isExpansionInMainFile(), to(InnerMatcher: Decls.bind(ID: "use"))), Action: this);
209 // Uses of function templates:
210 MatchFinder->addMatcher(
211 NodeMatch: declRefExpr(isExpansionInMainFile(),
212 to(InnerMatcher: functionDecl(hasParent(
213 functionTemplateDecl(has(Functions.bind(ID: "use"))))))),
214 Action: this);
215
216 // Uses of most types: just look at what the typeLoc refers to.
217 MatchFinder->addMatcher(
218 NodeMatch: typeLoc(isExpansionInMainFile(),
219 loc(InnerMatcher: qualType(allOf(unless(elaboratedType()),
220 hasDeclaration(InnerMatcher: Types.bind(ID: "use")))))),
221 Action: this);
222 // Uses of typedefs: these are often transparent to hasDeclaration, so we need
223 // to handle them explicitly.
224 MatchFinder->addMatcher(
225 NodeMatch: typeLoc(isExpansionInMainFile(),
226 loc(InnerMatcher: typedefType(hasDeclaration(InnerMatcher: Typedefs.bind(ID: "use"))))),
227 Action: this);
228 // Uses of class templates:
229 // The typeLoc names the templateSpecializationType. Its declaration is the
230 // ClassTemplateDecl, which contains the CXXRecordDecl we want.
231 MatchFinder->addMatcher(
232 NodeMatch: typeLoc(isExpansionInMainFile(),
233 loc(InnerMatcher: templateSpecializationType(hasDeclaration(
234 InnerMatcher: classTemplateSpecializationDecl(hasSpecializedTemplate(
235 InnerMatcher: classTemplateDecl(has(CXXRecords.bind(ID: "use"))))))))),
236 Action: this);
237}
238
239void FindAllSymbols::run(const MatchFinder::MatchResult &Result) {
240 // Ignore Results in failing TUs.
241 if (Result.Context->getDiagnostics().hasErrorOccurred()) {
242 return;
243 }
244
245 SymbolInfo::Signals Signals;
246 const NamedDecl *ND;
247 if ((ND = Result.Nodes.getNodeAs<NamedDecl>(ID: "use")))
248 Signals.Used = 1;
249 else if ((ND = Result.Nodes.getNodeAs<NamedDecl>(ID: "decl")))
250 Signals.Seen = 1;
251 else
252 assert(false && "Must match a NamedDecl!");
253
254 const SourceManager *SM = Result.SourceManager;
255 if (auto Symbol = CreateSymbolInfo(ND, SM: *SM, Collector)) {
256 Filename =
257 std::string(SM->getFileEntryRefForID(FID: SM->getMainFileID())->getName());
258 FileSymbols[*Symbol] += Signals;
259 }
260}
261
262void FindAllSymbols::onEndOfTranslationUnit() {
263 if (Filename != "") {
264 Reporter->reportSymbols(FileName: Filename, Symbols: FileSymbols);
265 FileSymbols.clear();
266 Filename = "";
267 }
268}
269
270} // namespace find_all_symbols
271} // namespace clang
272

source code of clang-tools-extra/clang-include-fixer/find-all-symbols/FindAllSymbols.cpp