Why setting object that is undergoing deallocation to weak property results in crash
Asked Answered
C

1

23

In Clang's Objective-C Automatic Reference Counting we see the following

For __weak objects, the lvalue is updated to point to the new pointee, unless the new pointee is an object currently undergoing deallocation, in which case the lvalue is updated to a null pointer. This must execute atomically with respect to other assignments to the object, to reads from the object, and to the final release of the new pointee.

In objc-weak.mm wee see the following chunk of code in weak_register_no_lock():

    if (deallocating) {
    if (crashIfDeallocating) {
        _objc_fatal("Cannot form weak reference to instance (%p) of "
                    "class %s. It is possible that this object was "
                    "over-released, or is in the process of deallocation.",
                    (void*)referent, object_getClassName((id)referent));
    } else {
        return nil;
    }
}

I set a breakpoint in my UIViewController subclass dealloc method and tried invoking [self allowsWeakReference] in lldb which resulted in NO value.

If we try to set self to weak property of another object the app will crash in accordance with the objc-weak.mm code.

The question is – why does this happen? Is the clang's specification wrong? Is is this a bug in objc implementation?


Here is a simple piece of code that will reproduce the crash:

//cc -fmodules -fobjc-arc -g crash.m -o crash
@import Foundation;

@interface Foo : NSObject
@end

@implementation Foo
- (void)dealloc {
  Foo * __weak weakSelf = self; // crashes on this line
}
@end

int main() {
  (void)[[Foo alloc] init];
  return 0;
}
Congo answered 14/3, 2016 at 15:18 Comment(15)
So what is it you want to know; why your app crashes or why setting a weak pointer to a deallocating object results in null? Also why have you assumed the two things are related?Versicular
I slightly edited my question to use the latest objc-weak.mm source.Congo
But you didn't answer my questions.Versicular
Sorry, I pressed enter to quickly. I see two contradicting statements. The first one is that setting a weak pointer to a deallocating object should result in nil. But in the source code of objc-weak we see that it will result in crash instead. I was assuming that it is perfectly legal to store self to weak properties during the deallocation, but now I see that it isn't. Isn't it a bug?Congo
And what sets crashIfDeallocating?Versicular
It is the argument in weak_register_no_lock function. Where it is coming from and on what it depends I don't knowCongo
Well that seems to be the key as to weather it performs as documented or crashes as you claim. I would assume, therefore, that's passed as false most/all of the time. You'd have to do further tracing in the source code to actually see what affects it.Versicular
I'm sure that it crashes because I reproduced it. I didn't find anything that suggests that crash is the expected behaviour.Congo
It doesn't sound like what you're testing is a scenario described by the first quote. You are setting a pointer from within an object that is being dealloc'd, not setting a pointer to an object being dealloc'd.Clemence
Frankly, I didn't quite get what you're saying. If I write someObject.delegate = self in the dealloc method, then I'm setting a pointer to an object that is currently undergoing deallocation.Congo
In NSObject.mm, you can see that objc_initWeak and objc_storeWeak pass crash = true, and there are also functions called objc_initWeakOrNil and objc_storeWeakOrNil that don't crash. I agree that this contradicts the ARC spec, where in the section for runtime functions at the end it explicitly says that objc_initWeak and objc_storeWeak are to store a null pointer if the pointee is undergoing deallocation.Freezedry
Looking at the history of obcj4 source, this behavior began in version 680 (corresponding to OS X 10.11 and iOS 9), and didn't exist in the previous version 647, where there was only one version of the functions objc_initWeak and objc_storeWeak, and they don't crash.Freezedry
Likely needed for Swift …Backwater
A weak reference doesn't have ownership of the object. When the variable it was referencing it discarded, the weak reference disappears and is set to nil.Royalroyalist
It seems I misspoke earlier about this beginning in version 680. In earlier versions, there was only one version, but it seems from looking at the source of weak_register_no_lock that it always crashes when deallocating.Freezedry
M
1

It's not a bug: it's obviously very intentional. It is a deviation from the spec, but it's an intentional one.

Based on the warning, it sounds like they wanted to make it easier to diagnose over-release scenarios, and catching objects that are being deallocated at the time might just be a side effect of that main goal.

They might also consider that, if you're trying to weakify self while being deallocated anyway, and you're not checking for a nil weakref (quite common - lots of block code repeatedly calls through a weakref that could go nil at any time!), you're setting yourself up for hard to debug bugs.

All that said, I'd love to see the notes behind that runtime change.

Melbourne answered 19/2, 2017 at 16:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.