diff --git a/Crashlytics/Crashlytics/Components/FIRCLSContext.h b/Crashlytics/Crashlytics/Components/FIRCLSContext.h index f3656087e81..bdb43418c69 100644 --- a/Crashlytics/Crashlytics/Components/FIRCLSContext.h +++ b/Crashlytics/Crashlytics/Components/FIRCLSContext.h @@ -33,6 +33,13 @@ __BEGIN_DECLS +#ifdef __OBJC__ +@class FIRCLSInternalReport; +@class FIRCLSSettings; +@class FIRCLSInstallIdentifierModel; +@class FIRCLSFileManager; +#endif + typedef struct { volatile bool initialized; volatile bool debuggerAttached; @@ -90,10 +97,18 @@ typedef struct { uint32_t maxKeyValues; } FIRCLSContextInitData; -bool FIRCLSContextInitialize(const FIRCLSContextInitData* initData); +#ifdef __OBJC__ +bool FIRCLSContextInitialize(FIRCLSInternalReport* report, + FIRCLSSettings* settings, + FIRCLSInstallIdentifierModel* installIDModel, + FIRCLSFileManager* fileManager); // Re-writes the metadata file on the current thread -void FIRCLSContextUpdateMetadata(const FIRCLSContextInitData* initData); +void FIRCLSContextUpdateMetadata(FIRCLSInternalReport* report, + FIRCLSSettings* settings, + FIRCLSInstallIdentifierModel* installIDModel, + FIRCLSFileManager* fileManager); +#endif void FIRCLSContextBaseInit(void); void FIRCLSContextBaseDeinit(void); diff --git a/Crashlytics/Crashlytics/Components/FIRCLSContext.m b/Crashlytics/Crashlytics/Components/FIRCLSContext.m index 501de89b458..26d46c72ec9 100644 --- a/Crashlytics/Crashlytics/Components/FIRCLSContext.m +++ b/Crashlytics/Crashlytics/Components/FIRCLSContext.m @@ -17,12 +17,16 @@ #include #include +#import "FIRCLSFileManager.h" +#import "FIRCLSInstallIdentifierModel.h" +#import "FIRCLSInternalReport.h" +#import "FIRCLSSettings.h" + #include "FIRCLSApplication.h" #include "FIRCLSCrashedMarkerFile.h" #include "FIRCLSDefines.h" #include "FIRCLSFeatures.h" #include "FIRCLSGlobals.h" -#include "FIRCLSInternalReport.h" #include "FIRCLSProcess.h" #include "FIRCLSUtility.h" @@ -45,7 +49,61 @@ static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component); static void FIRCLSContextAllocate(FIRCLSContext* context); -bool FIRCLSContextInitialize(const FIRCLSContextInitData* initData) { +FIRCLSContextInitData FIRCLSContextBuildInitData(FIRCLSInternalReport* report, + FIRCLSSettings* settings, + FIRCLSInstallIdentifierModel* installIDModel, + FIRCLSFileManager* fileManager) { + // Because we need to start the crash reporter right away, + // it starts up either with default settings, or cached settings + // from the last time they were fetched + + FIRCLSContextInitData initData; + + memset(&initData, 0, sizeof(FIRCLSContextInitData)); + + initData.customBundleId = nil; + initData.installId = [installIDModel.installID UTF8String]; + initData.sessionId = [[report identifier] UTF8String]; + initData.rootPath = [[report path] UTF8String]; + initData.previouslyCrashedFileRootPath = [[fileManager rootPath] UTF8String]; + initData.errorsEnabled = [settings errorReportingEnabled]; + initData.customExceptionsEnabled = [settings customExceptionsEnabled]; + initData.maxCustomExceptions = [settings maxCustomExceptions]; + initData.maxErrorLogSize = [settings errorLogBufferSize]; + initData.maxLogSize = [settings logBufferSize]; + initData.maxKeyValues = [settings maxCustomKeys]; + + // If this is set, then we could attempt to do a synchronous submission for certain kinds of + // events (exceptions). This is a very cool feature, but adds complexity to the backend. For now, + // we're going to leave this disabled. It does work in the exception case, but will ultimtely + // result in the following crash to be discared. Usually that crash isn't interesting. But, if it + // was, we'd never have a chance to see it. + initData.delegate = nil; + +#if CLS_MACH_EXCEPTION_SUPPORTED + __block exception_mask_t mask = 0; + + // TODO(b/141241224) This if statement was hardcoded to no, so this block was never run + // FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) { + // if ([self.delegate ensureDeliveryOfUnixSignal:signal]) { + // mask |= FIRCLSMachExceptionMaskForSignal(signal); + // } + // }); + + initData.machExceptionMask = mask; +#endif + + return initData; +} + +bool FIRCLSContextInitialize(FIRCLSInternalReport* report, + FIRCLSSettings* settings, + FIRCLSInstallIdentifierModel* installIDModel, + FIRCLSFileManager* fileManager) { + FIRCLSContextInitData initDataObj = + FIRCLSContextBuildInitData(report, settings, installIDModel, fileManager); + FIRCLSContextInitData* initData = &initDataObj; + if (!initData) { return false; } @@ -197,7 +255,14 @@ bool FIRCLSContextInitialize(const FIRCLSContextInitData* initData) { return true; } -void FIRCLSContextUpdateMetadata(const FIRCLSContextInitData* initData) { +void FIRCLSContextUpdateMetadata(FIRCLSInternalReport* report, + FIRCLSSettings* settings, + FIRCLSInstallIdentifierModel* installIDModel, + FIRCLSFileManager* fileManager) { + FIRCLSContextInitData initDataObj = + FIRCLSContextBuildInitData(report, settings, installIDModel, fileManager); + FIRCLSContextInitData* initData = &initDataObj; + NSString* rootPath = [NSString stringWithUTF8String:initData->rootPath]; const char* metaDataPath = diff --git a/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m b/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m index 10e2fd9be32..fafb2ca119d 100644 --- a/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m +++ b/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m @@ -445,9 +445,7 @@ - (void)checkAndRotateInstallUUIDIfNeededWithReport:(FIRCLSInternalReport *)repo return; } - FIRCLSContextInitData initData = [self initializeContextInitData:report]; - - FIRCLSContextUpdateMetadata(&initData); + FIRCLSContextUpdateMetadata(report, self.settings, self.installIDModel, self->_fileManager); }]; } @@ -473,53 +471,18 @@ - (void)startNetworkRequestsWithToken:(FIRCLSDataCollectionToken *)token [self handleContentsInOtherReportingDirectoriesWithToken:token]; } -- (FIRCLSContextInitData)initializeContextInitData:(FIRCLSInternalReport *)report { - FIRCLSContextInitData initData; - - memset(&initData, 0, sizeof(FIRCLSContextInitData)); - - // Because we need to start the crash reporter right away, - // it starts up either with default settings, or cached settings - // from the last time they were fetched - FIRCLSSettings *settings = self.settings; - - initData.customBundleId = NULL; - initData.installId = [self.installIDModel.installID UTF8String]; - initData.sessionId = [[report identifier] UTF8String]; - initData.rootPath = [[report path] UTF8String]; - initData.previouslyCrashedFileRootPath = [[_fileManager rootPath] UTF8String]; -#if CLS_MACH_EXCEPTION_SUPPORTED - initData.machExceptionMask = [self machExceptionMask]; -#endif - initData.errorsEnabled = [settings errorReportingEnabled]; - initData.customExceptionsEnabled = [settings customExceptionsEnabled]; - initData.maxCustomExceptions = [settings maxCustomExceptions]; - initData.maxErrorLogSize = [settings errorLogBufferSize]; - initData.maxLogSize = [settings logBufferSize]; - initData.maxKeyValues = [settings maxCustomKeys]; - - return initData; -} - - (BOOL)startCrashReporterWithProfilingMark:(FIRCLSProfileMark)mark report:(FIRCLSInternalReport *)report { if (!report) { return NO; } - FIRCLSContextInitData initData = [self initializeContextInitData:report]; - - // If this is set, then we could attempt to do a synchronous submission for certain kinds of - // events (exceptions). This is a very cool feature, but adds complexity to the backend. For now, - // we're going to leave this disabled. It does work in the exception case, but will ultimtely - // result in the following crash to be discared. Usually that crash isn't interesting. But, if it - // was, we'd never have a chance to see it. - initData.delegate = NULL; - - if (![self installCrashReportingHandlers:&initData]) { + if (!FIRCLSContextInitialize(report, self.settings, self.installIDModel, _fileManager)) { return NO; } + [self setupStateNotifications]; + [self registerAnalyticsEventListener]; [self crashReportingSetupCompleted:mark]; @@ -569,33 +532,8 @@ - (FIRCLSReportUploader *)uploader { return _uploader; } -#if CLS_MACH_EXCEPTION_SUPPORTED -- (exception_mask_t)machExceptionMask { - __block exception_mask_t mask = 0; - - // TODO(b/141241224) This if statement was hardcoded to no, so this block was never run - // FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) { - // if ([self.delegate ensureDeliveryOfUnixSignal:signal]) { - // mask |= FIRCLSMachExceptionMaskForSignal(signal); - // } - // }); - - return mask; -} -#endif - #pragma mark - Reporting Lifecycle -- (BOOL)installCrashReportingHandlers:(FIRCLSContextInitData *)initData { - if (!FIRCLSContextInitialize(initData)) { - return NO; - } - - [self setupStateNotifications]; - - return YES; -} - - (FIRCLSInternalReport *)setupCurrentReport:(NSString *)executionIdentifier { [self createLaunchFailureMarker]; diff --git a/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h b/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h index 4940855990e..f7f139740ac 100644 --- a/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h +++ b/Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h @@ -15,6 +15,8 @@ #import "FIRCLSReportManager.h" #import "FIRCLSReportUploader.h" +@class FIRCLSInstallIdentifierModel; + @interface FIRCLSReportManager () @property(nonatomic, strong) NSOperationQueue *operationQueue; diff --git a/Crashlytics/Crashlytics/FIRCrashlytics.m b/Crashlytics/Crashlytics/FIRCrashlytics.m index 1d01a6c6217..20650e531a8 100644 --- a/Crashlytics/Crashlytics/FIRCrashlytics.m +++ b/Crashlytics/Crashlytics/FIRCrashlytics.m @@ -307,11 +307,8 @@ - (void)recordError:(NSError *)error { FIRCLSUserLoggingRecordError(error, nil); } -- (void)recordCustomExceptionName:(NSString *)name - reason:(NSString *)reason - frameArray:(NSArray *)frameArray { - FIRCLSExceptionRecord(FIRCLSExceptionTypeCustom, [[name copy] UTF8String], - [[reason copy] UTF8String], [frameArray copy], NO); +- (void)recordExceptionModel:(FIRExceptionModel *)exceptionModel { + FIRCLSExceptionRecordModel(exceptionModel); } @end diff --git a/Crashlytics/Crashlytics/FIRExceptionModel.m b/Crashlytics/Crashlytics/FIRExceptionModel.m new file mode 100644 index 00000000000..f0c4697a869 --- /dev/null +++ b/Crashlytics/Crashlytics/FIRExceptionModel.m @@ -0,0 +1,42 @@ +// Copyright 2020 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRExceptionModel.h" + +@interface FIRExceptionModel () + +@property(nonatomic, copy) NSString *name; +@property(nonatomic, copy) NSString *reason; + +@end + +@implementation FIRExceptionModel + +- (instancetype)initWithName:(NSString *)name reason:(NSString *)reason { + self = [super init]; + if (!self) { + return nil; + } + + _name = [name copy]; + _reason = [reason copy]; + + return self; +} + ++ (instancetype)exceptionModelWithName:(NSString *)name reason:(NSString *)reason { + return [[FIRExceptionModel alloc] initWithName:name reason:reason]; +} + +@end diff --git a/Crashlytics/Crashlytics/FIRStackFrame.m b/Crashlytics/Crashlytics/FIRStackFrame.m new file mode 100644 index 00000000000..30449146506 --- /dev/null +++ b/Crashlytics/Crashlytics/FIRStackFrame.m @@ -0,0 +1,94 @@ +// Copyright 2020 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "FIRStackFrame_Private.h" + +@interface FIRStackFrame () + +@property(nonatomic, copy, nullable) NSString *symbol; +@property(nonatomic, copy, nullable) NSString *rawSymbol; +@property(nonatomic, copy, nullable) NSString *library; +@property(nonatomic, copy, nullable) NSString *fileName; +@property(nonatomic, assign) uint32_t lineNumber; +@property(nonatomic, assign) uint64_t offset; +@property(nonatomic, assign) uint64_t address; + +@property(nonatomic, assign) BOOL isSymbolicated; + +@end + +@implementation FIRStackFrame + +#pragma mark - Public Methods + +- (instancetype)initWithSymbol:(NSString *)symbol file:(NSString *)file line:(NSInteger)line { + self = [super init]; + if (!self) { + return nil; + } + + _symbol = [symbol copy]; + _fileName = [file copy]; + _lineNumber = (uint32_t)line; + + _isSymbolicated = true; + + return self; +} + ++ (instancetype)stackFrameWithSymbol:(NSString *)symbol file:(NSString *)file line:(NSInteger)line { + return [[FIRStackFrame alloc] initWithSymbol:symbol file:file line:line]; +} + +#pragma mark - Internal Methods + ++ (instancetype)stackFrame { + return [[self alloc] init]; +} + ++ (instancetype)stackFrameWithAddress:(NSUInteger)address { + FIRStackFrame *frame = [self stackFrame]; + + [frame setAddress:address]; + + return frame; +} + ++ (instancetype)stackFrameWithSymbol:(NSString *)symbol { + FIRStackFrame *frame = [self stackFrame]; + + frame.symbol = symbol; + frame.rawSymbol = symbol; + + return frame; +} + +#pragma mark - Overrides + +- (NSString *)description { + if (self.isSymbolicated) { + return [NSString + stringWithFormat:@"{%@ - %@:%u}", [self fileName], [self symbol], [self lineNumber]]; + } + + if (self.fileName) { + return [NSString stringWithFormat:@"{[0x%llx] %@ - %@:%u}", [self address], [self fileName], + [self symbol], [self lineNumber]]; + } + + return [NSString + stringWithFormat:@"{[0x%llx + %u] %@}", [self address], [self lineNumber], [self symbol]]; +} + +@end diff --git a/Crashlytics/Crashlytics/Handlers/FIRCLSException.h b/Crashlytics/Crashlytics/Handlers/FIRCLSException.h index 7287d9eeeee..61dac77c321 100644 --- a/Crashlytics/Crashlytics/Handlers/FIRCLSException.h +++ b/Crashlytics/Crashlytics/Handlers/FIRCLSException.h @@ -19,7 +19,8 @@ #ifdef __OBJC__ #import -@class FIRCLSStackFrame; +@class FIRStackFrame; +@class FIRExceptionModel; #endif #define CLS_EXCEPTION_STRING_LENGTH_MAX (1024 * 16) @@ -60,11 +61,12 @@ void FIRCLSExceptionRaiseTestObjCException(void) __attribute((noreturn)); void FIRCLSExceptionRaiseTestCppException(void) __attribute((noreturn)); #ifdef __OBJC__ +void FIRCLSExceptionRecordModel(FIRExceptionModel* exceptionModel); void FIRCLSExceptionRecordNSException(NSException* exception); void FIRCLSExceptionRecord(FIRCLSExceptionType type, const char* name, const char* reason, - NSArray* frames, + NSArray* frames, BOOL attemptDelivery); #endif diff --git a/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm b/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm index 1eeecb60e94..6f209904571 100644 --- a/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm +++ b/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm @@ -16,13 +16,15 @@ #include "FIRCLSException.h" +#import "FIRExceptionModel_Private.h" +#import "FIRStackFrame_Private.h" + #include "FIRCLSApplication.h" #include "FIRCLSFile.h" #include "FIRCLSGlobals.h" #include "FIRCLSHandler.h" #import "FIRCLSLogger.h" #include "FIRCLSProcess.h" -#import "FIRCLSStackFrame.h" #import "FIRCLSUserLogging.h" #import "FIRCLSUtility.h" @@ -76,6 +78,14 @@ void FIRCLSExceptionInitialize(FIRCLSExceptionReadOnlyContext *roContext, rwContext->customExceptionCount = 0; } +void FIRCLSExceptionRecordModel(FIRExceptionModel *exceptionModel) { + const char *name = [[exceptionModel.name copy] UTF8String]; + const char *reason = [[exceptionModel.reason copy] UTF8String]; + + FIRCLSExceptionRecord(FIRCLSExceptionTypeCustom, name, reason, [exceptionModel.stackTrace copy], + NO); +} + void FIRCLSExceptionRecordNSException(NSException *exception) { FIRCLSSDKLog("Recording an NSException\n"); @@ -93,14 +103,14 @@ void FIRCLSExceptionRecordNSException(NSException *exception) { NSMutableArray *frames = [NSMutableArray new]; for (NSNumber *address in returnAddresses) { - [frames addObject:[FIRCLSStackFrame stackFrameWithAddress:[address unsignedIntegerValue]]]; + [frames addObject:[FIRStackFrame stackFrameWithAddress:[address unsignedIntegerValue]]]; } FIRCLSExceptionRecord(FIRCLSExceptionTypeObjectiveC, [name UTF8String], [reason UTF8String], frames, YES); } -static void FIRCLSExceptionRecordFrame(FIRCLSFile *file, FIRCLSStackFrame *frame) { +static void FIRCLSExceptionRecordFrame(FIRCLSFile *file, FIRStackFrame *frame) { FIRCLSFileWriteHashStart(file); FIRCLSFileWriteHashEntryUint64(file, "pc", [frame address]); @@ -150,7 +160,7 @@ void FIRCLSExceptionWrite(FIRCLSFile *file, FIRCLSExceptionType type, const char *name, const char *reason, - NSArray *frames) { + NSArray *frames) { FIRCLSFileWriteSectionStart(file, "exception"); FIRCLSFileWriteHashStart(file); @@ -164,7 +174,7 @@ void FIRCLSExceptionWrite(FIRCLSFile *file, FIRCLSFileWriteHashKey(file, "frames"); FIRCLSFileWriteArrayStart(file); - for (FIRCLSStackFrame *frame in frames) { + for (FIRStackFrame *frame in frames) { FIRCLSExceptionRecordFrame(file, frame); } @@ -179,7 +189,7 @@ void FIRCLSExceptionWrite(FIRCLSFile *file, void FIRCLSExceptionRecord(FIRCLSExceptionType type, const char *name, const char *reason, - NSArray *frames, + NSArray *frames, BOOL attemptDelivery) { if (!FIRCLSContextIsInitialized()) { return; diff --git a/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.m b/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.m deleted file mode 100644 index 8227e4ff463..00000000000 --- a/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.m +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "FIRCLSStackFrame.h" - -@implementation FIRCLSStackFrame - -+ (instancetype)stackFrame { - return [[self alloc] init]; -} - -+ (instancetype)stackFrameWithAddress:(NSUInteger)address { - FIRCLSStackFrame* frame = [self stackFrame]; - - [frame setAddress:address]; - - return frame; -} - -+ (instancetype)stackFrameWithSymbol:(NSString*)symbol { - FIRCLSStackFrame* frame = [self stackFrame]; - - frame.symbol = symbol; - frame.rawSymbol = symbol; - - return frame; -} - -- (NSString*)description { - if ([self fileName]) { - return [NSString stringWithFormat:@"{[0x%llx] %@ - %@:%u}", [self address], [self fileName], - [self symbol], [self lineNumber]]; - } - - return [NSString - stringWithFormat:@"{[0x%llx + %u] %@}", [self address], [self lineNumber], [self symbol]]; -} - -@end diff --git a/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h b/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h index 85adc85094d..a18e60467e7 100644 --- a/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h +++ b/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h @@ -14,13 +14,13 @@ #import -@class FIRCLSStackFrame; +@class FIRStackFrame; @interface FIRCLSSymbolResolver : NSObject - (BOOL)loadBinaryImagesFromFile:(NSString *)path; -- (FIRCLSStackFrame *)frameForAddress:(uint64_t)address; -- (BOOL)updateStackFrame:(FIRCLSStackFrame *)frame; +- (FIRStackFrame *)frameForAddress:(uint64_t)address; +- (BOOL)updateStackFrame:(FIRStackFrame *)frame; @end diff --git a/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.m b/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.m index a96dd70aa38..522a14ec595 100644 --- a/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.m +++ b/Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.m @@ -19,7 +19,7 @@ #include "FIRCLSBinaryImage.h" #include "FIRCLSFile.h" #import "FIRCLSLogger.h" -#import "FIRCLSStackFrame.h" +#import "FIRStackFrame_Private.h" @interface FIRCLSSymbolResolver () { NSMutableArray* _binaryImages; @@ -107,8 +107,8 @@ - (BOOL)fillInImageDetails:(FIRCLSBinaryImageDetails*)details forUUID:(NSString* return FIRCLSBinaryImageFindImageForUUID([uuid UTF8String], details); } -- (FIRCLSStackFrame*)frameForAddress:(uint64_t)address { - FIRCLSStackFrame* frame = [FIRCLSStackFrame stackFrameWithAddress:(NSUInteger)address]; +- (FIRStackFrame*)frameForAddress:(uint64_t)address { + FIRStackFrame* frame = [FIRStackFrame stackFrameWithAddress:(NSUInteger)address]; if (![self updateStackFrame:frame]) { return nil; @@ -117,7 +117,7 @@ - (FIRCLSStackFrame*)frameForAddress:(uint64_t)address { return frame; } -- (BOOL)updateStackFrame:(FIRCLSStackFrame*)frame { +- (BOOL)updateStackFrame:(FIRStackFrame*)frame { uint64_t address = [frame address]; if (address == 0) { return NO; diff --git a/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.m b/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.m index 3c5ab8a1ba9..12ce601d684 100644 --- a/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.m +++ b/Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.m @@ -18,9 +18,9 @@ #import "FIRCLSFile.h" #import "FIRCLSInternalReport.h" #import "FIRCLSSerializeSymbolicatedFramesOperation.h" -#import "FIRCLSStackFrame.h" #import "FIRCLSSymbolResolver.h" #import "FIRCLSSymbolicationOperation.h" +#import "FIRStackFrame_Private.h" @implementation FIRCLSProcessReportOperation @@ -64,7 +64,7 @@ - (NSArray *)threadArrayFromFile:(NSString *)path { NSMutableArray *frameArray = [NSMutableArray array]; for (NSNumber *pc in [threadDetails objectForKey:@"stacktrace"]) { - FIRCLSStackFrame *frame = [FIRCLSStackFrame stackFrameWithAddress:[pc unsignedIntegerValue]]; + FIRStackFrame *frame = [FIRStackFrame stackFrameWithAddress:[pc unsignedIntegerValue]]; [frameArray addObject:frame]; } diff --git a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.mm b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.mm index 182f7a01849..364a6ed6ddb 100644 --- a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.mm +++ b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.mm @@ -13,7 +13,7 @@ // limitations under the License. #include "FIRCLSDemangleOperation.h" -#include "FIRCLSStackFrame.h" +#include "FIRStackFrame_Private.h" #import @@ -84,7 +84,7 @@ - (NSString *)demangleSymbol:(const char *)symbol { } - (void)main { - [self enumerateFramesWithBlock:^(FIRCLSStackFrame *frame) { + [self enumerateFramesWithBlock:^(FIRStackFrame *frame) { NSString *demangedSymbol = [self demangleSymbol:[[frame rawSymbol] UTF8String]]; if (demangedSymbol) { diff --git a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.m b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.m index 6b2b79b7466..b8701d261a7 100644 --- a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.m +++ b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSerializeSymbolicatedFramesOperation.m @@ -16,7 +16,7 @@ #import "FIRCLSFile.h" #import "FIRCLSLogger.h" -#import "FIRCLSStackFrame.h" +#import "FIRStackFrame_Private.h" @implementation FIRCLSSerializeSymbolicatedFramesOperation @@ -37,7 +37,7 @@ - (void)main { for (NSArray *frameArray in self.threadArray) { FIRCLSFileWriteArrayStart(&file); - for (FIRCLSStackFrame *frame in frameArray) { + for (FIRStackFrame *frame in frameArray) { FIRCLSFileWriteHashStart(&file); FIRCLSFileWriteHashEntryString(&file, "symbol", [[frame symbol] UTF8String]); diff --git a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.m b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.m index e2988b2d4b8..d15005bbeef 100644 --- a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.m +++ b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSSymbolicationOperation.m @@ -14,13 +14,12 @@ #import "FIRCLSSymbolicationOperation.h" -#import "FIRCLSStackFrame.h" #import "FIRCLSSymbolResolver.h" @implementation FIRCLSSymbolicationOperation - (void)main { - [self enumerateFramesWithBlock:^(FIRCLSStackFrame *frame) { + [self enumerateFramesWithBlock:^(FIRStackFrame *frame) { [self.symbolResolver updateStackFrame:frame]; }]; } diff --git a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.h b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.h index e223523ffff..0c2a1df54b2 100644 --- a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.h +++ b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.h @@ -14,12 +14,12 @@ #import -@class FIRCLSStackFrame; +@class FIRStackFrame; @interface FIRCLSThreadArrayOperation : NSOperation @property(nonatomic, strong) NSArray *threadArray; -- (void)enumerateFramesWithBlock:(void (^)(FIRCLSStackFrame *frame))block; +- (void)enumerateFramesWithBlock:(void (^)(FIRStackFrame *frame))block; @end diff --git a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.m b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.m index 96c73277626..3f7509e7e3e 100644 --- a/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.m +++ b/Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSThreadArrayOperation.m @@ -14,13 +14,11 @@ #import "FIRCLSThreadArrayOperation.h" -#import "FIRCLSStackFrame.h" - @implementation FIRCLSThreadArrayOperation -- (void)enumerateFramesWithBlock:(void (^)(FIRCLSStackFrame *frame))block { +- (void)enumerateFramesWithBlock:(void (^)(FIRStackFrame *frame))block { for (NSArray *frameArray in self.threadArray) { - for (FIRCLSStackFrame *frame in frameArray) { + for (FIRStackFrame *frame in frameArray) { block(frame); if ([self isCancelled]) { diff --git a/Crashlytics/Crashlytics/Private/FIRExceptionModel_Private.h b/Crashlytics/Crashlytics/Private/FIRExceptionModel_Private.h new file mode 100644 index 00000000000..7cd161a9568 --- /dev/null +++ b/Crashlytics/Crashlytics/Private/FIRExceptionModel_Private.h @@ -0,0 +1,33 @@ +// Copyright 2020 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FIRExceptionModel_Private_h +#define FIRExceptionModel_Private_h + +#import + +#import "FIRExceptionModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRExceptionModel (Private) + +@property(nonatomic, copy) NSString *name; +@property(nonatomic, copy) NSString *reason; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* FIRExceptionModel_Private_h */ diff --git a/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.h b/Crashlytics/Crashlytics/Private/FIRStackFrame_Private.h similarity index 69% rename from Crashlytics/Crashlytics/Models/FIRCLSStackFrame.h rename to Crashlytics/Crashlytics/Private/FIRStackFrame_Private.h index 24ef1cc8bc5..d4c0a44c9b3 100644 --- a/Crashlytics/Crashlytics/Models/FIRCLSStackFrame.h +++ b/Crashlytics/Crashlytics/Private/FIRStackFrame_Private.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google +// Copyright 2020 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,18 +14,18 @@ #import +#import "FIRStackFrame.h" + NS_ASSUME_NONNULL_BEGIN /** - * - * This class is used in conjunction with -[Crashlytics - * recordCustomExceptionName:reason:frameArray:] to record information about non-ObjC/C++ - * exceptions. All information included here will be displayed in the Crashlytics UI, and can - * influence crash grouping. Be particularly careful with the use of the address property. If set, - * Crashlytics will attempt symbolication and could overwrite other properities in the process. - * + * This class is used in conjunction with recordExceptionModel to record information about + * non-ObjC/C++ exceptions. All information included here will be displayed in the Crashlytics UI, + * and can influence crash grouping. Be particularly careful with the use of the address property. + *If set, Crashlytics will attempt symbolication and could overwrite other properities in the + *process. **/ -@interface FIRCLSStackFrame : NSObject +@interface FIRStackFrame (Private) + (instancetype)stackFrame; + (instancetype)stackFrameWithAddress:(NSUInteger)address; diff --git a/Crashlytics/Crashlytics/Public/FIRCrashlytics.h b/Crashlytics/Crashlytics/Public/FIRCrashlytics.h index c78a592de49..9f651537b17 100644 --- a/Crashlytics/Crashlytics/Public/FIRCrashlytics.h +++ b/Crashlytics/Crashlytics/Public/FIRCrashlytics.h @@ -14,6 +14,8 @@ #import +#import "FIRExceptionModel.h" + #if __has_include() #warning "FirebaseCrashlytics and Crashlytics are not compatible \ in the same app because including multiple crash reporters can \ @@ -95,7 +97,6 @@ NS_SWIFT_NAME(Crashlytics) - (void)setUserID:(NSString *)userID; /** - * * Records a non-fatal event described by an NSError object. The events are * grouped and displayed similarly to crashes. Keep in mind that this method can be expensive. * The total number of NSErrors that can be recorded during your app's life-cycle is limited by a @@ -106,6 +107,18 @@ NS_SWIFT_NAME(Crashlytics) */ - (void)recordError:(NSError *)error NS_SWIFT_NAME(record(error:)); +/** + * Records an Exception Model described by an FIRExceptionModel object. The events are + * grouped and displayed similarly to crashes. Keep in mind that this method can be expensive. + * The total number of FIRExceptionModels that can be recorded during your app's life-cycle is + * limited by a fixed-size circular buffer. If the buffer is overrun, the oldest data is dropped. + * Exception Models are relayed to Crashlytics on a subsequent launch of your application. + * + * @param exceptionModel Instance of the FIRExceptionModel to be recorded + */ +- (void)recordExceptionModel:(FIRExceptionModel *)exceptionModel + NS_SWIFT_NAME(record(exceptionModel:)); + /** * Returns whether the app crashed during the previous execution. */ diff --git a/Crashlytics/Crashlytics/Public/FIRExceptionModel.h b/Crashlytics/Crashlytics/Public/FIRExceptionModel.h new file mode 100644 index 00000000000..a0ee1579eed --- /dev/null +++ b/Crashlytics/Crashlytics/Public/FIRExceptionModel.h @@ -0,0 +1,57 @@ +// Copyright 2020 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "FIRStackFrame.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The Firebase Crashlytics Exception Model provides a way to report custom exceptions + * to Crashlytics that came from a runtime environment outside of the native + * platform Crashlytics is running in. + */ +NS_SWIFT_NAME(ExceptionModel) +@interface FIRExceptionModel : NSObject + +/** :nodoc: */ +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initializes an Exception Model model with the given required fields. + * + * @param name - typically the type of the Exception class + * @param reason - the human-readable reason the issue occurred + */ +- (instancetype)initWithName:(NSString *)name reason:(NSString *)reason; + +/** + * Creates an Exception Model model with the given required fields. + * + * @param name - typically the type of the Exception class + * @param reason - the human-readable reason the issue occurred + */ ++ (instancetype)exceptionModelWithName:(NSString *)name + reason:(NSString *)reason NS_SWIFT_UNAVAILABLE(""); + +/** + * A list of Stack Frames that make up the stack trace. The order of the stack trace is top-first, + * so typically the "main" function is the last element in this list. + */ +@property(nonatomic, copy) NSArray *stackTrace; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Crashlytics/Crashlytics/Public/FIRStackFrame.h b/Crashlytics/Crashlytics/Public/FIRStackFrame.h new file mode 100644 index 00000000000..ef9746fbb46 --- /dev/null +++ b/Crashlytics/Crashlytics/Public/FIRStackFrame.h @@ -0,0 +1,53 @@ +// Copyright 2020 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The Firebase Crashlytics Stack Frame provides a way to construct the lines of + * a stack trace for reporting along with a recorded Exception Model. + */ +NS_SWIFT_NAME(StackFrame) +@interface FIRStackFrame : NSObject + +/** :nodoc: */ +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initializes a symbolicated Stack Frame with the given required fields. Symbolicated + * Stack Frames will appear in the Crashlytics dashboard as reported in these fields. + * + * @param symbol - The function or method name + * @param file - the file where the exception occurred + * @param line - the line number + */ +- (instancetype)initWithSymbol:(NSString *)symbol file:(NSString *)file line:(NSInteger)line; + +/** + * Creates a symbolicated Stack Frame with the given required fields. Symbolicated + * Stack Frames will appear in the Crashlytics dashboard as reported in these fields. * + * + * @param symbol - The function or method name + * @param file - the file where the exception occurred + * @param line - the line number + */ ++ (instancetype)stackFrameWithSymbol:(NSString *)symbol + file:(NSString *)file + line:(NSInteger)line NS_SWIFT_UNAVAILABLE(""); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Crashlytics/Crashlytics/Public/FirebaseCrashlytics.h b/Crashlytics/Crashlytics/Public/FirebaseCrashlytics.h index d85680b0059..9022811ba1c 100644 --- a/Crashlytics/Crashlytics/Public/FirebaseCrashlytics.h +++ b/Crashlytics/Crashlytics/Public/FirebaseCrashlytics.h @@ -15,3 +15,5 @@ */ #import "FIRCrashlytics.h" +#import "FIRExceptionModel.h" +#import "FIRStackFrame.h" diff --git a/Crashlytics/UnitTests/FIRCLSDemangleOperationTests.m b/Crashlytics/UnitTests/FIRCLSDemangleOperationTests.m index 8deb8d29e94..eb1232a61b3 100644 --- a/Crashlytics/UnitTests/FIRCLSDemangleOperationTests.m +++ b/Crashlytics/UnitTests/FIRCLSDemangleOperationTests.m @@ -17,7 +17,7 @@ #import #import -#import "FIRCLSStackFrame.h" +#import "FIRStackFrame_Private.h" @interface FIRCLSDemangleOperationTests : XCTestCase @@ -57,9 +57,9 @@ - (void)testDemangleCppSymbolsWithBlockInvoke { - (void)testOperation { NSMutableArray *frameArray = [[NSMutableArray alloc] init]; - [frameArray addObject:[FIRCLSStackFrame stackFrameWithSymbol:@"_Z7monitorP8NSStringlS0_"]]; - [frameArray addObject:[FIRCLSStackFrame stackFrameWithSymbol:@"_ZN9wikipedia7article6formatEv"]]; - [frameArray addObject:[FIRCLSStackFrame stackFrameWithSymbol:@"unmangledSymbol"]]; + [frameArray addObject:[FIRStackFrame stackFrameWithSymbol:@"_Z7monitorP8NSStringlS0_"]]; + [frameArray addObject:[FIRStackFrame stackFrameWithSymbol:@"_ZN9wikipedia7article6formatEv"]]; + [frameArray addObject:[FIRStackFrame stackFrameWithSymbol:@"unmangledSymbol"]]; FIRCLSDemangleOperation *op = [[FIRCLSDemangleOperation alloc] init]; [op setThreadArray:@[ frameArray ]]; diff --git a/Crashlytics/UnitTests/FIRCLSProcessReportOperationTests.m b/Crashlytics/UnitTests/FIRCLSProcessReportOperationTests.m index ec56707c959..dafa36651d7 100644 --- a/Crashlytics/UnitTests/FIRCLSProcessReportOperationTests.m +++ b/Crashlytics/UnitTests/FIRCLSProcessReportOperationTests.m @@ -21,7 +21,7 @@ #import "FIRCLSFileManager.h" #import "FIRCLSInternalReport.h" #import "FIRCLSMockSymbolResolver.h" -#import "FIRCLSStackFrame.h" +#import "FIRStackFrame_Private.h" @interface FIRCLSProcessReportOperationTests : XCTestCase @@ -70,9 +70,9 @@ - (void)testExceptionSymbolication { // Setup a resolver that will work for the contents of the file FIRCLSMockSymbolResolver *resolver = [[FIRCLSMockSymbolResolver alloc] init]; - FIRCLSStackFrame *frame = nil; + FIRStackFrame *frame = nil; - frame = [FIRCLSStackFrame stackFrameWithSymbol:@"testSymbolA"]; + frame = [FIRStackFrame stackFrameWithSymbol:@"testSymbolA"]; [frame setLibrary:@"libA"]; [frame setOffset:10]; diff --git a/Crashlytics/UnitTests/FIRCLSSymbolicationOperationTests.m b/Crashlytics/UnitTests/FIRCLSSymbolicationOperationTests.m index 9a893336a17..276013d5b28 100644 --- a/Crashlytics/UnitTests/FIRCLSSymbolicationOperationTests.m +++ b/Crashlytics/UnitTests/FIRCLSSymbolicationOperationTests.m @@ -18,7 +18,7 @@ #import #import "FIRCLSMockSymbolResolver.h" -#import "FIRCLSStackFrame.h" +#import "FIRStackFrame_Private.h" @interface FIRCLSSymbolicationOperationTests : XCTestCase @@ -37,23 +37,23 @@ - (void)tearDown { - (void)testOperation { FIRCLSMockSymbolResolver* resolver = [[FIRCLSMockSymbolResolver alloc] init]; - FIRCLSStackFrame* frame = nil; + FIRStackFrame* frame = nil; - frame = [FIRCLSStackFrame stackFrameWithSymbol:@"testSymbolA"]; + frame = [FIRStackFrame stackFrameWithSymbol:@"testSymbolA"]; [frame setLibrary:@"libA"]; [frame setOffset:10]; [resolver addMockFrame:frame atAddress:100]; - frame = [FIRCLSStackFrame stackFrameWithSymbol:@"testSymbolB"]; + frame = [FIRStackFrame stackFrameWithSymbol:@"testSymbolB"]; [frame setLibrary:@"libB"]; [frame setOffset:20]; [resolver addMockFrame:frame atAddress:200]; NSMutableArray* frameArray = [[NSMutableArray alloc] init]; - [frameArray addObject:[FIRCLSStackFrame stackFrameWithAddress:100]]; - [frameArray addObject:[FIRCLSStackFrame stackFrameWithAddress:200]]; + [frameArray addObject:[FIRStackFrame stackFrameWithAddress:100]]; + [frameArray addObject:[FIRStackFrame stackFrameWithAddress:200]]; FIRCLSSymbolicationOperation* op = [[FIRCLSSymbolicationOperation alloc] init]; @@ -65,10 +65,10 @@ - (void)testOperation { XCTAssertEqual([frameArray count], 2, @""); XCTAssertEqualObjects([frameArray[0] symbol], @"testSymbolA", @""); XCTAssertEqualObjects([frameArray[0] library], @"libA", @""); - XCTAssertEqual([((FIRCLSStackFrame*)frameArray[0]) offset], 10, @""); + XCTAssertEqual([((FIRStackFrame*)frameArray[0]) offset], 10, @""); XCTAssertEqualObjects([frameArray[1] symbol], @"testSymbolB", @""); XCTAssertEqualObjects([frameArray[1] library], @"libB", @""); - XCTAssertEqual([((FIRCLSStackFrame*)frameArray[1]) offset], 20, @""); + XCTAssertEqual([((FIRStackFrame*)frameArray[1]) offset], 20, @""); } @end diff --git a/Crashlytics/UnitTests/FIRExceptionModelTests.m b/Crashlytics/UnitTests/FIRExceptionModelTests.m new file mode 100644 index 00000000000..1b3e93b74ec --- /dev/null +++ b/Crashlytics/UnitTests/FIRExceptionModelTests.m @@ -0,0 +1,80 @@ +// Copyright 2020 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "FIRExceptionModel_Private.h" +#import "FIRStackFrame_Private.h" + +@interface FIRExceptionModelTests : XCTestCase + +@end + +@implementation FIRExceptionModelTests + +- (void)testBasicOwnership { + NSArray *stackTrace = @[ + [FIRStackFrame stackFrameWithSymbol:@"CrashyFunc" file:@"AppLib.m" line:504], + [FIRStackFrame stackFrameWithSymbol:@"ApplicationMain" file:@"AppleLib" line:1], + [FIRStackFrame stackFrameWithSymbol:@"main()" file:@"main.m" line:201], + ]; + NSString *name = @"FIRExceptionModelTestsCrash"; + NSString *reason = @"Programmer made an error"; + + FIRExceptionModel *model = [FIRExceptionModel exceptionModelWithName:name reason:reason]; + model.stackTrace = stackTrace; + + name = @"NewName"; + reason = nil; + stackTrace = @[]; + + XCTAssertEqualObjects(model.name, @"FIRExceptionModelTestsCrash"); + XCTAssertEqualObjects(model.reason, @"Programmer made an error"); + XCTAssertEqual(model.stackTrace.count, 3); + XCTAssertEqualObjects(model.stackTrace[0].symbol, @"CrashyFunc"); + XCTAssertEqualObjects(model.stackTrace[2].fileName, @"main.m"); +} + +- (void)testMutableArrayOwnership { + NSMutableArray *stackTrace = [[NSMutableArray alloc] initWithArray:@[ + [FIRStackFrame stackFrameWithSymbol:@"CrashyFunc" file:@"AppLib.m" line:504], + [FIRStackFrame stackFrameWithSymbol:@"ApplicationMain" file:@"AppleLib" line:1], + [FIRStackFrame stackFrameWithSymbol:@"main()" file:@"main.m" line:201], + ]]; + NSString *name = @"FIRExceptionModelTestsCrash"; + NSString *reason = @"Programmer made an error"; + + FIRExceptionModel *model = [FIRExceptionModel exceptionModelWithName:name reason:reason]; + model.stackTrace = stackTrace; + + stackTrace[0].symbol = @"NewSymbol"; + + FIRStackFrame *newFrame = [FIRStackFrame stackFrameWithSymbol:@"NewMain" + file:@"below_main.m" + line:300]; + [stackTrace addObject:newFrame]; + [stackTrace insertObject:newFrame atIndex:1]; + + XCTAssertEqual(model.stackTrace.count, 3); + + // Modifying underlying frames in the stack trace will be reflected in the Exception Model's copy + // because we only shallow copy the array and not the contents. + XCTAssertEqualObjects(model.stackTrace[0].symbol, @"NewSymbol"); + + // Inserted frames into the mutable array after the fact do not impact the array passed to the + // Exception Model. + XCTAssertEqualObjects(model.stackTrace[1].symbol, @"ApplicationMain"); +} + +@end diff --git a/Crashlytics/UnitTests/FIRRecordExceptionModelTests.m b/Crashlytics/UnitTests/FIRRecordExceptionModelTests.m new file mode 100644 index 00000000000..f8bfaf2238c --- /dev/null +++ b/Crashlytics/UnitTests/FIRRecordExceptionModelTests.m @@ -0,0 +1,99 @@ +// Copyright 2020 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "FIRExceptionModel.h" +#import "FIRStackFrame.h" + +#import "FABMockApplicationIdentifierModel.h" +#import "FIRCLSContext.h" +#import "FIRCLSInstallIdentifierModel.h" +#import "FIRCLSInternalReport.h" +#import "FIRCLSMockFileManager.h" +#import "FIRCLSMockSettings.h" +#import "FIRMockInstallations.h" + +#define TEST_BUNDLE_ID (@"com.crashlytics.test") + +@interface FIRRecordExceptionModelTests : XCTestCase + +@property(nonatomic, strong) FIRCLSMockFileManager *fileManager; +@property(nonatomic, strong) FIRCLSMockSettings *mockSettings; +@property(nonatomic, strong) NSString *reportPath; + +@end + +@implementation FIRRecordExceptionModelTests + +- (void)setUp { + self.fileManager = [[FIRCLSMockFileManager alloc] init]; + [self.fileManager setPathNamespace:TEST_BUNDLE_ID]; + + FABMockApplicationIdentifierModel *appIDModel = [[FABMockApplicationIdentifierModel alloc] init]; + self.mockSettings = [[FIRCLSMockSettings alloc] initWithFileManager:self.fileManager + appIDModel:appIDModel]; + + FIRMockInstallations *iid = [[FIRMockInstallations alloc] initWithFID:@"test_instance_id"]; + + FIRCLSInstallIdentifierModel *installIDModel = + [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; + + NSString *name = @"exception_model_report"; + self.reportPath = [self.fileManager.rootPath stringByAppendingPathComponent:name]; + [self.fileManager createDirectoryAtPath:self.reportPath]; + + FIRCLSInternalReport *report = + [[FIRCLSInternalReport alloc] initWithPath:self.reportPath + executionIdentifier:@"TEST_EXECUTION_IDENTIFIER"]; + + FIRCLSContextInitialize(report, self.mockSettings, installIDModel, self.fileManager); +} + +- (void)tearDown { + [[NSFileManager defaultManager] removeItemAtPath:self.fileManager.rootPath error:nil]; +} + +- (void)testWrittenCLSRecordFile { + NSArray *stackTrace = @[ + [FIRStackFrame stackFrameWithSymbol:@"CrashyFunc" file:@"AppLib.m" line:504], + [FIRStackFrame stackFrameWithSymbol:@"ApplicationMain" file:@"AppleLib" line:1], + [FIRStackFrame stackFrameWithSymbol:@"main()" file:@"main.m" line:201], + ]; + NSString *name = @"FIRExceptionModelTestsCrash"; + NSString *reason = @"Programmer made an error"; + + FIRExceptionModel *exceptionModel = [FIRExceptionModel exceptionModelWithName:name reason:reason]; + exceptionModel.stackTrace = stackTrace; + + FIRCLSExceptionRecordModel(exceptionModel); + + NSData *data = [NSData + dataWithContentsOfFile:[self.reportPath + stringByAppendingPathComponent:@"custom_exception_a.clsrecord"]]; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + NSDictionary *exception = json[@"exception"]; + NSArray *frames = exception[@"frames"]; + XCTAssertEqualObjects(exception[@"name"], + @"464952457863657074696f6e4d6f64656c54657374734372617368"); + XCTAssertEqualObjects(exception[@"reason"], @"50726f6772616d6d6572206d61646520616e206572726f72"); + XCTAssertEqual(frames.count, 3); + XCTAssertEqualObjects(frames[2][@"file"], @"6d61696e2e6d"); + XCTAssertEqual([frames[2][@"line"] intValue], 201); + XCTAssertEqual([frames[2][@"offset"] intValue], 0); + XCTAssertEqual([frames[2][@"pc"] intValue], 0); + XCTAssertEqualObjects(frames[2][@"symbol"], @"6d61696e2829"); +} + +@end diff --git a/Crashlytics/UnitTests/FIRStackFrameTests.m b/Crashlytics/UnitTests/FIRStackFrameTests.m new file mode 100644 index 00000000000..7270609c71d --- /dev/null +++ b/Crashlytics/UnitTests/FIRStackFrameTests.m @@ -0,0 +1,62 @@ +// Copyright 2020 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "FIRStackFrame_Private.h" + +@interface FIRStackFrameTests : XCTestCase + +@end + +@implementation FIRStackFrameTests + +- (void)testBasicSymbolicatedCheck { + FIRStackFrame *stackFrame = [FIRStackFrame stackFrameWithSymbol:@"SYMBOL" + file:@"FILE" + line:54321]; + XCTAssertEqualObjects(stackFrame.symbol, @"SYMBOL"); + XCTAssertEqualObjects(stackFrame.fileName, @"FILE"); + XCTAssertEqual(stackFrame.lineNumber, 54321); +} + +- (void)testOwnership { + NSString *symbol = @"SYMBOL"; + NSString *file = @"FILE"; + FIRStackFrame *stackFrame = [FIRStackFrame stackFrameWithSymbol:symbol file:file line:54321]; + symbol = @"NEW_SYMBOL"; + file = nil; + XCTAssertEqualObjects(stackFrame.symbol, @"SYMBOL"); + XCTAssertEqualObjects(stackFrame.fileName, @"FILE"); + XCTAssertEqual(stackFrame.lineNumber, 54321); +} + +- (void)testIntUIntConversion { + FIRStackFrame *stackFrame = [FIRStackFrame stackFrameWithSymbol:@"SYMBOL" file:@"FILE" line:100]; + XCTAssertEqual(stackFrame.lineNumber, 100); + + FIRStackFrame *stackFrame2 = [FIRStackFrame stackFrameWithSymbol:@"SYMBOL" + file:@"FILE" + line:-100]; + XCTAssertEqual(stackFrame2.lineNumber, 4294967196); +} + +- (void)testDescription { + FIRStackFrame *stackFrame = [FIRStackFrame stackFrameWithSymbol:@"FIRStackFrameTests" + file:@"testDescription" + line:35]; + XCTAssertEqualObjects([stackFrame description], @"{testDescription - FIRStackFrameTests:35}"); +} + +@end diff --git a/Crashlytics/UnitTests/Mocks/FIRCLSMockSymbolResolver.h b/Crashlytics/UnitTests/Mocks/FIRCLSMockSymbolResolver.h index 2612803b217..fab3ee4f883 100644 --- a/Crashlytics/UnitTests/Mocks/FIRCLSMockSymbolResolver.h +++ b/Crashlytics/UnitTests/Mocks/FIRCLSMockSymbolResolver.h @@ -16,6 +16,6 @@ @interface FIRCLSMockSymbolResolver : FIRCLSSymbolResolver -- (void)addMockFrame:(FIRCLSStackFrame *)frame atAddress:(uint64_t)address; +- (void)addMockFrame:(FIRStackFrame *)frame atAddress:(uint64_t)address; @end diff --git a/Crashlytics/UnitTests/Mocks/FIRCLSMockSymbolResolver.m b/Crashlytics/UnitTests/Mocks/FIRCLSMockSymbolResolver.m index 1173f890cb3..62d6b7a9551 100644 --- a/Crashlytics/UnitTests/Mocks/FIRCLSMockSymbolResolver.m +++ b/Crashlytics/UnitTests/Mocks/FIRCLSMockSymbolResolver.m @@ -14,7 +14,7 @@ #import "FIRCLSMockSymbolResolver.h" -#import "FIRCLSStackFrame.h" +#import "FIRStackFrame_Private.h" @interface FIRCLSMockSymbolResolver () { NSMutableDictionary *_frames; @@ -35,20 +35,20 @@ - (instancetype)init { return self; } -- (void)addMockFrame:(FIRCLSStackFrame *)frame atAddress:(uint64_t)address { +- (void)addMockFrame:(FIRStackFrame *)frame atAddress:(uint64_t)address { [_frames setObject:frame forKey:@(address)]; } -- (BOOL)updateStackFrame:(FIRCLSStackFrame *)frame { - FIRCLSStackFrame *matchedFrame = [_frames objectForKey:@([frame address])]; +- (BOOL)updateStackFrame:(FIRStackFrame *)frame { + FIRStackFrame *matchedFrame = [_frames objectForKey:@(frame.address)]; if (!matchedFrame) { return NO; } - [frame setSymbol:[matchedFrame symbol]]; - [frame setLibrary:[matchedFrame library]]; - [frame setOffset:[matchedFrame offset]]; + [frame setSymbol:matchedFrame.symbol]; + [frame setLibrary:matchedFrame.library]; + [frame setOffset:matchedFrame.offset]; return YES; }