diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index fd301ccb0..0cbc9a796 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -10,7 +10,7 @@ steps: - label: 'iOS 13 E2E tests batch 1' depends_on: - cocoa_fixture - timeout_in_minutes: 45 + timeout_in_minutes: 60 agents: queue: opensource plugins: @@ -37,7 +37,7 @@ steps: - label: 'iOS 13 E2E tests batch 2' depends_on: - cocoa_fixture - timeout_in_minutes: 45 + timeout_in_minutes: 60 agents: queue: opensource plugins: @@ -64,7 +64,7 @@ steps: - label: 'iOS 12 E2E tests batch 1' depends_on: - cocoa_fixture - timeout_in_minutes: 45 + timeout_in_minutes: 60 agents: queue: opensource plugins: @@ -91,7 +91,7 @@ steps: - label: 'iOS 12 E2E tests batch 2' depends_on: - cocoa_fixture - timeout_in_minutes: 45 + timeout_in_minutes: 60 agents: queue: opensource plugins: diff --git a/.buildkite/pipeline.quick.yml b/.buildkite/pipeline.quick.yml index 7dd475d87..9fabde089 100644 --- a/.buildkite/pipeline.quick.yml +++ b/.buildkite/pipeline.quick.yml @@ -138,7 +138,7 @@ steps: - label: 'iOS 14 E2E tests batch 1' depends_on: - cocoa_fixture - timeout_in_minutes: 45 + timeout_in_minutes: 60 agents: queue: opensource plugins: @@ -166,7 +166,7 @@ steps: - label: 'iOS 14 E2E tests batch 2' depends_on: - cocoa_fixture - timeout_in_minutes: 45 + timeout_in_minutes: 60 agents: queue: opensource plugins: @@ -194,7 +194,7 @@ steps: - label: 'iOS 10 E2E tests batch 1' depends_on: - cocoa_fixture - timeout_in_minutes: 45 + timeout_in_minutes: 60 agents: queue: opensource plugins: @@ -221,7 +221,7 @@ steps: - label: 'iOS 10 E2E tests batch 2' depends_on: - cocoa_fixture - timeout_in_minutes: 45 + timeout_in_minutes: 60 agents: queue: opensource plugins: diff --git a/.jazzy.yaml b/.jazzy.yaml index 62af34c67..47049e001 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -2,11 +2,11 @@ author_url: "https://www.bugsnag.com" author: "Bugsnag Inc" clean: false # avoid deleting docs/.git framework_root: "Bugsnag" -github_file_prefix: "https://github.com/bugsnag/bugsnag-cocoa/tree/v6.10.0/Bugsnag" +github_file_prefix: "https://github.com/bugsnag/bugsnag-cocoa/tree/v6.10.1/Bugsnag" github_url: "https://github.com/bugsnag/bugsnag-cocoa" hide_documentation_coverage: true module: "Bugsnag" -module_version: "6.10.0" +module_version: "6.10.1" objc: true output: "docs" readme: "README.md" diff --git a/Bugsnag.podspec.json b/Bugsnag.podspec.json index 88ec76411..d7e97bb6a 100644 --- a/Bugsnag.podspec.json +++ b/Bugsnag.podspec.json @@ -1,6 +1,6 @@ { "name": "Bugsnag", - "version": "6.10.0", + "version": "6.10.1", "summary": "The Bugsnag crash reporting framework for Apple platforms.", "homepage": "https://bugsnag.com", "license": "MIT", @@ -9,7 +9,7 @@ }, "source": { "git": "https://github.com/bugsnag/bugsnag-cocoa.git", - "tag": "v6.10.0" + "tag": "v6.10.1" }, "frameworks": [ "Foundation", diff --git a/Bugsnag/Delivery/BSGEventUploadOperation.m b/Bugsnag/Delivery/BSGEventUploadOperation.m index c73589ecf..7fc9c0fd2 100644 --- a/Bugsnag/Delivery/BSGEventUploadOperation.m +++ b/Bugsnag/Delivery/BSGEventUploadOperation.m @@ -9,6 +9,7 @@ #import "BSGEventUploadOperation.h" #import "BSGFileLocations.h" +#import "BSGInternalErrorReporter.h" #import "BSG_RFC3339DateTool.h" #import "BugsnagAppWithState+Private.h" #import "BugsnagConfiguration+Private.h" @@ -174,9 +175,16 @@ - (void)start { self.state = BSGEventUploadOperationStateExecuting; [self didChangeValueForKey:NSStringFromSelector(@selector(isExecuting))]; - [self runWithDelegate:delegate completionHandler:^{ + @try { + [self runWithDelegate:delegate completionHandler:^{ + [self setFinished]; + }]; + } @catch (NSException *exception) { + [BSGInternalErrorReporter.sharedInstance reportException:exception diagnostics:nil groupingHash: + [NSString stringWithFormat:@"BSGEventUploadOperation -[runWithDelegate:completionHandler:] %@ %@", + exception.name, exception.reason]]; [self setFinished]; - }]; + } } - (void)setFinished { diff --git a/Bugsnag/Helpers/BSGInternalErrorReporter.h b/Bugsnag/Helpers/BSGInternalErrorReporter.h index 792399c26..781750169 100644 --- a/Bugsnag/Helpers/BSGInternalErrorReporter.h +++ b/Bugsnag/Helpers/BSGInternalErrorReporter.h @@ -55,6 +55,10 @@ FOUNDATION_EXPORT NSString *BSGErrorDescription(NSError *error); diagnostics:(nullable NSDictionary *)diagnostics groupingHash:(nullable NSString *)groupingHash; +- (void)reportException:(NSException *)exception + diagnostics:(nullable NSDictionary *)diagnostics + groupingHash:(nullable NSString *)groupingHash; + // Private - (nullable BugsnagEvent *)eventWithErrorClass:(NSString *)errorClass @@ -62,6 +66,10 @@ FOUNDATION_EXPORT NSString *BSGErrorDescription(NSError *error); diagnostics:(nullable NSDictionary *)diagnostics groupingHash:(nullable NSString *)groupingHash; +- (nullable BugsnagEvent *)eventWithException:(NSException *)exception + diagnostics:(nullable NSDictionary *)diagnostics + groupingHash:(nullable NSString *)groupingHash; + - (nullable NSURLRequest *)requestForEvent:(BugsnagEvent *)event error:(NSError * __autoreleasing *)errorPtr; @end diff --git a/Bugsnag/Helpers/BSGInternalErrorReporter.m b/Bugsnag/Helpers/BSGInternalErrorReporter.m index 9b99d5674..237c5de96 100644 --- a/Bugsnag/Helpers/BSGInternalErrorReporter.m +++ b/Bugsnag/Helpers/BSGInternalErrorReporter.m @@ -84,12 +84,57 @@ - (void)reportErrorWithClass:(NSString *)errorClass } } +- (void)reportException:(NSException *)exception + diagnostics:(nullable NSDictionary *)diagnostics + groupingHash:(nullable NSString *)groupingHash { + @try { + BugsnagEvent *event = [self eventWithException:exception diagnostics:diagnostics groupingHash:groupingHash]; + if (event) { + [self sendEvent:event]; + } + } @catch (NSException *exception) { + bsg_log_err(@"%@", exception); + } +} + // MARK: Private API - (nullable BugsnagEvent *)eventWithErrorClass:(NSString *)errorClass message:(nullable NSString *)message diagnostics:(nullable NSDictionary *)diagnostics groupingHash:(nullable NSString *)groupingHash { + + NSArray *stacktrace = [BugsnagStackframe stackframesWithCallStackReturnAddresses: + BSGArraySubarrayFromIndex(NSThread.callStackReturnAddresses, 2)]; + + BugsnagError *error = + [[BugsnagError alloc] initWithErrorClass:errorClass + errorMessage:message + errorType:BSGErrorTypeCocoa + stacktrace:stacktrace]; + + return [self eventWithError:error diagnostics:diagnostics groupingHash:groupingHash]; +} + +- (nullable BugsnagEvent *)eventWithException:(NSException *)exception + diagnostics:(nullable NSDictionary *)diagnostics + groupingHash:(nullable NSString *)groupingHash { + + NSArray *stacktrace = [BugsnagStackframe stackframesWithCallStackReturnAddresses:exception.callStackReturnAddresses]; + + BugsnagError *error = + [[BugsnagError alloc] initWithErrorClass:exception.name + errorMessage:exception.reason + errorType:BSGErrorTypeCocoa + stacktrace:stacktrace]; + + return [self eventWithError:error diagnostics:diagnostics groupingHash:groupingHash]; +} + +- (nullable BugsnagEvent *)eventWithError:(BugsnagError *)error + diagnostics:(nullable NSDictionary *)diagnostics + groupingHash:(nullable NSString *)groupingHash { + id dataSource = self.dataSource; if (!dataSource) { return nil; @@ -101,15 +146,6 @@ - (nullable BugsnagEvent *)eventWithErrorClass:(NSString *)errorClass } [metadata addMetadata:dataSource.configuration.apiKey withKey:BSGKeyApiKey toSection:BugsnagDiagnosticsKey]; - NSArray *stacktrace = [BugsnagStackframe stackframesWithCallStackReturnAddresses: - BSGArraySubarrayFromIndex(NSThread.callStackReturnAddresses, 2)]; - - BugsnagError *error = - [[BugsnagError alloc] initWithErrorClass:errorClass - errorMessage:message - errorType:BSGErrorTypeCocoa - stacktrace:stacktrace]; - NSDictionary *systemInfo = [BSG_KSSystemInfo systemInfo]; BugsnagEvent *event = @@ -128,6 +164,8 @@ - (nullable BugsnagEvent *)eventWithErrorClass:(NSString *)errorClass return event; } +// MARK: Delivery + - (NSURLRequest *)requestForEvent:(nonnull BugsnagEvent *)event error:(NSError * __autoreleasing *)errorPtr { id dataSource = self.dataSource; if (!dataSource) { diff --git a/Bugsnag/Helpers/BugsnagCollections.h b/Bugsnag/Helpers/BugsnagCollections.h index d5638e4ed..990c3f2fe 100644 --- a/Bugsnag/Helpers/BugsnagCollections.h +++ b/Bugsnag/Helpers/BugsnagCollections.h @@ -71,4 +71,6 @@ NSString * _Nullable BSGDeserializeString(id _Nullable rawValue); NSDate * _Nullable BSGDeserializeDate(id _Nullable rawValue); +NSNumber * _Nullable BSGDeserializeNumber(id _Nullable rawValue); + NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Helpers/BugsnagCollections.m b/Bugsnag/Helpers/BugsnagCollections.m index 99bc60485..8f83cb114 100644 --- a/Bugsnag/Helpers/BugsnagCollections.m +++ b/Bugsnag/Helpers/BugsnagCollections.m @@ -151,3 +151,10 @@ id _Nullable BSGDeserializeArrayOfObjects(id _Nullable rawValue, id _Nullable (^ } return [BSG_RFC3339DateTool dateFromString:(NSString *)rawValue]; } + +NSNumber * _Nullable BSGDeserializeNumber(id _Nullable rawValue) { + if (![rawValue isKindOfClass:[NSNumber class]]) { + return nil; + } + return (NSNumber *)rawValue; +} diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c index 330662986..7a314ec56 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c @@ -308,6 +308,7 @@ bool bsg_ksmachgetThreadQueueName(const thread_t thread, char *const buffer, thread_identifier_info_t idInfo = (thread_identifier_info_t)info; dispatch_queue_t dispatch_queue = NULL; + uintptr_t junk = 0; // thread_handle shouldn't be 0 also, because // identifier_info->dispatch_qaddr = identifier_info->thread_handle + // get_dispatchqueue_offset_from_proc(thread->task->bsd_info); @@ -315,7 +316,11 @@ bool bsg_ksmachgetThreadQueueName(const thread_t thread, char *const buffer, // sometimes the queue address is invalid, so avoid dereferencing bsg_ksmachcopyMem((const void *)idInfo->dispatch_qaddr, &dispatch_queue, sizeof(dispatch_queue)) != KERN_SUCCESS || - !dispatch_queue) { + // Sometimes dispatch_queue is invalid which causes an EXC_BAD_ACCESS + // crash in dispatch_queue_get_label(). Check that dispatch_queue can + // be dereferenced to work around this. + bsg_ksmachcopyMem((const void *)dispatch_queue, &junk, + sizeof(junk)) != KERN_SUCCESS) { BSG_KSLOG_TRACE( "This thread doesn't have a dispatch queue attached : %p", thread); return false; diff --git a/Bugsnag/Payload/BugsnagError.m b/Bugsnag/Payload/BugsnagError.m index 9e49653f7..14db32ab7 100644 --- a/Bugsnag/Payload/BugsnagError.m +++ b/Bugsnag/Payload/BugsnagError.m @@ -112,23 +112,13 @@ - (instancetype)initWithErrorClass:(NSString *)errorClass } + (BugsnagError *)errorFromJson:(NSDictionary *)json { - NSArray *trace = json[BSGKeyStacktrace]; - NSMutableArray *data = [NSMutableArray new]; - - if (trace != nil) { - for (NSDictionary *dict in trace) { - BugsnagStackframe *frame = [BugsnagStackframe frameFromJson:dict]; - - if (frame != nil) { - [data addObject:frame]; - } - } - } BugsnagError *error = [[BugsnagError alloc] init]; - error.errorClass = json[BSGKeyErrorClass]; - error.errorMessage = json[BSGKeyMessage]; - error.stacktrace = data; - error.typeString = json[BSGKeyType] ?: BSGErrorTypeStringCocoa; + error.errorClass = BSGDeserializeString(json[BSGKeyErrorClass]); + error.errorMessage = BSGDeserializeString(json[BSGKeyMessage]); + error.stacktrace = BSGDeserializeArrayOfObjects(json[BSGKeyStacktrace], ^id _Nullable(NSDictionary * _Nonnull dict) { + return [BugsnagStackframe frameFromJson:dict]; + }) ?: @[]; + error.typeString = BSGDeserializeString(json[BSGKeyType]) ?: BSGErrorTypeStringCocoa; return error; } diff --git a/Bugsnag/Payload/BugsnagNotifier.m b/Bugsnag/Payload/BugsnagNotifier.m index d069a1076..f716228e5 100644 --- a/Bugsnag/Payload/BugsnagNotifier.m +++ b/Bugsnag/Payload/BugsnagNotifier.m @@ -23,7 +23,7 @@ - (instancetype)init { #else self.name = @"Bugsnag Objective-C"; #endif - self.version = @"6.10.0"; + self.version = @"6.10.1"; self.url = @"https://github.com/bugsnag/bugsnag-cocoa"; self.dependencies = [NSMutableArray new]; } diff --git a/Bugsnag/Payload/BugsnagStackframe+Private.h b/Bugsnag/Payload/BugsnagStackframe+Private.h index 46f3de6f8..b24d25010 100644 --- a/Bugsnag/Payload/BugsnagStackframe+Private.h +++ b/Bugsnag/Payload/BugsnagStackframe+Private.h @@ -31,6 +31,13 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BOOL needsSymbolication; +// MARK: - Properties not used for Cocoa stack frames, but used by React Native and Unity. + +@property (strong, nullable, nonatomic) NSNumber *columnNumber; +@property (copy, nullable, nonatomic) NSString *file; +@property (strong, nullable, nonatomic) NSNumber *inProject; +@property (strong, nullable, nonatomic) NSNumber *lineNumber; + @end NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Payload/BugsnagStackframe.m b/Bugsnag/Payload/BugsnagStackframe.m index f389e6f27..36e73b246 100644 --- a/Bugsnag/Payload/BugsnagStackframe.m +++ b/Bugsnag/Payload/BugsnagStackframe.m @@ -22,18 +22,6 @@ } -// MARK: - Properties not used for Cocoa stack frames, but used by React Native and Unity. - -@interface BugsnagStackframe () - -@property (strong, nullable, nonatomic) NSNumber *columnNumber; -@property (copy, nullable, nonatomic) NSString *file; -@property (strong, nullable, nonatomic) NSNumber *inProject; -@property (strong, nullable, nonatomic) NSNumber *lineNumber; - -@end - - // MARK: - @implementation BugsnagStackframe @@ -49,26 +37,26 @@ + (NSDictionary *_Nullable)findImageAddr:(unsigned long)addr inImages:(NSArray * + (BugsnagStackframe *)frameFromJson:(NSDictionary *)json { BugsnagStackframe *frame = [BugsnagStackframe new]; - frame.machoFile = json[BSGKeyMachoFile]; - frame.method = json[BSGKeyMethod]; - frame.isPc = [json[BSGKeyIsPC] boolValue]; - frame.isLr = [json[BSGKeyIsLR] boolValue]; - frame.machoUuid = json[BSGKeyMachoUUID]; + frame.machoFile = BSGDeserializeString(json[BSGKeyMachoFile]); + frame.method = BSGDeserializeString(json[BSGKeyMethod]); + frame.isPc = BSGDeserializeNumber(json[BSGKeyIsPC]).boolValue; + frame.isLr = BSGDeserializeNumber(json[BSGKeyIsLR]).boolValue; + frame.machoUuid = BSGDeserializeString(json[BSGKeyMachoUUID]); frame.machoVmAddress = [self readInt:json key:BSGKeyMachoVMAddress]; frame.frameAddress = [self readInt:json key:BSGKeyFrameAddress]; frame.symbolAddress = [self readInt:json key:BSGKeySymbolAddr]; frame.machoLoadAddress = [self readInt:json key:BSGKeyMachoLoadAddr]; - frame.type = json[BSGKeyType]; - frame.columnNumber = json[@"columnNumber"]; - frame.file = json[@"file"]; - frame.inProject = json[@"inProject"]; - frame.lineNumber = json[@"lineNumber"]; + frame.type = BSGDeserializeString(json[BSGKeyType]); + frame.columnNumber = BSGDeserializeNumber(json[@"columnNumber"]); + frame.file = BSGDeserializeString(json[@"file"]); + frame.inProject = BSGDeserializeNumber(json[@"inProject"]); + frame.lineNumber = BSGDeserializeNumber(json[@"lineNumber"]); return frame; } + (NSNumber *)readInt:(NSDictionary *)json key:(NSString *)key { - NSString *obj = json[key]; - if (obj) { + id obj = json[key]; + if ([obj isKindOfClass:[NSString class]]) { return @(strtoul([obj UTF8String], NULL, 16)); } return nil; diff --git a/Bugsnag/Payload/BugsnagThread.m b/Bugsnag/Payload/BugsnagThread.m index e43384317..a625d93b4 100644 --- a/Bugsnag/Payload/BugsnagThread.m +++ b/Bugsnag/Payload/BugsnagThread.m @@ -213,19 +213,23 @@ + (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread thread_t *threads = NULL; mach_msg_type_number_t threadCount = 0; + if (task_threads(mach_task_self(), &threads, &threadCount) != KERN_SUCCESS) { + return @[]; + } + + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:threadCount]; + + struct backtrace_t *backtraces = calloc(threadCount, sizeof(struct backtrace_t)); + if (!backtraces) { + goto cleanup; + } + suspend_threads(); // While threads are suspended only async-signal-safe functions should be used, // as another threads may have been suspended while holding a lock in malloc, // the Objective-C runtime, or other subsystems. - if (task_threads(mach_task_self(), &threads, &threadCount) != KERN_SUCCESS) { - resume_threads(); - return @[]; - } - - struct backtrace_t backtraces[threadCount]; - for (mach_msg_type_number_t i = 0; i < threadCount; i++) { BOOL isCurrentThread = MACH_PORT_INDEX(threads[i]) == MACH_PORT_INDEX(bsg_ksmachthread_self()); if (isCurrentThread) { @@ -237,8 +241,6 @@ + (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread resume_threads(); - NSMutableArray *objects = [NSMutableArray arrayWithCapacity:threadCount]; - for (mach_msg_type_number_t i = 0; i < threadCount; i++) { BOOL isCurrentThread = MACH_PORT_INDEX(threads[i]) == MACH_PORT_INDEX(bsg_ksmachthread_self()); struct backtrace_t *backtrace = isCurrentThread ? currentThreadBacktrace : &backtraces[i]; @@ -249,11 +251,13 @@ + (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread index:i]]; } +cleanup: for (mach_msg_type_number_t i = 0; i < threadCount; i++) { mach_port_deallocate(mach_task_self(), threads[i]); } vm_deallocate(mach_task_self(), (vm_address_t)threads, sizeof(thread_t) * threadCount); + free(backtraces); return objects; } diff --git a/CHANGELOG.md b/CHANGELOG.md index 319cabc8c..46a79f69e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ Changelog ========= +## 6.10.1 (2021-07-07) + +### Bug fixes + +* Fix a potential stack overflow in `+[BugsnagThread allThreadsWithCurrentThreadBacktrace:]`. + [#1148](https://github.com/bugsnag/bugsnag-cocoa/pull/1148) + +* Fix `NSNull` handling in `+[BugsnagError errorFromJson:]` and `+[BugsnagStackframe frameFromJson:]`. + [#1143](https://github.com/bugsnag/bugsnag-cocoa/pull/1143) + [#1138](https://github.com/bugsnag/bugsnag-cocoa/issues/1138) + +* Fix a rare crash in `bsg_ksmachgetThreadQueueName`. + [#1147](https://github.com/bugsnag/bugsnag-cocoa/pull/1147) + ## 6.10.0 (2021-06-30) ### Bug fixes diff --git a/Framework/Info.plist b/Framework/Info.plist index 868b0a1a9..0692b65e7 100644 --- a/Framework/Info.plist +++ b/Framework/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.10.0 + 6.10.1 CFBundleVersion 1 diff --git a/Tests/BSGInternalErrorReporterTests.m b/Tests/BSGInternalErrorReporterTests.m index 77f8ce620..eb4cca17e 100644 --- a/Tests/BSGInternalErrorReporterTests.m +++ b/Tests/BSGInternalErrorReporterTests.m @@ -28,7 +28,7 @@ - (void)setUp { self.notifier = [[BugsnagNotifier alloc] init]; } -- (void)testEventForError { +- (void)testEventWithErrorClass { BugsnagConfiguration *configuration = [[BugsnagConfiguration alloc] initWithApiKey:@"0192837465afbecd0192837465afbecd"]; BSGInternalErrorReporter *reporter = [[BSGInternalErrorReporter alloc] initWithDataSource:self]; @@ -36,6 +36,31 @@ - (void)testEventForError { XCTAssertEqualObjects(event.errors[0].errorClass, @"Internal error"); XCTAssertEqualObjects(event.errors[0].errorMessage, @"Something went wrong"); XCTAssertEqualObjects(event.groupingHash, @"test"); + XCTAssertEqualObjects(event.threads, @[]); + XCTAssertGreaterThan(event.errors[0].stacktrace.count, 0); + XCTAssertNil(event.apiKey); + + NSDictionary *diagnostics = [event.metadata getMetadataFromSection:@"BugsnagDiagnostics"]; + XCTAssertEqualObjects(diagnostics[@"apiKey"], configuration.apiKey); +} + +- (void)testEventWithException { + BugsnagConfiguration *configuration = [[BugsnagConfiguration alloc] initWithApiKey:@"0192837465afbecd0192837465afbecd"]; + BSGInternalErrorReporter *reporter = [[BSGInternalErrorReporter alloc] initWithDataSource:self]; + + NSException *exception = nil; + @try { + NSLog(@"%@", @[][0]); + } @catch (NSException *e) { + exception = e; + } + + BugsnagEvent *event = [reporter eventWithException:exception diagnostics:nil groupingHash:@"test"]; + XCTAssertEqualObjects(event.errors[0].errorClass, @"NSRangeException"); + XCTAssertEqualObjects(event.errors[0].errorMessage, @"*** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray"); + XCTAssertEqualObjects(event.groupingHash, @"test"); + XCTAssertEqualObjects(event.threads, @[]); + XCTAssertGreaterThan(event.errors[0].stacktrace.count, 0); XCTAssertNil(event.apiKey); NSDictionary *diagnostics = [event.metadata getMetadataFromSection:@"BugsnagDiagnostics"]; diff --git a/Tests/BugsnagErrorTest.m b/Tests/BugsnagErrorTest.m index ba18fde06..dcabd713d 100644 --- a/Tests/BugsnagErrorTest.m +++ b/Tests/BugsnagErrorTest.m @@ -77,6 +77,20 @@ - (void)testErrorLoad { XCTAssertEqualObjects(@"D0A41830-4FD2-3B02-A23B-0741AD4C7F52", frame.machoUuid); } +- (void)testErrorFromInvalidJson { + BugsnagError *error; + + error = [BugsnagError errorFromJson:@{ + @"stacktrace": [NSNull null], + }]; + XCTAssertEqualObjects(error.stacktrace, @[]); + + error = [BugsnagError errorFromJson:@{ + @"stacktrace": @{@"foo": @"bar"}, + }]; + XCTAssertEqualObjects(error.stacktrace, @[]); +} + - (void)testToDictionary { BugsnagThread *thread = [self findErrorReportingThread:self.event]; BugsnagError *error = [[BugsnagError alloc] initWithKSCrashReport:self.event stacktrace:thread.stacktrace]; diff --git a/Tests/BugsnagStackframeTest.m b/Tests/BugsnagStackframeTest.m index 5478a0190..c165ba767 100644 --- a/Tests/BugsnagStackframeTest.m +++ b/Tests/BugsnagStackframeTest.m @@ -66,9 +66,12 @@ - (void)testStackframeToDict { - (void)testStackframeFromJson { BugsnagStackframe *frame = [BugsnagStackframe frameFromJson:@{ + @"columnNumber": @72, @"frameAddress": @"0x10b5756bf", + @"inProject": @YES, @"isLR": @NO, - @"isPC": @NO, + @"isPC": @YES, + @"lineNumber": @42, @"machoFile": @"/Users/foo/Bugsnag.h", @"machoLoadAddress": @"0x10b54b000", @"machoUUID": @"B6D80CB5-A772-3D2F-B5A1-A3A137B8B58F", @@ -78,8 +81,11 @@ - (void)testStackframeFromJson { @"type": @"cocoa", }]; XCTAssertEqual(frame.isLr, NO); - XCTAssertEqual(frame.isPc, NO); + XCTAssertEqual(frame.isPc, YES); + XCTAssertEqualObjects(frame.columnNumber, @72); XCTAssertEqualObjects(frame.frameAddress, @0x10b5756bf); + XCTAssertEqualObjects(frame.inProject, @YES); + XCTAssertEqualObjects(frame.lineNumber, @42); XCTAssertEqualObjects(frame.machoFile, @"/Users/foo/Bugsnag.h"); XCTAssertEqualObjects(frame.machoLoadAddress, @0x10b54b000); XCTAssertEqualObjects(frame.machoUuid, @"B6D80CB5-A772-3D2F-B5A1-A3A137B8B58F"); @@ -89,6 +95,37 @@ - (void)testStackframeFromJson { XCTAssertEqualObjects(frame.type, BugsnagStackframeTypeCocoa); } +- (void)testStackframeFromJsonWithNullValues { + BugsnagStackframe *frame = [BugsnagStackframe frameFromJson:@{ + @"columnNumber": [NSNull null], + @"frameAddress": [NSNull null], + @"inProject": [NSNull null], + @"isLR": [NSNull null], + @"isPC": [NSNull null], + @"lineNumber": [NSNull null], + @"machoFile": [NSNull null], + @"machoLoadAddress": [NSNull null], + @"machoUUID": [NSNull null], + @"machoVMAddress": [NSNull null], + @"method": [NSNull null], + @"symbolAddress": [NSNull null], + @"type": [NSNull null], + }]; + XCTAssertEqual(frame.isLr, NO); + XCTAssertEqual(frame.isPc, NO); + XCTAssertNil(frame.columnNumber); + XCTAssertNil(frame.frameAddress); + XCTAssertNil(frame.inProject); + XCTAssertNil(frame.lineNumber); + XCTAssertNil(frame.machoFile); + XCTAssertNil(frame.machoLoadAddress); + XCTAssertNil(frame.machoUuid); + XCTAssertNil(frame.machoVmAddress); + XCTAssertNil(frame.method); + XCTAssertNil(frame.symbolAddress); + XCTAssertNil(frame.type); +} + - (void)testStackframeFromJsonWithoutType { BugsnagStackframe *frame = [BugsnagStackframe frameFromJson:@{}]; XCTAssertNil(frame.type); diff --git a/Tests/BugsnagThreadTests.m b/Tests/BugsnagThreadTests.m index b6e1f629a..c27fd443e 100644 --- a/Tests/BugsnagThreadTests.m +++ b/Tests/BugsnagThreadTests.m @@ -222,6 +222,26 @@ - (void)testAllThreads { XCTAssertGreaterThan(threads.count, 1); } +- (void)testAllThreadsFromBackgroundDoesNotOverflowStack { + const int threadCount = 1000; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + for (int i = 0; i < threadCount; i++) { + [NSThread detachNewThreadSelector:@selector(waitForSemaphore:) toTarget:self withObject:semaphore]; + } + __block NSArray *threads = nil; + dispatch_sync(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + threads = [BugsnagThread allThreads:YES callStackReturnAddresses:NSThread.callStackReturnAddresses]; + }); + XCTAssertGreaterThan(threads.count, threadCount); + for (int i = 0; i < threadCount; i++) { + dispatch_semaphore_signal(semaphore); + } +} + +- (void)waitForSemaphore:(dispatch_semaphore_t)semaphore { + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); +} + - (void)testCurrentThread { NSArray *threads = [BugsnagThread allThreads:NO callStackReturnAddresses:NSThread.callStackReturnAddresses]; [threads[0].stacktrace makeObjectsPerformSelector:@selector(symbolicateIfNeeded)]; diff --git a/Tests/Info.plist b/Tests/Info.plist index f35423c4e..7f9e5d5c2 100644 --- a/Tests/Info.plist +++ b/Tests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 6.10.0 + 6.10.1 CFBundleVersion 1 diff --git a/VERSION b/VERSION index cf79bf90e..3094a4c66 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.10.0 +6.10.1 diff --git a/features/barebone_tests.feature b/features/barebone_tests.feature index 6d6a1b232..1e3949c24 100644 --- a/features/barebone_tests.feature +++ b/features/barebone_tests.feature @@ -175,6 +175,9 @@ Feature: Barebone tests # Wait for app to be killed for using too much memory Then the app is not running + # Wait because The launch_app command can fail when performed + # immediately after an app has stopped running + And I wait for 2 seconds And I relaunch the app And I configure Bugsnag for "OOMScenario"