| 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 | |