Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tk/dynamic crashlytics #71

Merged
merged 3 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ internal val Project.kotlinExtension: KotlinMultiplatformExtension get() = exten
class CrashlyticsLinkPlugin : Plugin<Project> {

override fun apply(project: Project): Unit = project.withKotlinMultiplatformPlugin {
val linkerArgs = "-U _FIRCLSExceptionRecordNSException " +
"-U _OBJC_CLASS_\$_FIRStackFrame " +
"-U _OBJC_CLASS_\$_FIRExceptionModel " +
"-U _OBJC_CLASS_\$_FIRCrashlytics"
val linkerArgs = "-U _FIRCLSExceptionRecordNSException"
afterEvaluate {
project.kotlinExtension.crashLinkerConfig(linkerArgs)
project.kotlinArtifactsExtension.crashLinkerConfigArtifacts(linkerArgs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,35 @@ import kotlinx.cinterop.convert

@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
actual class CrashlyticsCallsActual : CrashlyticsCalls {

init {
FIRCheckLinkDependencies()
}

override fun logMessage(message: String) {
FIRCrashlytics.crashlytics().log(message)
FIRCrashlyticsLog(message)
}

@OptIn(UnsafeNumber::class)
override fun sendHandledException(throwable: Throwable) {
val exceptionClassName = throwable::class.qualifiedName
val exModel = FIRExceptionModel.exceptionModelWithName(exceptionClassName, throwable.message)!!
exModel.setStackTrace(throwable.getFilteredStackTraceAddresses().map { FIRStackFrame.stackFrameWithAddress(it.convert()) })
FIRCrashlytics.crashlytics().recordExceptionModel(exModel)
val exceptionClassName = throwable::class.qualifiedName ?: throwable::class.simpleName ?: "kotlin.Throwable"
FIRCrashlyticsRecordHandledException(exceptionClassName, throwable.message ?: "", throwable.getFilteredStackTraceAddresses().map {
FIRStackFrameWithAddress(it.convert())
})
}

override fun sendFatalException(throwable: Throwable) {
val exception = throwable.asNSException(true)
// The recorded exception is persisted, so we can safely terminate afterwards.
// https://github.com/firebase/firebase-ios-sdk/blob/82f163bd86566f83c5d7572a1c2c0024a04eb4dc/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm#L227
FIRCLSExceptionRecordNSException(exception)
tryFIRCLSExceptionRecordNSException(exception)
}

override fun setCustomValue(key: String, value: Any) {
FIRCrashlytics.crashlytics().setCustomValue(value, key)
FIRCrashlyticsSetCustomValue(key, value)
}

override fun setUserId(identifier: String) {
FIRCrashlytics.crashlytics().setUserID(identifier)
FIRCrashlyticsSetUserID(identifier)
}
}
}
31 changes: 0 additions & 31 deletions crashlytics/src/include/FIRCrashlytics.h

This file was deleted.

29 changes: 0 additions & 29 deletions crashlytics/src/include/FIRExceptionModel.h

This file was deleted.

25 changes: 0 additions & 25 deletions crashlytics/src/include/FIRStackFrame.h

This file was deleted.

21 changes: 0 additions & 21 deletions crashlytics/src/include/Private/FIRCLSException.h

This file was deleted.

152 changes: 151 additions & 1 deletion crashlytics/src/nativeInterop/cinterop/crashlytics.def
Original file line number Diff line number Diff line change
@@ -1,3 +1,153 @@
package = co.touchlab.crashkios.crashlytics
language = Objective-C
headers = FIRCrashlytics.h FIRExceptionModel.h FIRStackFrame.h Private/FIRCLSException.h

---

#import <Foundation/Foundation.h>

extern void FIRCLSExceptionRecordNSException(NSException *exception) __attribute__((weak));

void __verifyFIRCLSExceptionRecordNSExceptionExists(void) {
if (!FIRCLSExceptionRecordNSException) {
@throw [NSException
exceptionWithName:@"FIRFunctionNotFound"
reason:@"Function 'FIRCLSExceptionRecordNSException' not available, make sure you're adding the Firebase Crashlytics dependency."
userInfo: nil
];
}
}

void tryFIRCLSExceptionRecordNSException(NSException *exception) {
__verifyFIRCLSExceptionRecordNSExceptionExists();
FIRCLSExceptionRecordNSException(exception);
}

#define LoadClass(name)\
static Class objClass;\
if (!objClass) {\
objClass = NSClassFromString(@OS_STRINGIFY(name));\
if (!objClass) {\
@throw [NSException\
exceptionWithName:@"FIRClassNotFound"\
reason: [NSString\
stringWithFormat:@"Class '%@' not available, make sure you're adding the Firebase Crashlytics dependency.",\
@OS_STRINGIFY(name)\
]\
userInfo: nil\
];\
}\
}

void* FIRMethodForSelector(id _Nonnull target, SEL _Nonnull selector) {
IMP method = [target methodForSelector:selector];
if (!method) {
@throw [NSException
exceptionWithName:@"FIRMethodNotFound"
reason: [NSString
stringWithFormat:@"Target '%@' returned nil method for selector '%@'. This is probably a bug in CrashKiOS",
target,
NSStringFromSelector(selector)
]
userInfo: nil
];
} else {
return method;
}
}

Class FIRExceptionModelClass(void) {
LoadClass(FIRExceptionModel);
return objClass;
}

Class FIRStackFrameClass(void) {
LoadClass(FIRStackFrame);
return objClass;
}

Class FIRCrashlyticsClass(void) {
LoadClass(FIRCrashlytics);
return objClass;
}

id FIRExceptionModelWithNameAndReason(NSString* _Nonnull name, NSString* _Nonnull reason) {
Class objClass = FIRExceptionModelClass();
SEL selector = NSSelectorFromString(@"exceptionModelWithName:reason:");
id (*exceptionModelWithNameAndReason)(id, SEL, NSString*, NSString*) = FIRMethodForSelector(objClass, selector);
return exceptionModelWithNameAndReason(objClass, selector, name, reason);
}

void FIRExceptionModelSetStackTrace(id exceptionModel, NSArray<id>* _Nonnull stackFrames) {
SEL selector = NSSelectorFromString(@"setStackTrace:");
void (*setStackTrace)(id, SEL, NSArray<id>*) = FIRMethodForSelector(exceptionModel, selector);
setStackTrace(exceptionModel, selector, stackFrames);
}

id FIRStackFrameWithAddress(NSUInteger address) {
Class objClass = FIRStackFrameClass();
SEL selector = NSSelectorFromString(@"stackFrameWithAddress:");
id (*stackFrameWithAddress)(id, SEL, NSUInteger) = FIRMethodForSelector(objClass, selector);
return stackFrameWithAddress(objClass, selector, address);
}

id _Nullable FIRCrashlyticsInstanceOrNull(void) {
Class objClass = FIRCrashlyticsClass();
SEL selector = NSSelectorFromString(@"crashlytics");
id (*crashlytics)(id, SEL) = FIRMethodForSelector(objClass, selector);
return crashlytics(objClass, selector);
}

id _Nonnull FIRCrashlyticsInstance(void) {
id instance = FIRCrashlyticsInstanceOrNull();
if (instance) {
return instance;
} else {
@throw [NSException
exceptionWithName:@"FIRCrashlyticsNil"
reason: @"[FirCrashlytics crashlytics] returned nil. Make sure you initialize Firebase before using it."
userInfo: nil
];
}
}

void FIRCrashlyticsRecordExceptionModel(id crashlytics, id exceptionModel) {
SEL selector = NSSelectorFromString(@"recordExceptionModel:");
void (*recordExceptionModel)(id, SEL, id) = FIRMethodForSelector(crashlytics, selector);
recordExceptionModel(crashlytics, selector, exceptionModel);
}

void FIRCrashlyticsRecordHandledException(NSString* _Nonnull name, NSString* _Nonnull reason, NSArray<id>* _Nonnull stackFrames) {
id exceptionModel = FIRExceptionModelWithNameAndReason(name, reason);
FIRExceptionModelSetStackTrace(exceptionModel, stackFrames);
FIRCrashlyticsRecordExceptionModel(FIRCrashlyticsInstance(), exceptionModel);
}

void FIRCrashlyticsLog(NSString* _Nonnull message) {
id crashlytics = FIRCrashlyticsInstance();
SEL selector = NSSelectorFromString(@"log:");
void (*log)(id, SEL, NSString* _Nonnull) = FIRMethodForSelector(crashlytics, selector);
log(crashlytics, selector, message);
}

void FIRCrashlyticsSetUserID(NSString* _Nonnull identifier) {
id crashlytics = FIRCrashlyticsInstance();
SEL selector = NSSelectorFromString(@"setUserID:");
void (*setUserID)(id, SEL, NSString* _Nonnull) = FIRMethodForSelector(crashlytics, selector);
setUserID(crashlytics, selector, identifier);
}

void FIRCrashlyticsSetCustomValue(NSString* _Nonnull key, id __nullable value) {
id crashlytics = FIRCrashlyticsInstance();
SEL selector = NSSelectorFromString(@"setCustomValue:forKey:");
void (*setCustomValueForKey)(id, SEL, id __nullable, NSString* _Nonnull) = FIRMethodForSelector(crashlytics, selector);
setCustomValueForKey(crashlytics, selector, value, key);
}

void FIRCheckLinkDependencies(void) {
__verifyFIRCLSExceptionRecordNSExceptionExists();

// Load classes to verify we have them all
FIRExceptionModelClass();
FIRStackFrameClass();
FIRCrashlyticsClass();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
6 changes: 5 additions & 1 deletion samples/sample-bugsnag/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ pluginManagement {
mavenCentral()
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
}
}

plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.8.0")
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading