arphaman created this revision.

The `performSelector` family of methods from Foundation use `objc_msgSend` to 
dispatch the selector invocations to objects. However, method calls to methods 
that return record types might have to use the `objc_msgSend_stret` as the 
return value won't find into the register [1]. This is also supported by this 
sentence from `performSelector` documentation: "The method should not have a 
significant return value and should take a single argument of type id, or no 
arguments" [2]. This patch adds a new warning that warns when a selector which 
corresponds to a method that returns a record type is passed into 
`performSelector`.

[1] 
http://www.tomdalling.com/blog/cocoa/why-performselector-is-more-dangerous-than-i-thought/
[2] 
https://developer.apple.com/reference/objectivec/nsobject/1416176-performselector?language=objc


Repository:
  rL LLVM

https://reviews.llvm.org/D30174

Files:
  include/clang/Basic/DiagnosticSemaKinds.td
  lib/AST/DeclObjC.cpp
  lib/Basic/IdentifierTable.cpp
  lib/Sema/SemaExprObjC.cpp
  test/SemaObjC/unsafe-perform-selector.m

Index: test/SemaObjC/unsafe-perform-selector.m
===================================================================
--- /dev/null
+++ test/SemaObjC/unsafe-perform-selector.m
@@ -0,0 +1,55 @@
+// RUN: %clang_cc1 -triple x86_64-apple-darwin11 -fsyntax-only -fobjc-arc -verify %s
+// rdar://12056271
+
+__attribute__((objc_root_class))
+@interface NSObject
+
+- (id)performSelector:(SEL)sel;
+- (void)performSelectorInBackground:(SEL)sel withObject:(id)arg;
+- (void)performSelectorOnMainThread:(SEL)sel;
+
+@end
+
+typedef struct { int x; int y; int width; int height; } Rectangle;
+
+struct Struct { Rectangle r; };
+
+typedef union { int x; float f; } Union;
+
+@interface Base : NSObject
+
+- (struct Struct)returnsStruct2; // expected-note {{method 'returnsStruct2' that returns 'struct Struct' declared here}}
+- (Union)returnsId;
+
+@end
+
+@protocol IP
+
+- (Union)returnsUnion; // expected-note {{method 'returnsUnion' that returns 'Union' declared here}}
+
+@end
+
+@interface I : Base<IP>
+
+- (Rectangle)returnsStruct; // expected-note 2 {{method 'returnsStruct' that returns 'Rectangle' declared here}}
+- (id)returnsId; // shadows base 'returnsId'
+- (int)returnsInt;
+- (I *)returnPtr;
+
++ (Rectangle)returnsStructClass; // expected-note {{method 'returnsStructClass' that returns 'Rectangle' declared here}}
++ (void)returnsUnion; // Not really
+
+@end
+
+void foo(I *i) {
+  [i performSelector: @selector(returnsStruct)]; // expected-warning {{'performSelector:' is incompatible with selectors that return a struct type}}
+  [i performSelectorInBackground: @selector(returnsStruct) withObject:0]; // expected-warning {{'performSelectorInBackground:withObject:' is incompatible with selectors that return a struct type}}
+  [i performSelector: ((@selector(returnsUnion)))]; // expected-warning {{'performSelector:' is incompatible with selectors that return a union type}}
+  [i performSelectorOnMainThread: @selector(returnsStruct2)]; // expected-warning {{'performSelectorOnMainThread:' is incompatible with selectors that return a struct type}}
+  [I performSelector: (@selector(returnsStructClass))]; // expected-warning {{'performSelector:' is incompatible with selectors that return a struct type}}
+
+  [i performSelector: @selector(returnsId)];
+  [i performSelector: @selector(returnsInt)];
+  [i performSelector: @selector(returnsPtr)];
+  [I performSelector: @selector(returnsUnion)]; // No warning expected
+}
Index: lib/Sema/SemaExprObjC.cpp
===================================================================
--- lib/Sema/SemaExprObjC.cpp
+++ lib/Sema/SemaExprObjC.cpp
@@ -2260,6 +2260,41 @@
                      edit::rewriteObjCRedundantCallWithLiteral);
 }
 
+static void checkFoundationAPI(Sema &S, SourceLocation Loc,
+                               const ObjCMethodDecl *Method,
+                               ArrayRef<Expr *> Args, QualType ReceiverType,
+                               bool IsInstanceMethod) {
+  // Check if this is a performSelector method that uses a selector that returns
+  // a record type.
+  if (Method->getMethodFamily() != OMF_performSelector || Args.empty())
+    return;
+  const auto *SE = dyn_cast<ObjCSelectorExpr>(Args[0]->IgnoreParens());
+  if (!SE)
+    return;
+  ObjCMethodDecl *ImpliedMethod;
+  if (IsInstanceMethod) {
+    const auto *OPT = ReceiverType->getAs<ObjCObjectPointerType>();
+    if (!OPT)
+      return;
+    ImpliedMethod =
+        OPT->getInterfaceDecl()->lookupInstanceMethod(SE->getSelector());
+  } else {
+    const auto *IT = ReceiverType->getAs<ObjCInterfaceType>();
+    if (!IT)
+      return;
+    ImpliedMethod = IT->getDecl()->lookupClassMethod(SE->getSelector());
+  }
+  if (ImpliedMethod && ImpliedMethod->getReturnType()->isRecordType()) {
+    QualType Ret = ImpliedMethod->getReturnType();
+    S.Diag(Loc, diag::warn_objc_unsafe_perform_selector)
+        << Method->getSelector()
+        << (Ret->isUnionType() ? /*Union*/ 1 : /*Struct*/ 0);
+    S.Diag(ImpliedMethod->getLocStart(),
+           diag::note_objc_unsafe_perform_selector_method_declared_here)
+        << ImpliedMethod->getSelector() << Ret;
+  }
+}
+
 /// \brief Diagnose use of %s directive in an NSString which is being passed
 /// as formatting string to formatting method.
 static void
@@ -2461,6 +2496,9 @@
                                      RBracLoc, isImplicit);
     if (!isImplicit)
       checkCocoaAPI(*this, Result);
+    if (Method)
+      checkFoundationAPI(*this, SelLoc, Method, makeArrayRef(Args, NumArgs),
+                         ReceiverType, /*IsInstanceMethod=*/false);
   }
   return MaybeBindToTemporary(Result);
 }
@@ -2987,6 +3025,9 @@
                                      isImplicit);
     if (!isImplicit)
       checkCocoaAPI(*this, Result);
+    if (Method)
+      checkFoundationAPI(*this, SelLoc, Method, makeArrayRef(Args, NumArgs),
+                         ReceiverType, /*IsInstanceMethod=*/true);
   }
 
   if (getLangOpts().ObjCAutoRefCount) {
Index: lib/Basic/IdentifierTable.cpp
===================================================================
--- lib/Basic/IdentifierTable.cpp
+++ lib/Basic/IdentifierTable.cpp
@@ -487,8 +487,10 @@
     if (name == "self") return OMF_self;
     if (name == "initialize") return OMF_initialize;
   }
- 
-  if (name == "performSelector") return OMF_performSelector;
+
+  if (name == "performSelector" || name == "performSelectorInBackground" ||
+      name == "performSelectorOnMainThread")
+    return OMF_performSelector;
 
   // The other method families may begin with a prefix of underscores.
   while (!name.empty() && name.front() == '_')
Index: lib/AST/DeclObjC.cpp
===================================================================
--- lib/AST/DeclObjC.cpp
+++ lib/AST/DeclObjC.cpp
@@ -979,7 +979,8 @@
     break;
       
   case OMF_performSelector:
-    if (!isInstanceMethod() || !getReturnType()->isObjCIdType())
+    if (!isInstanceMethod() ||
+        !(getReturnType()->isObjCIdType() || getReturnType()->isVoidType()))
       family = OMF_None;
     else {
       unsigned noParams = param_size();
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -6001,6 +6001,11 @@
   "adding '%0' to '%1' might cause circular dependency in container">,
   InGroup<DiagGroup<"objc-circular-container">>;
 def note_objc_circular_container_declared_here : Note<"'%0' declared here">;
+def warn_objc_unsafe_perform_selector : Warning<
+  "%0 is incompatible with selectors that return a %select{struct|union}1 "
+  "type">, InGroup<DiagGroup<"objc-unsafe-perform-selector">>;
+def note_objc_unsafe_perform_selector_method_declared_here :  Note<
+  "method %0 that returns %1 declared here">;
 
 def warn_setter_getter_impl_required : Warning<
   "property %0 requires method %1 to be defined - "
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to