Last active
August 9, 2016 14:14
-
-
Save depth42/8363309 to your computer and use it in GitHub Desktop.
Breaking on exceptions in Objective-C is cumbersome, because a breakpoint on objc_exception_throw cannot differentiate between an expected (caught) exception and an unexpected exception (like one from a failed assertion).
To work around this, we intercept objc_exception_throw using mach_override and apply filter rules to decide whether to break …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@implementation NSManagedObjectContext (PWExtensions) | |
#ifndef NDEBUG | |
// Core Data uses exceptions to notify itself about optimistic locking failures. These exceptions are intercepted by | |
// Core Data and never reach the client code. Such an exception should not drop in the debugger because it is not raised | |
// due to a programming error. | |
// Since the exception is thrown inside -[NSManagedObjectContext save:], this category is a logical place to install | |
// the filter. | |
+ (void) load | |
{ | |
registerExpectedExceptionFilter (^(NSException* exception) { | |
// Core Data throws an exception of the private class _NSCoreDataOptimisticLockingException with name | |
// NSInternalInconsistencyException. Until otherwise proven it seems therefore simplest to test against the | |
// rather specific reason string. | |
return [exception.reason isEqualToString:@"optimistic locking failure"]; | |
}); | |
} | |
#endif | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
// Return value of YES means that 'exception' is expected in normal operation and the exception breakpoint should not | |
// be triggered. | |
typedef BOOL (^PWExpectedExceptionFilter) (NSException* exception); | |
// Patches objc_exception_throw(), unless NDEBUG is defined. | |
void intercept_objc_exception_throw(); | |
void registerExpectedExceptionFilter (PWExpectedExceptionFilter filter); | |
// Unregistering will work only if the block has been copied before passing it to registerExpectedExceptionFilter() | |
// and that same pointer is passed to unregisterExpectedExceptionFilter(). | |
// Returns whether 'filter' was found and unregistered. | |
BOOL unregisterExpectedExceptionFilter (PWExpectedExceptionFilter filter); | |
#ifndef NDEBUG | |
BOOL isExceptionExpected (NSException* exception); | |
#endif | |
#ifdef __cplusplus | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "Intercept_objc_exception_throw.h" | |
#import "mach_override.h" | |
#import "PWLog.h" | |
#import "PWDispatch.h" | |
#ifndef NDEBUG | |
void objc_exception_throw (NSException* exception); | |
// The wrapper for objc_exception_throw is implemented in a separate file (Intercepted_objc_exception_throw.m) which | |
// is compiled without ARC. Somehow ARC messed up the retain count of the exception, resulting in the exception being | |
// overretained and leaked. | |
void intercepted_objc_exception_throw (NSException* exception); | |
typedef void (*objc_exception_throw_ptr) (NSException* exception); | |
objc_exception_throw_ptr g_objc_exception_throw; | |
static NSCountedSet* gExpectedExceptionFilters; | |
// Two accessor functions used by Intercepted_objc_exception_throw.m | |
objc_exception_throw_ptr original_objc_exception_throw() | |
{ | |
return g_objc_exception_throw; | |
} | |
#endif | |
PWDispatchQueue* expectedExceptionFiltersDispatchQueue() | |
{ | |
static PWDispatchQueue* queue; | |
PWDispatchOnce(^{ | |
queue = [[PWDispatchQueue alloc] initWithLabel:@"expectedExceptionFiltersDispatchQueue"]; | |
}); | |
return queue; | |
} | |
void registerExpectedExceptionFilter (PWExpectedExceptionFilter filter) | |
{ | |
#ifndef NDEBUG | |
NSCParameterAssert (filter); | |
NSCParameterAssert( [filter copy] == (id)filter); // To make unregistering work, the block needs to be already copied so that we remain pointer identity | |
[expectedExceptionFiltersDispatchQueue() asynchronouslyDispatchBlock:^{ | |
if (!gExpectedExceptionFilters) | |
gExpectedExceptionFilters = [[NSCountedSet alloc] init]; | |
[gExpectedExceptionFilters addObject:filter]; | |
}]; | |
#endif | |
} | |
BOOL unregisterExpectedExceptionFilter (PWExpectedExceptionFilter filter) | |
{ | |
__block BOOL success = YES; | |
#ifndef NDEBUG | |
NSCParameterAssert (filter); | |
[expectedExceptionFiltersDispatchQueue() synchronouslyDispatchBlock:^{ | |
success = [gExpectedExceptionFilters containsObject:filter]; | |
[gExpectedExceptionFilters removeObject:filter]; | |
}]; | |
#endif | |
return success; | |
} | |
#ifndef NDEBUG | |
BOOL isExceptionExpected (NSException* exception) | |
{ | |
NSCParameterAssert (exception); | |
__block BOOL result = NO; | |
[expectedExceptionFiltersDispatchQueue() synchronouslyDispatchBlock:^{ | |
for (PWExpectedExceptionFilter iFilter in gExpectedExceptionFilters) | |
{ | |
if(iFilter(exception)) | |
{ | |
result = YES; | |
break; | |
} | |
} | |
}]; | |
return result; | |
} | |
#endif | |
void intercept_objc_exception_throw() | |
{ | |
#ifndef NDEBUG | |
// Patch once only. | |
if (!g_objc_exception_throw) { | |
mach_error_t err = mach_override_ptr (objc_exception_throw, | |
intercepted_objc_exception_throw, | |
(void**)&g_objc_exception_throw); | |
if (err) { | |
PWLog (@"Could not patch objc_exception_throw(). "); | |
if (err == err_cannot_override) | |
PWLog (@"Make sure you do not have an active Objective-C Exception Breakpoint.\n"); | |
else | |
PWLog (@"Mach error = %i.\n", err); | |
} | |
} | |
#endif | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "Intercept_objc_exception_throw.h" | |
#import "mach_override.h" | |
#import "PWLog.h" | |
// IMPORTANT: this file must be compiled with ARC disabled. | |
// Somehow ARC messes up the retain count of the exception, resulting in the exception being overretained and leaked. | |
#ifndef NDEBUG | |
void intercepted_objc_exception_throw (NSException* exception); | |
typedef void (*objc_exception_throw_ptr) (NSException* exception); | |
// Accessor function for global in Intercept_objc_exception_throw.m | |
objc_exception_throw_ptr original_objc_exception_throw(); | |
void intercepted_objc_exception_throw (NSException* exception) | |
{ | |
if (!isExceptionExpected(exception)) | |
PWLog (@"Throwing exception %@\n", exception); // set exeption catch breakpoint on this line | |
original_objc_exception_throw() (exception); | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment