From: Aaron Ballman Date: Thu, 12 Mar 2015 13:21:19 +0000 (+0000) Subject: Added some matchers for objective c selectors and messages to ASTMatchers.h. Minor... X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=37b63a2e1af3c33507c18015159a21fb32f14a8a;p=clang Added some matchers for objective c selectors and messages to ASTMatchers.h. Minor mods to ASTMatchersTest.h to allow test files with ".m" extension in addition to ".cpp". New tests added to ASTMatchersTest.c. Patch by Dean Sutherland, reviewed by Manuel Klimek. From http://reviews.llvm.org/D7710 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@232034 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/ASTMatchers/ASTMatchers.h b/include/clang/ASTMatchers/ASTMatchers.h index 16d9e8dd52..6a7b530360 100644 --- a/include/clang/ASTMatchers/ASTMatchers.h +++ b/include/clang/ASTMatchers/ASTMatchers.h @@ -47,6 +47,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/DeclFriend.h" +#include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/ASTMatchers/ASTMatchersInternal.h" #include "clang/ASTMatchers/ASTMatchersMacros.h" @@ -869,6 +870,20 @@ const internal::VariadicDynCastAllOfMatcher< Stmt, CXXMemberCallExpr> memberCallExpr; +/// \brief Matches ObjectiveC Message invocation expressions. +/// +/// The innermost message send invokes the "alloc" class method on the +/// NSString class, while the outermost message send invokes the +/// "initWithString" instance method on the object returned from +/// NSString's "alloc". This matcher should match both message sends. +/// \code +/// [[NSString alloc] initWithString:@"Hello"] +/// \endcode +const internal::VariadicDynCastAllOfMatcher< + Stmt, + ObjCMessageExpr> objcMessageExpr; + + /// \brief Matches expressions that introduce cleanups to be run at the end /// of the sub-expression's evaluation. /// @@ -2007,6 +2022,104 @@ AST_MATCHER_P(CXXMemberCallExpr, on, internal::Matcher, InnerMatcher.matches(*ExprNode, Finder, Builder)); } + +/// \brief Matches on the receiver of an ObjectiveC Message expression. +/// +/// Example +/// matcher = objCMessageExpr(hasRecieverType(asString("UIWebView *"))); +/// matches the [webView ...] message invocation. +/// \code +/// NSString *webViewJavaScript = ... +/// UIWebView *webView = ... +/// [webView stringByEvaluatingJavaScriptFromString:webViewJavascript]; +/// \endcode +AST_MATCHER_P(ObjCMessageExpr, hasReceiverType, internal::Matcher, + InnerMatcher) { + const QualType TypeDecl = Node.getReceiverType(); + return InnerMatcher.matches(TypeDecl, Finder, Builder); +} + +/// \brief Matches when BaseName == Selector.getAsString() +/// +/// matcher = objCMessageExpr(hasSelector("loadHTMLString:baseURL:")); +/// matches the outer message expr in the code below, but NOT the message +/// invocation for self.bodyView. +/// \code +/// [self.bodyView loadHTMLString:html baseURL:NULL]; +/// \endcode + AST_MATCHER_P(ObjCMessageExpr, hasSelector, std::string, BaseName) { + Selector Sel = Node.getSelector(); + return BaseName.compare(Sel.getAsString()) == 0; +} + + +/// \brief Matches ObjC selectors whose name contains +/// a substring matched by the given RegExp. +/// matcher = objCMessageExpr(matchesSelector("loadHTMLString\:baseURL?")); +/// matches the outer message expr in the code below, but NOT the message +/// invocation for self.bodyView. +/// \code +/// [self.bodyView loadHTMLString:html baseURL:NULL]; +/// \endcode +AST_MATCHER_P(ObjCMessageExpr, matchesSelector, std::string, RegExp) { + assert(!RegExp.empty()); + std::string SelectorString = Node.getSelector().getAsString(); + llvm::Regex RE(RegExp); + return RE.match(SelectorString); +} + +/// \brief Matches when the selector is the empty selector +/// +/// Matches only when the selector of the objCMessageExpr is NULL. This may +/// represent an error condition in the tree! +AST_MATCHER(ObjCMessageExpr, hasNullSelector) { + return Node.getSelector().isNull(); +} + +/// \brief Matches when the selector is a Unary Selector +/// +/// matcher = objCMessageExpr(matchesSelector(hasUnarySelector()); +/// matches self.bodyView in the code below, but NOT the outer message +/// invocation of "loadHTMLString:baseURL:". +/// \code +/// [self.bodyView loadHTMLString:html baseURL:NULL]; +/// \endcode +AST_MATCHER(ObjCMessageExpr, hasUnarySelector) { + return Node.getSelector().isUnarySelector(); +} + +/// \brief Matches when the selector is a keyword selector +/// +/// objCMessageExpr(hasKeywordSelector()) matches the generated setFrame +/// message expression in +/// +/// \code +/// UIWebView *webView = ...; +/// CGRect bodyFrame = webView.frame; +/// bodyFrame.size.height = self.bodyContentHeight; +/// webView.frame = bodyFrame; +/// // ^---- matches here +/// \endcode + +AST_MATCHER(ObjCMessageExpr, hasKeywordSelector) { + return Node.getSelector().isKeywordSelector(); +} + +/// \brief Matches when the selector has the specified number of arguments +/// +/// matcher = objCMessageExpr(numSelectorArgs(1)); +/// matches self.bodyView in the code below +/// +/// matcher = objCMessageExpr(numSelectorArgs(2)); +/// matches the invocation of "loadHTMLString:baseURL:" but not that +/// of self.bodyView +/// \code +/// [self.bodyView loadHTMLString:html baseURL:NULL]; +/// \endcode +AST_MATCHER_P(ObjCMessageExpr, numSelectorArgs, unsigned, N) { + return Node.getSelector().getNumArgs() == N; +} + /// \brief Matches if the call expression's callee expression matches. /// /// Given @@ -2316,7 +2429,8 @@ AST_MATCHER(VarDecl, hasGlobalStorage) { /// \endcode AST_POLYMORPHIC_MATCHER_P(argumentCountIs, AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr, - CXXConstructExpr), + CXXConstructExpr, + ObjCMessageExpr), unsigned, N) { return Node.getNumArgs() == N; } @@ -2331,7 +2445,8 @@ AST_POLYMORPHIC_MATCHER_P(argumentCountIs, /// \endcode AST_POLYMORPHIC_MATCHER_P2(hasArgument, AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr, - CXXConstructExpr), + CXXConstructExpr, + ObjCMessageExpr), unsigned, N, internal::Matcher, InnerMatcher) { return (N < Node.getNumArgs() && InnerMatcher.matches( diff --git a/include/clang/ASTMatchers/ASTMatchersInternal.h b/include/clang/ASTMatchers/ASTMatchersInternal.h index d96e3a9f7c..c4093898b6 100644 --- a/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -38,9 +38,12 @@ #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" +#include "clang/AST/StmtObjC.h" #include "clang/AST/Type.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/VariadicFunction.h" diff --git a/lib/ASTMatchers/Dynamic/Registry.cpp b/lib/ASTMatchers/Dynamic/Registry.cpp index c074279a90..e46e6dafad 100644 --- a/lib/ASTMatchers/Dynamic/Registry.cpp +++ b/lib/ASTMatchers/Dynamic/Registry.cpp @@ -198,6 +198,7 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(hasIncrement); REGISTER_MATCHER(hasIndex); REGISTER_MATCHER(hasInitializer); + REGISTER_MATCHER(hasKeywordSelector); REGISTER_MATCHER(hasLHS); REGISTER_MATCHER(hasLocalQualifiers); REGISTER_MATCHER(hasLocalStorage); @@ -205,6 +206,7 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(hasLoopVariable); REGISTER_MATCHER(hasMethod); REGISTER_MATCHER(hasName); + REGISTER_MATCHER(hasNullSelector); REGISTER_MATCHER(hasObjectExpression); REGISTER_MATCHER(hasOperatorName); REGISTER_MATCHER(hasOverloadedOperatorName); @@ -212,7 +214,9 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(hasParent); REGISTER_MATCHER(hasQualifier); REGISTER_MATCHER(hasRangeInit); + REGISTER_MATCHER(hasReceiverType); REGISTER_MATCHER(hasRHS); + REGISTER_MATCHER(hasSelector); REGISTER_MATCHER(hasSingleDecl); REGISTER_MATCHER(hasSize); REGISTER_MATCHER(hasSizeExpr); @@ -223,6 +227,7 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(hasTrueExpression); REGISTER_MATCHER(hasTypeLoc); REGISTER_MATCHER(hasUnaryOperand); + REGISTER_MATCHER(hasUnarySelector); REGISTER_MATCHER(hasValueType); REGISTER_MATCHER(ifStmt); REGISTER_MATCHER(ignoringImpCasts); @@ -262,6 +267,7 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(lambdaExpr); REGISTER_MATCHER(lValueReferenceType); REGISTER_MATCHER(matchesName); + REGISTER_MATCHER(matchesSelector); REGISTER_MATCHER(materializeTemporaryExpr); REGISTER_MATCHER(member); REGISTER_MATCHER(memberCallExpr); @@ -276,7 +282,9 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(newExpr); REGISTER_MATCHER(nullPtrLiteralExpr); REGISTER_MATCHER(nullStmt); + REGISTER_MATCHER(numSelectorArgs); REGISTER_MATCHER(ofClass); + REGISTER_MATCHER(objcMessageExpr); REGISTER_MATCHER(on); REGISTER_MATCHER(onImplicitObjectArgument); REGISTER_MATCHER(operatorCallExpr); diff --git a/unittests/ASTMatchers/ASTMatchersTest.cpp b/unittests/ASTMatchers/ASTMatchersTest.cpp index d43e3f339b..7edec96c0f 100644 --- a/unittests/ASTMatchers/ASTMatchersTest.cpp +++ b/unittests/ASTMatchers/ASTMatchersTest.cpp @@ -4714,5 +4714,50 @@ TEST(Matcher, IsExpansionInFileMatching) { #endif // LLVM_ON_WIN32 + +TEST(ObjCMessageExprMatcher, SimpleExprs) { + // don't find ObjCMessageExpr where none are present + EXPECT_TRUE(notMatchesObjC("", objcMessageExpr(anything()))); + + std::string Objc1String = + "@interface Str " + " - (Str *)uppercaseString:(Str *)str;" + "@end " + "@interface foo " + "- (void)meth:(Str *)text;" + "@end " + " " + "@implementation foo " + "- (void) meth:(Str *)text { " + " [self contents];" + " Str *up = [text uppercaseString];" + "} " + "@end "; + EXPECT_TRUE(matchesObjC( + Objc1String, + objcMessageExpr(anything()))); + EXPECT_TRUE(matchesObjC( + Objc1String, + objcMessageExpr(hasSelector("contents")))); + EXPECT_TRUE(matchesObjC( + Objc1String, + objcMessageExpr(matchesSelector("cont*")))); + EXPECT_FALSE(matchesObjC( + Objc1String, + objcMessageExpr(matchesSelector("?cont*")))); + EXPECT_TRUE(notMatchesObjC( + Objc1String, + objcMessageExpr(hasSelector("contents"), hasNullSelector()))); + EXPECT_TRUE(matchesObjC( + Objc1String, + objcMessageExpr(hasSelector("contents"), hasUnarySelector()))); + EXPECT_TRUE(matchesObjC( + Objc1String, + objcMessageExpr(matchesSelector("uppercase*"), + argumentCountIs(0) + ))); + +} + } // end namespace ast_matchers } // end namespace clang diff --git a/unittests/ASTMatchers/ASTMatchersTest.h b/unittests/ASTMatchers/ASTMatchersTest.h index eb4cac5abc..4befe4a129 100644 --- a/unittests/ASTMatchers/ASTMatchersTest.h +++ b/unittests/ASTMatchers/ASTMatchersTest.h @@ -62,7 +62,8 @@ template testing::AssertionResult matchesConditionally( const std::string &Code, const T &AMatcher, bool ExpectMatch, llvm::StringRef CompileArg, - const FileContentMappings &VirtualMappedFiles = FileContentMappings()) { + const FileContentMappings &VirtualMappedFiles = FileContentMappings(), + const std::string &Filename = "input.cc") { bool Found = false, DynamicFound = false; MatchFinder Finder; VerifyMatch VerifyFound(nullptr, &Found); @@ -78,7 +79,7 @@ testing::AssertionResult matchesConditionally( // Some tests need rtti/exceptions on Args.push_back("-frtti"); Args.push_back("-fexceptions"); - if (!runToolOnCodeWithArgs(Factory->create(), Code, Args, "input.cc", + if (!runToolOnCodeWithArgs(Factory->create(), Code, Args, Filename, VirtualMappedFiles)) { return testing::AssertionFailure() << "Parsing error in \"" << Code << "\""; } @@ -109,6 +110,23 @@ testing::AssertionResult notMatches(const std::string &Code, return matchesConditionally(Code, AMatcher, false, "-std=c++11"); } +template +testing::AssertionResult matchesObjC(const std::string &Code, + const T &AMatcher) { + return matchesConditionally( + Code, AMatcher, true, + "", FileContentMappings(), "input.m"); +} + +template +testing::AssertionResult notMatchesObjC(const std::string &Code, + const T &AMatcher) { + return matchesConditionally( + Code, AMatcher, false, + "", FileContentMappings(), "input.m"); +} + + // Function based on matchesConditionally with "-x cuda" argument added and // small CUDA header prepended to the code string. template