diff --git a/Test/AutoreleasedReturnValue_arc.m b/Test/AutoreleasedReturnValue_arc.m new file mode 100644 index 00000000..d71a46b9 --- /dev/null +++ b/Test/AutoreleasedReturnValue_arc.m @@ -0,0 +1,45 @@ +#include +#include "Test.h" + +// A getter synthesized for a nonatomic strong property returns its ivar +// without retaining it, and its caller still calls +// objc_retainAutoreleasedReturnValue(). That must retain the value, not +// take over an autorelease of the same object that was made for another +// reason and happens to be on top of the pool. + +static int deallocated; + +@interface Canary : Test +@end +@implementation Canary +- (void)dealloc +{ + deallocated++; +} +@end + +@interface Holder : Test +@property (nonatomic, strong) id value; +@end +@implementation Holder +- (id)storeValue: (__autoreleasing id *)out +{ + *out = self.value; + return self.value ? nil : self; +} +@end + +int main(void) +{ + __autoreleasing id out = nil; + @autoreleasepool + { + Holder *holder = [Holder new]; + holder.value = [Canary new]; + [holder storeValue: &out]; + holder = nil; + assert(deallocated == 0); + } + assert(deallocated == 1); + return 0; +} diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index b88dda39..5e356bd6 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -87,6 +87,7 @@ endif () if (ENABLE_ALL_OBJC_ARC_TESTS) list(APPEND TESTS ARCTest_arc.m + AutoreleasedReturnValue_arc.m PropertyIntrospectionTest2_arc.m ) endif() diff --git a/arc.mm b/arc.mm index 10d4acb6..6d0dfd2c 100644 --- a/arc.mm +++ b/arc.mm @@ -127,6 +127,11 @@ - (void)release; { struct arc_autorelease_pool *pool; id returnRetained; + /** + * The pool slot filled by the last objc_autoreleaseReturnValue(), which + * objc_retainAutoreleasedReturnValue() may take back. + */ + id *returnSlot; }; /** @@ -712,9 +717,9 @@ static inline id autorelease(id obj) extern "C" OBJC_PUBLIC id objc_autoreleaseReturnValue(id obj) { + struct arc_tls* tls = getARCThreadData(); if (!useARCAutoreleasePool) { - struct arc_tls* tls = getARCThreadData(); if (NULL != tls) { objc_autorelease(tls->returnRetained); @@ -722,7 +727,14 @@ static inline id autorelease(id obj) return obj; } } - return objc_autorelease(obj); + obj = objc_autorelease(obj); + if ((NULL != tls) && useARCAutoreleasePool && (NULL != obj) && + (NULL != tls->pool) && (tls->pool->insert > tls->pool->pool) && + (*(tls->pool->insert-1) == obj)) + { + tls->returnSlot = tls->pool->insert-1; + } + return obj; } extern "C" OBJC_PUBLIC id objc_retainAutoreleasedReturnValue(id obj) @@ -738,11 +750,14 @@ static inline id autorelease(id obj) struct arc_tls* tls = getARCThreadData(); if (NULL != tls) { - // If we're using our own autorelease pool, just pop the object from the top + // If we're using our own autorelease pool, pop the object from the top, + // but only if objc_autoreleaseReturnValue() put it there. if (useARCAutoreleasePool) { - if ((NULL != tls->pool) && - (*(tls->pool->insert-1) == obj)) + id *slot = tls->returnSlot; + tls->returnSlot = NULL; + if ((NULL != tls->pool) && (NULL != slot) && + (slot == tls->pool->insert-1) && (*slot == obj)) { tls->pool->insert--; return obj;