ARC Optimization vs. -fstack-protector
After months of painstaking work, we’ve got our apps building, and most of our tests building, and almost most of them passing.
Except for some tests that use test fixtures. And assert that those test fixtures get deallocated. And they passed in Xcode. And failed when run via Bazel.
[…]
However, due to the way the stack protector works (by adding instructions at the start and end of function), they can interfere with a call to
objc_autoreleaseReturnValue
being able to see it’s matching call toobjc_retainAutoreleaseReturnValue
, and then[object autorelease]
will actually have to do an autorelease. Which means that the object will go into the autoreleasepool. And it won’t be deallocated until that pool drains. AndXCTestCase
’s-setUp
and-tearDown
methods happen inside the same autoreleasepool.[…]
What made this bug so fun (and infuriating) to investigate was that it sat at the intersection of a bunch of different moving pieces. Our code was technically wrong (relying on performance optimizations in the runtime isn’t especially safe). Bazel did something incredibly unexpected (passing
-fstack-protector
when I didn’t ask it to). The Objective-C runtime has a performance optimization that does more than optimize (this is valid code under ARC, and yet it’s behavior is different from what ARC’s semantics say it should be). And, finally, clang allows me to pass a compiler option that changes observable behavior, without documenting that it can do more than catch a small set of bugs.
Previously:
- Why objc_autoreleaseReturnValue Differs for x86_64 and ARM
- ARC’s Fast Autorelease
- Automatic Reference Counting
1 Comment RSS · Twitter · Mastodon
Like the author said, this code:
__weak __typeof__(self.testFixture) weakTestFixture = self.testFixture;
self.testFixture = nil;
XCTAssertNil(weakTestFixture, @"Expected thing to be nil");
Relies heavily on undocumented compiler nuance and ordering, and doing that in a unit test is never a good idea. The better approach is to pass an XCTestExpectation, fulfill that in the dealloc method and wait in the test for that expectation to be fulfilled.