From f5d1b9974285a101b15e35c5d8ca73550d2a8c73 Mon Sep 17 00:00:00 2001 From: Stephane Moore <mog@google.com> Date: Mon, 12 Aug 2019 23:23:35 +0000 Subject: [PATCH] =?utf8?q?[clang]=20Update=20isDerivedFrom=20to=20support?= =?utf8?q?=20Objective-C=20classes=20=F0=9F=94=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Summary: This change updates `isDerivedFrom` to support Objective-C classes by converting it to a polymorphic matcher. Notes: The matching behavior for Objective-C classes is modeled to match the behavior of `isDerivedFrom` with C++ classes. To that effect, `isDerivedFrom` matches aliased types of derived Objective-C classes, including compatibility aliases. To achieve this, the AST visitor has been updated to map compatibility aliases to their underlying Objective-C class. `isSameOrDerivedFrom` also provides similar behaviors for C++ and Objective-C classes. The behavior that `cxxRecordDecl(isSameOrDerivedFrom("X"))` does not match `class Y {}; typedef Y X;` is mirrored for Objective-C in that `objcInterfaceDecl(isSameOrDerivedFrom("X"))` does not match either `@interface Y @end typedef Y X;` or `@interface Y @end @compatibility_alias X Y;`. Test Notes: Ran clang unit tests. Reviewers: aaron.ballman, jordan_rose, rjmccall, klimek, alexfh, gribozavr Reviewed By: aaron.ballman, gribozavr Subscribers: cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D60543 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@368632 91177308-0d34-0410-b5e6-96231b3b80d8 --- docs/LibASTMatchersReference.html | 97 ++++++++++++++-- include/clang/ASTMatchers/ASTMatchers.h | 105 ++++++++++++++---- .../clang/ASTMatchers/ASTMatchersInternal.h | 11 +- lib/ASTMatchers/ASTMatchFinder.cpp | 63 ++++++++++- .../ASTMatchers/ASTMatchersNarrowingTest.cpp | 105 ++++++++++++++++++ .../ASTMatchers/Dynamic/RegistryTest.cpp | 2 +- 6 files changed, 349 insertions(+), 34 deletions(-) diff --git a/docs/LibASTMatchersReference.html b/docs/LibASTMatchersReference.html index 68d536a070..d9e867c951 100644 --- a/docs/LibASTMatchersReference.html +++ b/docs/LibASTMatchersReference.html @@ -2576,13 +2576,13 @@ class y; </pre></td></tr> -<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>></td><td class="name" onclick="toggle('isDerivedFrom1')"><a name="isDerivedFrom1Anchor">isDerivedFrom</a></td><td>std::string BaseName</td></tr> -<tr><td colspan="4" class="doc" id="isDerivedFrom1"><pre>Overloaded method as shortcut for isDerivedFrom(hasName(...)). +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>></td><td class="name" onclick="toggle('isDerivedFrom2')"><a name="isDerivedFrom2Anchor">isDerivedFrom</a></td><td>std::string BaseName</td></tr> +<tr><td colspan="4" class="doc" id="isDerivedFrom2"><pre>Overloaded method as shortcut for isDerivedFrom(hasName(...)). </pre></td></tr> -<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>></td><td class="name" onclick="toggle('isDirectlyDerivedFrom1')"><a name="isDirectlyDerivedFrom1Anchor">isDirectlyDerivedFrom</a></td><td>std::string BaseName</td></tr> -<tr><td colspan="4" class="doc" id="isDirectlyDerivedFrom1"><pre>Overloaded method as shortcut for isDirectlyDerivedFrom(hasName(...)). +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>></td><td class="name" onclick="toggle('isDirectlyDerivedFrom2')"><a name="isDirectlyDerivedFrom2Anchor">isDirectlyDerivedFrom</a></td><td>std::string BaseName</td></tr> +<tr><td colspan="4" class="doc" id="isDirectlyDerivedFrom2"><pre>Overloaded method as shortcut for isDirectlyDerivedFrom(hasName(...)). </pre></td></tr> @@ -2628,8 +2628,8 @@ decltype(x) </pre></td></tr> -<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>></td><td class="name" onclick="toggle('isSameOrDerivedFrom1')"><a name="isSameOrDerivedFrom1Anchor">isSameOrDerivedFrom</a></td><td>std::string BaseName</td></tr> -<tr><td colspan="4" class="doc" id="isSameOrDerivedFrom1"><pre>Overloaded method as shortcut for +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>></td><td class="name" onclick="toggle('isSameOrDerivedFrom2')"><a name="isSameOrDerivedFrom2Anchor">isSameOrDerivedFrom</a></td><td>std::string BaseName</td></tr> +<tr><td colspan="4" class="doc" id="isSameOrDerivedFrom2"><pre>Overloaded method as shortcut for isSameOrDerivedFrom(hasName(...)). </pre></td></tr> @@ -3593,6 +3593,22 @@ Given </pre></td></tr> +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCInterfaceDecl.html">ObjCInterfaceDecl</a>></td><td class="name" onclick="toggle('isDerivedFrom3')"><a name="isDerivedFrom3Anchor">isDerivedFrom</a></td><td>std::string BaseName</td></tr> +<tr><td colspan="4" class="doc" id="isDerivedFrom3"><pre>Overloaded method as shortcut for isDerivedFrom(hasName(...)). +</pre></td></tr> + + +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCInterfaceDecl.html">ObjCInterfaceDecl</a>></td><td class="name" onclick="toggle('isDirectlyDerivedFrom3')"><a name="isDirectlyDerivedFrom3Anchor">isDirectlyDerivedFrom</a></td><td>std::string BaseName</td></tr> +<tr><td colspan="4" class="doc" id="isDirectlyDerivedFrom3"><pre>Overloaded method as shortcut for isDirectlyDerivedFrom(hasName(...)). +</pre></td></tr> + + +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCInterfaceDecl.html">ObjCInterfaceDecl</a>></td><td class="name" onclick="toggle('isSameOrDerivedFrom3')"><a name="isSameOrDerivedFrom3Anchor">isSameOrDerivedFrom</a></td><td>std::string BaseName</td></tr> +<tr><td colspan="4" class="doc" id="isSameOrDerivedFrom3"><pre>Overloaded method as shortcut for +isSameOrDerivedFrom(hasName(...)). +</pre></td></tr> + + <tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCMessageExpr.html">ObjCMessageExpr</a>></td><td class="name" onclick="toggle('argumentCountIs2')"><a name="argumentCountIs2Anchor">argumentCountIs</a></td><td>unsigned N</td></tr> <tr><td colspan="4" class="doc" id="argumentCountIs2"><pre>Checks that a call expression or a constructor call expression has a specific number of arguments (including absent default arguments). @@ -5248,8 +5264,9 @@ A but not B. <tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>></td><td class="name" onclick="toggle('isDerivedFrom0')"><a name="isDerivedFrom0Anchor">isDerivedFrom</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1NamedDecl.html">NamedDecl</a>> Base</td></tr> -<tr><td colspan="4" class="doc" id="isDerivedFrom0"><pre>Matches C++ classes that are directly or indirectly derived from -a class matching Base. +<tr><td colspan="4" class="doc" id="isDerivedFrom0"><pre>Matches C++ classes that are directly or indirectly derived from a class +matching Base, or Objective-C classes that directly or indirectly +subclass a class matching Base. Note that a class is not considered to be derived from itself. @@ -5265,11 +5282,18 @@ In the following example, Bar matches isDerivedFrom(hasName("X")): class Foo; typedef Foo X; class Bar : public Foo {}; // derived from a type that X is a typedef of + +In the following example, Bar matches isDerivedFrom(hasName("NSObject")) + @interface NSObject @end + @interface Bar : NSObject @end + +Usable as: Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>>, Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCInterfaceDecl.html">ObjCInterfaceDecl</a>> </pre></td></tr> <tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>></td><td class="name" onclick="toggle('isDirectlyDerivedFrom0')"><a name="isDirectlyDerivedFrom0Anchor">isDirectlyDerivedFrom</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1NamedDecl.html">NamedDecl</a>> Base</td></tr> -<tr><td colspan="4" class="doc" id="isDirectlyDerivedFrom0"><pre>Matches C++ classes that are directly derived from a class matching Base. +<tr><td colspan="4" class="doc" id="isDirectlyDerivedFrom0"><pre>Matches C++ or Objective-C classes that are directly derived from a class +matching Base. Note that a class is not considered to be derived from itself. @@ -6468,6 +6492,61 @@ Given </pre></td></tr> +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCInterfaceDecl.html">ObjCInterfaceDecl</a>></td><td class="name" onclick="toggle('isDerivedFrom1')"><a name="isDerivedFrom1Anchor">isDerivedFrom</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1NamedDecl.html">NamedDecl</a>> Base</td></tr> +<tr><td colspan="4" class="doc" id="isDerivedFrom1"><pre>Matches C++ classes that are directly or indirectly derived from a class +matching Base, or Objective-C classes that directly or indirectly +subclass a class matching Base. + +Note that a class is not considered to be derived from itself. + +Example matches Y, Z, C (Base == hasName("X")) + class X; + class Y : public X {}; // directly derived + class Z : public Y {}; // indirectly derived + typedef X A; + typedef A B; + class C : public B {}; // derived from a typedef of X + +In the following example, Bar matches isDerivedFrom(hasName("X")): + class Foo; + typedef Foo X; + class Bar : public Foo {}; // derived from a type that X is a typedef of + +In the following example, Bar matches isDerivedFrom(hasName("NSObject")) + @interface NSObject @end + @interface Bar : NSObject @end + +Usable as: Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXRecordDecl.html">CXXRecordDecl</a>>, Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCInterfaceDecl.html">ObjCInterfaceDecl</a>> +</pre></td></tr> + + +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCInterfaceDecl.html">ObjCInterfaceDecl</a>></td><td class="name" onclick="toggle('isDirectlyDerivedFrom1')"><a name="isDirectlyDerivedFrom1Anchor">isDirectlyDerivedFrom</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1NamedDecl.html">NamedDecl</a>> Base</td></tr> +<tr><td colspan="4" class="doc" id="isDirectlyDerivedFrom1"><pre>Matches C++ or Objective-C classes that are directly derived from a class +matching Base. + +Note that a class is not considered to be derived from itself. + +Example matches Y, C (Base == hasName("X")) + class X; + class Y : public X {}; // directly derived + class Z : public Y {}; // indirectly derived + typedef X A; + typedef A B; + class C : public B {}; // derived from a typedef of X + +In the following example, Bar matches isDerivedFrom(hasName("X")): + class Foo; + typedef Foo X; + class Bar : public Foo {}; // derived from a type that X is a typedef of +</pre></td></tr> + + +<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCInterfaceDecl.html">ObjCInterfaceDecl</a>></td><td class="name" onclick="toggle('isSameOrDerivedFrom1')"><a name="isSameOrDerivedFrom1Anchor">isSameOrDerivedFrom</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1NamedDecl.html">NamedDecl</a>> Base</td></tr> +<tr><td colspan="4" class="doc" id="isSameOrDerivedFrom1"><pre>Similar to isDerivedFrom(), but also matches classes that directly +match Base. +</pre></td></tr> + + <tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1ObjCMessageExpr.html">ObjCMessageExpr</a>></td><td class="name" onclick="toggle('hasAnyArgument3')"><a name="hasAnyArgument3Anchor">hasAnyArgument</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>> InnerMatcher</td></tr> <tr><td colspan="4" class="doc" id="hasAnyArgument3"><pre>Matches any argument of a call expression or a constructor call expression, or an ObjC-message-send expression. diff --git a/include/clang/ASTMatchers/ASTMatchers.h b/include/clang/ASTMatchers/ASTMatchers.h index 6bf20ead1b..e6d25069f1 100644 --- a/include/clang/ASTMatchers/ASTMatchers.h +++ b/include/clang/ASTMatchers/ASTMatchers.h @@ -2611,8 +2611,9 @@ hasOverloadedOperatorName(StringRef Name) { AST_POLYMORPHIC_SUPPORTED_TYPES(CXXOperatorCallExpr, FunctionDecl)>(Name); } -/// Matches C++ classes that are directly or indirectly derived from -/// a class matching \c Base. +/// Matches C++ classes that are directly or indirectly derived from a class +/// matching \c Base, or Objective-C classes that directly or indirectly +/// subclass a class matching \c Base. /// /// Note that a class is not considered to be derived from itself. /// @@ -2632,36 +2633,80 @@ hasOverloadedOperatorName(StringRef Name) { /// typedef Foo X; /// class Bar : public Foo {}; // derived from a type that X is a typedef of /// \endcode -AST_MATCHER_P(CXXRecordDecl, isDerivedFrom, - internal::Matcher<NamedDecl>, Base) { - return Finder->classIsDerivedFrom(&Node, Base, Builder, /*Directly=*/false); +/// +/// In the following example, Bar matches isDerivedFrom(hasName("NSObject")) +/// \code +/// @interface NSObject @end +/// @interface Bar : NSObject @end +/// \endcode +/// +/// Usable as: Matcher<CXXRecordDecl>, Matcher<ObjCInterfaceDecl> +AST_POLYMORPHIC_MATCHER_P( + isDerivedFrom, + AST_POLYMORPHIC_SUPPORTED_TYPES(CXXRecordDecl, ObjCInterfaceDecl), + internal::Matcher<NamedDecl>, Base) { + // Check if the node is a C++ struct/union/class. + if (const auto *RD = dyn_cast<CXXRecordDecl>(&Node)) + return Finder->classIsDerivedFrom(RD, Base, Builder, /*Directly=*/false); + + // The node must be an Objective-C class. + const auto *InterfaceDecl = cast<ObjCInterfaceDecl>(&Node); + return Finder->objcClassIsDerivedFrom(InterfaceDecl, Base, Builder, + /*Directly=*/false); } /// Overloaded method as shortcut for \c isDerivedFrom(hasName(...)). -AST_MATCHER_P_OVERLOAD(CXXRecordDecl, isDerivedFrom, std::string, BaseName, 1) { +AST_POLYMORPHIC_MATCHER_P_OVERLOAD( + isDerivedFrom, + AST_POLYMORPHIC_SUPPORTED_TYPES(CXXRecordDecl, ObjCInterfaceDecl), + std::string, BaseName, 1) { if (BaseName.empty()) return false; - return isDerivedFrom(hasName(BaseName)).matches(Node, Finder, Builder); + + const auto M = isDerivedFrom(hasName(BaseName)); + + if (const auto *RD = dyn_cast<CXXRecordDecl>(&Node)) + return Matcher<CXXRecordDecl>(M).matches(*RD, Finder, Builder); + + const auto *InterfaceDecl = cast<ObjCInterfaceDecl>(&Node); + return Matcher<ObjCInterfaceDecl>(M).matches(*InterfaceDecl, Finder, Builder); } /// Similar to \c isDerivedFrom(), but also matches classes that directly /// match \c Base. -AST_MATCHER_P_OVERLOAD(CXXRecordDecl, isSameOrDerivedFrom, - internal::Matcher<NamedDecl>, Base, 0) { - return Matcher<CXXRecordDecl>(anyOf(Base, isDerivedFrom(Base))) - .matches(Node, Finder, Builder); +AST_POLYMORPHIC_MATCHER_P_OVERLOAD( + isSameOrDerivedFrom, + AST_POLYMORPHIC_SUPPORTED_TYPES(CXXRecordDecl, ObjCInterfaceDecl), + internal::Matcher<NamedDecl>, Base, 0) { + const auto M = anyOf(Base, isDerivedFrom(Base)); + + if (const auto *RD = dyn_cast<CXXRecordDecl>(&Node)) + return Matcher<CXXRecordDecl>(M).matches(*RD, Finder, Builder); + + const auto *InterfaceDecl = cast<ObjCInterfaceDecl>(&Node); + return Matcher<ObjCInterfaceDecl>(M).matches(*InterfaceDecl, Finder, Builder); } /// Overloaded method as shortcut for /// \c isSameOrDerivedFrom(hasName(...)). -AST_MATCHER_P_OVERLOAD(CXXRecordDecl, isSameOrDerivedFrom, std::string, - BaseName, 1) { +AST_POLYMORPHIC_MATCHER_P_OVERLOAD( + isSameOrDerivedFrom, + AST_POLYMORPHIC_SUPPORTED_TYPES(CXXRecordDecl, ObjCInterfaceDecl), + std::string, BaseName, 1) { if (BaseName.empty()) return false; - return isSameOrDerivedFrom(hasName(BaseName)).matches(Node, Finder, Builder); + + const auto M = isSameOrDerivedFrom(hasName(BaseName)); + + if (const auto *RD = dyn_cast<CXXRecordDecl>(&Node)) + return Matcher<CXXRecordDecl>(M).matches(*RD, Finder, Builder); + + const auto *InterfaceDecl = cast<ObjCInterfaceDecl>(&Node); + return Matcher<ObjCInterfaceDecl>(M).matches(*InterfaceDecl, Finder, Builder); } -/// Matches C++ classes that are directly derived from a class matching \c Base. +/// Matches C++ or Objective-C classes that are directly derived from a class +/// matching \c Base. /// /// Note that a class is not considered to be derived from itself. /// @@ -2681,18 +2726,34 @@ AST_MATCHER_P_OVERLOAD(CXXRecordDecl, isSameOrDerivedFrom, std::string, /// typedef Foo X; /// class Bar : public Foo {}; // derived from a type that X is a typedef of /// \endcode -AST_MATCHER_P_OVERLOAD(CXXRecordDecl, isDirectlyDerivedFrom, - internal::Matcher<NamedDecl>, Base, 0) { - return Finder->classIsDerivedFrom(&Node, Base, Builder, /*Directly=*/true); +AST_POLYMORPHIC_MATCHER_P_OVERLOAD( + isDirectlyDerivedFrom, + AST_POLYMORPHIC_SUPPORTED_TYPES(CXXRecordDecl, ObjCInterfaceDecl), + internal::Matcher<NamedDecl>, Base, 0) { + // Check if the node is a C++ struct/union/class. + if (const auto *RD = dyn_cast<CXXRecordDecl>(&Node)) + return Finder->classIsDerivedFrom(RD, Base, Builder, /*Directly=*/true); + + // The node must be an Objective-C class. + const auto *InterfaceDecl = cast<ObjCInterfaceDecl>(&Node); + return Finder->objcClassIsDerivedFrom(InterfaceDecl, Base, Builder, + /*Directly=*/true); } /// Overloaded method as shortcut for \c isDirectlyDerivedFrom(hasName(...)). -AST_MATCHER_P_OVERLOAD(CXXRecordDecl, isDirectlyDerivedFrom, std::string, - BaseName, 1) { +AST_POLYMORPHIC_MATCHER_P_OVERLOAD( + isDirectlyDerivedFrom, + AST_POLYMORPHIC_SUPPORTED_TYPES(CXXRecordDecl, ObjCInterfaceDecl), + std::string, BaseName, 1) { if (BaseName.empty()) return false; - return isDirectlyDerivedFrom(hasName(BaseName)) - .matches(Node, Finder, Builder); + const auto M = isDirectlyDerivedFrom(hasName(BaseName)); + + if (const auto *RD = dyn_cast<CXXRecordDecl>(&Node)) + return Matcher<CXXRecordDecl>(M).matches(*RD, Finder, Builder); + + const auto *InterfaceDecl = cast<ObjCInterfaceDecl>(&Node); + return Matcher<ObjCInterfaceDecl>(M).matches(*InterfaceDecl, Finder, Builder); } /// Matches the first method of a class or struct that satisfies \c /// InnerMatcher. diff --git a/include/clang/ASTMatchers/ASTMatchersInternal.h b/include/clang/ASTMatchers/ASTMatchersInternal.h index 7b68d598c8..46e47a3100 100644 --- a/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -971,7 +971,7 @@ public: virtual ~ASTMatchFinder() = default; - /// Returns true if the given class is directly or indirectly derived + /// Returns true if the given C++ class is directly or indirectly derived /// from a base type matching \c base. /// /// A class is not considered to be derived from itself. @@ -980,6 +980,15 @@ public: BoundNodesTreeBuilder *Builder, bool Directly) = 0; + /// Returns true if the given Objective-C class is directly or indirectly + /// derived from a base class matching \c base. + /// + /// A class is not considered to be derived from itself. + virtual bool objcClassIsDerivedFrom(const ObjCInterfaceDecl *Declaration, + const Matcher<NamedDecl> &Base, + BoundNodesTreeBuilder *Builder, + bool Directly) = 0; + template <typename T> bool matchesChildOf(const T &Node, const DynTypedMatcher &Matcher, BoundNodesTreeBuilder *Builder, diff --git a/lib/ASTMatchers/ASTMatchFinder.cpp b/lib/ASTMatchers/ASTMatchFinder.cpp index 6a4715ebe1..6eb4a2c426 100644 --- a/lib/ASTMatchers/ASTMatchFinder.cpp +++ b/lib/ASTMatchers/ASTMatchFinder.cpp @@ -374,6 +374,12 @@ public: return true; } + bool VisitObjCCompatibleAliasDecl(ObjCCompatibleAliasDecl *CAD) { + const ObjCInterfaceDecl *InterfaceDecl = CAD->getClassInterface(); + CompatibleAliases[InterfaceDecl].insert(CAD); + return true; + } + bool TraverseDecl(Decl *DeclNode); bool TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue = nullptr); bool TraverseType(QualType TypeNode); @@ -433,6 +439,11 @@ public: BoundNodesTreeBuilder *Builder, bool Directly) override; + bool objcClassIsDerivedFrom(const ObjCInterfaceDecl *Declaration, + const Matcher<NamedDecl> &Base, + BoundNodesTreeBuilder *Builder, + bool Directly) override; + // Implements ASTMatchFinder::matchesChildOf. bool matchesChildOf(const ast_type_traits::DynTypedNode &Node, const DynTypedMatcher &Matcher, @@ -763,6 +774,23 @@ private: return false; } + bool + objcClassHasMatchingCompatibilityAlias(const ObjCInterfaceDecl *InterfaceDecl, + const Matcher<NamedDecl> &Matcher, + BoundNodesTreeBuilder *Builder) { + auto Aliases = CompatibleAliases.find(InterfaceDecl); + if (Aliases == CompatibleAliases.end()) + return false; + for (const ObjCCompatibleAliasDecl *Alias : Aliases->second) { + BoundNodesTreeBuilder Result(*Builder); + if (Matcher.matches(*Alias, this, &Result)) { + *Builder = std::move(Result); + return true; + } + } + return false; + } + /// Bucket to record map. /// /// Used to get the appropriate bucket for each matcher. @@ -787,6 +815,11 @@ private: // Maps a canonical type to its TypedefDecls. llvm::DenseMap<const Type*, std::set<const TypedefNameDecl*> > TypeAliases; + // Maps an Objective-C interface to its ObjCCompatibleAliasDecls. + llvm::DenseMap<const ObjCInterfaceDecl *, + llvm::SmallPtrSet<const ObjCCompatibleAliasDecl *, 2>> + CompatibleAliases; + // Maps (matcher, node) -> the match result for memoization. typedef std::map<MatchKey, MemoizedMatchResult> MemoizationMap; MemoizationMap ResultCache; @@ -813,7 +846,7 @@ getAsCXXRecordDeclOrPrimaryTemplate(const Type *TypeNode) { return nullptr; } -// Returns true if the given class is directly or indirectly derived +// Returns true if the given C++ class is directly or indirectly derived // from a base type with the given name. A class is not considered to be // derived from itself. bool MatchASTVisitor::classIsDerivedFrom(const CXXRecordDecl *Declaration, @@ -850,6 +883,34 @@ bool MatchASTVisitor::classIsDerivedFrom(const CXXRecordDecl *Declaration, return false; } +// Returns true if the given Objective-C class is directly or indirectly +// derived from a matching base class. A class is not considered to be derived +// from itself. +bool MatchASTVisitor::objcClassIsDerivedFrom( + const ObjCInterfaceDecl *Declaration, const Matcher<NamedDecl> &Base, + BoundNodesTreeBuilder *Builder, bool Directly) { + // Check if any of the superclasses of the class match. + for (const ObjCInterfaceDecl *ClassDecl = Declaration->getSuperClass(); + ClassDecl != nullptr; ClassDecl = ClassDecl->getSuperClass()) { + // Check if there are any matching compatibility aliases. + if (objcClassHasMatchingCompatibilityAlias(ClassDecl, Base, Builder)) + return true; + + // Check if there are any matching type aliases. + const Type *TypeNode = ClassDecl->getTypeForDecl(); + if (typeHasMatchingAlias(TypeNode, Base, Builder)) + return true; + + if (Base.matches(*ClassDecl, this, Builder)) + return true; + + if (Directly) + return false; + } + + return false; +} + bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) { if (!DeclNode) { return true; diff --git a/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp index 2038e9b6c0..52cd042c96 100644 --- a/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp +++ b/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp @@ -573,6 +573,111 @@ TEST(DeclarationMatcher, IsDerivedFromEmptyName) { EXPECT_TRUE(notMatches(Code, cxxRecordDecl(isSameOrDerivedFrom("")))); } +TEST(DeclarationMatcher, ObjCClassIsDerived) { + DeclarationMatcher IsDerivedFromX = objcInterfaceDecl(isDerivedFrom("X")); + EXPECT_TRUE( + matchesObjC("@interface X @end @interface Y : X @end", IsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end @interface Y<__covariant ObjectType> : X @end", + IsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end @compatibility_alias Y X; @interface Z : Y @end", + IsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end typedef X Y; @interface Z : Y @end", IsDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@interface X @end", IsDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@class X;", IsDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@class Y;", IsDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@interface X @end @compatibility_alias Y X;", + IsDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@interface X @end typedef X Y;", IsDerivedFromX)); + + DeclarationMatcher IsDirectlyDerivedFromX = + objcInterfaceDecl(isDirectlyDerivedFrom("X")); + EXPECT_TRUE( + matchesObjC("@interface X @end @interface Y : X @end", IsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end @interface Y<__covariant ObjectType> : X @end", + IsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end @compatibility_alias Y X; @interface Z : Y @end", + IsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end typedef X Y; @interface Z : Y @end", + IsDirectlyDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@interface X @end", IsDirectlyDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@class X;", IsDirectlyDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@class Y;", IsDirectlyDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@interface X @end @compatibility_alias Y X;", + IsDirectlyDerivedFromX)); + EXPECT_TRUE(notMatchesObjC("@interface X @end typedef X Y;", + IsDirectlyDerivedFromX)); + + DeclarationMatcher IsAX = objcInterfaceDecl(isSameOrDerivedFrom("X")); + EXPECT_TRUE(matchesObjC("@interface X @end @interface Y : X @end", IsAX)); + EXPECT_TRUE(matchesObjC("@interface X @end", IsAX)); + EXPECT_TRUE(matchesObjC("@class X;", IsAX)); + EXPECT_TRUE(notMatchesObjC("@interface Y @end", IsAX)); + EXPECT_TRUE(notMatchesObjC("@class Y;", IsAX)); + + DeclarationMatcher ZIsDerivedFromX = + objcInterfaceDecl(hasName("Z"), isDerivedFrom("X")); + DeclarationMatcher ZIsDirectlyDerivedFromX = + objcInterfaceDecl(hasName("Z"), isDirectlyDerivedFrom("X")); + EXPECT_TRUE(matchesObjC( + "@interface X @end @interface Y : X @end @interface Z : Y @end", + ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC("@interface X @end @interface Y : X @end typedef Y " + "V; typedef V W; @interface Z : W @end", + ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end typedef X Y; @interface Z : Y @end", ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end typedef X Y; @interface Z : Y @end", + ZIsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface A @end typedef A X; typedef A Y; @interface Z : Y @end", + ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface A @end typedef A X; typedef A Y; @interface Z : Y @end", + ZIsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end @compatibility_alias Y X; @interface Z : Y @end", + ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface X @end @compatibility_alias Y X; @interface Z : Y @end", + ZIsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface Y @end @compatibility_alias X Y; @interface Z : Y @end", + ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface Y @end @compatibility_alias X Y; @interface Z : Y @end", + ZIsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface A @end @compatibility_alias X A; @compatibility_alias Y A;" + "@interface Z : Y @end", ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface A @end @compatibility_alias X A; @compatibility_alias Y A;" + "@interface Z : Y @end", ZIsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface Y @end typedef Y X; @interface Z : X @end", ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface Y @end typedef Y X; @interface Z : X @end", + ZIsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface A @end @compatibility_alias Y A; typedef Y X;" + "@interface Z : A @end", ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface A @end @compatibility_alias Y A; typedef Y X;" + "@interface Z : A @end", ZIsDirectlyDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface A @end typedef A Y; @compatibility_alias X Y;" + "@interface Z : A @end", ZIsDerivedFromX)); + EXPECT_TRUE(matchesObjC( + "@interface A @end typedef A Y; @compatibility_alias X Y;" + "@interface Z : A @end", ZIsDirectlyDerivedFromX)); +} + TEST(DeclarationMatcher, IsLambda) { const auto IsLambda = cxxMethodDecl(ofClass(cxxRecordDecl(isLambda()))); EXPECT_TRUE(matches("auto x = []{};", IsLambda)); diff --git a/unittests/ASTMatchers/Dynamic/RegistryTest.cpp b/unittests/ASTMatchers/Dynamic/RegistryTest.cpp index cf016a120b..4e5c0ffe2b 100644 --- a/unittests/ASTMatchers/Dynamic/RegistryTest.cpp +++ b/unittests/ASTMatchers/Dynamic/RegistryTest.cpp @@ -439,7 +439,7 @@ TEST_F(RegistryTest, Errors) { Error.get()).isNull()); EXPECT_EQ("Incorrect type for arg 1. " "(Expected = Matcher<CXXRecordDecl>) != " - "(Actual = Matcher<CXXRecordDecl>&Matcher" + "(Actual = Matcher<CXXRecordDecl|ObjCInterfaceDecl>&Matcher" "<MemberExpr|UnresolvedMemberExpr|CXXDependentScopeMemberExpr>)", Error->toString()); } -- 2.40.0