Skip to content

Commit

Permalink
Make Doctor an optional filter (#559)
Browse files Browse the repository at this point in the history
* Move CrashDoctor to filters

* Enable doctor in intstallations

* Fix format

* Update tests

* Fix format

* Fix import

* Remove unused variable

* Address code review comments
  • Loading branch information
bamx23 authored Sep 8, 2024
1 parent 85d4eb5 commit 528d4ea
Show file tree
Hide file tree
Showing 14 changed files with 238 additions and 66 deletions.
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ let package = Package(
.target(name: Targets.recording),
.target(name: Targets.recordingCore),
.target(name: Targets.reportingCore),
],
resources: [
.process("Resources")
]
),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

@interface KSCrashDoctor : NSObject

+ (KSCrashDoctor *)doctor;

- (NSString *)diagnoseCrash:(NSDictionary *)crashReport;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

#import "KSCrashDoctor.h"
#import "KSCrashMonitor_System.h"
#import "KSCrashReportFields.h"

typedef enum { CPUFamilyUnknown, CPUFamilyArm, CPUFamilyX86, CPUFamilyX86_64 } CPUFamily;
Expand Down Expand Up @@ -122,11 +121,6 @@ - (NSString *)descriptionWithParamCount:(int)paramCount

@implementation KSCrashDoctor

+ (KSCrashDoctor *)doctor
{
return [[self alloc] init];
}

- (NSDictionary *)recrashReport:(NSDictionary *)report
{
return [report objectForKey:KSCrashField_RecrashReport];
Expand Down
78 changes: 78 additions & 0 deletions Sources/KSCrashFilters/KSCrashReportFilterDoctor.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// KSCrashReportFilterDoctor.m
//
// Created by Karl Stenerud on 2024-09-05.
//
// Copyright (c) 2012 Karl Stenerud. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall remain in place
// in this source code.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

#import "KSCrashReportFilterDoctor.h"
#import "KSCrashDoctor.h"
#import "KSCrashReport.h"
#import "KSCrashReportFields.h"

// #define KSLogger_LocalLevel TRACE
#import "KSLogger.h"

@interface KSCrashReportFilterDoctor ()

@end

@implementation KSCrashReportFilterDoctor

+ (NSString *)diagnoseCrash:(NSDictionary *)crashReport
{
return [[KSCrashDoctor new] diagnoseCrash:crashReport];
}

- (void)filterReports:(NSArray<id<KSCrashReport>> *)reports onCompletion:(KSCrashReportFilterCompletion)onCompletion
{
NSMutableArray<id<KSCrashReport>> *filteredReports = [NSMutableArray arrayWithCapacity:[reports count]];
for (KSCrashReportDictionary *report in reports) {
if ([report isKindOfClass:[KSCrashReportDictionary class]] == NO) {
KSLOG_ERROR(@"Unexpected non-dictionary report: %@", report);
continue;
}

NSString *diagnose = [[self class] diagnoseCrash:report.value];
NSMutableDictionary *crashReport = [report.value mutableCopy];
if (diagnose != nil) {
if (crashReport[KSCrashField_Crash] != nil) {
NSMutableDictionary *crashDict = [crashReport[KSCrashField_Crash] mutableCopy];
crashDict[KSCrashField_Diagnosis] = diagnose;
crashReport[KSCrashField_Crash] = crashDict;
}
if (crashReport[KSCrashField_RecrashReport][KSCrashField_Crash] != nil) {
NSMutableDictionary *recrashReport = [crashReport[KSCrashField_RecrashReport] mutableCopy];
NSMutableDictionary *crashDict = [recrashReport[KSCrashField_Crash] mutableCopy];
crashDict[KSCrashField_Diagnosis] = diagnose;
recrashReport[KSCrashField_Crash] = crashDict;
crashReport[KSCrashField_RecrashReport] = recrashReport;
}
}

[filteredReports addObject:[KSCrashReportDictionary reportWithValue:crashReport]];
}

kscrash_callCompletion(onCompletion, filteredReports, nil);
}

@end
46 changes: 46 additions & 0 deletions Sources/KSCrashFilters/include/KSCrashReportFilterDoctor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// KSCrashReportFilterDoctor.h
//
// Created by Karl Stenerud on 2024-09-15.
//
// Copyright (c) 2012 Karl Stenerud. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall remain in place
// in this source code.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

#import "KSCrashReportFilter.h"

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/**
* Adds an automated diagnostis section to reports.
*
* Input: NSDictionary
* Output: NSDictionary
*/
NS_SWIFT_NAME(CrashReportFilterDoctor)
@interface KSCrashReportFilterDoctor : NSObject <KSCrashReportFilter>

+ (NSString *)diagnoseCrash:(NSDictionary *)crashReport;

@end

NS_ASSUME_NONNULL_END
25 changes: 15 additions & 10 deletions Sources/KSCrashInstallations/KSCrashInstallation.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#import "KSCrashReportFilterAlert.h"
#import "KSCrashReportFilterBasic.h"
#import "KSCrashReportFilterDemangle.h"
#import "KSCrashReportFilterDoctor.h"
#import "KSJSONCodecObjC.h"
#import "KSLogger.h"
#import "KSNSErrorHelper.h"
Expand Down Expand Up @@ -146,6 +147,7 @@ - (id)initWithRequiredProperties:(NSArray *)requiredProperties
{
if ((self = [super init])) {
_isDemangleEnabled = YES;
_isDoctorEnabled = YES;
_crashHandlerDataBacking =
[NSMutableData dataWithLength:sizeof(*self.crashHandlerData) +
sizeof(*self.crashHandlerData->reportFields) * kMaxProperties];
Expand Down Expand Up @@ -313,15 +315,6 @@ - (void)sendAllReportsWithCompletion:(KSCrashReportFilterCompletion)onCompletion
return;
}

NSMutableArray *sinkFilters = [@[
self.prependedFilters,
sink,
] mutableCopy];
if (self.isDemangleEnabled) {
[sinkFilters insertObject:[KSCrashReportFilterDemangle new] atIndex:0];
}
sink = [[KSCrashReportFilterPipeline alloc] initWithFilters:sinkFilters];

KSCrashReportStore *store = [KSCrash sharedInstance].reportStore;
if (store == nil) {
onCompletion(
Expand All @@ -332,7 +325,19 @@ - (void)sendAllReportsWithCompletion:(KSCrashReportFilterCompletion)onCompletion
return;
}

store.sink = sink;
NSMutableArray *installationFilters = [NSMutableArray array];
if (self.isDemangleEnabled) {
[installationFilters addObject:[KSCrashReportFilterDemangle new]];
}
if (self.isDoctorEnabled) {
[installationFilters addObject:[KSCrashReportFilterDoctor new]];
}
[installationFilters addObjectsFromArray:@[
self.prependedFilters,
sink,
]];
store.sink = [[KSCrashReportFilterPipeline alloc] initWithFilters:installationFilters];

[store sendAllReportsWithCompletion:onCompletion];
}

Expand Down
8 changes: 7 additions & 1 deletion Sources/KSCrashInstallations/include/KSCrashInstallation.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,17 @@ NS_SWIFT_NAME(CrashInstallation)
@property(atomic, readwrite, assign, nullable) KSReportWriteCallback onCrash;

/** Flag for disabling built-in demangling pre-filter.
* If enabled an additional KSCrashReportFilterDemangle filter will be applied first.
* If enabled an additional `KSCrashReportFilterDemangle` filter will be applied first.
* @note Enabled by-default.
*/
@property(nonatomic, assign) BOOL isDemangleEnabled;

/** Flag for disabling a pre-filter for automated diagnostics.
* If enabled an additional `KSCrashReportFilterDoctor` filter will be applied.
* @note Enabled by-default.
*/
@property(nonatomic, assign) BOOL isDoctorEnabled;

/** Install this crash handler with a specific configuration.
* Call this method instead of `-[KSCrash installWithConfiguration:error:]` to set up the crash handler
* tailored for your specific backend requirements.
Expand Down
1 change: 0 additions & 1 deletion Sources/KSCrashRecording/KSCrash.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

#import "KSCrashC.h"
#import "KSCrashConfiguration+Private.h"
#import "KSCrashDoctor.h"
#import "KSCrashMonitorContext.h"
#import "KSCrashMonitor_AppState.h"
#import "KSCrashMonitor_Memory.h"
Expand Down
14 changes: 0 additions & 14 deletions Sources/KSCrashRecording/KSCrashReportStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

#import "KSCrash+Private.h"
#import "KSCrashConfiguration+Private.h"
#import "KSCrashDoctor.h"
#import "KSCrashReport.h"
#import "KSCrashReportFields.h"
#import "KSCrashReportFilter.h"
Expand Down Expand Up @@ -143,18 +142,6 @@ - (NSData *)loadCrashReportJSONWithID:(int64_t)reportID
return nil;
}

- (void)doctorReport:(NSMutableDictionary *)report
{
NSMutableDictionary *crashReport = report[KSCrashField_Crash];
if (crashReport != nil) {
crashReport[KSCrashField_Diagnosis] = [[KSCrashDoctor doctor] diagnoseCrash:report];
}
crashReport = report[KSCrashField_RecrashReport][KSCrashField_Crash];
if (crashReport != nil) {
crashReport[KSCrashField_Diagnosis] = [[KSCrashDoctor doctor] diagnoseCrash:report];
}
}

- (NSArray<NSNumber *> *)reportIDs
{
int reportCount = kscrs_getReportCount(&_cConfig);
Expand Down Expand Up @@ -187,7 +174,6 @@ - (KSCrashReportDictionary *)reportForID:(int64_t)reportID
KSLOG_ERROR(@"Could not load crash report");
return nil;
}
[self doctorReport:crashReport];

return [KSCrashReportDictionary reportWithValue:crashReport];
}
Expand Down
49 changes: 49 additions & 0 deletions Tests/KSCrashFiltersTests/KSCrashReportFilterDoctor_Tests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#import <XCTest/XCTest.h>

#import "KSCrashReport.h"
#import "KSCrashReportFields.h"
#import "KSCrashReportFilterDoctor.h"
#import "KSTestModuleConfig.h"

@interface KSCrashDoctor_Tests : XCTestCase
@end

@implementation KSCrashDoctor_Tests

- (KSCrashReportDictionary *)_crashReportAsJSON:(NSString *)filename
{
NSURL *url = [KS_TEST_MODULE_BUNDLE URLForResource:filename withExtension:@"json"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSDictionary *reportDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
return [KSCrashReportDictionary reportWithValue:reportDict];
}

- (KSCrashReportDictionary *)_filteredReport:(KSCrashReportDictionary *)report
{
KSCrashReportDictionary *__block result = nil;
KSCrashReportFilterDoctor *filter = [KSCrashReportFilterDoctor new];
[filter filterReports:@[ report ]
onCompletion:^(NSArray<id<KSCrashReport>> *filteredReports, NSError *error) {
result = filteredReports.firstObject;
XCTAssertNil(error);
}];
return result;
}

- (void)testGracefulTermination
{
KSCrashReportDictionary *report = [self _crashReportAsJSON:@"sigterm"];
KSCrashReportDictionary *resultReport = [self _filteredReport:report];
NSString *diagnostic = resultReport.value[KSCrashField_Crash][KSCrashField_Diagnosis];
XCTAssertEqual(diagnostic, @"The OS request the app be gracefully terminated.");
}

- (void)testOOM
{
KSCrashReportDictionary *report = [self _crashReportAsJSON:@"oom"];
KSCrashReportDictionary *resultReport = [self _filteredReport:report];
NSString *diagnostic = resultReport.value[KSCrashField_Crash][KSCrashField_Diagnosis];
XCTAssertEqual(diagnostic, @"The app was terminated due to running out of memory (OOM).");
}

@end
40 changes: 40 additions & 0 deletions Tests/KSCrashFiltersTests/KSTestModuleConfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// KSTestModuleConfig.h
//
// Created by Nikolay Volosatov on 2024-09-08.
//
// Copyright (c) 2012 Karl Stenerud. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall remain in place
// in this source code.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

#ifndef KSTestModuleConfig_h
#define KSTestModuleConfig_h

#import <XCTest/XCTest.h>

#if !defined(KS_TEST_MODULE_BUNDLE)
#ifdef SWIFTPM_MODULE_BUNDLE
#define KS_TEST_MODULE_BUNDLE SWIFTPM_MODULE_BUNDLE
#else
#define KS_TEST_MODULE_BUNDLE ([NSBundle bundleForClass:[self class]])
#endif
#endif

#endif // KSTestModuleConfig_h
Loading

0 comments on commit 528d4ea

Please sign in to comment.