| //===-- FindAllSymbols.cpp - find all symbols--------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "FindAllSymbols.h" |
| #include "HeaderMapCollector.h" |
| #include "PathConfig.h" |
| #include "SymbolInfo.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/Type.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Tooling/Tooling.h" |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/Support/FileSystem.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace find_all_symbols { |
| namespace { |
| |
| AST_MATCHER(EnumConstantDecl, isInScopedEnum) { |
| if (const auto *ED = dyn_cast<EnumDecl>(Node.getDeclContext())) |
| return ED->isScoped(); |
| return false; |
| } |
| |
| AST_POLYMORPHIC_MATCHER(isFullySpecialized, |
| AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl, |
| CXXRecordDecl)) { |
| if (Node.getTemplateSpecializationKind() == TSK_ExplicitSpecialization) { |
| bool IsPartialSpecialization = |
| llvm::isa<VarTemplatePartialSpecializationDecl>(Node) || |
| llvm::isa<ClassTemplatePartialSpecializationDecl>(Node); |
| return !IsPartialSpecialization; |
| } |
| return false; |
| } |
| |
| std::vector<SymbolInfo::Context> GetContexts(const NamedDecl *ND) { |
| std::vector<SymbolInfo::Context> Contexts; |
| for (const auto *Context = ND->getDeclContext(); Context; |
| Context = Context->getParent()) { |
| if (llvm::isa<TranslationUnitDecl>(Context) || |
| llvm::isa<LinkageSpecDecl>(Context)) |
| break; |
| |
| assert(llvm::isa<NamedDecl>(Context) && |
| "Expect Context to be a NamedDecl"); |
| if (const auto *NSD = dyn_cast<NamespaceDecl>(Context)) { |
| if (!NSD->isInlineNamespace()) |
| Contexts.emplace_back(SymbolInfo::ContextType::Namespace, |
| NSD->getName().str()); |
| } else if (const auto *ED = dyn_cast<EnumDecl>(Context)) { |
| Contexts.emplace_back(SymbolInfo::ContextType::EnumDecl, |
| ED->getName().str()); |
| } else { |
| const auto *RD = cast<RecordDecl>(Context); |
| Contexts.emplace_back(SymbolInfo::ContextType::Record, |
| RD->getName().str()); |
| } |
| } |
| return Contexts; |
| } |
| |
| llvm::Optional<SymbolInfo> |
| CreateSymbolInfo(const NamedDecl *ND, const SourceManager &SM, |
| const HeaderMapCollector *Collector) { |
| SymbolInfo::SymbolKind Type; |
| if (llvm::isa<VarDecl>(ND)) { |
| Type = SymbolInfo::SymbolKind::Variable; |
| } else if (llvm::isa<FunctionDecl>(ND)) { |
| Type = SymbolInfo::SymbolKind::Function; |
| } else if (llvm::isa<TypedefNameDecl>(ND)) { |
| Type = SymbolInfo::SymbolKind::TypedefName; |
| } else if (llvm::isa<EnumConstantDecl>(ND)) { |
| Type = SymbolInfo::SymbolKind::EnumConstantDecl; |
| } else if (llvm::isa<EnumDecl>(ND)) { |
| Type = SymbolInfo::SymbolKind::EnumDecl; |
| // Ignore anonymous enum declarations. |
| if (ND->getName().empty()) |
| return llvm::None; |
| } else { |
| assert(llvm::isa<RecordDecl>(ND) && |
| "Matched decl must be one of VarDecl, " |
| "FunctionDecl, TypedefNameDecl, EnumConstantDecl, " |
| "EnumDecl and RecordDecl!"); |
| // C-style record decl can have empty name, e.g "struct { ... } var;". |
| if (ND->getName().empty()) |
| return llvm::None; |
| Type = SymbolInfo::SymbolKind::Class; |
| } |
| |
| SourceLocation Loc = SM.getExpansionLoc(ND->getLocation()); |
| if (!Loc.isValid()) { |
| llvm::errs() << "Declaration " << ND->getNameAsString() << "(" |
| << ND->getDeclKindName() |
| << ") has invalid declaration location."; |
| return llvm::None; |
| } |
| |
| std::string FilePath = getIncludePath(SM, Loc, Collector); |
| if (FilePath.empty()) return llvm::None; |
| |
| return SymbolInfo(ND->getNameAsString(), Type, FilePath, GetContexts(ND)); |
| } |
| |
| } // namespace |
| |
| void FindAllSymbols::registerMatchers(MatchFinder *MatchFinder) { |
| // FIXME: Handle specialization. |
| auto IsInSpecialization = hasAncestor( |
| decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()), |
| functionDecl(isExplicitTemplateSpecialization())))); |
| |
| // Matchers for both C and C++. |
| // We only match symbols from header files, i.e. not from main files (see |
| // function's comment for detailed explanation). |
| auto CommonFilter = |
| allOf(unless(isImplicit()), unless(isExpansionInMainFile())); |
| |
| auto HasNSOrTUCtxMatcher = |
| hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())); |
| |
| // We need seperate rules for C record types and C++ record types since some |
| // template related matchers are inapplicable on C record declarations. |
| // |
| // Matchers specific to C++ code. |
| // All declarations should be in namespace or translation unit. |
| auto CCMatcher = |
| allOf(HasNSOrTUCtxMatcher, unless(IsInSpecialization), |
| unless(ast_matchers::isTemplateInstantiation()), |
| unless(isInstantiated()), unless(isFullySpecialized())); |
| |
| // Matchers specific to code in extern "C" {...}. |
| auto ExternCMatcher = hasDeclContext(linkageSpecDecl()); |
| |
| // Matchers for variable declarations. |
| // |
| // In most cases, `ParmVarDecl` is filtered out by hasDeclContext(...) |
| // matcher since the declaration context is usually `MethodDecl`. However, |
| // this assumption does not hold for parameters of a function pointer |
| // parameter. |
| // For example, consider a function declaration: |
| // void Func(void (*)(float), int); |
| // The float parameter of the function pointer has an empty name, and its |
| // declaration context is an anonymous namespace; therefore, it won't be |
| // filtered out by our matchers above. |
| auto Vars = varDecl(CommonFilter, anyOf(ExternCMatcher, CCMatcher), |
| unless(parmVarDecl())); |
| |
| // Matchers for C-style record declarations in extern "C" {...}. |
| auto CRecords = recordDecl(CommonFilter, ExternCMatcher, isDefinition()); |
| // Matchers for C++ record declarations. |
| auto CXXRecords = cxxRecordDecl(CommonFilter, CCMatcher, isDefinition()); |
| |
| // Matchers for function declarations. |
| // We want to exclude friend declaration, but the `DeclContext` of a friend |
| // function declaration is not the class in which it is declared, so we need |
| // to explicitly check if the parent is a `friendDecl`. |
| auto Functions = functionDecl(CommonFilter, unless(hasParent(friendDecl())), |
| anyOf(ExternCMatcher, CCMatcher)); |
| |
| // Matcher for typedef and type alias declarations. |
| // |
| // typedef and type alias can come from C-style headers and C++ headers. |
| // For C-style headers, `DeclContxet` can be either `TranslationUnitDecl` |
| // or `LinkageSpecDecl`. |
| // For C++ headers, `DeclContext ` can be either `TranslationUnitDecl` |
| // or `NamespaceDecl`. |
| // With the following context matcher, we can match `typedefNameDecl` from |
| // both C-style headers and C++ headers (except for those in classes). |
| // "cc_matchers" are not included since template-related matchers are not |
| // applicable on `TypedefNameDecl`. |
| auto Typedefs = |
| typedefNameDecl(CommonFilter, anyOf(HasNSOrTUCtxMatcher, |
| hasDeclContext(linkageSpecDecl()))); |
| |
| // Matchers for enum declarations. |
| auto Enums = enumDecl(CommonFilter, isDefinition(), |
| anyOf(HasNSOrTUCtxMatcher, ExternCMatcher)); |
| |
| // Matchers for enum constant declarations. |
| // We only match the enum constants in non-scoped enum declarations which are |
| // inside toplevel translation unit or a namespace. |
| auto EnumConstants = enumConstantDecl( |
| CommonFilter, unless(isInScopedEnum()), |
| anyOf(hasDeclContext(enumDecl(HasNSOrTUCtxMatcher)), ExternCMatcher)); |
| |
| // Most of the time we care about all matchable decls, or all types. |
| auto Types = namedDecl(anyOf(CRecords, CXXRecords, Enums)); |
| auto Decls = namedDecl(anyOf(CRecords, CXXRecords, Enums, Typedefs, Vars, |
| EnumConstants, Functions)); |
| |
| // We want eligible decls bound to "decl"... |
| MatchFinder->addMatcher(Decls.bind("decl"), this); |
| |
| // ... and all uses of them bound to "use". These have many cases: |
| // Uses of values/functions: these generate a declRefExpr. |
| MatchFinder->addMatcher( |
| declRefExpr(isExpansionInMainFile(), to(Decls.bind("use"))), this); |
| // Uses of function templates: |
| MatchFinder->addMatcher( |
| declRefExpr(isExpansionInMainFile(), |
| to(functionDecl(hasParent( |
| functionTemplateDecl(has(Functions.bind("use"))))))), |
| this); |
| |
| // Uses of most types: just look at what the typeLoc refers to. |
| MatchFinder->addMatcher( |
| typeLoc(isExpansionInMainFile(), |
| loc(qualType(hasDeclaration(Types.bind("use"))))), |
| this); |
| // Uses of typedefs: these are often transparent to hasDeclaration, so we need |
| // to handle them explicitly. |
| MatchFinder->addMatcher( |
| typeLoc(isExpansionInMainFile(), |
| loc(typedefType(hasDeclaration(Typedefs.bind("use"))))), |
| this); |
| // Uses of class templates: |
| // The typeLoc names the templateSpecializationType. Its declaration is the |
| // ClassTemplateDecl, which contains the CXXRecordDecl we want. |
| MatchFinder->addMatcher( |
| typeLoc(isExpansionInMainFile(), |
| loc(templateSpecializationType(hasDeclaration( |
| classTemplateSpecializationDecl(hasSpecializedTemplate( |
| classTemplateDecl(has(CXXRecords.bind("use"))))))))), |
| this); |
| } |
| |
| void FindAllSymbols::run(const MatchFinder::MatchResult &Result) { |
| // Ignore Results in failing TUs. |
| if (Result.Context->getDiagnostics().hasErrorOccurred()) { |
| return; |
| } |
| |
| SymbolInfo::Signals Signals; |
| const NamedDecl *ND; |
| if ((ND = Result.Nodes.getNodeAs<NamedDecl>("use"))) |
| Signals.Used = 1; |
| else if ((ND = Result.Nodes.getNodeAs<NamedDecl>("decl"))) |
| Signals.Seen = 1; |
| else |
| assert(false && "Must match a NamedDecl!"); |
| |
| const SourceManager *SM = Result.SourceManager; |
| if (auto Symbol = CreateSymbolInfo(ND, *SM, Collector)) { |
| Filename = SM->getFileEntryForID(SM->getMainFileID())->getName(); |
| FileSymbols[*Symbol] += Signals; |
| } |
| } |
| |
| void FindAllSymbols::onEndOfTranslationUnit() { |
| if (Filename != "") { |
| Reporter->reportSymbols(Filename, FileSymbols); |
| FileSymbols.clear(); |
| Filename = ""; |
| } |
| } |
| |
| } // namespace find_all_symbols |
| } // namespace clang |