diff options
author | Manuel Klimek <klimek@google.com> | 2013-02-28 13:21:39 +0000 |
---|---|---|
committer | Manuel Klimek <klimek@google.com> | 2013-02-28 13:21:39 +0000 |
commit | ff9a01000ff74a994aa3da26ea2ec732c97291b7 (patch) | |
tree | b979e136b99083c2f2e8e1a47e919b15c2cfd93b | |
parent | a4d4621b206f941cc58d9d0bc7c67a8e705c9d49 (diff) |
First step towards adding a parent map to the ASTContext.
This does not yet implement the LimitNode approach discussed.
The impact of this is an O(n) in the number of nodes in the AST
reduction of complexity for certain kinds of matchers (as otherwise the
parent map gets recreated for every new MatchFinder).
See FIXMEs in the comments for the direction of future work.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@176251 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | include/clang/AST/ASTContext.h | 129 | ||||
-rw-r--r-- | include/clang/AST/ASTTypeTraits.h (renamed from include/clang/ASTMatchers/ASTTypeTraits.h) | 13 | ||||
-rw-r--r-- | include/clang/ASTMatchers/ASTMatchersInternal.h | 6 | ||||
-rw-r--r-- | lib/ASTMatchers/ASTMatchFinder.cpp | 91 | ||||
-rw-r--r-- | unittests/AST/ASTContextParentMapTest.cpp | 71 | ||||
-rw-r--r-- | unittests/AST/CMakeLists.txt | 1 | ||||
-rw-r--r-- | unittests/AST/MatchVerifier.h | 2 |
7 files changed, 217 insertions, 96 deletions
diff --git a/include/clang/AST/ASTContext.h b/include/clang/AST/ASTContext.h index 7c02699ee4..d12d0545c5 100644 --- a/include/clang/AST/ASTContext.h +++ b/include/clang/AST/ASTContext.h @@ -15,6 +15,7 @@ #ifndef LLVM_CLANG_AST_ASTCONTEXT_H #define LLVM_CLANG_AST_ASTCONTEXT_H +#include "clang/AST/ASTTypeTraits.h" #include "clang/AST/CanonicalType.h" #include "clang/AST/CommentCommandTraits.h" #include "clang/AST/Decl.h" @@ -22,6 +23,7 @@ #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/PrettyPrinter.h" #include "clang/AST/RawCommentList.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/TemplateName.h" #include "clang/AST/Type.h" #include "clang/Basic/AddressSpaces.h" @@ -382,6 +384,58 @@ public: OwningPtr<ExternalASTSource> ExternalSource; ASTMutationListener *Listener; + /// \brief Contains parents of a node. + typedef llvm::SmallVector<ast_type_traits::DynTypedNode, 1> ParentVector; + + /// \brief Maps from a node to its parents. + typedef llvm::DenseMap<const void *, ParentVector> ParentMap; + + /// \brief Returns the parents of the given node. + /// + /// Note that this will lazily compute the parents of all nodes + /// and store them for later retrieval. Thus, the first call is O(n) + /// in the number of AST nodes. + /// + /// Caveats and FIXMEs: + /// Calculating the parent map over all AST nodes will need to load the + /// full AST. This can be undesirable in the case where the full AST is + /// expensive to create (for example, when using precompiled header + /// preambles). Thus, there are good opportunities for optimization here. + /// One idea is to walk the given node downwards, looking for references + /// to declaration contexts - once a declaration context is found, compute + /// the parent map for the declaration context; if that can satisfy the + /// request, loading the whole AST can be avoided. Note that this is made + /// more complex by statements in templates having multiple parents - those + /// problems can be solved by building closure over the templated parts of + /// the AST, which also avoids touching large parts of the AST. + /// Additionally, we will want to add an interface to already give a hint + /// where to search for the parents, for example when looking at a statement + /// inside a certain function. + /// + /// 'NodeT' can be one of Decl, Stmt, Type, TypeLoc, + /// NestedNameSpecifier or NestedNameSpecifierLoc. + template <typename NodeT> + ParentVector getParents(const NodeT &Node) { + return getParents(ast_type_traits::DynTypedNode::create(Node)); + } + + ParentVector getParents(const ast_type_traits::DynTypedNode &Node) { + assert(Node.getMemoizationData() && + "Invariant broken: only nodes that support memoization may be " + "used in the parent map."); + if (!AllParents) { + // We always need to run over the whole translation unit, as + // hasAncestor can escape any subtree. + AllParents.reset( + ParentMapASTVisitor::buildMap(*getTranslationUnitDecl())); + } + ParentMap::const_iterator I = AllParents->find(Node.getMemoizationData()); + if (I == AllParents->end()) { + return ParentVector(); + } + return I->second; + } + const clang::PrintingPolicy &getPrintingPolicy() const { return PrintingPolicy; } @@ -2136,8 +2190,81 @@ private: friend class DeclContext; friend class DeclarationNameTable; void ReleaseDeclContextMaps(); + + /// \brief A \c RecursiveASTVisitor that builds a map from nodes to their + /// parents as defined by the \c RecursiveASTVisitor. + /// + /// Note that the relationship described here is purely in terms of AST + /// traversal - there are other relationships (for example declaration context) + /// in the AST that are better modeled by special matchers. + /// + /// FIXME: Currently only builds up the map using \c Stmt and \c Decl nodes. + class ParentMapASTVisitor : public RecursiveASTVisitor<ParentMapASTVisitor> { + public: + /// \brief Builds and returns the translation unit's parent map. + /// + /// The caller takes ownership of the returned \c ParentMap. + static ParentMap *buildMap(TranslationUnitDecl &TU) { + ParentMapASTVisitor Visitor(new ParentMap); + Visitor.TraverseDecl(&TU); + return Visitor.Parents; + } + + private: + typedef RecursiveASTVisitor<ParentMapASTVisitor> VisitorBase; + + ParentMapASTVisitor(ParentMap *Parents) : Parents(Parents) { + } + + bool shouldVisitTemplateInstantiations() const { + return true; + } + bool shouldVisitImplicitCode() const { + return true; + } + // Disables data recursion. We intercept Traverse* methods in the RAV, which + // are not triggered during data recursion. + bool shouldUseDataRecursionFor(clang::Stmt *S) const { + return false; + } + + template <typename T> + bool TraverseNode(T *Node, bool(VisitorBase:: *traverse) (T *)) { + if (Node == NULL) + return true; + if (ParentStack.size() > 0) + // FIXME: Currently we add the same parent multiple times, for example + // when we visit all subexpressions of template instantiations; this is + // suboptimal, bug benign: the only way to visit those is with + // hasAncestor / hasParent, and those do not create new matches. + // The plan is to enable DynTypedNode to be storable in a map or hash + // map. The main problem there is to implement hash functions / + // comparison operators for all types that DynTypedNode supports that + // do not have pointer identity. + (*Parents)[Node].push_back(ParentStack.back()); + ParentStack.push_back(ast_type_traits::DynTypedNode::create(*Node)); + bool Result = (this ->* traverse) (Node); + ParentStack.pop_back(); + return Result; + } + + bool TraverseDecl(Decl *DeclNode) { + return TraverseNode(DeclNode, &VisitorBase::TraverseDecl); + } + + bool TraverseStmt(Stmt *StmtNode) { + return TraverseNode(StmtNode, &VisitorBase::TraverseStmt); + } + + ParentMap *Parents; + llvm::SmallVector<ast_type_traits::DynTypedNode, 16> ParentStack; + + friend class RecursiveASTVisitor<ParentMapASTVisitor>; + }; + + llvm::OwningPtr<ParentMap> AllParents; }; - + /// \brief Utility function for constructing a nullary selector. static inline Selector GetNullarySelector(StringRef name, ASTContext& Ctx) { IdentifierInfo* II = &Ctx.Idents.get(name); diff --git a/include/clang/ASTMatchers/ASTTypeTraits.h b/include/clang/AST/ASTTypeTraits.h index 1dc5441bbb..4688b12de7 100644 --- a/include/clang/ASTMatchers/ASTTypeTraits.h +++ b/include/clang/AST/ASTTypeTraits.h @@ -1,4 +1,4 @@ -//===--- ASTMatchersTypeTraits.h --------------------------------*- C++ -*-===// +//===--- ASTTypeTraits.h ----------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_AST_MATCHERS_AST_TYPE_TRAITS_H -#define LLVM_CLANG_AST_MATCHERS_AST_TYPE_TRAITS_H +#ifndef LLVM_CLANG_AST_AST_TYPE_TRAITS_H +#define LLVM_CLANG_AST_AST_TYPE_TRAITS_H #include "clang/AST/Decl.h" #include "clang/AST/Stmt.h" @@ -88,8 +88,9 @@ private: /// guaranteed to be unique pointers pointing to dedicated storage in the /// AST. \c QualTypes on the other hand do not have storage or unique /// pointers and thus need to be stored by value. - llvm::AlignedCharArrayUnion<Decl*, QualType, TypeLoc, NestedNameSpecifierLoc> - Storage; + llvm::AlignedCharArrayUnion<Decl *, Stmt *, NestedNameSpecifier, + NestedNameSpecifierLoc, QualType, Type, + TypeLoc> Storage; }; // FIXME: Pull out abstraction for the following. @@ -207,4 +208,4 @@ inline const void *DynTypedNode::getMemoizationData() const { } // end namespace ast_type_traits } // end namespace clang -#endif // LLVM_CLANG_AST_MATCHERS_AST_TYPE_TRAITS_H +#endif // LLVM_CLANG_AST_AST_TYPE_TRAITS_H diff --git a/include/clang/ASTMatchers/ASTMatchersInternal.h b/include/clang/ASTMatchers/ASTMatchersInternal.h index 1119a72a42..f309794034 100644 --- a/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -35,13 +35,13 @@ #ifndef LLVM_CLANG_AST_MATCHERS_AST_MATCHERS_INTERNAL_H #define LLVM_CLANG_AST_MATCHERS_AST_MATCHERS_INTERNAL_H -#include "clang/AST/Decl.h" +#include "clang/AST/ASTTypeTraits.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/Decl.h" #include "clang/AST/ExprCXX.h" -#include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" +#include "clang/AST/Stmt.h" #include "clang/AST/Type.h" -#include "clang/ASTMatchers/ASTTypeTraits.h" #include "llvm/ADT/VariadicFunction.h" #include "llvm/Support/type_traits.h" #include <map> diff --git a/lib/ASTMatchers/ASTMatchFinder.cpp b/lib/ASTMatchers/ASTMatchFinder.cpp index 3a3c1fb6e1..1af2f09c3e 100644 --- a/lib/ASTMatchers/ASTMatchFinder.cpp +++ b/lib/ASTMatchers/ASTMatchFinder.cpp @@ -29,76 +29,6 @@ namespace { typedef MatchFinder::MatchCallback MatchCallback; -/// \brief A \c RecursiveASTVisitor that builds a map from nodes to their -/// parents as defined by the \c RecursiveASTVisitor. -/// -/// Note that the relationship described here is purely in terms of AST -/// traversal - there are other relationships (for example declaration context) -/// in the AST that are better modeled by special matchers. -/// -/// FIXME: Currently only builds up the map using \c Stmt and \c Decl nodes. -class ParentMapASTVisitor : public RecursiveASTVisitor<ParentMapASTVisitor> { -public: - /// \brief Contains parents of a node. - typedef SmallVector<ast_type_traits::DynTypedNode, 1> ParentVector; - - /// \brief Maps from a node to its parents. - typedef llvm::DenseMap<const void *, ParentVector> ParentMap; - - /// \brief Builds and returns the translation unit's parent map. - /// - /// The caller takes ownership of the returned \c ParentMap. - static ParentMap *buildMap(TranslationUnitDecl &TU) { - ParentMapASTVisitor Visitor(new ParentMap); - Visitor.TraverseDecl(&TU); - return Visitor.Parents; - } - -private: - typedef RecursiveASTVisitor<ParentMapASTVisitor> VisitorBase; - - ParentMapASTVisitor(ParentMap *Parents) : Parents(Parents) {} - - bool shouldVisitTemplateInstantiations() const { return true; } - bool shouldVisitImplicitCode() const { return true; } - // Disables data recursion. We intercept Traverse* methods in the RAV, which - // are not triggered during data recursion. - bool shouldUseDataRecursionFor(clang::Stmt *S) const { return false; } - - template <typename T> - bool TraverseNode(T *Node, bool (VisitorBase::*traverse)(T*)) { - if (Node == NULL) - return true; - if (ParentStack.size() > 0) - // FIXME: Currently we add the same parent multiple times, for example - // when we visit all subexpressions of template instantiations; this is - // suboptimal, bug benign: the only way to visit those is with - // hasAncestor / hasParent, and those do not create new matches. - // The plan is to enable DynTypedNode to be storable in a map or hash - // map. The main problem there is to implement hash functions / - // comparison operators for all types that DynTypedNode supports that - // do not have pointer identity. - (*Parents)[Node].push_back(ParentStack.back()); - ParentStack.push_back(ast_type_traits::DynTypedNode::create(*Node)); - bool Result = (this->*traverse)(Node); - ParentStack.pop_back(); - return Result; - } - - bool TraverseDecl(Decl *DeclNode) { - return TraverseNode(DeclNode, &VisitorBase::TraverseDecl); - } - - bool TraverseStmt(Stmt *StmtNode) { - return TraverseNode(StmtNode, &VisitorBase::TraverseStmt); - } - - ParentMap *Parents; - SmallVector<ast_type_traits::DynTypedNode, 16> ParentStack; - - friend class RecursiveASTVisitor<ParentMapASTVisitor>; -}; - // We use memoization to avoid running the same matcher on the same // AST node twice. This pair is the key for looking up match // result. It consists of an ID of the MatcherInterface (for @@ -458,12 +388,6 @@ public: const DynTypedMatcher &Matcher, BoundNodesTreeBuilder *Builder, AncestorMatchMode MatchMode) { - if (!Parents) { - // We always need to run over the whole translation unit, as - // \c hasAncestor can escape any subtree. - Parents.reset(ParentMapASTVisitor::buildMap( - *ActiveASTContext->getTranslationUnitDecl())); - } return matchesAncestorOfRecursively(Node, Matcher, Builder, MatchMode); } @@ -506,22 +430,21 @@ private: assert(Node.getMemoizationData() && "Invariant broken: only nodes that support memoization may be " "used in the parent map."); - ParentMapASTVisitor::ParentMap::const_iterator I = - Parents->find(Node.getMemoizationData()); - if (I == Parents->end()) { + ASTContext::ParentVector Parents = ActiveASTContext->getParents(Node); + if (Parents.empty()) { assert(false && "Found node that is not in the parent map."); return false; } - for (ParentMapASTVisitor::ParentVector::const_iterator AncestorI = - I->second.begin(), AncestorE = I->second.end(); + for (ASTContext::ParentVector::const_iterator AncestorI = Parents.begin(), + AncestorE = Parents.end(); AncestorI != AncestorE; ++AncestorI) { if (Matcher.matches(*AncestorI, this, Builder)) return true; } if (MatchMode == ASTMatchFinder::AMM_ParentOnly) return false; - for (ParentMapASTVisitor::ParentVector::const_iterator AncestorI = - I->second.begin(), AncestorE = I->second.end(); + for (ASTContext::ParentVector::const_iterator AncestorI = Parents.begin(), + AncestorE = Parents.end(); AncestorI != AncestorE; ++AncestorI) { if (matchesAncestorOfRecursively(*AncestorI, Matcher, Builder, MatchMode)) return true; @@ -574,8 +497,6 @@ private: // Maps (matcher, node) -> the match result for memoization. typedef llvm::DenseMap<UntypedMatchInput, MemoizedMatchResult> MemoizationMap; MemoizationMap ResultCache; - - OwningPtr<ParentMapASTVisitor::ParentMap> Parents; }; // Returns true if the given class is directly or indirectly derived diff --git a/unittests/AST/ASTContextParentMapTest.cpp b/unittests/AST/ASTContextParentMapTest.cpp new file mode 100644 index 0000000000..c1910a8231 --- /dev/null +++ b/unittests/AST/ASTContextParentMapTest.cpp @@ -0,0 +1,71 @@ +//===- unittest/AST/ASTContextParentMapTest.cpp - AST parent map test -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Tests for the getParents(...) methods of ASTContext. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" +#include "MatchVerifier.h" + +namespace clang { +namespace ast_matchers { + +using clang::tooling::newFrontendActionFactory; +using clang::tooling::runToolOnCodeWithArgs; +using clang::tooling::FrontendActionFactory; + +TEST(GetParents, ReturnsParentForDecl) { + MatchVerifier<Decl> Verifier; + EXPECT_TRUE(Verifier.match("class C { void f(); };", + methodDecl(hasParent(recordDecl(hasName("C")))))); +} + +TEST(GetParents, ReturnsParentForStmt) { + MatchVerifier<Stmt> Verifier; + EXPECT_TRUE(Verifier.match("class C { void f() { if (true) {} } };", + ifStmt(hasParent(compoundStmt())))); +} + +TEST(GetParents, ReturnsParentInsideTemplateInstantiations) { + MatchVerifier<Decl> DeclVerifier; + EXPECT_TRUE(DeclVerifier.match( + "template<typename T> struct C { void f() {} };" + "void g() { C<int> c; c.f(); }", + methodDecl(hasName("f"), + hasParent(recordDecl(isTemplateInstantiation()))))); + EXPECT_TRUE(DeclVerifier.match( + "template<typename T> struct C { void f() {} };" + "void g() { C<int> c; c.f(); }", + methodDecl(hasName("f"), + hasParent(recordDecl(unless(isTemplateInstantiation())))))); + EXPECT_FALSE(DeclVerifier.match( + "template<typename T> struct C { void f() {} };" + "void g() { C<int> c; c.f(); }", + methodDecl(hasName("f"), + allOf(hasParent(recordDecl(unless(isTemplateInstantiation()))), + hasParent(recordDecl(isTemplateInstantiation())))))); +} + +TEST(GetParents, ReturnsMultipleParentsInTemplateInstantiations) { + MatchVerifier<Stmt> TemplateVerifier; + EXPECT_TRUE(TemplateVerifier.match( + "template<typename T> struct C { void f() {} };" + "void g() { C<int> c; c.f(); }", + compoundStmt( + allOf(hasAncestor(recordDecl(isTemplateInstantiation())), + hasAncestor(recordDecl(unless(isTemplateInstantiation()))))))); +} + +} // end namespace ast_matchers +} // end namespace clang diff --git a/unittests/AST/CMakeLists.txt b/unittests/AST/CMakeLists.txt index 1ea293ee83..ad29428220 100644 --- a/unittests/AST/CMakeLists.txt +++ b/unittests/AST/CMakeLists.txt @@ -1,4 +1,5 @@ add_clang_unittest(ASTTests + ASTContextParentMapTest.cpp CommentLexer.cpp CommentParser.cpp DeclPrinterTest.cpp diff --git a/unittests/AST/MatchVerifier.h b/unittests/AST/MatchVerifier.h index f0a5853704..7aa78860aa 100644 --- a/unittests/AST/MatchVerifier.h +++ b/unittests/AST/MatchVerifier.h @@ -44,7 +44,7 @@ public: protected: virtual void run(const MatchFinder::MatchResult &Result); virtual void verify(const MatchFinder::MatchResult &Result, - const NodeType &Node) = 0; + const NodeType &Node) {} void setFailure(const Twine &Result) { Verified = false; |