From cf52ca6bb6dd76a1bd967bc422287fafafa1e45a Mon Sep 17 00:00:00 2001 From: Manuel Klimek <klimek@google.com> Date: Thu, 20 Jun 2013 14:06:32 +0000 Subject: [PATCH] Adds the equalsBoundNode matcher. Most of the tests contributed by Edwin Vane. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@184427 91177308-0d34-0410-b5e6-96231b3b80d8 --- docs/LibASTMatchersReference.html | 92 +++++++++++++++++++ include/clang/AST/ASTTypeTraits.h | 37 +++++--- include/clang/ASTMatchers/ASTMatchers.h | 49 ++++++++++ .../clang/ASTMatchers/ASTMatchersInternal.h | 15 +++ lib/AST/ASTTypeTraits.cpp | 4 +- unittests/ASTMatchers/ASTMatchersTest.cpp | 89 ++++++++++++++++++ 6 files changed, 272 insertions(+), 14 deletions(-) diff --git a/docs/LibASTMatchersReference.html b/docs/LibASTMatchersReference.html index 8e31d583cb7..50ff38edfad 100644 --- a/docs/LibASTMatchersReference.html +++ b/docs/LibASTMatchersReference.html @@ -1730,6 +1730,29 @@ declCountIs(2) </pre></td></tr> +<tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1Decl.html">Decl</a>></td><td class="name" onclick="toggle('equalsBoundNode1')"><a name="equalsBoundNode1Anchor">equalsBoundNode</a></td><td>std::string ID</td></tr> +<tr><td colspan="4" class="doc" id="equalsBoundNode1"><pre>Matches if a node equals a previously bound node. + +Matches a node if it equals the node previously bound to ID. + +Given + class X { int a; int b; }; +recordDecl( + has(fieldDecl(hasName("a"), hasType(type().bind("t")))), + has(fieldDecl(hasName("b"), hasType(type(equalsBoundNode("t")))))) + matches the class X, as a and b have the same type. + +Note that when multiple matches are involved via forEach* matchers, +equalsBoundNodes acts as a filter. +For example: +compoundStmt( + forEachDescendant(varDecl().bind("d")), + forEachDescendant(declRefExpr(to(decl(equalsBoundNode("d")))))) +will trigger a match for each combination of variable declaration +and reference to that variable declaration within a compound statement. +</pre></td></tr> + + <tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1Decl.html">Decl</a>></td><td class="name" onclick="toggle('equalsNode0')"><a name="equalsNode0Anchor">equalsNode</a></td><td>Decl* Other</td></tr> <tr><td colspan="4" class="doc" id="equalsNode0"><pre>Matches if a node equals another node. @@ -1933,6 +1956,29 @@ callExpr(on(hasType(asString("class Y *")))) </pre></td></tr> +<tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1QualType.html">QualType</a>></td><td class="name" onclick="toggle('equalsBoundNode3')"><a name="equalsBoundNode3Anchor">equalsBoundNode</a></td><td>std::string ID</td></tr> +<tr><td colspan="4" class="doc" id="equalsBoundNode3"><pre>Matches if a node equals a previously bound node. + +Matches a node if it equals the node previously bound to ID. + +Given + class X { int a; int b; }; +recordDecl( + has(fieldDecl(hasName("a"), hasType(type().bind("t")))), + has(fieldDecl(hasName("b"), hasType(type(equalsBoundNode("t")))))) + matches the class X, as a and b have the same type. + +Note that when multiple matches are involved via forEach* matchers, +equalsBoundNodes acts as a filter. +For example: +compoundStmt( + forEachDescendant(varDecl().bind("d")), + forEachDescendant(declRefExpr(to(decl(equalsBoundNode("d")))))) +will trigger a match for each combination of variable declaration +and reference to that variable declaration within a compound statement. +</pre></td></tr> + + <tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1QualType.html">QualType</a>></td><td class="name" onclick="toggle('hasLocalQualifiers0')"><a name="hasLocalQualifiers0Anchor">hasLocalQualifiers</a></td><td></td></tr> <tr><td colspan="4" class="doc" id="hasLocalQualifiers0"><pre>Matches QualType nodes that have local CV-qualifiers attached to the node, not hidden within a typedef. @@ -1977,6 +2023,29 @@ matches "a(int)", "b(long)", but not "c(double)". </pre></td></tr> +<tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>></td><td class="name" onclick="toggle('equalsBoundNode0')"><a name="equalsBoundNode0Anchor">equalsBoundNode</a></td><td>std::string ID</td></tr> +<tr><td colspan="4" class="doc" id="equalsBoundNode0"><pre>Matches if a node equals a previously bound node. + +Matches a node if it equals the node previously bound to ID. + +Given + class X { int a; int b; }; +recordDecl( + has(fieldDecl(hasName("a"), hasType(type().bind("t")))), + has(fieldDecl(hasName("b"), hasType(type(equalsBoundNode("t")))))) + matches the class X, as a and b have the same type. + +Note that when multiple matches are involved via forEach* matchers, +equalsBoundNodes acts as a filter. +For example: +compoundStmt( + forEachDescendant(varDecl().bind("d")), + forEachDescendant(declRefExpr(to(decl(equalsBoundNode("d")))))) +will trigger a match for each combination of variable declaration +and reference to that variable declaration within a compound statement. +</pre></td></tr> + + <tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>></td><td class="name" onclick="toggle('equalsNode1')"><a name="equalsNode1Anchor">equalsNode</a></td><td>Stmt* Other</td></tr> <tr><td colspan="4" class="doc" id="equalsNode1"><pre>Matches if a node equals another node. @@ -2000,6 +2069,29 @@ Usable as: Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1TagDec </pre></td></tr> +<tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1Type.html">Type</a>></td><td class="name" onclick="toggle('equalsBoundNode2')"><a name="equalsBoundNode2Anchor">equalsBoundNode</a></td><td>std::string ID</td></tr> +<tr><td colspan="4" class="doc" id="equalsBoundNode2"><pre>Matches if a node equals a previously bound node. + +Matches a node if it equals the node previously bound to ID. + +Given + class X { int a; int b; }; +recordDecl( + has(fieldDecl(hasName("a"), hasType(type().bind("t")))), + has(fieldDecl(hasName("b"), hasType(type(equalsBoundNode("t")))))) + matches the class X, as a and b have the same type. + +Note that when multiple matches are involved via forEach* matchers, +equalsBoundNodes acts as a filter. +For example: +compoundStmt( + forEachDescendant(varDecl().bind("d")), + forEachDescendant(declRefExpr(to(decl(equalsBoundNode("d")))))) +will trigger a match for each combination of variable declaration +and reference to that variable declaration within a compound statement. +</pre></td></tr> + + <tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1UnaryExprOrTypeTraitExpr.html">UnaryExprOrTypeTraitExpr</a>></td><td class="name" onclick="toggle('ofKind0')"><a name="ofKind0Anchor">ofKind</a></td><td>UnaryExprOrTypeTrait Kind</td></tr> <tr><td colspan="4" class="doc" id="ofKind0"><pre>Matches unary expressions of a certain kind. diff --git a/include/clang/AST/ASTTypeTraits.h b/include/clang/AST/ASTTypeTraits.h index d98ad33a2ae..fc099874f0a 100644 --- a/include/clang/AST/ASTTypeTraits.h +++ b/include/clang/AST/ASTTypeTraits.h @@ -43,10 +43,10 @@ public: } /// \brief Returns \c true if \c this and \c Other represent the same kind. - bool isSame(ASTNodeKind Other); + bool isSame(ASTNodeKind Other) const; /// \brief Returns \c true if \c this is a base kind of (or same as) \c Other - bool isBaseOf(ASTNodeKind Other); + bool isBaseOf(ASTNodeKind Other) const; /// \brief String representation of the kind. StringRef asStringRef() const; @@ -144,8 +144,8 @@ public: /// convertible to \c T. /// /// For types that have identity via their pointer in the AST - /// (like \c Stmt and \c Decl) the returned pointer points to the - /// referenced AST node. + /// (like \c Stmt, \c Decl, \c Type and \c NestedNameSpecifier) the returned + /// pointer points to the referenced AST node. /// For other types (like \c QualType) the value is stored directly /// in the \c DynTypedNode, and the returned pointer points at /// the storage inside DynTypedNode. For those nodes, do not @@ -167,12 +167,20 @@ public: /// /// Supports comparison of nodes that support memoization. /// FIXME: Implement comparsion for other node types (currently - /// only Stmt and Decl return memoization data). + /// only Stmt, Decl, Type and NestedNameSpecifier return memoization data). bool operator<(const DynTypedNode &Other) const { assert(getMemoizationData() && Other.getMemoizationData()); return getMemoizationData() < Other.getMemoizationData(); } bool operator==(const DynTypedNode &Other) const { + // Nodes with different types cannot be equal. + if (!NodeKind.isSame(Other.NodeKind)) + return false; + + // FIXME: Implement for other types. + if (ASTNodeKind::getFromNodeKind<QualType>().isBaseOf(NodeKind)) { + return *get<QualType>() == *Other.get<QualType>(); + } assert(getMemoizationData() && Other.getMemoizationData()); return getMemoizationData() == Other.getMemoizationData(); } @@ -189,13 +197,14 @@ private: /// \brief Stores the data of the node. /// - /// Note that we can store \c Decls and \c Stmts by pointer as they are - /// 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 *, Stmt *, NestedNameSpecifier *, - NestedNameSpecifierLoc, QualType, Type *, - TypeLoc> Storage; + /// Note that we can store \c Decls, \c Stmts, \c Types and + /// \c NestedNameSpecifiers by pointer as they are 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 *, Stmt *, Type *, NestedNameSpecifier *, + NestedNameSpecifierLoc, QualType, TypeLoc> + Storage; }; // FIXME: Pull out abstraction for the following. @@ -311,6 +320,10 @@ inline const void *DynTypedNode::getMemoizationData() const { return BaseConverter<Decl>::get(NodeKind, Storage.buffer); } else if (ASTNodeKind::getFromNodeKind<Stmt>().isBaseOf(NodeKind)) { return BaseConverter<Stmt>::get(NodeKind, Storage.buffer); + } else if (ASTNodeKind::getFromNodeKind<Type>().isBaseOf(NodeKind)) { + return BaseConverter<Type>::get(NodeKind, Storage.buffer); + } else if (ASTNodeKind::getFromNodeKind<NestedNameSpecifier>().isBaseOf(NodeKind)) { + return BaseConverter<NestedNameSpecifier>::get(NodeKind, Storage.buffer); } return NULL; } diff --git a/include/clang/ASTMatchers/ASTMatchers.h b/include/clang/ASTMatchers/ASTMatchers.h index 50da7d6a9c1..366707b0a44 100644 --- a/include/clang/ASTMatchers/ASTMatchers.h +++ b/include/clang/ASTMatchers/ASTMatchers.h @@ -2294,6 +2294,55 @@ AST_POLYMORPHIC_MATCHER_P(hasCondition, internal::Matcher<Expr>, InnerMatcher.matches(*Condition, Finder, Builder)); } +namespace internal { +struct NotEqualsBoundNodePredicate { + bool operator()(const internal::BoundNodesMap &Nodes) const { + return Nodes.getNode(ID) != Node; + } + std::string ID; + ast_type_traits::DynTypedNode Node; +}; +} // namespace internal + +/// \brief Matches if a node equals a previously bound node. +/// +/// Matches a node if it equals the node previously bound to \p ID. +/// +/// Given +/// \code +/// class X { int a; int b; }; +/// \endcode +/// recordDecl( +/// has(fieldDecl(hasName("a"), hasType(type().bind("t")))), +/// has(fieldDecl(hasName("b"), hasType(type(equalsBoundNode("t")))))) +/// matches the class \c X, as \c a and \c b have the same type. +/// +/// Note that when multiple matches are involved via \c forEach* matchers, +/// \c equalsBoundNodes acts as a filter. +/// For example: +/// compoundStmt( +/// forEachDescendant(varDecl().bind("d")), +/// forEachDescendant(declRefExpr(to(decl(equalsBoundNode("d")))))) +/// will trigger a match for each combination of variable declaration +/// and reference to that variable declaration within a compound statement. +AST_POLYMORPHIC_MATCHER_P(equalsBoundNode, std::string, ID) { + // FIXME: Figure out whether it makes sense to allow this + // on any other node types. + // For *Loc it probably does not make sense, as those seem + // unique. For NestedNameSepcifier it might make sense, as + // those also have pointer identity, but I'm not sure whether + // they're ever reused. + TOOLING_COMPILE_ASSERT((llvm::is_base_of<Stmt, NodeType>::value || + llvm::is_base_of<Decl, NodeType>::value || + llvm::is_base_of<Type, NodeType>::value || + llvm::is_base_of<QualType, NodeType>::value), + equals_bound_node_requires_non_unique_node_class); + internal::NotEqualsBoundNodePredicate Predicate; + Predicate.ID = ID; + Predicate.Node = ast_type_traits::DynTypedNode::create(Node); + return Builder->removeBindings(Predicate); +} + /// \brief Matches the condition variable statement in an if statement. /// /// Given diff --git a/include/clang/ASTMatchers/ASTMatchersInternal.h b/include/clang/ASTMatchers/ASTMatchersInternal.h index b27ec57f7dd..7d09e67cbe9 100644 --- a/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -84,6 +84,14 @@ public: return It->second.get<T>(); } + ast_type_traits::DynTypedNode getNode(StringRef ID) const { + IDToNodeMap::const_iterator It = NodeMap.find(ID); + if (It == NodeMap.end()) { + return ast_type_traits::DynTypedNode(); + } + return It->second; + } + /// \brief Imposes an order on BoundNodesMaps. bool operator<(const BoundNodesMap &Other) const { return NodeMap < Other.NodeMap; @@ -134,6 +142,13 @@ public: /// The ownership of 'ResultVisitor' remains at the caller. void visitMatches(Visitor* ResultVisitor); + template <typename ExcludePredicate> + bool removeBindings(const ExcludePredicate &Predicate) { + Bindings.erase(std::remove_if(Bindings.begin(), Bindings.end(), Predicate), + Bindings.end()); + return !Bindings.empty(); + } + /// \brief Imposes an order on BoundNodesTreeBuilders. bool operator<(const BoundNodesTreeBuilder &Other) const { return Bindings < Other.Bindings; diff --git a/lib/AST/ASTTypeTraits.cpp b/lib/AST/ASTTypeTraits.cpp index 40e669d7ad9..7f930b47b55 100644 --- a/lib/AST/ASTTypeTraits.cpp +++ b/lib/AST/ASTTypeTraits.cpp @@ -35,11 +35,11 @@ const ASTNodeKind::KindInfo ASTNodeKind::AllKindInfo[] = { #include "clang/AST/TypeNodes.def" }; -bool ASTNodeKind::isBaseOf(ASTNodeKind Other) { +bool ASTNodeKind::isBaseOf(ASTNodeKind Other) const { return isBaseOf(KindId, Other.KindId); } -bool ASTNodeKind::isSame(ASTNodeKind Other) { +bool ASTNodeKind::isSame(ASTNodeKind Other) const { return KindId != NKI_None && KindId == Other.KindId; } diff --git a/unittests/ASTMatchers/ASTMatchersTest.cpp b/unittests/ASTMatchers/ASTMatchersTest.cpp index a7ccb56cd64..839c447061f 100644 --- a/unittests/ASTMatchers/ASTMatchersTest.cpp +++ b/unittests/ASTMatchers/ASTMatchersTest.cpp @@ -4081,5 +4081,94 @@ TEST(MatchFinder, InterceptsEndOfTranslationUnit) { EXPECT_TRUE(VerifyCallback.Called); } +TEST(EqualsBoundNodeMatcher, QualType) { + EXPECT_TRUE(matches( + "int i = 1;", varDecl(hasType(qualType().bind("type")), + hasInitializer(ignoringParenImpCasts( + hasType(qualType(equalsBoundNode("type")))))))); + EXPECT_TRUE(notMatches("int i = 1.f;", + varDecl(hasType(qualType().bind("type")), + hasInitializer(ignoringParenImpCasts(hasType( + qualType(equalsBoundNode("type")))))))); +} + +TEST(EqualsBoundNodeMatcher, NonMatchingTypes) { + EXPECT_TRUE(notMatches( + "int i = 1;", varDecl(namedDecl(hasName("i")).bind("name"), + hasInitializer(ignoringParenImpCasts( + hasType(qualType(equalsBoundNode("type")))))))); +} + +TEST(EqualsBoundNodeMatcher, Stmt) { + EXPECT_TRUE( + matches("void f() { if(true) {} }", + stmt(allOf(ifStmt().bind("if"), + hasParent(stmt(has(stmt(equalsBoundNode("if"))))))))); + + EXPECT_TRUE(notMatches( + "void f() { if(true) { if (true) {} } }", + stmt(allOf(ifStmt().bind("if"), has(stmt(equalsBoundNode("if"))))))); +} + +TEST(EqualsBoundNodeMatcher, Decl) { + EXPECT_TRUE(matches( + "class X { class Y {}; };", + decl(allOf(recordDecl(hasName("::X::Y")).bind("record"), + hasParent(decl(has(decl(equalsBoundNode("record"))))))))); + + EXPECT_TRUE(notMatches("class X { class Y {}; };", + decl(allOf(recordDecl(hasName("::X")).bind("record"), + has(decl(equalsBoundNode("record"))))))); +} + +TEST(EqualsBoundNodeMatcher, Type) { + EXPECT_TRUE(matches( + "class X { int a; int b; };", + recordDecl( + has(fieldDecl(hasName("a"), hasType(type().bind("t")))), + has(fieldDecl(hasName("b"), hasType(type(equalsBoundNode("t")))))))); + + EXPECT_TRUE(notMatches( + "class X { int a; double b; };", + recordDecl( + has(fieldDecl(hasName("a"), hasType(type().bind("t")))), + has(fieldDecl(hasName("b"), hasType(type(equalsBoundNode("t")))))))); +} + +TEST(EqualsBoundNodeMatcher, UsingForEachDescendant) { + + EXPECT_TRUE(matchAndVerifyResultTrue( + "int f() {" + " if (1) {" + " int i = 9;" + " }" + " int j = 10;" + " {" + " float k = 9.0;" + " }" + " return 0;" + "}", + // Look for variable declarations within functions whose type is the same + // as the function return type. + functionDecl(returns(qualType().bind("type")), + forEachDescendant(varDecl(hasType( + qualType(equalsBoundNode("type")))).bind("decl"))), + // Only i and j should match, not k. + new VerifyIdIsBoundTo<VarDecl>("decl", 2))); +} + +TEST(EqualsBoundNodeMatcher, FiltersMatchedCombinations) { + EXPECT_TRUE(matchAndVerifyResultTrue( + "void f() {" + " int x;" + " double d;" + " x = d + x - d + x;" + "}", + functionDecl( + hasName("f"), forEachDescendant(varDecl().bind("d")), + forEachDescendant(declRefExpr(to(decl(equalsBoundNode("d")))))), + new VerifyIdIsBoundTo<VarDecl>("d", 5))); +} + } // end namespace ast_matchers } // end namespace clang -- GitLab