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/ADT/Optional.h"
20 #include "llvm/Support/FileSystem.h"
21 
22 using namespace clang::ast_matchers;
23 
24 namespace clang {
25 namespace find_all_symbols {
26 namespace {
27 
AST_MATCHER(EnumConstantDecl,isInScopedEnum)28 AST_MATCHER(EnumConstantDecl, isInScopedEnum) {
29   if (const auto *ED = dyn_cast<EnumDecl>(Node.getDeclContext()))
30     return ED->isScoped();
31   return false;
32 }
33 
AST_POLYMORPHIC_MATCHER(isFullySpecialized,AST_POLYMORPHIC_SUPPORTED_TYPES (FunctionDecl,VarDecl,CXXRecordDecl))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 
GetContexts(const NamedDecl * ND)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 llvm::Optional<SymbolInfo>
CreateSymbolInfo(const NamedDecl * ND,const SourceManager & SM,const HeaderMapCollector * Collector)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())
88       return llvm::None;
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 llvm::None;
97     Type = SymbolInfo::SymbolKind::Class;
98   }
99 
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.";
105     return llvm::None;
106   }
107 
108   std::string FilePath = getIncludePath(SM, Loc, Collector);
109   if (FilePath.empty()) return llvm::None;
110 
111   return SymbolInfo(ND->getNameAsString(), Type, FilePath, GetContexts(ND));
112 }
113 
114 } // namespace
115 
registerMatchers(MatchFinder * MatchFinder)116 void FindAllSymbols::registerMatchers(MatchFinder *MatchFinder) {
117   // FIXME: Handle specialization.
118   auto IsInSpecialization = hasAncestor(
119       decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()),
120                  functionDecl(isExplicitTemplateSpecialization()))));
121 
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).
125   auto CommonFilter =
126       allOf(unless(isImplicit()), unless(isExpansionInMainFile()));
127 
128   auto HasNSOrTUCtxMatcher =
129       hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl()));
130 
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.
133   //
134   // Matchers specific to C++ code.
135   // All declarations should be in namespace or translation unit.
136   auto CCMatcher =
137       allOf(HasNSOrTUCtxMatcher, unless(IsInSpecialization),
138             unless(ast_matchers::isTemplateInstantiation()),
139             unless(isInstantiated()), unless(isFullySpecialized()));
140 
141   // Matchers specific to code in extern "C" {...}.
142   auto ExternCMatcher = hasDeclContext(linkageSpecDecl());
143 
144   // Matchers for variable declarations.
145   //
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
149   // parameter.
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()));
157 
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());
162 
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));
169 
170   // Matcher for typedef and type alias declarations.
171   //
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`.
181   auto Typedefs =
182       typedefNameDecl(CommonFilter, anyOf(HasNSOrTUCtxMatcher,
183                                           hasDeclContext(linkageSpecDecl())));
184 
185   // Matchers for enum declarations.
186   auto Enums = enumDecl(CommonFilter, isDefinition(),
187                         anyOf(HasNSOrTUCtxMatcher, ExternCMatcher));
188 
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));
195 
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));
200 
201   // We want eligible decls bound to "decl"...
202   MatchFinder->addMatcher(Decls.bind("decl"), this);
203 
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"))))))),
213       this);
214 
215   // Uses of most types: just look at what the typeLoc refers to.
216   MatchFinder->addMatcher(
217       typeLoc(isExpansionInMainFile(),
218               loc(qualType(hasDeclaration(Types.bind("use"))))),
219       this);
220   // Uses of typedefs: these are often transparent to hasDeclaration, so we need
221   // to handle them explicitly.
222   MatchFinder->addMatcher(
223       typeLoc(isExpansionInMainFile(),
224               loc(typedefType(hasDeclaration(Typedefs.bind("use"))))),
225       this);
226   // Uses of class templates:
227   // The typeLoc names the templateSpecializationType. Its declaration is the
228   // ClassTemplateDecl, which contains the CXXRecordDecl we want.
229   MatchFinder->addMatcher(
230       typeLoc(isExpansionInMainFile(),
231               loc(templateSpecializationType(hasDeclaration(
232                   classTemplateSpecializationDecl(hasSpecializedTemplate(
233                       classTemplateDecl(has(CXXRecords.bind("use"))))))))),
234       this);
235 }
236 
run(const MatchFinder::MatchResult & Result)237 void FindAllSymbols::run(const MatchFinder::MatchResult &Result) {
238   // Ignore Results in failing TUs.
239   if (Result.Context->getDiagnostics().hasErrorOccurred()) {
240     return;
241   }
242 
243   SymbolInfo::Signals Signals;
244   const NamedDecl *ND;
245   if ((ND = Result.Nodes.getNodeAs<NamedDecl>("use")))
246     Signals.Used = 1;
247   else if ((ND = Result.Nodes.getNodeAs<NamedDecl>("decl")))
248     Signals.Seen = 1;
249   else
250     assert(false && "Must match a NamedDecl!");
251 
252   const SourceManager *SM = Result.SourceManager;
253   if (auto Symbol = CreateSymbolInfo(ND, *SM, Collector)) {
254     Filename =
255         std::string(SM->getFileEntryForID(SM->getMainFileID())->getName());
256     FileSymbols[*Symbol] += Signals;
257   }
258 }
259 
onEndOfTranslationUnit()260 void FindAllSymbols::onEndOfTranslationUnit() {
261   if (Filename != "") {
262     Reporter->reportSymbols(Filename, FileSymbols);
263     FileSymbols.clear();
264     Filename = "";
265   }
266 }
267 
268 } // namespace find_all_symbols
269 } // namespace clang
270