-
Notifications
You must be signed in to change notification settings - Fork 607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Crash in NSInvocation -dealloc when using OCMPartialMock #346
Comments
I've debugged this some more and was able to create a smaller test that reproduce this crash reliably without using @interface MyObject : NSObject
@end
@implementation MyObject {
MyObject* _contained;
}
- (void)setContained:(MyObject*)contained {
if (_contained) {
[_contained removedFrom:self];
_contained = nil;
}
_contained = contained;
}
- (void)removedFrom:(MyObject*)container {
NSLog(@"-removedFrom:%@ invoked on @%", container, self);
}
- (void)dealloc {
[setContained:nil;
}
@end
- (void)testDealloc {
MyObject* object1 = [[MyObject alloc] init];
MyObject* object2 = [[MyObject alloc] init];
[object1 setContained:object2];
id objectMock = OCMPartialMock(object2);
NSLog(@"silence warning: Unused variable 'objectMock', %p", (__bridge void*)objectMock);
} The crash happens when the current autorelease pool is drained because the following succession of events happen:
With the original test case, the same thing happens, TL;DR: I think capturing the |
Here is the call stack when the method
As you can see, we capture an invocation of |
I think I understand what is going on, but I don't know what to do. On the one hand there are good reasons to retain the arguments. On the other hand it can, under the conditions you describe, cause a crash. That said, the case you describe is a real edge case that, arguably, violates an implicit contract in Objective-C. It is normally a fair assumption for a method that it can retain an object argument it receives, but here you're basically creating a situation where that is not true and the receiver is not allowed to hold onto an argument it receives. Somehow the receiver must know that, unlike all "normal" object arguments, it must not hold onto (retain) that argument. When this tacit "side-contract" is coded into a class, as it is in your example and as it seems with UIView, then it's not a problem, because the class doesn't do what it shouldn't do. However, that special contract is not explicit and OCMock can't know about it. Interestingly, #347 is also about a problem with mocks retaining arguments of invocations. Maybe we really need some extra API on OCMock to deal with these edge cases, allowing the test case more control over how a mock manages invocations. I'm not sure exactly how such an API would look like. Ideas? |
Regarding the API, what about the suggestions atop #347? |
I think an API as described in #347 to disable retaining of the arguments of the captured NSInvocation would work in my case. I found this "side-contract" in UIView while trying to understand why the some of the Chromium tests where crashing with the latest version of OCMock. Since there are only a few tests that try to mock a class from Apple frameworks, and we do not have such side-contract, it is a sustainable solution for us. Thank you for looking into this issue. |
I also agree that there needs to be a way to specify this behavior for testing deallocations, as described in #347 -- there isn't another way to do so outside of making the tests non-ARC, which is pretty unreasonable in 2017. +1 |
Yes, passing a reference to self in a method call from -dealloc is dangerous. You have to be sure that the value is not autoreleased in the calling code (or any other delayed release like these captured NSInvocations) -- even a normal retain/release added by ARC can be a bad idea. I think it's even too late to obtain a weak reference to such an object. There is a private NSObject _isDeallocating method I believe. It may be possible to use that to avoid capturing the NSInvocation in the first place, or at least don't retain such arguments in the NSInvocation (better would be to invoke the original method with the value but store nil in the NSInvocation). Another option would be to add the ability to specify that -removedFrom: or movedFromSuperview: (or similar specific methods on a particular class) should not have its NSInvocation recorded. Or a way to turn off the implicit capturing of all methods completely for a particular test, such that you need to specify all your expects before the code is invoked, and only those specific methods will be mocked (the old OCMock behavior, which is waaaay less invasive runtime-wise for edge cases like these). |
I'm encountering the same issue. The test is as follows: MyController *controller = [[MyController alloc] init];
OCMockObject *mockNotificationCenter = [OCMockObject partialMockForObject:NSNotificationCenter.defaultCenter];
[[mockNotificationCenter expect] postNotificationName:MyControllerViewDidLoadNotification object:controller];
[controller viewDidLoad];
[mockNotificationCenter verify];
XCTAssertEqual(controller.edgesForExtendedLayout, UIRectEdgeNone);
XCTAssertFalse(controller.webView.translatesAutoresizingMaskIntoConstraints);
XCTAssertNotNil(controller.activityIndicator);
[mockNotificationCenter stopMocking]; Granted, I can switch this particular test to leverage a XCTestExpectation via expectationForNotification:object:handler:, but other tests are experiencing this issue. I was able to confirm what @sdefresne reported and that it is crashing during the dealloc of the mock object when the release is sent to the mock's array of NSInvocations. ocmock/Source/OCMock/OCMockObject.m Lines 108 to 115 in 812b5db
Environment: |
Having the same issue:
If I don't call getArgumnet the crash is gone |
See #470 |
When using
OCMPartialMock
to mock aUIView
that has been inserted in the view hierarchy (passed to-addSubView:
of some other view), then the app crashes inNSInvocation
-dealloc
. This can be reproduced with the following test:To test, just paste this in
iOS9ExampleTests.m
, build with Xcode 9.0, and run the tests on iPhone 6s 10.0 simulator, and you'll have a crash with the following callstack:This used to work with revision f03b3cc but fails with the most recent revision from github. Using
git bisect
, I've found that this was introduced by bf6f59a (reland of 982c6f7 with compilation fixes, this version also fails with the same error when the compilation is fixed).The crash happens in
OCMockObject
-dealloc
method when sending the-release
signal toinvocations
ivar. This release the object side-table used to store the association created by-retainObjectArgumentsExcluding:
so I suspect that the bug is present in that method (or maybe the bug was always there bug never visible due to the object cycle this method is trying to prevent).The text was updated successfully, but these errors were encountered: