Skip to content

Commit

Permalink
Make all but one call to Crashlytics dynamic to avoid linking issues.
Browse files Browse the repository at this point in the history
TadeasKriz committed Jun 27, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 36b927b commit f35584a
Showing 20 changed files with 1,076 additions and 122 deletions.
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -7,30 +7,32 @@ import kotlinx.cinterop.convert

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


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.

121 changes: 120 additions & 1 deletion crashlytics/src/nativeInterop/cinterop/crashlytics.def
Original file line number Diff line number Diff line change
@@ -1,3 +1,122 @@
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 tryFIRCLSExceptionRecordNSException(NSException *exception) {
if (FIRCLSExceptionRecordNSException) {
FIRCLSExceptionRecordNSException(exception);
} else {
@throw [NSException
exceptionWithName:@"FIRFunctionNotFound"
reason:@"Function 'FIRCLSExceptionRecordNSException' not available, make sure you're adding the Firebase Crashlytics dependency."
userInfo: nil
];
}
}

#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;
}
}

id FIRExceptionModelWithNameAndReason(NSString* _Nonnull name, NSString* _Nonnull reason) {
LoadClass(FIRExceptionModel);
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) {
LoadClass(FIRStackFrame);
SEL selector = NSSelectorFromString(@"stackFrameWithAddress:");
id (*stackFrameWithAddress)(id, SEL, NSUInteger) = FIRMethodForSelector(objClass, selector);
return stackFrameWithAddress(objClass, selector, address);
}

id FIRCrashlyticsInstance(void) {
LoadClass(FIRCrashlytics);
SEL selector = NSSelectorFromString(@"crashlytics");
id (*crashlytics)(id, SEL) = FIRMethodForSelector(objClass, selector);
id instance = crashlytics(objClass, selector);
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);
}
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
138 changes: 138 additions & 0 deletions samples/sample-crashlytics/ios-spm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Created by https://www.gitignore.io/api/xcode,swift,objective-c
# Edit at https://www.gitignore.io/?templates=xcode,swift,objective-c

### Objective-C ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build

# fastlane
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/

### Objective-C Patch ###

### Swift ###
# Xcode
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore





## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
.build/
# Add this line if you want to avoid checking in Xcode SPM integration.
# .swiftpm/xcode

# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts


# Accio dependency management
Dependencies/
.accio/

# fastlane
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control


# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode


### Xcode ###
# Xcode
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)

## Xcode Patch
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno

### Xcode Patch ###
**/xcshareddata/WorkspaceSettings.xcsettings

# End of https://www.gitignore.io/api/xcode,swift,objective-c
455 changes: 455 additions & 0 deletions samples/sample-crashlytics/ios-spm/ios.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6381FF99277D0BE900EF27B0"
BuildableName = "ios.app"
BlueprintName = "ios"
ReferencedContainer = "container:ios.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6381FF99277D0BE900EF27B0"
BuildableName = "ios.app"
BlueprintName = "ios"
ReferencedContainer = "container:ios.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6381FF99277D0BE900EF27B0"
BuildableName = "ios.app"
BlueprintName = "ios"
ReferencedContainer = "container:ios.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
22 changes: 22 additions & 0 deletions samples/sample-crashlytics/ios-spm/ios/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2021 Touchlab
// 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 UIKit
import shared
import Firebase

class AppDelegate: NSObject, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
HelperKt.startCrashKiOS()

return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
76 changes: 76 additions & 0 deletions samples/sample-crashlytics/ios-spm/ios/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// ContentView.swift
// ios
//
// Created by Kevin Galligan on 12/29/21.
//

import SwiftUI
import shared

struct ContentView: View {

let common: SampleCommon
let cb = CrashBot()

init() {
self.common = SampleCommon()
}

var body: some View {
VStack(spacing: 50){
Button(action: {
self.common.setUserId(identifier: "123")
}){
Text("Set User Id").padding()
.background(Color.blue)
.foregroundColor(.white)
.font(.title)
}
Button(action: {
self.common.onClick()
}){
Text("Click Count").padding()
.background(Color.blue)
.foregroundColor(.white)
.font(.title)
}
Button(action: {
self.common.logException()
}){
Text("Log Exception").padding()
.background(Color.blue)
.foregroundColor(.white)
.font(.title)
}
Button(action: {
self.cb.goCrash()
}){
Text("Kotlin Crash").padding()
.background(Color.blue)
.foregroundColor(.white)
.font(.title)
}

Button(action: {
realCrash()
}){
Text("Swift Crash").padding()
.background(Color.blue)
.foregroundColor(.white)
.font(.title)
}
}
}
}

func realCrash() {
let numbers = [0]
let _ = numbers[1]
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
30 changes: 30 additions & 0 deletions samples/sample-crashlytics/ios-spm/ios/GoogleService-Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyAEDOEwsd0-F3kg5QbOGqc9fNPsF5Ergbs</string>
<key>GCM_SENDER_ID</key>
<string>262996390724</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>co.touchlab.crashkios.sample</string>
<key>PROJECT_ID</key>
<string>crashkiossample</string>
<key>STORAGE_BUCKET</key>
<string>crashkiossample.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:262996390724:ios:7a848afa4b4ca8448add11</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
20 changes: 20 additions & 0 deletions samples/sample-crashlytics/ios-spm/ios/iosApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// iosApp.swift
// ios
//
// Created by Kevin Galligan on 12/29/21.
//

import SwiftUI

@main
struct iosApp: App {

@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

var body: some Scene {
WindowGroup {
ContentView()
}
}
}
4 changes: 3 additions & 1 deletion samples/sample-crashlytics/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -22,6 +22,8 @@ includeBuild("../..") {
dependencySubstitution {
substitute(module("co.touchlab.crashkios:crashlytics"))
.using(project(":crashlytics")).because("we want to auto-wire up sample dependency")
substitute(module("co.touchlab.crashkios.crashlyticslink:co.touchlab.crashkios.crashlyticslink.gradle.plugin"))
.using(project(":crashlytics-ios-link")).because("we want to auto-wire up sample dependency")
}
}

@@ -32,4 +34,4 @@ pluginManagement {
mavenCentral()
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
}
}

0 comments on commit f35584a

Please sign in to comment.