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 | |
22 | using namespace clang::ast_matchers; |
23 | |
24 | namespace clang { |
25 | namespace find_all_symbols { |
26 | namespace { |
27 | |
28 | AST_MATCHER(EnumConstantDecl, isInScopedEnum) { |
29 | if (const auto *ED = dyn_cast<EnumDecl>(Node.getDeclContext())) |
30 | return ED->isScoped(); |
31 | return false; |
32 | } |
33 | |
34 | AST_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 | |
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)) |
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 | |
72 | std::optional<SymbolInfo> |
73 | (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 | |
117 | void 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 | |
239 | void 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 | |
262 | void 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 | |