//===--- ASTMatchFinder.h - Structural query framework ----------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Provides a way to construct an ASTConsumer that runs given matchers // over the AST and invokes a given callback on every match. // // The general idea is to construct a matcher expression that describes a // subtree match on the AST. Next, a callback that is executed every time the // expression matches is registered, and the matcher is run over the AST of // some code. Matched subexpressions can be bound to string IDs and easily // be accessed from the registered callback. The callback can than use the // AST nodes that the subexpressions matched on to output information about // the match or construct changes that can be applied to the code. // // Example: // class HandleMatch : public MatchFinder::MatchCallback { // public: // virtual void Run(const MatchFinder::MatchResult &Result) { // const CXXRecordDecl *Class = // Result.Nodes.GetDeclAs("id"); // ... // } // }; // // int main(int argc, char **argv) { // ClangTool Tool(argc, argv); // MatchFinder finder; // finder.AddMatcher(Id("id", record(hasName("::a_namespace::AClass"))), // new HandleMatch); // return Tool.Run(newFrontendActionFactory(&finder)); // } // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_ASTMATCHERS_ASTMATCHFINDER_H #define LLVM_CLANG_ASTMATCHERS_ASTMATCHFINDER_H #include "clang/ASTMatchers/ASTMatchers.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/Timer.h" #include namespace clang { namespace ast_matchers { /// A class to allow finding matches over the Clang AST. /// /// After creation, you can add multiple matchers to the MatchFinder via /// calls to addMatcher(...). /// /// Once all matchers are added, newASTConsumer() returns an ASTConsumer /// that will trigger the callbacks specified via addMatcher(...) when a match /// is found. /// /// The order of matches is guaranteed to be equivalent to doing a pre-order /// traversal on the AST, and applying the matchers in the order in which they /// were added to the MatchFinder. /// /// See ASTMatchers.h for more information about how to create matchers. /// /// Not intended to be subclassed. class MatchFinder { public: /// Contains all information for a given match. /// /// Every time a match is found, the MatchFinder will invoke the registered /// MatchCallback with a MatchResult containing information about the match. struct MatchResult { MatchResult(const BoundNodes &Nodes, clang::ASTContext *Context); /// Contains the nodes bound on the current match. /// /// This allows user code to easily extract matched AST nodes. const BoundNodes Nodes; /// Utilities for interpreting the matched AST structures. /// @{ clang::ASTContext * const Context; clang::SourceManager * const SourceManager; /// @} }; /// Called when the Match registered for it was successfully found /// in the AST. class MatchCallback { public: virtual ~MatchCallback(); /// Called on every match by the \c MatchFinder. virtual void run(const MatchResult &Result) = 0; /// Called at the start of each translation unit. /// /// Optionally override to do per translation unit tasks. virtual void onStartOfTranslationUnit() {} /// Called at the end of each translation unit. /// /// Optionally override to do per translation unit tasks. virtual void onEndOfTranslationUnit() {} /// An id used to group the matchers. /// /// This id is used, for example, for the profiling output. /// It defaults to "". virtual StringRef getID() const; /// TraversalKind to use while matching and processing /// the result nodes. This API is temporary to facilitate /// third parties porting existing code to the default /// behavior of clang-tidy. virtual std::optional getCheckTraversalKind() const; }; /// Called when parsing is finished. Intended for testing only. class ParsingDoneTestCallback { public: virtual ~ParsingDoneTestCallback(); virtual void run() = 0; }; struct MatchFinderOptions { struct Profiling { Profiling(llvm::StringMap &Records) : Records(Records) {} /// Per bucket timing information. llvm::StringMap &Records; }; /// Enables per-check timers. /// /// It prints a report after match. std::optional CheckProfiling; }; MatchFinder(MatchFinderOptions Options = MatchFinderOptions()); ~MatchFinder(); /// Adds a matcher to execute when running over the AST. /// /// Calls 'Action' with the BoundNodes on every match. /// Adding more than one 'NodeMatch' allows finding different matches in a /// single pass over the AST. /// /// Does not take ownership of 'Action'. /// @{ void addMatcher(const DeclarationMatcher &NodeMatch, MatchCallback *Action); void addMatcher(const TypeMatcher &NodeMatch, MatchCallback *Action); void addMatcher(const StatementMatcher &NodeMatch, MatchCallback *Action); void addMatcher(const NestedNameSpecifierMatcher &NodeMatch, MatchCallback *Action); void addMatcher(const NestedNameSpecifierLocMatcher &NodeMatch, MatchCallback *Action); void addMatcher(const TypeLocMatcher &NodeMatch, MatchCallback *Action); void addMatcher(const CXXCtorInitializerMatcher &NodeMatch, MatchCallback *Action); void addMatcher(const TemplateArgumentLocMatcher &NodeMatch, MatchCallback *Action); void addMatcher(const AttrMatcher &NodeMatch, MatchCallback *Action); /// @} /// Adds a matcher to execute when running over the AST. /// /// This is similar to \c addMatcher(), but it uses the dynamic interface. It /// is more flexible, but the lost type information enables a caller to pass /// a matcher that cannot match anything. /// /// \returns \c true if the matcher is a valid top-level matcher, \c false /// otherwise. bool addDynamicMatcher(const internal::DynTypedMatcher &NodeMatch, MatchCallback *Action); /// Creates a clang ASTConsumer that finds all matches. std::unique_ptr newASTConsumer(); /// Calls the registered callbacks on all matches on the given \p Node. /// /// Note that there can be multiple matches on a single node, for /// example when using decl(forEachDescendant(stmt())). /// /// @{ template void match(const T &Node, ASTContext &Context) { match(clang::DynTypedNode::create(Node), Context); } void match(const clang::DynTypedNode &Node, ASTContext &Context); /// @} /// Finds all matches in the given AST. void matchAST(ASTContext &Context); /// Registers a callback to notify the end of parsing. /// /// The provided closure is called after parsing is done, before the AST is /// traversed. Useful for benchmarking. /// Each call to FindAll(...) will call the closure once. void registerTestCallbackAfterParsing(ParsingDoneTestCallback *ParsingDone); /// For each \c Matcher<> a \c MatchCallback that will be called /// when it matches. struct MatchersByType { std::vector> DeclOrStmt; std::vector> Type; std::vector> NestedNameSpecifier; std::vector> NestedNameSpecifierLoc; std::vector> TypeLoc; std::vector> CtorInit; std::vector> TemplateArgumentLoc; std::vector> Attr; /// All the callbacks in one container to simplify iteration. llvm::SmallPtrSet AllCallbacks; }; private: MatchersByType Matchers; MatchFinderOptions Options; /// Called when parsing is done. ParsingDoneTestCallback *ParsingDone; }; /// Returns the results of matching \p Matcher on \p Node. /// /// Collects the \c BoundNodes of all callback invocations when matching /// \p Matcher on \p Node and returns the collected results. /// /// Multiple results occur when using matchers like \c forEachDescendant, /// which generate a result for each sub-match. /// /// If you want to find all matches on the sub-tree rooted at \c Node (rather /// than only the matches on \c Node itself), surround the \c Matcher with a /// \c findAll(). /// /// \see selectFirst /// @{ template SmallVector match(MatcherT Matcher, const NodeT &Node, ASTContext &Context); template SmallVector match(MatcherT Matcher, const DynTypedNode &Node, ASTContext &Context); /// @} /// Returns the results of matching \p Matcher on the translation unit of /// \p Context and collects the \c BoundNodes of all callback invocations. template SmallVector match(MatcherT Matcher, ASTContext &Context); /// Returns the first result of type \c NodeT bound to \p BoundTo. /// /// Returns \c NULL if there is no match, or if the matching node cannot be /// casted to \c NodeT. /// /// This is useful in combanation with \c match(): /// \code /// const Decl *D = selectFirst("id", match(Matcher.bind("id"), /// Node, Context)); /// \endcode template const NodeT * selectFirst(StringRef BoundTo, const SmallVectorImpl &Results) { for (const BoundNodes &N : Results) { if (const NodeT *Node = N.getNodeAs(BoundTo)) return Node; } return nullptr; } namespace internal { class CollectMatchesCallback : public MatchFinder::MatchCallback { public: void run(const MatchFinder::MatchResult &Result) override { Nodes.push_back(Result.Nodes); } std::optional getCheckTraversalKind() const override { return std::nullopt; } SmallVector Nodes; }; } template SmallVector match(MatcherT Matcher, const DynTypedNode &Node, ASTContext &Context) { internal::CollectMatchesCallback Callback; MatchFinder Finder; Finder.addMatcher(Matcher, &Callback); Finder.match(Node, Context); return std::move(Callback.Nodes); } template SmallVector match(MatcherT Matcher, const NodeT &Node, ASTContext &Context) { return match(Matcher, DynTypedNode::create(Node), Context); } template SmallVector match(MatcherT Matcher, ASTContext &Context) { internal::CollectMatchesCallback Callback; MatchFinder Finder; Finder.addMatcher(Matcher, &Callback); Finder.matchAST(Context); return std::move(Callback.Nodes); } inline SmallVector matchDynamic(internal::DynTypedMatcher Matcher, const DynTypedNode &Node, ASTContext &Context) { internal::CollectMatchesCallback Callback; MatchFinder Finder; Finder.addDynamicMatcher(Matcher, &Callback); Finder.match(Node, Context); return std::move(Callback.Nodes); } template SmallVector matchDynamic(internal::DynTypedMatcher Matcher, const NodeT &Node, ASTContext &Context) { return matchDynamic(Matcher, DynTypedNode::create(Node), Context); } inline SmallVector matchDynamic(internal::DynTypedMatcher Matcher, ASTContext &Context) { internal::CollectMatchesCallback Callback; MatchFinder Finder; Finder.addDynamicMatcher(Matcher, &Callback); Finder.matchAST(Context); return std::move(Callback.Nodes); } } // end namespace ast_matchers } // end namespace clang #endif