From 1c44dd11b70393c11eb31deb93a273686008b953 Mon Sep 17 00:00:00 2001 From: YingYing Chen <40571804+YYChen01988@users.noreply.github.com> Date: Tue, 16 Apr 2024 10:13:14 +0100 Subject: [PATCH 01/23] fix(test) added more end to end test for validated all the changeable fields in an error callback (#2010) --- .../jvm-scenarios/detekt-baseline.xml | 2 + ...ndledExceptionEventDetailChangeScenario.kt | 86 +++++++++++++++++++ .../error_callback_alters_fields.feature | 59 +++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/UnhandledExceptionEventDetailChangeScenario.kt create mode 100644 features/full_tests/error_callback_alters_fields.feature diff --git a/features/fixtures/mazerunner/jvm-scenarios/detekt-baseline.xml b/features/fixtures/mazerunner/jvm-scenarios/detekt-baseline.xml index 1fcda340f9..d18bb02cbd 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/detekt-baseline.xml +++ b/features/fixtures/mazerunner/jvm-scenarios/detekt-baseline.xml @@ -24,6 +24,8 @@ MagicNumber:StartupCrashFlushScenario.kt$StartupCrashFlushScenario$6000 MagicNumber:TestHarnessHooks.kt$<no name provided>$500 MagicNumber:TrimmedStacktraceScenario.kt$TrimmedStacktraceScenario$100000 + MagicNumber:UnhandledExceptionEventDetailChangeScenario.kt$UnhandledExceptionEventDetailChangeScenario$123 + MagicNumber:UnhandledExceptionEventDetailChangeScenario.kt$UnhandledExceptionEventDetailChangeScenario$123456 ThrowingExceptionsWithoutMessageOrCause:AnrHelper.kt$<no name provided>$IllegalStateException() ThrowingExceptionsWithoutMessageOrCause:BugsnagInitScenario.kt$BugsnagInitScenario$RuntimeException() ThrowingExceptionsWithoutMessageOrCause:CustomPluginNotifierDescriptionScenario.kt$CustomPluginNotifierDescriptionScenario$RuntimeException() diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/UnhandledExceptionEventDetailChangeScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/UnhandledExceptionEventDetailChangeScenario.kt new file mode 100644 index 0000000000..fdacbf4c3e --- /dev/null +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/UnhandledExceptionEventDetailChangeScenario.kt @@ -0,0 +1,86 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.BreadcrumbType +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import com.bugsnag.android.OnErrorCallback +import com.bugsnag.android.Severity + +/** + * Sends an unhandled exception to Bugsnag where the event details are changed in a callback + */ +internal class UnhandledExceptionEventDetailChangeScenario( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + init { + + config.addOnError( + OnErrorCallback { event -> + event.apiKey = "0000111122223333aaaabbbbcccc9999" + event.severity = Severity.ERROR + event.groupingHash = "groupingHash1" + event.context = "new-context" + event.setUser("abc", "joe@test.com", "Joe") + + event.clearMetadata("custom_data1") + event.clearMetadata("custom_data2", "data") + event.addMetadata("custom_data2", "test_data", "this is test") + + event.clearFeatureFlag("test1") + event.addFeatureFlag("beta", "b") + event.addFeatureFlag("gamma") + + event.isUnhandled = false + event.app.binaryArch = "x86" + event.app.id = "12345" + event.app.releaseStage = "custom" + event.app.version = "1.2.3" + event.app.buildUuid = "12345678" + event.app.type = "android_custom" + event.app.versionCode = 123 + event.app.duration = 123456 + event.app.durationInForeground = 123456 + event.app.inForeground = false + event.app.isLaunching = false + + event.device.id = "12345" + event.device.jailbroken = true + event.device.locale = "en-UK" + event.device.totalMemory = 123456 + event.device.runtimeVersions = mutableMapOf("androidApiLevel" to "30") + event.device.freeDisk = 123456 + event.device.freeMemory = 123456 + event.device.orientation = "portrait" + + event.breadcrumbs.removeLast() + event.breadcrumbs.first().type = BreadcrumbType.ERROR + event.breadcrumbs.first().message = "new breadcrumb message" + event.breadcrumbs[1].type = BreadcrumbType.ERROR + event.breadcrumbs[1].message = "Second breadcrumb message" + event.breadcrumbs.first().metadata = mapOf("foo" to "data") + true + } + ) + } + + override fun startScenario() { + super.startScenario() + Bugsnag.leaveBreadcrumb("Hello1") + Bugsnag.leaveBreadcrumb("Hello2") + Bugsnag.leaveBreadcrumb("Hello3") + + Bugsnag.addMetadata("custom_data1", "data", "hello") + Bugsnag.addMetadata("custom_data2", "data", "hello") + Bugsnag.addMetadata("custom_data3", "test data", "divert all available power to the crash reporter") + + Bugsnag.addFeatureFlag("test1") + Bugsnag.addFeatureFlag("test2") + + Bugsnag.notify(RuntimeException("UnhandledExceptionEventDetailChangeScenario")) + throw NullPointerException("something broke") + } +} diff --git a/features/full_tests/error_callback_alters_fields.feature b/features/full_tests/error_callback_alters_fields.feature new file mode 100644 index 0000000000..2748be9376 --- /dev/null +++ b/features/full_tests/error_callback_alters_fields.feature @@ -0,0 +1,59 @@ +Feature: When the api key is altered in an Event the JSON payload reflects this + + Background: + Given I clear all persistent data + + Scenario: Unhandled exception with altered event details + When I run "UnhandledExceptionEventDetailChangeScenario" and relaunch the crashed app + And I configure Bugsnag for "UnhandledExceptionApiKeyChangeScenario" + And I wait to receive an error + And the error payload field "events" is an array with 1 elements + And the exception "message" equals "UnhandledExceptionEventDetailChangeScenario" + And the error payload field "apiKey" equals "0000111122223333aaaabbbbcccc9999" + And the error "Bugsnag-Api-Key" header equals "0000111122223333aaaabbbbcccc9999" + And the event "severity" equals "error" + And the event "context" equals "new-context" + And the event "groupingHash" equals "groupingHash1" + And the event "user.id" equals "abc" + And the event "user.email" equals "joe@test.com" + And the event "user.name" equals "Joe" + And the event "metaData.custom_data2.test_data" equals "this is test" + And the event "metaData.custom_data1" is null + And the event "metaData.custom_data2.data" is null + And the event "metaData.custom_data3.test data" equals "divert all available power to the crash reporter" + And event 0 contains the feature flag "beta" with variant "b" + And event 0 does not contain the feature flag "alpha" + And event 0 contains the feature flag "gamma" with no variant + And event 0 does not contain the feature flag "test1" + And event 0 contains the feature flag "test2" with no variant + + # app fields + And the event "unhandled" is false + And the event "app.binaryArch" equals "x86" + And the event "app.id" equals "12345" + And the event "app.releaseStage" equals "custom" + And the event "app.version" equals "1.2.3" + And the event "app.buildUUID" equals "12345678" + And the event "app.type" equals "android_custom" + And the event "app.versionCode" equals 123 + And the event "app.duration" equals 123456 + And the event "app.durationInForeground" equals 123456 + And the event "app.inForeground" is false + And the event "app.isLaunching" is false + + # device fields + And the event "device.id" equals "12345" + And the event "device.jailbroken" is true + And the event "device.locale" equals "en-UK" + And the event "device.totalMemory" equals 123456 + And the event "device.runtimeVersions.androidApiLevel" equals "30" + And the event "device.freeDisk" equals 123456 + And the event "device.freeMemory" equals 123456 + And the event "device.orientation" equals "portrait" + + # breadcrumbs fields + And the event "breadcrumbs.0.type" equals "error" + And the event "breadcrumbs.0.name" equals "new breadcrumb message" + And the event "breadcrumbs.0.metaData.foo" equals "data" + And the event "breadcrumbs.1.type" equals "error" + And the event "breadcrumbs.1.name" equals "Second breadcrumb message" \ No newline at end of file From 55311962eaea3cab0741008a4d21e10f84e6bffe Mon Sep 17 00:00:00 2001 From: YingYing Chen <40571804+YYChen01988@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:35:25 +0100 Subject: [PATCH 02/23] Reduce use of Atomics in Session (#2013) * feat(session) Change autoCaptured type from AtomicBoolean to volatile boolean since it does not use specific atomic method * feat(session) Make isPaused private --- .../java/com/bugsnag/android/Session.java | 28 +++++++++++++------ .../com/bugsnag/android/SessionTracker.java | 8 +++--- .../java/com/bugsnag/android/SessionTest.kt | 5 +++- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Session.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Session.java index 408d6fc0f7..5081ad22e7 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Session.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Session.java @@ -28,11 +28,11 @@ public final class Session implements JsonStream.Streamable, UserAware { private App app; private Device device; - private final AtomicBoolean autoCaptured = new AtomicBoolean(false); + private volatile boolean autoCaptured = false; private final AtomicInteger unhandledCount = new AtomicInteger(); private final AtomicInteger handledCount = new AtomicInteger(); private final AtomicBoolean tracked = new AtomicBoolean(false); - final AtomicBoolean isPaused = new AtomicBoolean(false); + private final AtomicBoolean isPaused = new AtomicBoolean(false); private String apiKey; @@ -41,7 +41,7 @@ static Session copySession(Session session) { session.unhandledCount.get(), session.handledCount.get(), session.notifier, session.logger, session.getApiKey()); copy.tracked.set(session.tracked.get()); - copy.autoCaptured.set(session.isAutoCaptured()); + copy.autoCaptured = session.isAutoCaptured(); return copy; } @@ -68,7 +68,7 @@ static Session copySession(Session session) { this.id = id; this.startedAt = new Date(startedAt.getTime()); this.user = user; - this.autoCaptured.set(autoCaptured); + this.autoCaptured = autoCaptured; this.apiKey = apiKey; } @@ -198,16 +198,28 @@ Session incrementUnhandledAndCopy() { return copySession(this); } - AtomicBoolean isTracked() { - return tracked; + boolean markTracked() { + return tracked.compareAndSet(false, true); + } + + boolean markResumed() { + return isPaused.compareAndSet(true, false); + } + + void markPaused() { + isPaused.set(true); + } + + boolean isPaused() { + return isPaused.get(); } boolean isAutoCaptured() { - return autoCaptured.get(); + return autoCaptured; } void setAutoCaptured(boolean autoCaptured) { - this.autoCaptured.set(autoCaptured); + this.autoCaptured = autoCaptured; } /** diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java index e9e15b2a2e..9fc2747f5b 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java @@ -120,7 +120,7 @@ void pauseSession() { Session session = currentSession; if (session != null) { - session.isPaused.set(true); + session.markPaused(); updateState(StateEvent.PauseSession.INSTANCE); } } @@ -133,7 +133,7 @@ boolean resumeSession() { session = startSession(false); resumed = false; } else { - resumed = session.isPaused.compareAndSet(true, false); + resumed = session.markResumed(); } if (session != null) { @@ -191,7 +191,7 @@ private boolean trackSessionIfNeeded(final Session session) { session.setDevice(client.getDeviceDataCollector().generateDevice()); boolean deliverSession = callbackState.runOnSessionTasks(session, logger); - if (deliverSession && session.isTracked().compareAndSet(false, true)) { + if (deliverSession && session.markTracked()) { currentSession = session; notifySessionStartObserver(session); flushInMemorySession(session); @@ -205,7 +205,7 @@ private boolean trackSessionIfNeeded(final Session session) { Session getCurrentSession() { Session session = currentSession; - if (session != null && !session.isPaused.get()) { + if (session != null && !session.isPaused()) { return session; } return null; diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTest.kt index 73f80449c0..ae7c0f6fcd 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTest.kt @@ -141,7 +141,10 @@ class SessionTest { assertEquals(startedAt, copy.startedAt) assertEquals(getUser(), copy.getUser()) // make a shallow copy assertEquals(isAutoCaptured, copy.isAutoCaptured) - assertEquals(isTracked.get(), copy.isTracked.get()) + assertEquals(markTracked(), copy.markTracked()) + assertEquals(markResumed(), copy.markResumed()) + assertEquals(isPaused, copy.isPaused) + assertEquals(markPaused(), copy.markPaused()) assertEquals(session.unhandledCount, copy.unhandledCount) assertEquals(session.handledCount, copy.handledCount) } From f1753a4cfb00c6243b4a24ad109ae4838206e347 Mon Sep 17 00:00:00 2001 From: YingYing Chen <40571804+YYChen01988@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:13:33 +0100 Subject: [PATCH 03/23] fix(gradle) implementation, plugins and ndk version updated (#2015) --- examples/sdk-app-example/app/build.gradle | 11 ++++++----- .../sdk-app-example/app/src/main/AndroidManifest.xml | 3 +-- examples/sdk-app-example/build.gradle | 4 ++-- examples/sdk-app-example/gradle.properties | 3 +++ .../gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/sdk-app-example/app/build.gradle b/examples/sdk-app-example/app/build.gradle index d03a5b6c74..9b25b2fd0c 100644 --- a/examples/sdk-app-example/app/build.gradle +++ b/examples/sdk-app-example/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: "com.android.application" apply plugin: "kotlin-android" android { - compileSdk 31 + compileSdk 34 ndkVersion "21.4.7075529" defaultConfig { applicationId "com.example.bugsnag.android" minSdkVersion 16 - targetSdkVersion 31 + targetSdkVersion 34 versionCode 1 versionName "1.0" } @@ -38,15 +38,16 @@ android { buildFeatures.prefab = true packagingOptions.jniLibs.pickFirsts += ["**/libbugsnag-ndk.so"] + namespace 'com.example.bugsnag.android' } dependencies { implementation "com.bugsnag:bugsnag-android:6.4.0" implementation "com.bugsnag:bugsnag-plugin-android-okhttp:6.4.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "androidx.appcompat:appcompat:1.4.0" - implementation "com.google.android.material:material:1.4.0" - implementation "com.squareup.okhttp3:okhttp:4.10.0" + implementation "androidx.appcompat:appcompat:1.6.1" + implementation "com.google.android.material:material:1.11.0" + implementation "com.squareup.okhttp3:okhttp:4.12.0" } apply plugin: "com.bugsnag.android.gradle" diff --git a/examples/sdk-app-example/app/src/main/AndroidManifest.xml b/examples/sdk-app-example/app/src/main/AndroidManifest.xml index 9e3bd6979b..5017380f20 100644 --- a/examples/sdk-app-example/app/src/main/AndroidManifest.xml +++ b/examples/sdk-app-example/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/examples/sdk-app-example/build.gradle b/examples/sdk-app-example/build.gradle index bd8394d830..bf66353fdf 100644 --- a/examples/sdk-app-example/build.gradle +++ b/examples/sdk-app-example/build.gradle @@ -6,9 +6,9 @@ buildscript { mavenLocal() } dependencies { - classpath "com.android.tools.build:gradle:7.4.2" + classpath "com.android.tools.build:gradle:8.3.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "com.bugsnag:bugsnag-android-gradle-plugin:7.1.0" + classpath "com.bugsnag:bugsnag-android-gradle-plugin:8.+" } } diff --git a/examples/sdk-app-example/gradle.properties b/examples/sdk-app-example/gradle.properties index b7aa9d48f5..fa41b5bf20 100644 --- a/examples/sdk-app-example/gradle.properties +++ b/examples/sdk-app-example/gradle.properties @@ -2,3 +2,6 @@ android.useAndroidX=true org.gradle.jvmargs=-Xmx4096m org.gradle.parallel=true kotlin.code.style=official +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/examples/sdk-app-example/gradle/wrapper/gradle-wrapper.properties b/examples/sdk-app-example/gradle/wrapper/gradle-wrapper.properties index 068cdb2dc2..e411586a54 100644 --- a/examples/sdk-app-example/gradle/wrapper/gradle-wrapper.properties +++ b/examples/sdk-app-example/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 9f23de0332626dfe6d11b106cc4ac34af04664a3 Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 16 Apr 2024 11:39:59 +0100 Subject: [PATCH 04/23] feat(ndk): changed the breadcrumb-type transfer type from a string to an int --- .../com/bugsnag/android/ndk/NativeBridge.kt | 38 +++++++++++++++++- .../src/main/jni/bugsnag_ndk.c | 40 ++++++++++++------- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt index 3ff0b0e87e..988176b230 100644 --- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt +++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt @@ -1,6 +1,7 @@ package com.bugsnag.android.ndk import android.os.Build +import com.bugsnag.android.BreadcrumbType import com.bugsnag.android.NativeInterface import com.bugsnag.android.StateEvent import com.bugsnag.android.StateEvent.AddBreadcrumb @@ -62,7 +63,15 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse ) external fun deliverReportAtPath(filePath: String) - external fun addBreadcrumb(name: String, type: String, timestamp: String, metadata: Any) + fun addBreadcrumb(name: String, type: String, timestamp: String, metadata: Any) { + val breadcrumbType = BreadcrumbType.values() + .find { it.toString() == type } + ?: BreadcrumbType.MANUAL + + addBreadcrumb(name, breadcrumbType.toNativeValue(), timestamp, metadata) + } + + private external fun addBreadcrumb(name: String, type: Int, timestamp: String, metadata: Any) external fun addMetadataString(tab: String, key: String, value: String) external fun addMetadataDouble(tab: String, key: String, value: Double) external fun addMetadataBoolean(tab: String, key: String, value: Boolean) @@ -106,12 +115,14 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse event.section, event.key ?: "" ) + is AddBreadcrumb -> addBreadcrumb( event.message, - event.type.toString(), + event.type.toNativeValue(), event.timestamp, makeSafeMetadata(event.metadata) ) + NotifyHandled -> addHandledEvent() NotifyUnhandled -> addUnhandledEvent() PauseSession -> pausedSession() @@ -121,11 +132,13 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse event.handledCount, event.unhandledCount ) + is UpdateContext -> updateContext(event.context ?: "") is UpdateInForeground -> updateInForeground( event.inForeground, event.contextActivity ?: "" ) + is StateEvent.UpdateLastRunInfo -> updateLastRunInfo(event.consecutiveLaunchCrashes) is StateEvent.UpdateIsLaunching -> { updateIsLaunching(event.isLaunching) @@ -135,20 +148,24 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse bgTaskService.submitTask(TaskType.DEFAULT, this::refreshSymbolTable) } } + is UpdateOrientation -> updateOrientation(event.orientation ?: "") is UpdateUser -> { updateUserId(event.user.id ?: "") updateUserName(event.user.name ?: "") updateUserEmail(event.user.email ?: "") } + is StateEvent.UpdateMemoryTrimEvent -> updateLowMemory( event.isLowMemory, event.memoryTrimLevelDescription ) + is StateEvent.AddFeatureFlag -> addFeatureFlag( event.name, event.variant ) + is StateEvent.ClearFeatureFlag -> clearFeatureFlag(event.name) is StateEvent.ClearFeatureFlags -> clearFeatureFlags() } @@ -227,4 +244,21 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse } } } + + /** + * Convert a [BreadcrumbType] to the value expected by the [addBreadcrumb] implementation. This + * is implemented as an exhaustive when so that any changes to the `enum` are picked up and/or + * don't directly impact the implementation. + */ + @Suppress("MagicNumber") // introducing consts would reduce readability + private fun BreadcrumbType.toNativeValue(): Int = when (this) { + BreadcrumbType.ERROR -> 0 + BreadcrumbType.LOG -> 1 + BreadcrumbType.MANUAL -> 2 + BreadcrumbType.NAVIGATION -> 3 + BreadcrumbType.PROCESS -> 4 + BreadcrumbType.REQUEST -> 5 + BreadcrumbType.STATE -> 6 + BreadcrumbType.USER -> 7 + } } diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c index e2fbd419b4..207a254d39 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c @@ -387,7 +387,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_pausedSession( } JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb( - JNIEnv *env, jobject _this, jstring name_, jstring crumb_type, + JNIEnv *env, jobject _this, jstring name_, jint crumb_type, jstring timestamp_, jobject metadata) { if (!bsg_jni_cache->initialized) { @@ -395,28 +395,41 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb( return; } const char *name = bsg_safe_get_string_utf_chars(env, name_); - const char *type = bsg_safe_get_string_utf_chars(env, crumb_type); const char *timestamp = bsg_safe_get_string_utf_chars(env, timestamp_); - if (name != NULL && type != NULL && timestamp != NULL) { + if (name != NULL && timestamp != NULL) { bugsnag_breadcrumb *crumb = calloc(1, sizeof(bugsnag_breadcrumb)); bsg_strncpy(crumb->name, name, sizeof(crumb->name)); bsg_strncpy(crumb->timestamp, timestamp, sizeof(crumb->timestamp)); - if (strcmp(type, "user") == 0) { - crumb->type = BSG_CRUMB_USER; - } else if (strcmp(type, "error") == 0) { + + // the values of crumb_type are defined in + // NativeBridge.BreadcrumbType.toNativeValue() + switch (crumb_type) { + case 0: crumb->type = BSG_CRUMB_ERROR; - } else if (strcmp(type, "log") == 0) { + break; + case 1: crumb->type = BSG_CRUMB_LOG; - } else if (strcmp(type, "navigation") == 0) { + break; + case 2: + crumb->type = BSG_CRUMB_MANUAL; + break; + case 3: crumb->type = BSG_CRUMB_NAVIGATION; - } else if (strcmp(type, "request") == 0) { + break; + case 4: + crumb->type = BSG_CRUMB_PROCESS; + break; + case 5: crumb->type = BSG_CRUMB_REQUEST; - } else if (strcmp(type, "state") == 0) { + break; + case 6: crumb->type = BSG_CRUMB_STATE; - } else if (strcmp(type, "process") == 0) { - crumb->type = BSG_CRUMB_PROCESS; - } else { + break; + case 7: + crumb->type = BSG_CRUMB_USER; + break; + default: crumb->type = BSG_CRUMB_MANUAL; } @@ -428,7 +441,6 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb( free(crumb); } bsg_safe_release_string_utf_chars(env, name_, name); - bsg_safe_release_string_utf_chars(env, crumb_type, type); bsg_safe_release_string_utf_chars(env, timestamp_, timestamp); } From 16c3a6e14cd947972b9a1e0a0f1a637a6cd55a8d Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 22 Apr 2024 16:45:11 +0100 Subject: [PATCH 05/23] feat(ndk): create NDK breadcrumb buffer dynamically instead of as a static part of the `bugsnag_event` struct --- CHANGELOG.md | 7 ++++ Makefile | 2 +- .../api/bugsnag-android-core.api | 3 +- bugsnag-android-core/detekt-baseline.xml | 2 +- .../com/bugsnag/android/ClientObservable.kt | 3 +- .../java/com/bugsnag/android/StateEvent.kt | 3 +- .../api/bugsnag-plugin-android-ndk.api | 2 +- .../detekt-baseline.xml | 2 +- .../com/bugsnag/android/ndk/NativeBridge.kt | 6 ++- .../src/main/jni/bugsnag_ndk.c | 10 ++++- .../src/main/jni/event.c | 4 +- .../src/main/jni/event.h | 12 ++---- .../main/jni/utils/serializer/event_reader.c | 37 +++++++++++++++++-- .../main/jni/utils/serializer/event_writer.c | 11 +++++- .../main/jni/utils/serializer/json_writer.c | 2 +- .../src/test/cpp/main.c | 2 + .../src/test/cpp/test_breadcrumbs.c | 2 +- .../src/test/cpp/test_serializer.c | 6 +-- 18 files changed, 87 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cecf42f66..6984e8da91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## TBD + +### Enhancements + +* `Configuration.maxBreadcrumbs` is now obeyed by `bugsnag-plugin-android-ndk`, so native events will include the correct number of breadcrumbs + []() + ## 6.4.0 (2024-04-15) ### Enhancements diff --git a/Makefile b/Makefile index 42696fe2cf..0e5bba7b8f 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ fixture-debug: notifier example-app: @./gradlew assembleRelease publishToMavenLocal -x check # Build Android example app - @./examples/sdk-app-example/gradlew clean assembleRelease + @cd ./examples/sdk-app-example/ && ./gradlew clean assembleRelease bump: ifneq ($(shell git diff --staged),) diff --git a/bugsnag-android-core/api/bugsnag-android-core.api b/bugsnag-android-core/api/bugsnag-android-core.api index 10b89831f3..ecca487084 100644 --- a/bugsnag-android-core/api/bugsnag-android-core.api +++ b/bugsnag-android-core/api/bugsnag-android-core.api @@ -687,9 +687,10 @@ public final class com/bugsnag/android/StateEvent$Install : com/bugsnag/android/ public final field buildUuid Ljava/lang/String; public final field consecutiveLaunchCrashes I public final field lastRunInfoPath Ljava/lang/String; + public final field maxBreadcrumbs I public final field releaseStage Ljava/lang/String; public final field sendThreads Lcom/bugsnag/android/ThreadSendPolicy; - public fun (Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/bugsnag/android/ThreadSendPolicy;)V + public fun (Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/bugsnag/android/ThreadSendPolicy;I)V } public final class com/bugsnag/android/StateEvent$NotifyHandled : com/bugsnag/android/StateEvent { diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index 1e44fd786f..a5e99e257b 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -18,7 +18,7 @@ LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, logger: Logger, breadcrumbs: MutableList<Breadcrumb> = mutableListOf(), discardClasses: Set<Pattern> = setOf(), errors: MutableList<Error> = mutableListOf(), metadata: Metadata = Metadata(), featureFlags: FeatureFlags = FeatureFlags(), originalError: Throwable? = null, projectPackages: Collection<String> = setOf(), severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION), threads: MutableList<Thread> = mutableListOf(), user: User = User(), redactionKeys: Set<Pattern>? = null ) LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier, callbackState: CallbackState ) LongParameterList:NativeStackframe.kt$NativeStackframe$( /** * The name of the method that was being executed */ var method: String?, /** * The location of the source file */ var file: String?, /** * The line number within the source file this stackframe refers to */ var lineNumber: Number?, /** * The address of the instruction where the event occurred. */ var frameAddress: Long?, /** * The address of the function where the event occurred. */ var symbolAddress: Long?, /** * The address of the library where the event occurred. */ var loadAddress: Long?, /** * Whether this frame identifies the program counter */ var isPC: Boolean?, /** * The type of the error */ var type: ErrorType? = null, /** * Identifies the exact build this frame originates from. */ var codeIdentifier: String? = null, ) - LongParameterList:StateEvent.kt$StateEvent.Install$( @JvmField val apiKey: String, @JvmField val autoDetectNdkCrashes: Boolean, @JvmField val appVersion: String?, @JvmField val buildUuid: String?, @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int, @JvmField val sendThreads: ThreadSendPolicy ) + LongParameterList:StateEvent.kt$StateEvent.Install$( @JvmField val apiKey: String, @JvmField val autoDetectNdkCrashes: Boolean, @JvmField val appVersion: String?, @JvmField val buildUuid: String?, @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int, @JvmField val sendThreads: ThreadSendPolicy, @JvmField val maxBreadcrumbs: Int ) LongParameterList:ThreadState.kt$ThreadState$( allThreads: List<JavaThread>, currentThread: JavaThread, exc: Throwable?, isUnhandled: Boolean, maxThreadCount: Int, threadCollectionTimeLimitMillis: Long, projectPackages: Collection<String>, logger: Logger ) MagicNumber:DefaultDelivery.kt$DefaultDelivery$299 MagicNumber:DefaultDelivery.kt$DefaultDelivery$429 diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt index cd6ab1fe5d..b099c7edc3 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt @@ -22,7 +22,8 @@ internal class ClientObservable : BaseObservable() { conf.releaseStage, lastRunInfoPath, consecutiveLaunchCrashes, - conf.sendThreads + conf.sendThreads, + conf.maxBreadcrumbs ) } } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt index 729c9b9fb7..e16671559f 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt @@ -10,7 +10,8 @@ sealed class StateEvent { // JvmField allows direct field access optimizations @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int, - @JvmField val sendThreads: ThreadSendPolicy + @JvmField val sendThreads: ThreadSendPolicy, + @JvmField val maxBreadcrumbs: Int ) : StateEvent() object DeliverPending : StateEvent() diff --git a/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api b/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api index ab910c524c..cdc5a7496c 100644 --- a/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api +++ b/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api @@ -21,7 +21,7 @@ public final class com/bugsnag/android/ndk/NativeBridge : com/bugsnag/android/in public final fun getCurrentNativeApiCallUsage ()Ljava/util/Map; public final fun getSignalUnwindStackFunction ()J public final fun initCallbackCounts (Ljava/util/Map;)V - public final fun install (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZIZI)V + public final fun install (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZIZII)V public final fun notifyAddCallback (Ljava/lang/String;)V public final fun notifyRemoveCallback (Ljava/lang/String;)V public fun onStateChange (Lcom/bugsnag/android/StateEvent;)V diff --git a/bugsnag-plugin-android-ndk/detekt-baseline.xml b/bugsnag-plugin-android-ndk/detekt-baseline.xml index dbc3020513..57df3f72f2 100644 --- a/bugsnag-plugin-android-ndk/detekt-baseline.xml +++ b/bugsnag-plugin-android-ndk/detekt-baseline.xml @@ -4,7 +4,7 @@ CyclomaticComplexMethod:NativeBridge.kt$NativeBridge$override fun onStateChange(event: StateEvent) LongMethod:EventOnDiskTests.kt$EventOnDiskTests$@Test fun testEvent() - LongParameterList:NativeBridge.kt$NativeBridge$( apiKey: String, reportingDirectory: String, lastRunInfoPath: String, consecutiveLaunchCrashes: Int, autoDetectNdkCrashes: Boolean, apiLevel: Int, is32bit: Boolean, threadSendPolicy: Int ) + LongParameterList:NativeBridge.kt$NativeBridge$( apiKey: String, reportingDirectory: String, lastRunInfoPath: String, consecutiveLaunchCrashes: Int, autoDetectNdkCrashes: Boolean, apiLevel: Int, is32bit: Boolean, threadSendPolicy: Int, maxBreadcrumbs: Int, ) NestedBlockDepth:NativeBridge.kt$NativeBridge$private fun deliverPendingReports() TooManyFunctions:NativeBridge.kt$NativeBridge : StateObserver UseCheckOrError:ResourceUtils.kt$throw IllegalStateException("Failed to read JSON from $resourceName") diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt index 988176b230..9ff476b93a 100644 --- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt +++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt @@ -52,7 +52,8 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse autoDetectNdkCrashes: Boolean, apiLevel: Int, is32bit: Boolean, - threadSendPolicy: Int + threadSendPolicy: Int, + maxBreadcrumbs: Int, ) external fun startedSession( @@ -226,7 +227,8 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse arg.autoDetectNdkCrashes, Build.VERSION.SDK_INT, is32bit, - arg.sendThreads.ordinal + arg.sendThreads.ordinal, + arg.maxBreadcrumbs ) installed.set(true) } diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c index 207a254d39..8b06c9b17d 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c @@ -150,7 +150,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install( JNIEnv *env, jobject _this, jstring _api_key, jstring _event_path, jstring _last_run_info_path, jint consecutive_launch_crashes, jboolean auto_detect_ndk_crashes, jint _api_level, jboolean is32bit, - jint send_threads) { + jint send_threads, jint max_breadcrumbs) { if (!bsg_jni_cache_init(env)) { BUGSNAG_LOG("Could not init JNI jni_cache."); @@ -165,6 +165,14 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install( bugsnag_env->send_threads = send_threads; bugsnag_env->handling_crash = ATOMIC_VAR_INIT(false); + bugsnag_env->next_event.max_crumb_count = max_breadcrumbs; + bugsnag_env->next_event.breadcrumbs = + calloc(max_breadcrumbs, sizeof(bugsnag_breadcrumb)); + + if (bugsnag_env->next_event.breadcrumbs == NULL) { + goto error; + } + // copy event path to env struct const char *event_path = bsg_safe_get_string_utf_chars(env, _event_path); if (event_path == NULL) { diff --git a/bugsnag-plugin-android-ndk/src/main/jni/event.c b/bugsnag-plugin-android-ndk/src/main/jni/event.c index 5ef89aea8b..e76618690b 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/event.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/event.c @@ -353,13 +353,13 @@ void bugsnag_event_set_user(void *event_ptr, const char *id, const char *email, void bsg_event_add_breadcrumb(bugsnag_event *event, bugsnag_breadcrumb *crumb) { int crumb_index; - if (event->crumb_count < BUGSNAG_CRUMBS_MAX) { + if (event->crumb_count < event->max_crumb_count) { crumb_index = event->crumb_count; event->crumb_count++; } else { crumb_index = event->crumb_first_index; event->crumb_first_index = - (event->crumb_first_index + 1) % BUGSNAG_CRUMBS_MAX; + (event->crumb_first_index + 1) % event->max_crumb_count; } bsg_free_opaque_metadata(&(event->breadcrumbs[crumb_index].metadata)); diff --git a/bugsnag-plugin-android-ndk/src/main/jni/event.h b/bugsnag-plugin-android-ndk/src/main/jni/event.h index c129743439..d92324f394 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/event.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/event.h @@ -12,12 +12,6 @@ */ #define BUGSNAG_METADATA_MAX 128 #endif -#ifndef BUGSNAG_CRUMBS_MAX -/** - * Max number of breadcrumbs in an event. Configures a default if not defined. - */ -#define BUGSNAG_CRUMBS_MAX 50 -#endif #ifndef BUGSNAG_DEFAULT_EX_TYPE /** * Type assigned to exceptions. Configures a default if not defined. @@ -34,7 +28,7 @@ /** * Version of the bugsnag_event struct. Serialized to report header. */ -#define BUGSNAG_EVENT_VERSION 13 +#define BUGSNAG_EVENT_VERSION 14 #ifdef __cplusplus extern "C" { @@ -229,7 +223,9 @@ typedef struct { // Breadcrumbs are a ring; the first index moves as the // structure is filled and replaced. int crumb_first_index; - bugsnag_breadcrumb breadcrumbs[BUGSNAG_CRUMBS_MAX]; + // the maximum number of breadcrumbs that can be placed in the buffer + int max_crumb_count; + bugsnag_breadcrumb *breadcrumbs; char context[64]; bugsnag_severity severity; diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_reader.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_reader.c index 729ac9b30f..021de3e18e 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_reader.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_reader.c @@ -2,6 +2,7 @@ #include "../../event.h" #include "../string.h" +#include "utils/logger.h" #include #include @@ -9,7 +10,7 @@ #include #include -const int BSG_MIGRATOR_CURRENT_VERSION = 13; +const int BSG_MIGRATOR_CURRENT_VERSION = 14; void bsg_read_feature_flags(int fd, bool expect_verification, bsg_feature_flag **out_feature_flags, @@ -21,6 +22,8 @@ void bsg_read_opaque_breadcrumb_metadata(int fd, bugsnag_breadcrumb *breadcrumbs, int crumb_count); +bool bsg_read_breadcrumbs(int fd, bugsnag_event *event); + bsg_report_header *bsg_report_header_read(int fd) { bsg_report_header *header = calloc(1, sizeof(bsg_report_header)); ssize_t len = read(fd, header, sizeof(bsg_report_header)); @@ -55,10 +58,13 @@ bugsnag_event *bsg_read_event(char *filepath) { ssize_t len = read(fd, event, event_size); if (len != event_size) { - free(event); - return NULL; + goto error; } + // read the breadcrumbs + if (!bsg_read_breadcrumbs(fd, event)) { + goto error; + } // read the feature flags, if possible bsg_read_feature_flags(fd, true, &event->feature_flags, &event->feature_flag_count); @@ -67,6 +73,31 @@ bugsnag_event *bsg_read_event(char *filepath) { event->crumb_count); return event; +error: + free(event); + return NULL; +} + +bool bsg_read_breadcrumbs(int fd, bugsnag_event *event) { + bugsnag_breadcrumb *breadcrumbs = + calloc(event->max_crumb_count, sizeof(bugsnag_breadcrumb)); + if (breadcrumbs == NULL) { + goto error; + } + const size_t bytes_to_read = + event->max_crumb_count * sizeof(bugsnag_breadcrumb); + const ssize_t read_count = read(fd, breadcrumbs, bytes_to_read); + if (read_count != bytes_to_read) { + goto error; + } + + event->breadcrumbs = breadcrumbs; + return true; +error: + event->breadcrumbs = NULL; + event->crumb_count = 0; + event->crumb_first_index = 0; + return false; } static char *read_string(int fd) { diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c index b81987e78a..df787e479d 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c @@ -8,11 +8,13 @@ #include "buffered_writer.h" #include "utils/seqlock.h" +bool bsg_write_breadcrumbs(bugsnag_event *event, bsg_buffered_writer *writer); bool bsg_write_feature_flags(bugsnag_event *event, bsg_buffered_writer *writer); bool bsg_write_opaque_metadata(bugsnag_event *event, bsg_buffered_writer *writer); +bool bsg_write_breadcrumbs(bugsnag_event *event, bsg_buffered_writer *writer); bool bsg_report_header_write(bsg_report_header *header, int fd) { ssize_t len = write(fd, header, sizeof(bsg_report_header)); @@ -30,7 +32,9 @@ bool bsg_event_write(bsg_environment *env) { bsg_report_header_write(&env->report_header, writer.fd) && // add cached event info writer.write(&writer, &env->next_event, sizeof(bugsnag_event)) && - // append feature flags after event structure + // append the breadcrumbs after the event structure + bsg_write_breadcrumbs(&env->next_event, &writer) && + // append feature flags bsg_write_feature_flags(&env->next_event, &writer) && // append opaque metadata after the feature flags bsg_write_opaque_metadata(&env->next_event, &writer); @@ -85,6 +89,11 @@ static bool write_feature_flag(bsg_buffered_writer *writer, return true; } +bool bsg_write_breadcrumbs(bugsnag_event *event, bsg_buffered_writer *writer) { + return writer->write(writer, event->breadcrumbs, + sizeof(bugsnag_breadcrumb) * event->max_crumb_count); +} + extern bsg_seqlock_t bsg_feature_flag_lock; bool bsg_write_feature_flags(bugsnag_event *event, diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/json_writer.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/json_writer.c index 390439c56d..ad3c356a5b 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/json_writer.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/json_writer.c @@ -365,7 +365,7 @@ void bsg_serialize_breadcrumbs(const bugsnag_event *event, JSON_Array *crumbs) { bsg_crumb_type_string(breadcrumb.type)); bsg_serialize_breadcrumb_metadata(breadcrumb.metadata, crumb); current_index++; - if (current_index == BUGSNAG_CRUMBS_MAX) { + if (current_index == event->max_crumb_count) { current_index = 0; } } diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/main.c b/bugsnag-plugin-android-ndk/src/test/cpp/main.c index 76c03c88f8..68624d369a 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/main.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/main.c @@ -191,6 +191,8 @@ JNIEXPORT jstring JNICALL Java_com_bugsnag_android_ndk_BreadcrumbStateSerializationTest_run(JNIEnv *env, jobject thiz) { bugsnag_event *event = calloc(1, sizeof(bugsnag_event)); + event->max_crumb_count = 50; + event->breadcrumbs = calloc(event->max_crumb_count, sizeof(bugsnag_breadcrumb)); loadBreadcrumbsTestCase(event); JSON_Value *eventVal = json_value_init_array(); JSON_Array *eventAry = json_value_get_array(eventVal); diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c b/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c index 7ddf7da67f..9c10aee9de 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c @@ -59,7 +59,7 @@ TEST test_add_breadcrumbs_over_max(void) { } // assertions assume that the crumb count is always 50 - ASSERT_EQ(BUGSNAG_CRUMBS_MAX, event->crumb_count); + ASSERT_EQ(event->max_crumb_count, event->crumb_count); ASSERT_EQ(14, event->crumb_first_index); ASSERT_STR_EQ("crumb: 50", event->breadcrumbs[0].name); diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/test_serializer.c b/bugsnag-plugin-android-ndk/src/test/cpp/test_serializer.c index 69da848637..682b8b2e71 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/test_serializer.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/test_serializer.c @@ -108,12 +108,12 @@ void loadBreadcrumbsTestCase(bugsnag_event *event) { bugsnag_breadcrumb *crumb = calloc(1, sizeof(bugsnag_breadcrumb)); memset(crumb, 0, sizeof(bugsnag_breadcrumb)); event->crumb_count = 4; - event->crumb_first_index = BUGSNAG_CRUMBS_MAX - 2; + event->crumb_first_index = event->max_crumb_count - 2; // ensure that serialization loop is covered by test // first breadcrumb - crumb = &event->breadcrumbs[BUGSNAG_CRUMBS_MAX - 2]; + crumb = &event->breadcrumbs[event->max_crumb_count - 2]; crumb->type = BSG_CRUMB_USER; strcpy(crumb->name, "Jane"); strcpy(crumb->timestamp, "2018-10-08T12:07:09Z"); @@ -127,7 +127,7 @@ void loadBreadcrumbsTestCase(bugsnag_event *event) { strcpy(data->values[0].char_value, "Foo"); // second breadcrumb - crumb = &event->breadcrumbs[BUGSNAG_CRUMBS_MAX - 1]; + crumb = &event->breadcrumbs[event->max_crumb_count - 1]; crumb->type = BSG_CRUMB_MANUAL; strcpy(crumb->name, "Something went wrong"); strcpy(crumb->timestamp, "2018-10-08T12:07:11Z"); From 5c93c27ad2d9d781fd3ed6baade4cee1348c682b Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 23 Apr 2024 11:37:15 +0100 Subject: [PATCH 06/23] test(ndk): end-to-end test with 500 breadcrumbs in a native crash --- .buildkite/pipeline.full.yml | 2 ++ .../test/cpp/migrations/EventOnDiskTests.cpp | 7 +++-- .../src/test/cpp/test_breadcrumbs.c | 8 ++++++ .../src/test/cpp/test_utils_serialize.c | 9 ++++++ .../src/main/cpp/cxx-scenarios.cpp | 5 ++++ .../CXXMaxBreadcrumbCrashScenario.kt | 28 +++++++++++++++++++ .../full_tests/native_breadcrumbs.feature | 8 ++++++ 7 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXMaxBreadcrumbCrashScenario.kt diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index 668b6a9bdd..02fe640f07 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -11,6 +11,8 @@ steps: timeout_in_minutes: 5 agents: queue: macos-14 + env: + JAVA_VERSION: 17 command: 'make example-app' - label: ':android: Build debug fixture APK' diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/migrations/EventOnDiskTests.cpp b/bugsnag-plugin-android-ndk/src/test/cpp/migrations/EventOnDiskTests.cpp index 8ba09f69c2..18a06b4acf 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/migrations/EventOnDiskTests.cpp +++ b/bugsnag-plugin-android-ndk/src/test/cpp/migrations/EventOnDiskTests.cpp @@ -41,7 +41,9 @@ static void *create_full_event() { event->app.version_code = 8139512718; // breadcrumbs - auto max = 50; + event->max_crumb_count = 50; + event->breadcrumbs = new bugsnag_breadcrumb[event->max_crumb_count]; + auto max = event->max_crumb_count; event->crumb_first_index = 2; // test the circular buffer logic char name[30]; for (int i = event->crumb_first_index; i < max; i++) { @@ -132,7 +134,7 @@ static void *create_full_event() { static const char *write_event(JNIEnv *env, jstring temp_file, void *(event_generator)()) { auto event_ctx = (bsg_environment *)calloc(1, sizeof(bsg_environment)); - event_ctx->report_header.version = 13; + event_ctx->report_header.version = BUGSNAG_EVENT_VERSION; const char *path = (*env).GetStringUTFChars(temp_file, nullptr); sprintf(event_ctx->next_event_path, "%s", path); @@ -185,6 +187,7 @@ Java_com_bugsnag_android_ndk_migrations_EventOnDiskTests_generateAndStoreEvent( free(parsed_event->feature_flags[i].name); free(parsed_event->feature_flags[i].variant); } + free(parsed_event->breadcrumbs); free(parsed_event->feature_flags); free(parsed_event); diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c b/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c index 9c10aee9de..5f5a9588c8 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c @@ -20,6 +20,9 @@ bugsnag_breadcrumb *init_breadcrumb(const char *name, const char *message, bugsn TEST test_add_breadcrumb(void) { bugsnag_event *event = calloc(1, sizeof(bugsnag_event)); + event->max_crumb_count = 50; + event->breadcrumbs = + calloc(event->max_crumb_count, sizeof(bugsnag_breadcrumb)); bugsnag_breadcrumb *crumb = init_breadcrumb("stroll", "this is a drill.", BSG_CRUMB_USER); bsg_event_add_breadcrumb(event, crumb); ASSERT_EQ(1, event->crumb_count); @@ -39,6 +42,7 @@ TEST test_add_breadcrumb(void) { ASSERT(strcmp("message", event->breadcrumbs[1].metadata.values[0].name) == 0); ASSERT(strcmp("this is not a drill.", event->breadcrumbs[1].metadata.values[0].char_value) == 0); + free(event->breadcrumbs); free(event); free(crumb2); PASS(); @@ -46,6 +50,9 @@ TEST test_add_breadcrumb(void) { TEST test_add_breadcrumbs_over_max(void) { bugsnag_event *event = calloc(1, sizeof(bugsnag_event)); + event->max_crumb_count = 50; + event->breadcrumbs = + calloc(event->max_crumb_count, sizeof(bugsnag_breadcrumb)); int breadcrumb_count = 64; for (int i=0; i < breadcrumb_count; i++) { @@ -71,6 +78,7 @@ TEST test_add_breadcrumbs_over_max(void) { ASSERT_STR_EQ("crumb: 14", event->breadcrumbs[14].name); ASSERT_STR_EQ("crumb: 15", event->breadcrumbs[15].name); ASSERT_STR_EQ("crumb: 16", event->breadcrumbs[16].name); + free(event->breadcrumbs); free(event); PASS(); } diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/test_utils_serialize.c b/bugsnag-plugin-android-ndk/src/test/cpp/test_utils_serialize.c index 15953ecd34..cd1835580d 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/test_utils_serialize.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/test_utils_serialize.c @@ -87,6 +87,9 @@ void generate_basic_report(bugsnag_event *event) { bugsnag_event *bsg_generate_event(void) { bugsnag_event *report = calloc(1, sizeof(bugsnag_event)); + report->max_crumb_count = 50; + report->breadcrumbs = + calloc(report->max_crumb_count, sizeof(bugsnag_breadcrumb)); strcpy(report->grouping_hash, "foo-hash"); strcpy(report->api_key, "5d1e5fbd39a74caa1200142706a90b20"); strcpy(report->context, "SomeActivity"); @@ -177,6 +180,7 @@ TEST test_report_to_file(void) { strcpy(env->report_header.os_build, "macOS Sierra"); strcpy(env->next_event_path, SERIALIZE_TEST_FILE); ASSERT(bsg_serialize_event_to_file(env)); + free(report->breadcrumbs); free(report); free(env); PASS(); @@ -193,6 +197,7 @@ TEST test_report_with_feature_flags_to_file(void) { strcpy(env->report_header.os_build, "macOS Sierra"); strcpy(env->next_event_path, SERIALIZE_TEST_FILE); ASSERT(bsg_serialize_event_to_file(env)); + free(report->breadcrumbs); free(report); free(env); PASS(); @@ -212,6 +217,7 @@ TEST test_file_to_report(void) { ASSERT(report != NULL); ASSERT(strcmp("SIGBUS", report->error.errorClass) == 0); ASSERT(strcmp("POSIX is serious about oncoming traffic", report->error.errorMessage) == 0); + free(generated_report->breadcrumbs); free(generated_report); free(env); free(report); @@ -234,6 +240,7 @@ TEST test_report_with_feature_flags_from_file(void) { ASSERT_EQ(2, event->feature_flag_count); + free(report->breadcrumbs); free(report); free(env); free(event); @@ -259,6 +266,7 @@ TEST test_report_with_opaque_metadata_from_file(void) { ASSERT_EQ(BSG_METADATA_OPAQUE_VALUE, bugsnag_event_has_metadata(event, "opaque", "map")); ASSERT_EQ(BSG_METADATA_OPAQUE_VALUE, bugsnag_event_has_metadata(event, "opaque", "list")); + free(report->breadcrumbs); free(report); free(env); free(event); @@ -271,6 +279,7 @@ JSON_Value *bsg_generate_json(void) { char *json = bsg_serialize_event_to_json_string(event); JSON_Value *root_value = json_parse_string(json); free(json); + free(event->breadcrumbs); free(event); return root_value; } diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp index c6fe958d2e..e188aa0864 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp @@ -274,5 +274,10 @@ Java_com_bugsnag_android_mazerunner_scenarios_UnhandledNdkAutoNotifyFalseScenari abort(); } +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXMaxBreadcrumbCrashScenario_activate(JNIEnv *env, + jobject thiz) { + abort(); } +} \ No newline at end of file diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXMaxBreadcrumbCrashScenario.kt b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXMaxBreadcrumbCrashScenario.kt new file mode 100644 index 0000000000..9dffb9ea54 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXMaxBreadcrumbCrashScenario.kt @@ -0,0 +1,28 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration + +private const val MAX_BREADCRUMB_COUNT = 500 + +class CXXMaxBreadcrumbCrashScenario( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + init { + config.maxBreadcrumbs = MAX_BREADCRUMB_COUNT + } + + external fun activate() + + override fun startScenario() { + super.startScenario() + repeat(config.maxBreadcrumbs) { index -> + Bugsnag.leaveBreadcrumb("this is breadcrumb $index") + } + + activate() + } +} diff --git a/features/full_tests/native_breadcrumbs.feature b/features/full_tests/native_breadcrumbs.feature index 14388f3a3d..3775e0cfe1 100644 --- a/features/full_tests/native_breadcrumbs.feature +++ b/features/full_tests/native_breadcrumbs.feature @@ -23,3 +23,11 @@ Feature: Native Breadcrumbs API And the event "severity" equals "warning" And the event has a "process" breadcrumb named "Rerun field analysis" And the event "unhandled" is false + + Scenario: Leaving the maximum number of native breadcrumbs + When I run "CXXMaxBreadcrumbCrashScenario" and relaunch the crashed app + And I configure Bugsnag for "CXXMaxBreadcrumbCrashScenario" + And I wait to receive an error + And the error payload contains a completed handled native report + And the event "unhandled" is true + And the event has 500 breadcrumbs \ No newline at end of file From 198036f499cbd7ab91835d6d4ad41a136f9b9240 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 24 Apr 2024 08:08:08 +0100 Subject: [PATCH 07/23] fix(OpaqueValue): OpaqueValue should not redact keys when marshalling values --- .../src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt index 426b5baf83..585fa38ae2 100644 --- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt +++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt @@ -28,7 +28,7 @@ internal class OpaqueValue(val json: String) { private fun encode(value: Any): String { val writer = StringWriter() - writer.use { JsonStream(it).value(value, true) } + writer.use { JsonStream(it).value(value, false) } return writer.toString() } From e20e1be34957420248ba8c6daf19d61027b340f9 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 24 Apr 2024 08:10:21 +0100 Subject: [PATCH 08/23] fix(OpaqueValue): set StringWriter initial size to 256 chars since most encoded strings are longer than the default 16 characters --- .../src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt index 585fa38ae2..35dc0cec5c 100644 --- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt +++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt @@ -10,6 +10,7 @@ internal class OpaqueValue(val json: String) { companion object { private const val MAX_NDK_STRING_LENGTH = 64 private const val US_ASCII_MAX_CODEPOINT = 127 + private const val INITIAL_BUFFER_SIZE = 256 private fun isStringNDKSupported(value: String): Boolean { // anything over 63 characters is definitely not supported @@ -27,7 +28,7 @@ internal class OpaqueValue(val json: String) { } private fun encode(value: Any): String { - val writer = StringWriter() + val writer = StringWriter(INITIAL_BUFFER_SIZE) writer.use { JsonStream(it).value(value, false) } return writer.toString() } From 3bf0721ae68b0c6b48351bb16798dc268dd1b7fc Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 24 Apr 2024 13:36:55 +0100 Subject: [PATCH 09/23] chore(build): removed the instrumented test GitHub workflow pending a ticket to migrate them --- .github/workflows/build.yml | 49 ------------------- .../com/bugsnag/android/MemoryTrimTest.java | 3 ++ 2 files changed, 3 insertions(+), 49 deletions(-) delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 966414a05a..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: instrumentation_tests - -on: [ push, pull_request ] - -env: - GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dorg.gradle.parallel=true" - ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL: 60 - -jobs: - android: - runs-on: macos-latest - - strategy: - fail-fast: true - matrix: - api-level: - - 29 - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: gradle/wrapper-validation-action@v1 - - - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: 11 - - - name: Gradle cache - uses: gradle/gradle-build-action@v2 - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: Run Tests - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - script: ./gradlew connectedCheck -x :bugsnag-benchmarks:connectedCheck - env: - API_LEVEL: ${{ matrix.api-level }} diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/MemoryTrimTest.java b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/MemoryTrimTest.java index 8d8799b65d..838f1de2a5 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/MemoryTrimTest.java +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/MemoryTrimTest.java @@ -2,6 +2,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,6 +36,7 @@ public class MemoryTrimTest { @Test public void onLowMemoryEvent() { when(context.getApplicationContext()).thenReturn(context); + doNothing().when(context).registerComponentCallbacks(any()); Client client = new Client(context, BugsnagTestUtils.generateConfiguration()); // block until observer is registered From 2c766a6ff51c9e8111d028fe8c4a84da8db0ce22 Mon Sep 17 00:00:00 2001 From: jason Date: Thu, 25 Apr 2024 13:34:28 +0100 Subject: [PATCH 10/23] fix(ndk): iterate over the `Map.entrySet` directly instead of copying the keys to an `ArrayList` when copying the metadata into the NDK structures --- CHANGELOG.md | 4 +- bugsnag-plugin-android-ndk/proguard-rules.pro | 1 + .../com/bugsnag/android/ndk/NativeBridge.kt | 9 +-- .../com/bugsnag/android/ndk/OpaqueValue.kt | 7 +- .../src/main/jni/jni_cache.c | 2 + .../src/main/jni/jni_cache.h | 1 + .../src/main/jni/metadata.c | 76 +++++++++++-------- 7 files changed, 55 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6984e8da91..44c8f5d355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ ### Enhancements * `Configuration.maxBreadcrumbs` is now obeyed by `bugsnag-plugin-android-ndk`, so native events will include the correct number of breadcrumbs - []() + [#2020](https://github.com/bugsnag/bugsnag-android/pull/2020) +* `bugsnag-plugin-android-ndk` will no longer create an `ArrayList` copy of metadata keys when leaving breadcrumbs + [#2022](https://github.com/bugsnag/bugsnag-android/pull/2022) ## 6.4.0 (2024-04-15) diff --git a/bugsnag-plugin-android-ndk/proguard-rules.pro b/bugsnag-plugin-android-ndk/proguard-rules.pro index 3fa50bcdb9..eb3469de29 100644 --- a/bugsnag-plugin-android-ndk/proguard-rules.pro +++ b/bugsnag-plugin-android-ndk/proguard-rules.pro @@ -1,6 +1,7 @@ -keepattributes LineNumberTable,SourceFile -keep class com.bugsnag.android.ndk.OpaqueValue { java.lang.String getJson(); + static java.lang.Object makeSafe(java.lang.Object); } -keep class com.bugsnag.android.ndk.NativeBridge { *; } -keep class com.bugsnag.android.NdkPlugin { *; } diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt index 9ff476b93a..33115f355c 100644 --- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt +++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt @@ -121,7 +121,7 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse event.message, event.type.toNativeValue(), event.timestamp, - makeSafeMetadata(event.metadata) + event.metadata ) NotifyHandled -> addHandledEvent() @@ -172,13 +172,6 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse } } - private fun makeSafeMetadata(metadata: Map): Map { - if (metadata.isEmpty()) return metadata - return object : Map by metadata { - override fun get(key: String): Any? = OpaqueValue.makeSafe(metadata[key]) - } - } - private fun isInvalidMessage(msg: Any?): Boolean { if (msg == null || msg !is StateEvent) { return true diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt index 35dc0cec5c..c4383c1655 100644 --- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt +++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt @@ -7,12 +7,12 @@ import java.io.StringWriter * Marker class for values that are `BSG_METADATA_OPAQUE_VALUE` in the C layer */ internal class OpaqueValue(val json: String) { - companion object { + internal companion object { private const val MAX_NDK_STRING_LENGTH = 64 private const val US_ASCII_MAX_CODEPOINT = 127 private const val INITIAL_BUFFER_SIZE = 256 - private fun isStringNDKSupported(value: String): Boolean { + fun isStringNDKSupported(value: String): Boolean { // anything over 63 characters is definitely not supported if (value.length >= MAX_NDK_STRING_LENGTH) return false @@ -27,7 +27,7 @@ internal class OpaqueValue(val json: String) { return value.toByteArray().size < MAX_NDK_STRING_LENGTH } - private fun encode(value: Any): String { + fun encode(value: Any): String { val writer = StringWriter(INITIAL_BUFFER_SIZE) writer.use { JsonStream(it).value(value, false) } return writer.toString() @@ -38,6 +38,7 @@ internal class OpaqueValue(val json: String) { * is both a compatible type and fits into the available space. This method can return * any one of: `Boolean`, `Number`, `String`, `OpaqueValue` or `null`. */ + @JvmStatic fun makeSafe(value: Any?): Any? = when { value is Boolean -> value value is Number -> value diff --git a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c index 21b60ec282..b5909e885d 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c @@ -218,6 +218,8 @@ bool bsg_jni_cache_init(JNIEnv *env) { CACHE_CLASS(OpaqueValue, "com/bugsnag/android/ndk/OpaqueValue"); CACHE_METHOD(OpaqueValue, OpaqueValue_getJson, "getJson", "()Ljava/lang/String;"); + CACHE_STATIC_METHOD(OpaqueValue, OpaqueValue_makeSafe, "makeSafe", + "(Ljava/lang/Object;)Ljava/lang/Object;"); CACHE_ENUM_CONSTANT(ErrorType_C, "com/bugsnag/android/ErrorType", "C"); diff --git a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h index 9b3200379a..44253a390c 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h @@ -89,6 +89,7 @@ typedef struct { jclass OpaqueValue; jmethodID OpaqueValue_getJson; + jmethodID OpaqueValue_makeSafe; jobject ErrorType_C; } bsg_jni_cache_t; diff --git a/bugsnag-plugin-android-ndk/src/main/jni/metadata.c b/bugsnag-plugin-android-ndk/src/main/jni/metadata.c index 818e0f7866..f466322c9a 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/metadata.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/metadata.c @@ -343,24 +343,29 @@ static void populate_metadata_value(JNIEnv *env, bugsnag_metadata *dst, return; } - if (bsg_safe_is_instance_of(env, _value, bsg_jni_cache->number)) { + jobject safe_value = bsg_safe_call_static_object_method( + env, bsg_jni_cache->OpaqueValue, bsg_jni_cache->OpaqueValue_makeSafe, + _value); + + if (bsg_safe_is_instance_of(env, safe_value, bsg_jni_cache->number)) { // add a double metadata value double value = bsg_safe_call_double_method( - env, _value, bsg_jni_cache->number_double_value); + env, safe_value, bsg_jni_cache->number_double_value); bsg_add_metadata_value_double(dst, section, name, value); - } else if (bsg_safe_is_instance_of(env, _value, bsg_jni_cache->Boolean)) { + } else if (bsg_safe_is_instance_of(env, safe_value, bsg_jni_cache->Boolean)) { // add a boolean metadata value bool value = bsg_safe_call_boolean_method( - env, _value, bsg_jni_cache->Boolean_booleanValue); + env, safe_value, bsg_jni_cache->Boolean_booleanValue); bsg_add_metadata_value_bool(dst, section, name, value); - } else if (bsg_safe_is_instance_of(env, _value, bsg_jni_cache->String)) { - const char *value = bsg_safe_get_string_utf_chars(env, _value); + } else if (bsg_safe_is_instance_of(env, safe_value, bsg_jni_cache->String)) { + const char *value = bsg_safe_get_string_utf_chars(env, safe_value); if (value != NULL) { bsg_add_metadata_value_str(dst, section, name, value); } - } else if (bsg_safe_is_instance_of(env, _value, bsg_jni_cache->OpaqueValue)) { + } else if (bsg_safe_is_instance_of(env, safe_value, + bsg_jni_cache->OpaqueValue)) { jstring _json = bsg_safe_call_object_method( - env, _value, bsg_jni_cache->OpaqueValue_getJson); + env, safe_value, bsg_jni_cache->OpaqueValue_getJson); const char *json = bsg_safe_get_string_utf_chars(env, _json); if (json != NULL) { @@ -513,8 +518,8 @@ void bsg_populate_metadata(JNIEnv *env, bugsnag_metadata *dst, void bsg_populate_crumb_metadata(JNIEnv *env, bugsnag_breadcrumb *crumb, jobject metadata) { - jobject keyset = NULL; - jobject keylist = NULL; + jobject entryset = NULL; + jobject entries = NULL; if (metadata == NULL) { goto exit; @@ -526,43 +531,48 @@ void bsg_populate_crumb_metadata(JNIEnv *env, bugsnag_breadcrumb *crumb, // get size of metadata map jint map_size = bsg_safe_call_int_method(env, metadata, bsg_jni_cache->Map_size); - if (map_size == -1) { + if (map_size <= 0) { goto exit; } // create a list of metadata keys - keyset = - bsg_safe_call_object_method(env, metadata, bsg_jni_cache->Map_keySet); - if (keyset == NULL) { + entryset = + bsg_safe_call_object_method(env, metadata, bsg_jni_cache->Map_entrySet); + if (entryset == NULL) { goto exit; } - keylist = bsg_safe_new_object(env, bsg_jni_cache->ArrayList, - bsg_jni_cache->ArrayList_constructor_collection, - keyset); - if (keylist == NULL) { + entries = + bsg_safe_call_object_method(env, entryset, bsg_jni_cache->Set_iterator); + if (entries == NULL) { goto exit; } - for (int i = 0; i < map_size; i++) { - jstring _key = bsg_safe_call_object_method( - env, keylist, bsg_jni_cache->ArrayList_get, (jint)i); - jobject _value = bsg_safe_call_object_method(env, metadata, - bsg_jni_cache->Map_get, _key); - - if (_key != NULL && _value != NULL) { - const char *key = bsg_safe_get_string_utf_chars(env, _key); - if (key != NULL) { - populate_metadata_value(env, &crumb->metadata, "metaData", key, _value); - bsg_safe_release_string_utf_chars(env, _key, key); + while (bsg_safe_call_boolean_method(env, entries, + bsg_jni_cache->Iterator_hasNext)) { + (*env)->PushLocalFrame(env, 3); + { + jobject entry = bsg_safe_call_object_method(env, entries, + bsg_jni_cache->Iterator_next); + jstring _key = bsg_safe_call_object_method( + env, entry, bsg_jni_cache->MapEntry_getKey); + jobject _value = bsg_safe_call_object_method( + env, entry, bsg_jni_cache->MapEntry_getValue); + + if (_key != NULL && _value != NULL) { + const char *key = bsg_safe_get_string_utf_chars(env, _key); + if (key != NULL) { + populate_metadata_value(env, &crumb->metadata, "metaData", key, + _value); + bsg_safe_release_string_utf_chars(env, _key, key); + } } } - bsg_safe_delete_local_ref(env, _key); - bsg_safe_delete_local_ref(env, _value); + (*env)->PopLocalFrame(env, NULL); } exit: - bsg_safe_delete_local_ref(env, keyset); - bsg_safe_delete_local_ref(env, keylist); + bsg_safe_delete_local_ref(env, entries); + bsg_safe_delete_local_ref(env, entryset); } void bsg_populate_event(JNIEnv *env, bugsnag_event *event) { From 278a11cb951ec168ceed54e5fd98bafc2cce14ac Mon Sep 17 00:00:00 2001 From: YYChen01988 Date: Thu, 18 Apr 2024 05:49:11 +0100 Subject: [PATCH 11/23] fix(gradle) convert build.gradle (bugsnag-android) to KTS --- build.gradle | 35 ------------------- build.gradle.kts | 34 ++++++++++++++++++ .../sdk-app-example/app/proguard-rules.pro | 2 +- features/fixtures/minimalapp/build.gradle | 2 +- 4 files changed, 36 insertions(+), 37 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index c6dcc94a09..0000000000 --- a/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -import com.bugsnag.android.Versions - -buildscript { - repositories { - google() - mavenCentral() - maven { url "https://plugins.gradle.org/m2/" } - } - dependencies { - classpath "com.android.tools.build:gradle:${Versions.androidGradlePlugin}" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" - classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:${Versions.detektPlugin}" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:${Versions.dokkaPlugin}" - classpath "org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktlintPlugin}" - classpath "androidx.benchmark:benchmark-gradle-plugin:${Versions.benchmarkPlugin}" - } -} - -plugins { - id "com.github.hierynomus.license" version "0.16.1" - id "org.jetbrains.kotlinx.binary-compatibility-validator" version "0.13.1" apply false -} - -allprojects { - repositories { - google() - mavenCentral() - } - - gradle.projectsEvaluated { - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:all" << "-Werror" - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..5ff26b96fc --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,34 @@ + +buildscript { + repositories { + google() + mavenCentral() + maven(url = "https://plugins.gradle.org/m2/") + } + dependencies { + classpath("com.android.tools.build:gradle:7.0.4") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10") + classpath("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.1") + classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0") + classpath("org.jlleitschuh.gradle:ktlint-gradle:10.2.0") + classpath("androidx.benchmark:benchmark-gradle-plugin:1.1.1") + } +} + +plugins { + id("com.github.hierynomus.license") version "0.16.1" + id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.13.1" apply false +} + +allprojects { + repositories { + google() + mavenCentral() + } + + gradle.projectsEvaluated { + tasks.withType { + options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) + } + } +} \ No newline at end of file diff --git a/examples/sdk-app-example/app/proguard-rules.pro b/examples/sdk-app-example/app/proguard-rules.pro index f1b424510d..2f9dc5a47e 100644 --- a/examples/sdk-app-example/app/proguard-rules.pro +++ b/examples/sdk-app-example/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/features/fixtures/minimalapp/build.gradle b/features/fixtures/minimalapp/build.gradle index e294e05703..e9c0e57f3d 100644 --- a/features/fixtures/minimalapp/build.gradle +++ b/features/fixtures/minimalapp/build.gradle @@ -13,7 +13,7 @@ buildscript { classpath "com.bugsnag:bugsnag-android-gradle-plugin:7.1.0" } // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + // in the individual module build.gradle.kts files } } From c62096a0a9fc1009744247a9785738b099cda1c8 Mon Sep 17 00:00:00 2001 From: YYChen01988 Date: Thu, 18 Apr 2024 08:45:48 +0100 Subject: [PATCH 12/23] fix(gradle) convert build.gradle (bugsnag--plugin-react-native) to KTS --- bugsnag-plugin-react-native/build.gradle | 9 --------- bugsnag-plugin-react-native/build.gradle.kts | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 bugsnag-plugin-react-native/build.gradle create mode 100644 bugsnag-plugin-react-native/build.gradle.kts diff --git a/bugsnag-plugin-react-native/build.gradle b/bugsnag-plugin-react-native/build.gradle deleted file mode 100644 index 345adbf3ae..0000000000 --- a/bugsnag-plugin-react-native/build.gradle +++ /dev/null @@ -1,9 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) -} diff --git a/bugsnag-plugin-react-native/build.gradle.kts b/bugsnag-plugin-react-native/build.gradle.kts new file mode 100644 index 0000000000..c9d46adb15 --- /dev/null +++ b/bugsnag-plugin-react-native/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("bugsnag-build-plugin") +} + +apply(plugin = "com.android.library") + +dependencies { + add("api", project(":bugsnag-android-core")) +} \ No newline at end of file From 5a4b101fa4c4deb5a915c54656ce6f4f7807915e Mon Sep 17 00:00:00 2001 From: YYChen01988 Date: Thu, 18 Apr 2024 08:49:48 +0100 Subject: [PATCH 13/23] fix(gradle) convert build.gradle (bugsnag-android) to KTS --- bugsnag-android/build.gradle | 15 --------------- bugsnag-android/build.gradle.kts | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 bugsnag-android/build.gradle create mode 100644 bugsnag-android/build.gradle.kts diff --git a/bugsnag-android/build.gradle b/bugsnag-android/build.gradle deleted file mode 100644 index 3b6d9059b7..0000000000 --- a/bugsnag-android/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -bugsnagBuildOptions { - compilesCode = false -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) - api(project(":bugsnag-plugin-android-anr")) - api(project(":bugsnag-plugin-android-ndk")) -} diff --git a/bugsnag-android/build.gradle.kts b/bugsnag-android/build.gradle.kts new file mode 100644 index 0000000000..3f1ecbf8fb --- /dev/null +++ b/bugsnag-android/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("bugsnag-build-plugin") +} + +bugsnagBuildOptions { + compilesCode = false +} + +apply(plugin = "com.android.library") + +dependencies { + add("api", project(":bugsnag-android-core")) + add("api", project(":bugsnag-plugin-android-anr")) + add("api", project(":bugsnag-plugin-android-ndk")) +} From a8b4fec6b6f04944dffd83b572e93f8bd83397ee Mon Sep 17 00:00:00 2001 From: YYChen01988 Date: Thu, 18 Apr 2024 09:07:53 +0100 Subject: [PATCH 14/23] fix(gradle) convert build.gradle (bugsnag-plugin-android-anr) to KTS --- bugsnag-plugin-android-anr/build.gradle | 15 --------------- bugsnag-plugin-android-anr/build.gradle.kts | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 15 deletions(-) delete mode 100644 bugsnag-plugin-android-anr/build.gradle create mode 100644 bugsnag-plugin-android-anr/build.gradle.kts diff --git a/bugsnag-plugin-android-anr/build.gradle b/bugsnag-plugin-android-anr/build.gradle deleted file mode 100644 index 169d3dd223..0000000000 --- a/bugsnag-plugin-android-anr/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -bugsnagBuildOptions { - usesNdk = true -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) -} - -apply from: "../gradle/kotlin.gradle" diff --git a/bugsnag-plugin-android-anr/build.gradle.kts b/bugsnag-plugin-android-anr/build.gradle.kts new file mode 100644 index 0000000000..221a66cdaa --- /dev/null +++ b/bugsnag-plugin-android-anr/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("bugsnag-build-plugin") +} + +bugsnagBuildOptions { + usesNdk = true +} + +apply(plugin = "com.android.library") + +dependencies { + add("api", project(":bugsnag-android-core")) +} +apply(from = "../gradle/kotlin.gradle") From bb20f34d847dc34f5e2282794995b45016b99313 Mon Sep 17 00:00:00 2001 From: YYChen01988 Date: Thu, 18 Apr 2024 09:59:19 +0100 Subject: [PATCH 15/23] fix(gradle) convert build.gradle (bugsnag-plugin-android-okhttp) to KTS --- bugsnag-plugin-android-okhttp/build.gradle | 21 ------------------- .../build.gradle.kts | 21 +++++++++++++++++++ bugsnag-plugin-react-native/build.gradle.kts | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) delete mode 100644 bugsnag-plugin-android-okhttp/build.gradle create mode 100644 bugsnag-plugin-android-okhttp/build.gradle.kts diff --git a/bugsnag-plugin-android-okhttp/build.gradle b/bugsnag-plugin-android-okhttp/build.gradle deleted file mode 100644 index 0cee87d116..0000000000 --- a/bugsnag-plugin-android-okhttp/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) -} - -apply from: "../gradle/kotlin.gradle" - -dependencies { - compileOnly("com.squareup.okhttp3:okhttp:4.9.1") { - exclude group: "org.jetbrains.kotlin" - } - - testImplementation("com.squareup.okhttp3:mockwebserver:4.9.1") { - exclude group: "org.jetbrains.kotlin" - } -} diff --git a/bugsnag-plugin-android-okhttp/build.gradle.kts b/bugsnag-plugin-android-okhttp/build.gradle.kts new file mode 100644 index 0000000000..9944bae668 --- /dev/null +++ b/bugsnag-plugin-android-okhttp/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("bugsnag-build-plugin") +} + +apply(plugin = "com.android.library") + +dependencies { + add("api", project(":bugsnag-android-core")) +} + +apply(from = "../gradle/kotlin.gradle") + +dependencies { + "compileOnly"("com.squareup.okhttp3:okhttp:4.9.1") { + exclude(group = "org.jetbrains.kotlin") + } + + "testImplementation"("com.squareup.okhttp3:mockwebserver:4.9.1") { + exclude(group = "org.jetbrains.kotlin") + } +} diff --git a/bugsnag-plugin-react-native/build.gradle.kts b/bugsnag-plugin-react-native/build.gradle.kts index c9d46adb15..9c2c33a38e 100644 --- a/bugsnag-plugin-react-native/build.gradle.kts +++ b/bugsnag-plugin-react-native/build.gradle.kts @@ -6,4 +6,4 @@ apply(plugin = "com.android.library") dependencies { add("api", project(":bugsnag-android-core")) -} \ No newline at end of file +} From ad6716b2d2b0db759d29b53d9bfde247a9ab7a2e Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 30 Apr 2024 16:16:47 +0100 Subject: [PATCH 16/23] chore(build): all primary build scripts -> kotlin --- .../{build.gradle => build.gradle.kts} | 21 +++++----- bugsnag-android/build.gradle.kts | 3 +- bugsnag-plugin-android-anr/build.gradle.kts | 4 +- bugsnag-plugin-android-exitinfo/build.gradle | 39 ------------------- .../build.gradle.kts | 38 ++++++++++++++++++ .../{build.gradle => build.gradle.kts} | 9 ++--- .../build.gradle.kts | 11 ++---- bugsnag-plugin-react-native/build.gradle.kts | 3 +- build.gradle.kts | 2 +- settings.gradle => settings.gradle.kts | 2 +- 10 files changed, 63 insertions(+), 69 deletions(-) rename bugsnag-android-core/{build.gradle => build.gradle.kts} (50%) delete mode 100644 bugsnag-plugin-android-exitinfo/build.gradle create mode 100644 bugsnag-plugin-android-exitinfo/build.gradle.kts rename bugsnag-plugin-android-ndk/{build.gradle => build.gradle.kts} (67%) rename settings.gradle => settings.gradle.kts (90%) diff --git a/bugsnag-android-core/build.gradle b/bugsnag-android-core/build.gradle.kts similarity index 50% rename from bugsnag-android-core/build.gradle rename to bugsnag-android-core/build.gradle.kts index e1d57b95f7..db30980a95 100644 --- a/bugsnag-android-core/build.gradle +++ b/bugsnag-android-core/build.gradle.kts @@ -1,15 +1,17 @@ +import org.jetbrains.dokka.gradle.DokkaTask + plugins { id("bugsnag-build-plugin") + id("com.android.library") } bugsnagBuildOptions { usesNdk = true } -apply plugin: "com.android.library" -apply plugin: "org.jetbrains.dokka" +apply(plugin = "org.jetbrains.dokka") -dokkaHtml.configure { +tasks.getByName("dokkaHtml") { dokkaSourceSets { named("main") { noAndroidSdkLink.set(false) @@ -21,19 +23,18 @@ dokkaHtml.configure { } } -apply from: "../gradle/kotlin.gradle" +apply(from = "../gradle/kotlin.gradle") // pick up dsl-json by adding to the default sourcesets android { sourceSets { - main { - java.srcDirs += ["dsl-json/library/src/main/java"] + named("main") { + java.srcDirs("dsl-json/library/src/main/java") } - test { - java.srcDirs += ["dsl-json/library/src/test/java"] + named("test") { + java.srcDirs("dsl-json/library/src/test/java") } } } -apiValidation.ignoredPackages += ["com.bugsnag.android.repackaged.dslplatform.json"] - +apiValidation.ignoredPackages.add("com.bugsnag.android.repackaged.dslplatform.json") diff --git a/bugsnag-android/build.gradle.kts b/bugsnag-android/build.gradle.kts index 3f1ecbf8fb..bd5c5a6162 100644 --- a/bugsnag-android/build.gradle.kts +++ b/bugsnag-android/build.gradle.kts @@ -1,13 +1,12 @@ plugins { id("bugsnag-build-plugin") + id("com.android.library") } bugsnagBuildOptions { compilesCode = false } -apply(plugin = "com.android.library") - dependencies { add("api", project(":bugsnag-android-core")) add("api", project(":bugsnag-plugin-android-anr")) diff --git a/bugsnag-plugin-android-anr/build.gradle.kts b/bugsnag-plugin-android-anr/build.gradle.kts index 221a66cdaa..1574b67ef9 100644 --- a/bugsnag-plugin-android-anr/build.gradle.kts +++ b/bugsnag-plugin-android-anr/build.gradle.kts @@ -1,14 +1,14 @@ plugins { id("bugsnag-build-plugin") + id("com.android.library") } bugsnagBuildOptions { usesNdk = true } -apply(plugin = "com.android.library") - dependencies { add("api", project(":bugsnag-android-core")) } + apply(from = "../gradle/kotlin.gradle") diff --git a/bugsnag-plugin-android-exitinfo/build.gradle b/bugsnag-plugin-android-exitinfo/build.gradle deleted file mode 100644 index 835102de1c..0000000000 --- a/bugsnag-plugin-android-exitinfo/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id("bugsnag-build-plugin") - id "com.google.protobuf" version "0.9.4" -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) -} - -apply from: "../gradle/kotlin.gradle" - -dependencies { - implementation 'com.google.protobuf:protobuf-javalite:3.24.2' -} - -android.libraryVariants.configureEach { variant -> - variant.processJavaResourcesProvider.configure { - exclude('**/*.proto') - } -} - -protobuf { - protoc { - artifact = 'com.google.protobuf:protoc:3.24.2' - } - generateProtoTasks { - all().configureEach { task -> - task.builtins { - java { - option "lite" - } - } - } - } -} - -apiValidation.ignoredPackages += ["com.bugsnag.android.repackaged.server.os"] diff --git a/bugsnag-plugin-android-exitinfo/build.gradle.kts b/bugsnag-plugin-android-exitinfo/build.gradle.kts new file mode 100644 index 0000000000..c0b736c803 --- /dev/null +++ b/bugsnag-plugin-android-exitinfo/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + id("bugsnag-build-plugin") + id("com.android.library") + id("com.google.protobuf") version "0.9.4" +} + +dependencies { + api(project(":bugsnag-android-core")) +} + +apply(from = "../gradle/kotlin.gradle") + +dependencies { + implementation("com.google.protobuf:protobuf-javalite:3.24.2") +} + +android.libraryVariants.configureEach { + processJavaResourcesProvider { + exclude("**/*.proto") + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.24.2" + } + generateProtoTasks { + all().configureEach { + builtins { + create("java") { + option("lite") + } + } + } + } +} + +apiValidation.ignoredPackages += "com.bugsnag.android.repackaged.server.os" diff --git a/bugsnag-plugin-android-ndk/build.gradle b/bugsnag-plugin-android-ndk/build.gradle.kts similarity index 67% rename from bugsnag-plugin-android-ndk/build.gradle rename to bugsnag-plugin-android-ndk/build.gradle.kts index 47dcec3f26..bc576cc3a3 100644 --- a/bugsnag-plugin-android-ndk/build.gradle +++ b/bugsnag-plugin-android-ndk/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("bugsnag-build-plugin") + id("com.android.library") } bugsnagBuildOptions { @@ -7,21 +8,19 @@ bugsnagBuildOptions { publishesPrefab = "bugsnag-ndk" } -apply plugin: "com.android.library" - dependencies { api(project(":bugsnag-android-core")) } -apply from: "../gradle/kotlin.gradle" +apply(from = "../gradle/kotlin.gradle") afterEvaluate { - tasks.named("prefabReleasePackage") { + tasks.create("prefabReleasePackage") { doLast { project.fileTree("build/intermediates/prefab_package/") { include("**/abi.json") }.forEach { file -> - file.text = file.text.replace("c++_static", "none") + file.writeText(file.readText().replace("c++_static", "none")) } } } diff --git a/bugsnag-plugin-android-okhttp/build.gradle.kts b/bugsnag-plugin-android-okhttp/build.gradle.kts index 9944bae668..e01e18be95 100644 --- a/bugsnag-plugin-android-okhttp/build.gradle.kts +++ b/bugsnag-plugin-android-okhttp/build.gradle.kts @@ -1,21 +1,18 @@ plugins { id("bugsnag-build-plugin") + id("com.android.library") } -apply(plugin = "com.android.library") +apply(from = "../gradle/kotlin.gradle") dependencies { add("api", project(":bugsnag-android-core")) -} -apply(from = "../gradle/kotlin.gradle") - -dependencies { - "compileOnly"("com.squareup.okhttp3:okhttp:4.9.1") { + add("compileOnly", "com.squareup.okhttp3:okhttp:4.9.1") { exclude(group = "org.jetbrains.kotlin") } - "testImplementation"("com.squareup.okhttp3:mockwebserver:4.9.1") { + add("testImplementation", "com.squareup.okhttp3:mockwebserver:4.9.1") { exclude(group = "org.jetbrains.kotlin") } } diff --git a/bugsnag-plugin-react-native/build.gradle.kts b/bugsnag-plugin-react-native/build.gradle.kts index 9c2c33a38e..da00b5fbe3 100644 --- a/bugsnag-plugin-react-native/build.gradle.kts +++ b/bugsnag-plugin-react-native/build.gradle.kts @@ -1,9 +1,8 @@ plugins { id("bugsnag-build-plugin") + id("com.android.library") } -apply(plugin = "com.android.library") - dependencies { add("api", project(":bugsnag-android-core")) } diff --git a/build.gradle.kts b/build.gradle.kts index 5ff26b96fc..07f756315d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,10 @@ - buildscript { repositories { google() mavenCentral() maven(url = "https://plugins.gradle.org/m2/") } + dependencies { classpath("com.android.tools.build:gradle:7.0.4") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10") diff --git a/settings.gradle b/settings.gradle.kts similarity index 90% rename from settings.gradle rename to settings.gradle.kts index a561e56bf8..a093eafc48 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id "com.gradle.enterprise" version "3.5" + id("com.gradle.enterprise") version "3.5" } gradleEnterprise { From 048c0a8759e42e9f08ef540ff256310b7891d25c Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 30 Apr 2024 17:11:57 +0100 Subject: [PATCH 17/23] chore(build): moved kotlin compiler options into BugsnagBuildPlugin --- bugsnag-android-core/build.gradle.kts | 2 -- bugsnag-plugin-android-anr/build.gradle.kts | 2 -- .../build.gradle.kts | 5 ----- bugsnag-plugin-android-ndk/build.gradle.kts | 2 -- bugsnag-plugin-android-okhttp/build.gradle.kts | 2 -- buildSrc/build.gradle.kts | 1 + .../com/bugsnag/android/BugsnagBuildPlugin.kt | 18 ++++++++++++++++++ .../kotlin/com/bugsnag/android/Versions.kt | 1 + gradle/kotlin.gradle | 13 ------------- 9 files changed, 20 insertions(+), 26 deletions(-) delete mode 100644 gradle/kotlin.gradle diff --git a/bugsnag-android-core/build.gradle.kts b/bugsnag-android-core/build.gradle.kts index db30980a95..6640936437 100644 --- a/bugsnag-android-core/build.gradle.kts +++ b/bugsnag-android-core/build.gradle.kts @@ -23,8 +23,6 @@ tasks.getByName("dokkaHtml") { } } -apply(from = "../gradle/kotlin.gradle") - // pick up dsl-json by adding to the default sourcesets android { sourceSets { diff --git a/bugsnag-plugin-android-anr/build.gradle.kts b/bugsnag-plugin-android-anr/build.gradle.kts index 1574b67ef9..12457bd329 100644 --- a/bugsnag-plugin-android-anr/build.gradle.kts +++ b/bugsnag-plugin-android-anr/build.gradle.kts @@ -10,5 +10,3 @@ bugsnagBuildOptions { dependencies { add("api", project(":bugsnag-android-core")) } - -apply(from = "../gradle/kotlin.gradle") diff --git a/bugsnag-plugin-android-exitinfo/build.gradle.kts b/bugsnag-plugin-android-exitinfo/build.gradle.kts index c0b736c803..6a5fdc1b5a 100644 --- a/bugsnag-plugin-android-exitinfo/build.gradle.kts +++ b/bugsnag-plugin-android-exitinfo/build.gradle.kts @@ -6,11 +6,6 @@ plugins { dependencies { api(project(":bugsnag-android-core")) -} - -apply(from = "../gradle/kotlin.gradle") - -dependencies { implementation("com.google.protobuf:protobuf-javalite:3.24.2") } diff --git a/bugsnag-plugin-android-ndk/build.gradle.kts b/bugsnag-plugin-android-ndk/build.gradle.kts index bc576cc3a3..10104bb5ae 100644 --- a/bugsnag-plugin-android-ndk/build.gradle.kts +++ b/bugsnag-plugin-android-ndk/build.gradle.kts @@ -12,8 +12,6 @@ dependencies { api(project(":bugsnag-android-core")) } -apply(from = "../gradle/kotlin.gradle") - afterEvaluate { tasks.create("prefabReleasePackage") { doLast { diff --git a/bugsnag-plugin-android-okhttp/build.gradle.kts b/bugsnag-plugin-android-okhttp/build.gradle.kts index e01e18be95..9bfba99091 100644 --- a/bugsnag-plugin-android-okhttp/build.gradle.kts +++ b/bugsnag-plugin-android-okhttp/build.gradle.kts @@ -3,8 +3,6 @@ plugins { id("com.android.library") } -apply(from = "../gradle/kotlin.gradle") - dependencies { add("api", project(":bugsnag-android-core")) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index f34c558161..901628b472 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -20,4 +20,5 @@ repositories { dependencies { compileOnly(gradleApi()) implementation("com.android.tools.build:gradle:7.0.2") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10") } diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt index dc7571d57d..74a3e05ed6 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt @@ -6,6 +6,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.dependencies +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import java.io.File /** @@ -65,6 +66,8 @@ class BugsnagBuildPlugin : Plugin { project.apply(from = project.file("../gradle/license-check.gradle")) if (bugsnag.compilesCode) { + project.configureKotlinOptions() + project.apply(from = project.file("../gradle/detekt.gradle")) project.apply(from = project.file("../gradle/checkstyle.gradle")) } @@ -175,6 +178,21 @@ class BugsnagBuildPlugin : Plugin { } } + private fun Project.configureKotlinOptions() { + tasks.withType(KotlinCompile::class.java).configureEach { + kotlinOptions { + allWarningsAsErrors = true + apiVersion = Versions.kotlinLang + languageVersion = Versions.kotlinLang + freeCompilerArgs += listOf( + "-Xno-call-assertions", + "-Xno-receiver-assertions", + "-Xno-param-assertions" + ) + } + } + } + /** * Configures Android project defaults such as minSdkVersion. */ diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt index 6e672819bb..ae6b8ecc45 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt @@ -12,6 +12,7 @@ object Versions { val ndk = "23.1.7779620" val java = JavaVersion.VERSION_1_8 val kotlin = "1.5.10" + val kotlinLang = "1.5" // plugins val androidGradlePlugin = "7.0.4" diff --git a/gradle/kotlin.gradle b/gradle/kotlin.gradle deleted file mode 100644 index 2d3be612fd..0000000000 --- a/gradle/kotlin.gradle +++ /dev/null @@ -1,13 +0,0 @@ -android { - kotlinOptions { - allWarningsAsErrors = true - jvmTarget = "1.8" - apiVersion = "1.5" - languageVersion = "1.5" - freeCompilerArgs += [ - '-Xno-call-assertions', - '-Xno-receiver-assertions', - '-Xno-param-assertions' - ] - } -} From 2fca9e08b78bfe342918afcc7214fa4397093cad Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 1 May 2024 08:47:32 +0100 Subject: [PATCH 18/23] chore(build): moved checkstyle options into BugsnagBuildPlugin --- .../com/bugsnag/android/BugsnagBuildPlugin.kt | 3 ++- .../kotlin/com/bugsnag/android/Checkstyle.kt | 23 +++++++++++++++++++ features/fixtures/mazerunner/app/build.gradle | 2 +- .../cxx-scenarios-bugsnag/build.gradle | 2 +- .../mazerunner/cxx-scenarios/build.gradle | 2 +- .../mazerunner/gradle}/checkstyle.gradle | 0 .../mazerunner/jvm-scenarios/build.gradle | 2 +- 7 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 buildSrc/src/main/kotlin/com/bugsnag/android/Checkstyle.kt rename {gradle => features/fixtures/mazerunner/gradle}/checkstyle.gradle (100%) diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt index 74a3e05ed6..5026b29086 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt @@ -67,9 +67,9 @@ class BugsnagBuildPlugin : Plugin { if (bugsnag.compilesCode) { project.configureKotlinOptions() + project.configureCheckstyle() project.apply(from = project.file("../gradle/detekt.gradle")) - project.apply(from = project.file("../gradle/checkstyle.gradle")) } } @@ -215,6 +215,7 @@ class BugsnagBuildPlugin : Plugin { plugins.apply("com.github.hierynomus.license") if (bugsnag.compilesCode) { + plugins.apply("checkstyle") plugins.apply("kotlin-android") plugins.apply("io.gitlab.arturbosch.detekt") plugins.apply("org.jlleitschuh.gradle.ktlint") diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/Checkstyle.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/Checkstyle.kt new file mode 100644 index 0000000000..989a9ae6ad --- /dev/null +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/Checkstyle.kt @@ -0,0 +1,23 @@ +package com.bugsnag.android + +import org.gradle.api.Project +import org.gradle.api.plugins.quality.Checkstyle +import org.gradle.api.plugins.quality.CheckstyleExtension +import org.gradle.kotlin.dsl.get + +fun Project.configureCheckstyle() { + extensions.getByType(CheckstyleExtension::class.java).apply { + toolVersion = "8.18" + } + + val checkstyle = tasks.register("checkstyle", Checkstyle::class.java) { + configFile = rootProject.file("config/checkstyle/checkstyle.xml") + source = fileTree("src/") { + include("**/*.java") + exclude("**/external/**/*.java") + } + classpath = files() + } + + tasks["check"].dependsOn(checkstyle) +} \ No newline at end of file diff --git a/features/fixtures/mazerunner/app/build.gradle b/features/fixtures/mazerunner/app/build.gradle index 79eab90a7a..d63778abf7 100644 --- a/features/fixtures/mazerunner/app/build.gradle +++ b/features/fixtures/mazerunner/app/build.gradle @@ -94,4 +94,4 @@ dependencies { apply from: "../bugsnag-dependency.gradle" apply from: "../../../../gradle/detekt.gradle" -apply from: "../../../../gradle/checkstyle.gradle" +apply from: "../gradle/checkstyle.gradle" diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/build.gradle b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/build.gradle index 341504a053..3d2486d0c1 100644 --- a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/build.gradle +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/build.gradle @@ -46,4 +46,4 @@ dependencies { } apply from: "../../../../gradle/detekt.gradle" -apply from: "../../../../gradle/checkstyle.gradle" +apply from: "../gradle/checkstyle.gradle" diff --git a/features/fixtures/mazerunner/cxx-scenarios/build.gradle b/features/fixtures/mazerunner/cxx-scenarios/build.gradle index 9e5c2da16b..ca3600068b 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/build.gradle +++ b/features/fixtures/mazerunner/cxx-scenarios/build.gradle @@ -43,4 +43,4 @@ dependencies { apply from: "../bugsnag-dependency.gradle" apply from: "../../../../gradle/detekt.gradle" -apply from: "../../../../gradle/checkstyle.gradle" +apply from: "../gradle/checkstyle.gradle" diff --git a/gradle/checkstyle.gradle b/features/fixtures/mazerunner/gradle/checkstyle.gradle similarity index 100% rename from gradle/checkstyle.gradle rename to features/fixtures/mazerunner/gradle/checkstyle.gradle diff --git a/features/fixtures/mazerunner/jvm-scenarios/build.gradle b/features/fixtures/mazerunner/jvm-scenarios/build.gradle index 411e5d1294..79f5df6035 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/build.gradle +++ b/features/fixtures/mazerunner/jvm-scenarios/build.gradle @@ -48,4 +48,4 @@ private boolean useLegacyOkHttp() { apply from: "../bugsnag-dependency.gradle" apply from: "../../../../gradle/detekt.gradle" -apply from: "../../../../gradle/checkstyle.gradle" +apply from: "../gradle/checkstyle.gradle" From cfc20061a6f51782e54b058e1c82355a6b36690f Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 1 May 2024 09:00:26 +0100 Subject: [PATCH 19/23] test(mazerunner): fixed use of Handler() constructor --- .../src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt | 3 ++- .../android/mazerunner/scenarios/StartupCrashFlushScenario.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt index 71e5af0b66..f60934082c 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt @@ -1,6 +1,7 @@ package com.bugsnag.android.mazerunner import android.os.Handler +import android.os.Looper val mutex = Any() @@ -21,7 +22,7 @@ fun createDeadlock() { } ).start() - Handler().postDelayed( + Handler(Looper.getMainLooper()).postDelayed( object : java.lang.Runnable { override fun run() { synchronized(mutex) { throw java.lang.IllegalStateException() } diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartupCrashFlushScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartupCrashFlushScenario.kt index 531890eb0f..faf9f675bc 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartupCrashFlushScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartupCrashFlushScenario.kt @@ -2,6 +2,7 @@ package com.bugsnag.android.mazerunner.scenarios import android.content.Context import android.os.Handler +import android.os.Looper import com.bugsnag.android.Configuration import com.bugsnag.android.mazerunner.disableAllDelivery @@ -35,7 +36,7 @@ internal class StartupCrashFlushScenario( override fun startScenario() { super.startScenario() if ("CrashOfflineWithDelay" == eventMetadata) { - Handler().postDelayed( + Handler(Looper.getMainLooper()).postDelayed( Runnable { throw RuntimeException("Regular crash") }, From 2cf873c11ed2aeed1b4b41dd4a73443b5c575f8c Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 1 May 2024 09:18:44 +0100 Subject: [PATCH 20/23] chore(build): corrected the build plugin apply order --- bugsnag-android-core/build.gradle.kts | 29 ++++++++++--------- bugsnag-plugin-android-anr/build.gradle.kts | 3 +- bugsnag-plugin-android-ndk/build.gradle.kts | 7 +++-- .../com/bugsnag/android/BugsnagBuildPlugin.kt | 10 ++++--- .../android/BugsnagBuildPluginExtension.kt | 6 ++++ 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/bugsnag-android-core/build.gradle.kts b/bugsnag-android-core/build.gradle.kts index 6640936437..d6774d451a 100644 --- a/bugsnag-android-core/build.gradle.kts +++ b/bugsnag-android-core/build.gradle.kts @@ -1,14 +1,27 @@ +import kotlinx.validation.ApiValidationExtension import org.jetbrains.dokka.gradle.DokkaTask plugins { id("bugsnag-build-plugin") - id("com.android.library") } bugsnagBuildOptions { usesNdk = true + + // pick up dsl-json by adding to the default sourcesets + android { + sourceSets { + named("main") { + java.srcDirs("dsl-json/library/src/main/java") + } + named("test") { + java.srcDirs("dsl-json/library/src/test/java") + } + } + } } +apply(plugin = "com.android.library") apply(plugin = "org.jetbrains.dokka") tasks.getByName("dokkaHtml") { @@ -23,16 +36,6 @@ tasks.getByName("dokkaHtml") { } } -// pick up dsl-json by adding to the default sourcesets -android { - sourceSets { - named("main") { - java.srcDirs("dsl-json/library/src/main/java") - } - named("test") { - java.srcDirs("dsl-json/library/src/test/java") - } - } +plugins.withId("org.jetbrains.kotlinx.binary-compatibility-validator") { + project.extensions.getByType(ApiValidationExtension::class.java).ignoredPackages.add("com.bugsnag.android.repackaged.dslplatform.json") } - -apiValidation.ignoredPackages.add("com.bugsnag.android.repackaged.dslplatform.json") diff --git a/bugsnag-plugin-android-anr/build.gradle.kts b/bugsnag-plugin-android-anr/build.gradle.kts index 12457bd329..9f778ce9f9 100644 --- a/bugsnag-plugin-android-anr/build.gradle.kts +++ b/bugsnag-plugin-android-anr/build.gradle.kts @@ -1,12 +1,13 @@ plugins { id("bugsnag-build-plugin") - id("com.android.library") } bugsnagBuildOptions { usesNdk = true } +apply(plugin = "com.android.library") + dependencies { add("api", project(":bugsnag-android-core")) } diff --git a/bugsnag-plugin-android-ndk/build.gradle.kts b/bugsnag-plugin-android-ndk/build.gradle.kts index 10104bb5ae..1006109c80 100644 --- a/bugsnag-plugin-android-ndk/build.gradle.kts +++ b/bugsnag-plugin-android-ndk/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("bugsnag-build-plugin") - id("com.android.library") } bugsnagBuildOptions { @@ -8,12 +7,14 @@ bugsnagBuildOptions { publishesPrefab = "bugsnag-ndk" } +apply(plugin = "com.android.library") + dependencies { - api(project(":bugsnag-android-core")) + add("api", project(":bugsnag-android-core")) } afterEvaluate { - tasks.create("prefabReleasePackage") { + tasks.getByName("prefabReleasePackage") { doLast { project.fileTree("build/intermediates/prefab_package/") { include("**/abi.json") diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt index 5026b29086..3f0d29e096 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt @@ -40,7 +40,7 @@ class BugsnagBuildPlugin : Plugin { // load 3rd party gradle plugins project.applyPlugins(bugsnag) - val android = project.extensions.getByType(BaseExtension::class.java) + val android = project.extensions.getByType(LibraryExtension::class.java) android.apply { configureDefaults() configureAndroidLint(project) @@ -51,11 +51,13 @@ class BugsnagBuildPlugin : Plugin { configureNdk(project) bugsnag.publishesPrefab?.let { prefabModuleName -> - (android as? LibraryExtension)?.run { - configurePrefabPublishing(prefabModuleName) - } + configurePrefabPublishing(prefabModuleName) } } + + bugsnag.androidConfiguration.forEach { config -> + config(android) + } } // add 3rd party dependencies to the project diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPluginExtension.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPluginExtension.kt index c1eabb8def..d2637be468 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPluginExtension.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPluginExtension.kt @@ -1,5 +1,6 @@ package com.bugsnag.android +import com.android.build.api.dsl.LibraryExtension import org.gradle.api.model.ObjectFactory /** @@ -9,6 +10,8 @@ import org.gradle.api.model.ObjectFactory */ open class BugsnagBuildPluginExtension(@Suppress("UNUSED_PARAMETER") objects: ObjectFactory) { + internal val androidConfiguration = ArrayList Unit>() + /** * Whether this project compiles code or not. If this is set to false then unnecessary * plugins are not applied, which speeds up the build. By default this is enabled. @@ -25,4 +28,7 @@ open class BugsnagBuildPluginExtension(@Suppress("UNUSED_PARAMETER") objects: Ob */ open var publishesPrefab: String? = null + fun android(config: LibraryExtension.() -> Unit) { + androidConfiguration.add(config) + } } From 7d49790f4f29e4e15c67a182b0d689ac5c762612 Mon Sep 17 00:00:00 2001 From: YingYing Chen <40571804+YYChen01988@users.noreply.github.com> Date: Mon, 13 May 2024 09:33:36 +0100 Subject: [PATCH 21/23] fix(event)Remove Failed delivery internal reports with logger error message (#2026) --- .../src/main/java/com/bugsnag/android/EventStore.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt index bb1172c769..6eb471d22d 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt @@ -208,7 +208,7 @@ internal class EventStore( } private fun handleEventFlushFailure(exc: Exception, eventFile: File) { - delegate?.onErrorIOFailure(exc, eventFile, "Crash Report Deserialization") + logger.e(exc.message ?: "Failed to send event", exc) deleteStoredFiles(setOf(eventFile)) } From 2c5a6d847d307cf15fdd1f2cdcc1e1faf4fc4577 Mon Sep 17 00:00:00 2001 From: YingYing Chen <40571804+YYChen01988@users.noreply.github.com> Date: Mon, 13 May 2024 11:55:56 +0100 Subject: [PATCH 22/23] Integrate bugsnag-android-performance into bugsnag-android MazeRunner (#2024) * feat(implementation)implement bugsnag performance to measure the start span * fix(test)set bugsnag performance to environment variable and fix test --- features/fixtures/mazerunner/app/build.gradle | 5 ++++- .../mazerunner/app/src/main/AndroidManifest.xml | 1 + .../com/bugsnag/android/mazerunner/MazerunnerApp.kt | 5 +++++ .../fixtures/mazerunner/jvm-scenarios/build.gradle | 7 ++++++- .../scenarios/LoadConfigurationFromManifestScenario.kt | 3 +-- .../scenarios/LoadConfigurationKotlinScenario.kt | 2 +- .../scenarios/MultiThreadedStartupScenario.kt | 4 ++-- .../bugsnag/android/mazerunner/scenarios/Scenario.kt | 10 +++++++++- .../scenarios/SharedPrefMigrationScenario.kt | 3 +++ 9 files changed, 32 insertions(+), 8 deletions(-) diff --git a/features/fixtures/mazerunner/app/build.gradle b/features/fixtures/mazerunner/app/build.gradle index d63778abf7..aab4b30864 100644 --- a/features/fixtures/mazerunner/app/build.gradle +++ b/features/fixtures/mazerunner/app/build.gradle @@ -8,13 +8,15 @@ android { ndkVersion parent.ext.ndkVersion defaultConfig { - minSdkVersion 16 + minSdkVersion 17 targetSdkVersion 31 versionCode 34 versionName "1.1.14" manifestPlaceholders = [ // omit any of the following placeholders to use the default values bugsnagApiKey: "abc12312312312312312312312312312", + bugsnagPerformanceApiKey: System.getenv("BUGSNAG_PERFORMANCE_API_KEY") + ?: "abc12312312312312312312312312312", bugsnagAppType: "test", bugsnagAppVersion: "7.5.3", bugsnagAutoDetectErrors: true, @@ -90,6 +92,7 @@ dependencies { } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.annotation:annotation:1.2.0" + implementation "com.bugsnag:bugsnag-android-performance:1.2.2" } apply from: "../bugsnag-dependency.gradle" diff --git a/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml b/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml index 1e4571c14e..d0439114c9 100644 --- a/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml +++ b/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ /> + diff --git a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt index ad586c4f5d..f07e7fd46c 100644 --- a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt +++ b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt @@ -3,6 +3,9 @@ package com.bugsnag.android.mazerunner import android.app.Application import android.os.Build import android.os.StrictMode +import com.bugsnag.android.performance.BugsnagPerformance +import com.bugsnag.android.performance.PerformanceConfiguration +import com.bugsnag.android.performance.internal.InternalDebug class MazerunnerApp : Application() { @@ -11,6 +14,8 @@ class MazerunnerApp : Application() { triggerStartupAnrIfRequired() setupNonSdkUsageStrictMode() triggerManualSessionIfRequired() + InternalDebug.spanBatchSizeSendTriggerPoint = 1 + BugsnagPerformance.start(PerformanceConfiguration.load(this)) } /** diff --git a/features/fixtures/mazerunner/jvm-scenarios/build.gradle b/features/fixtures/mazerunner/jvm-scenarios/build.gradle index 79f5df6035..c14c318d52 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/build.gradle +++ b/features/fixtures/mazerunner/jvm-scenarios/build.gradle @@ -10,7 +10,11 @@ android { compileSdkVersion 31 defaultConfig { - minSdkVersion 14 + minSdkVersion 17 + manifestPlaceholders = [ + bugsnagPerformanceApiKey: System.getenv("BUGSNAG_PERFORMANCE_API_KEY") + ?: "abc12312312312312312312312312312", + ] } buildTypes { @@ -40,6 +44,7 @@ dependencies { project.logger.lifecycle("Using OkHttp 4 dependency in test fixture") implementation "com.squareup.okhttp3:okhttp:4.9.1" } + implementation "com.bugsnag:bugsnag-android-performance:1.2.2" } private boolean useLegacyOkHttp() { diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationFromManifestScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationFromManifestScenario.kt index 72c04300b0..5162172192 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationFromManifestScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationFromManifestScenario.kt @@ -30,8 +30,7 @@ internal class LoadConfigurationFromManifestScenario( true } ) - - reportBugsnagStartupDuration { Bugsnag.start(this.context, testConfig) } + measureBugsnagStartupDuration(this.context, testConfig) } override fun startScenario() { diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationKotlinScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationKotlinScenario.kt index 46700a5620..73dd62feef 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationKotlinScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationKotlinScenario.kt @@ -46,7 +46,7 @@ internal class LoadConfigurationKotlinScenario( } ) - reportBugsnagStartupDuration { Bugsnag.start(this.context, testConfig) } + measureBugsnagStartupDuration(this.context, testConfig) } override fun startScenario() { diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/MultiThreadedStartupScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/MultiThreadedStartupScenario.kt index c29d85fd33..aea3d67d10 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/MultiThreadedStartupScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/MultiThreadedStartupScenario.kt @@ -14,7 +14,7 @@ class MultiThreadedStartupScenario( override fun startScenario() { val startThread = thread(name = "AsyncStart") { - reportBugsnagStartupDuration { Bugsnag.start(context, config) } + measureBugsnagStartupDuration(context, config) } thread(name = "leaveBreadcrumb") { @@ -24,7 +24,7 @@ class MultiThreadedStartupScenario( Bugsnag.leaveBreadcrumb("I'm leaving a breadcrumb on another thread") Bugsnag.notify(Exception("Scenario complete")) } catch (e: Exception) { - reportBugsnagStartupDuration { Bugsnag.start(context, config) } + measureBugsnagStartupDuration(context, config) Bugsnag.notify(e) } } diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt index 232c5ef09d..eddb4e51f8 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt @@ -14,12 +14,14 @@ import android.os.HandlerThread import android.os.Looper import android.util.Log import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Client import com.bugsnag.android.Configuration import com.bugsnag.android.mazerunner.BugsnagIntentParams import com.bugsnag.android.mazerunner.MazerunnerHttpClient import com.bugsnag.android.mazerunner.log import com.bugsnag.android.mazerunner.multiprocess.MultiProcessService import com.bugsnag.android.mazerunner.multiprocess.findCurrentProcessName +import com.bugsnag.android.performance.measureSpan import java.io.File import kotlin.system.measureNanoTime @@ -77,13 +79,19 @@ abstract class Scenario( ) { startup() } } + fun measureBugsnagStartupDuration(context: Context, config: Configuration): Client { + return measureSpan("Bugsnag Startup") { + Bugsnag.start(context, config) + } + } + /** * Initializes Bugsnag. It is possible to override this method if the scenario requires * it - e.g., if the config needs to be loaded from the manifest. */ open fun startBugsnag(startBugsnagOnly: Boolean) { this.startBugsnagOnly = startBugsnagOnly - reportBugsnagStartupDuration { Bugsnag.start(context, config) } + measureBugsnagStartupDuration(context, config) } /** diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/SharedPrefMigrationScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/SharedPrefMigrationScenario.kt index 8f66d59c08..cbc9bb9375 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/SharedPrefMigrationScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/SharedPrefMigrationScenario.kt @@ -5,6 +5,7 @@ import android.content.Context import com.bugsnag.android.Bugsnag import com.bugsnag.android.Configuration import com.bugsnag.android.mazerunner.BugsnagIntentParams +import java.io.File /** * User/device information is migrated from the legacy [SharedPreferences] location @@ -21,6 +22,8 @@ internal class SharedPrefMigrationScenario( override fun startBugsnag(startBugsnagOnly: Boolean) { persistLegacyPrefs() + // make sure there is no "leftover" device-id file to interfere with the test + File(context.filesDir, "device-id").delete() super.startBugsnag(startBugsnagOnly) } From 2992145562254cbd8f05667d020b94b5b728d9d3 Mon Sep 17 00:00:00 2001 From: YYChen01988 Date: Wed, 15 May 2024 11:08:58 +0100 Subject: [PATCH 23/23] release v6.5.0 --- CHANGELOG.md | 2 +- .../src/main/java/com/bugsnag/android/Notifier.kt | 2 +- examples/sdk-app-example/app/build.gradle | 4 ++-- gradle.properties | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c8f5d355..9e501cbd60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## TBD +## 6.5.0 (2024-05-15) ### Enhancements diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt index bea14c68a8..4cc446d679 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt @@ -7,7 +7,7 @@ import java.io.IOException */ class Notifier @JvmOverloads constructor( var name: String = "Android Bugsnag Notifier", - var version: String = "6.4.0", + var version: String = "6.5.0", var url: String = "https://bugsnag.com" ) : JsonStream.Streamable { diff --git a/examples/sdk-app-example/app/build.gradle b/examples/sdk-app-example/app/build.gradle index 9b25b2fd0c..28a225a86b 100644 --- a/examples/sdk-app-example/app/build.gradle +++ b/examples/sdk-app-example/app/build.gradle @@ -42,8 +42,8 @@ android { } dependencies { - implementation "com.bugsnag:bugsnag-android:6.4.0" - implementation "com.bugsnag:bugsnag-plugin-android-okhttp:6.4.0" + implementation "com.bugsnag:bugsnag-android:6.5.0" + implementation "com.bugsnag:bugsnag-plugin-android-okhttp:6.5.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.appcompat:appcompat:1.6.1" implementation "com.google.android.material:material:1.11.0" diff --git a/gradle.properties b/gradle.properties index 73dfdc3f4e..f27b1d23a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ org.gradle.jvmargs=-Xmx4096m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects org.gradle.parallel=true -VERSION_NAME=6.4.0 +VERSION_NAME=6.5.0 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git