This is an archive of the discontinued LLVM Phabricator instance.

[Sema][ObjC] Warn about 'performSelector' calls with selectors that return record types
ClosedPublic

Authored by arphaman on Feb 20 2017, 10:29 AM.

Details

Summary

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

Diff Detail

Repository
rL LLVM

Event Timeline

arphaman created this revision.Feb 20 2017, 10:29 AM
ahatanak edited edge metadata.Feb 20 2017, 1:41 PM

Do we still issue a warning even when the struct can be returned in a register? For example, x86 can return a small struct (for example, a struct with one int field) in a single register, in which case it's fine to pass it to performSelector via @selector.

If we should warn only when the method has to return via sret, then it looks like we have to delay issuing the warning until we know where the return value goes (IRGen?).

Do we still issue a warning even when the struct can be returned in a register? For example, x86 can return a small struct (for example, a struct with one int field) in a single register, in which case it's fine to pass it to performSelector via @selector.

Yes, we do. Primarily for the following reason: even if some target may return a struct in a register, another target isn't guaranteed to do the same thing. It's better to always warn about this rather than accept some small structs. Furthermore, the official documentation states that "For methods that return anything other than an object, use NSInvocation." [1], so methods that return records are completely prohibited by the docs.

If we should warn only when the method has to return via sret, then it looks like we have to delay issuing the warning until we know where the return value goes (IRGen?).

[1] https://developer.apple.com/reference/objectivec/1418956-nsobject/1418867-performselector?language=objc

Yes, we do. Primarily for the following reason: even if some target may return a struct in a register, another target isn't guaranteed to do the same thing. It's better to always warn about this rather than accept some small structs. Furthermore, the official documentation states that "For methods that return anything other than an object, use NSInvocation." [1], so methods that return records are completely prohibited by the docs.

OK, I think you are right. It doesn't seem like a good idea to warn when compiling for one target and not warn when compiling for another target.

If the official documentation says the method should return an object, why not prohibit all methods that don't return a pointer to an object (methods like returnsInt in the test case)? Doing so will also take care of methods that don't return struct but still return via sret (for example, I think some targets return vectors via sret).

Also, the documentation says that methods passed to performSelectorInBackground and performSelectorOnMainThread should not have a significant return value. Does it mean that the methods can have any return type but the return value will be ignored (I noticed that those two methods return "void" unlike performSelector which returns "id")?

Yes, we do. Primarily for the following reason: even if some target may return a struct in a register, another target isn't guaranteed to do the same thing. It's better to always warn about this rather than accept some small structs. Furthermore, the official documentation states that "For methods that return anything other than an object, use NSInvocation." [1], so methods that return records are completely prohibited by the docs.

OK, I think you are right. It doesn't seem like a good idea to warn when compiling for one target and not warn when compiling for another target.

If the official documentation says the method should return an object, why not prohibit all methods that don't return a pointer to an object (methods like returnsInt in the test case)? Doing so will also take care of methods that don't return struct but still return via sret (for example, I think some targets return vectors via sret).

I think it would catch too many "false positives" if we prohibit all primitive types. This might make the warning less effective as well, since users might get annoyed and completely disable the warning if they think that it's too strict. Maybe we can have a stricter version of the warning that's not on by default? I agree that we can warn on vectors as well though.

Also, the documentation says that methods passed to performSelectorInBackground and performSelectorOnMainThread should not have a significant return value. Does it mean that the methods can have any return type but the return value will be ignored (I noticed that those two methods return "void" unlike performSelector which returns "id")?

No, they still use objc_msgSend, so the memory corruption problem still applies to them as they can write to the object thinking that it's the pointer to the return value. The fact that performSelectorInBackground and performSelectorOnMainThread don't return anything doesn't really make a difference.

I'm not sure how common it is to pass a function that doesn't return an object or void, I think it's OK to allow returning primitive types if you think being too strict would catch too many false positives.

lib/AST/DeclObjC.cpp
987 ↗(On Diff #89132)

It seems like this code would set "family" to OMF_None for some of the performSelector functions. For example:

https://developer.apple.com/reference/objectivec/nsobject/1411637-performselectoronmainthread?language=objc
https://developer.apple.com/reference/objectivec/nsobject/1417922-performselector?language=objc

Do those functions belong to the performSelector family of methods?

lib/Sema/SemaExprObjC.cpp
2280 ↗(On Diff #89132)

Do you need to check if OPT->getInterfaceDecl() returns null here? What happens if OPT is id?

2499 ↗(On Diff #89132)

I'm not sure why checkFoundationAPI has to be called inside the else statement. Was there a reason you didn't or couldn't move it outside?

arphaman marked 3 inline comments as done.Mar 3 2017, 6:44 AM
arphaman added inline comments.
lib/AST/DeclObjC.cpp
987 ↗(On Diff #89132)

Good catch! Yes, we should be more inclusive here.

lib/Sema/SemaExprObjC.cpp
2280 ↗(On Diff #89132)

You're right, my mistake.

2499 ↗(On Diff #89132)

We should check for 'super' calls as wells, you're right.

arphaman updated this revision to Diff 90473.Mar 3 2017, 6:46 AM
arphaman marked 3 inline comments as done.

The updated diff:

  • Warns for vector types.
  • Addresses Akira's comments.
ahatanak accepted this revision.Mar 5 2017, 10:28 PM

Looks good, thanks!

This revision is now accepted and ready to land.Mar 5 2017, 10:28 PM
This revision was automatically updated to reflect the committed changes.