This is an archive of the discontinued LLVM Phabricator instance.

[Sema][ObjC] Warn about implicitly autoreleasing indirect parameters that are captured by blocks
ClosedPublic

Authored by ahatanak on Oct 20 2016, 1:51 PM.

Details

Summary

ARC implicitly marks indirect parameters passed to a function as autoreleasing and passing a block that captures those parameters to another function sometimes causes problems that are hard to debug.

For example, in the code below, a block capturing fillMeIn is passed to enumerateObjectsUsingBlock, in which an autorelease pool is pushed and popped before and after the block is invoked:

void doStuff(NSString **fillMeIn) {
    [@[@"array"] enumerateObjectsUsingBlock:
      ^(id obj, NSUInteger idx, BOOL* stop) {
        *stop = YES;
        *fillMeIn = [@"wow" mutableCopy];
      }
     ];
}

The object assigned to *fillMeIn gets autoreleased inside the block, so it gets destructed when the autorelease pool is drained, and the program will crash if it tries to access the NSString returned in fillMeIn after doStuff returns.

To help the users figure out why the program is crashing, this patch adds a new warning "-Wblock-capture-autoreleasing" which warns about implicitly autoreleasing indirect parameters captured by blocks and suggests explicitly specifying ownership qualification.

Diff Detail

Repository
rL LLVM

Event Timeline

ahatanak updated this revision to Diff 75343.Oct 20 2016, 1:51 PM
ahatanak retitled this revision from to [Sema][ObjC] Warn about implicitly autoreleasing indirect parameters that are captured by blocks.
ahatanak updated this object.
ahatanak added a reviewer: rjmccall.
ahatanak added a subscriber: cfe-commits.
rjmccall added inline comments.Oct 20 2016, 10:13 PM
include/clang/Basic/DiagnosticSemaKinds.td
5080 ↗(On Diff #75343)

How about:

warning: block captures an autoreleasing out-parameter, which may result in use-after-free bugs
note: declare the parameter __autoreleasing explicitly to suppress this warning  (include a fixit on this one)
note: declare the parameter __strong or capture a __block __strong variable to keep values alive across autorelease pools
ahatanak updated this revision to Diff 75442.Oct 21 2016, 9:19 AM
ahatanak marked an inline comment as done.

Improve warning messages.

rjmccall edited edge metadata.Oct 22 2016, 10:16 AM

Thanks. LGTM.

This revision was automatically updated to reflect the committed changes.
Cy-4AH added a subscriber: Cy-4AH.Dec 3 2021, 2:58 AM

Hello!

What compiler do when call doStuff:

NSString * __strong fillMe;
NSString * __autoreleasing autorelesingFillMe = fillMe;
doStuff(&fillMe);
fillMe = autoreleasingFillMe;

Why it's not making the same thing in doStuff body?
Compiler generated code for yours example should be:

void doStuff(NSString **fillMeIn) {
    __block NSString *fillMeInResult;
    __block BOOL fillMeInResultHasChanged = NO;
    [@[@"array"] enumerateObjectsUsingBlock:
      ^(id obj, NSUInteger idx, BOOL* stop) {
        *stop = YES;
        fillMeInResult = [@"wow" mutableCopy];
        fillMeInResultHasChanged = YES;
      }
    ];
    if (fillMeInResultHasChanged) {
      *fillMeIn = fillMeInResult;
    }
}

Instead we got this warning and had to do the same stuff in every implementation

That's not an unreasonable idea, and we did consider it, and in fact it's still on the table as something we could do. However, it's a significantly more complex change, and we're not committed to doing it, and so we wanted to at least put this warning in place to help people avoid a relatively common bug.