Skip to content

Only take back an autorelease that objc_autoreleaseReturnValue() made - #426

Closed
ashalkhakov wants to merge 1 commit into
gnustep:masterfrom
ashalkhakov:fix/autoreleased-return-value
Closed

ashalkhakov wants to merge 1 commit into
gnustep:masterfrom
ashalkhakov:fix/autoreleased-return-value

Conversation

@ashalkhakov

Copy link
Copy Markdown

With the runtime's own autorelease pool, objc_retainAutoreleasedReturnValue() decides that its argument was autoreleased by the callee's objc_autoreleaseReturnValue() when the object on top of the pool is the same object, and then pops it instead of retaining. That is not enough to know. A callee need not have autoreleased what it returns at all: the getter clang synthesizes for a nonatomic strong property returns its ivar unretained, and its caller still calls objc_retainAutoreleasedReturnValue(). If the top of the pool holds the same object for another reason, that autorelease is taken for the return value's: popped, not retained, and released at the end of the caller's use. The object loses the reference the pool was keeping for someone else.

It happens in the most ordinary error-reporting code:

- (id)doSomething: (NSError **)error
{
  ...
  *error = self.error;             // retained, autoreleased: on top of the pool
  return self.error ? nil : ...;   // the getter again: that autorelease is popped
}

The caller receives an error the pool no longer keeps, and once the object that held it goes away, the error is freed while the caller still has it. On macOS the same code is fine.

objc_autoreleaseReturnValue() now records the pool slot it filled, and objc_retainAutoreleasedReturnValue() pops only that slot, and only while it is still the top of the pool and still holds the object; the record is cleared either way. This also stops the check from reading below the start of a pool page that has just been started.

Test/AutoreleasedReturnValue_arc.m aborts without this change, in the plain and the optimised builds, and passes with it; the rest of the suite passes either way.

🤖 Generated with Claude Code

With the runtime's own autorelease pool, objc_retainAutoreleasedReturnValue()
decides that its argument was autoreleased by the callee's
objc_autoreleaseReturnValue() when the object on top of the pool is the
same object, and then pops it instead of retaining. That is not enough to
know. A callee need not have autoreleased what it returns at all: the
getter clang synthesizes for a nonatomic strong property returns its ivar
unretained, and its caller still calls objc_retainAutoreleasedReturnValue().
If the top of the pool holds the same object for another reason, that
autorelease is taken for the return value's: popped, not retained, and
released at the end of the caller's use. The object loses the reference
the pool was keeping for someone else.

It happens in the most ordinary error-reporting code:

    - (id)doSomething: (NSError **)error
    {
      ...
      *error = self.error;             // retained, autoreleased: on top of the pool
      return self.error ? nil : ...;   // the getter again: that autorelease is popped
    }

The caller receives an error the pool no longer keeps, and once the
object that held it goes away, the error is freed while the caller still
has it. On macOS the same code is fine.

objc_autoreleaseReturnValue() now records the pool slot it filled, and
objc_retainAutoreleasedReturnValue() pops only that slot, and only while
it is still the top of the pool and still holds the object; the record is
cleared either way. This also stops the check from reading below the
start of a pool page that has just been started.

Test/AutoreleasedReturnValue_arc.m aborts without this change, in the
plain and the optimised builds, and passes with it; the rest of the
suite passes either way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants