From fcf6136939d3226a068f913362859848e3400d2f Mon Sep 17 00:00:00 2001 From: Karl Stenerud Date: Mon, 27 Jun 2022 16:56:29 +0200 Subject: [PATCH 1/6] Store addresses in string form when writing to JSON --- bugsnag-android-core/detekt-baseline.xml | 10 +++ .../com/bugsnag/android/BugsnagEventMapper.kt | 26 +------- .../com/bugsnag/android/NativeStackframe.kt | 7 +- .../java/com/bugsnag/android/Stackframe.kt | 17 ++--- .../bugsnag/android/internal/JsonHelper.kt | 64 +++++++++++++++++++ .../android/StackframeSerializationTest.kt | 4 +- .../android/internal/JsonHelperTest.kt | 47 ++++++++++++++ .../native_stackframe_serialization_0.json | 6 +- .../resources/stackframe_serialization_1.json | 6 +- .../ndk/migrations/EventMigrationV4Tests.kt | 12 ++-- .../ndk/migrations/EventMigrationV5Tests.kt | 12 ++-- .../ndk/migrations/EventMigrationV6Tests.kt | 12 ++-- .../ndk/migrations/EventMigrationV7Tests.kt | 12 ++-- .../ndk/migrations/EventMigrationV8Tests.kt | 12 ++-- .../ndk/migrations/EventMigrationV9Tests.kt | 12 ++-- .../resources/exception_serialization.json | 2 +- .../resources/stackframe_serialization.json | 2 +- .../main/jni/utils/serializer/json_writer.c | 18 ++++-- .../src/test/cpp/test_utils_serialize.c | 5 +- features/smoke_tests/unhandled.feature | 8 +-- 20 files changed, 198 insertions(+), 96 deletions(-) create mode 100644 bugsnag-android-core/src/test/java/com/bugsnag/android/internal/JsonHelperTest.kt diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index 7376eafd3a..517ebb8201 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -22,9 +22,17 @@ MagicNumber:DefaultDelivery.kt$DefaultDelivery$299 MagicNumber:DefaultDelivery.kt$DefaultDelivery$429 MagicNumber:DefaultDelivery.kt$DefaultDelivery$499 + MagicNumber:JsonHelper.kt$JsonHelper$0xff + MagicNumber:JsonHelper.kt$JsonHelper$1000 + MagicNumber:JsonHelper.kt$JsonHelper$16 + MagicNumber:JsonHelper.kt$JsonHelper$18 + MagicNumber:JsonHelper.kt$JsonHelper$19 + MagicNumber:JsonHelper.kt$JsonHelper$3 + MagicNumber:JsonHelper.kt$JsonHelper$8 MagicNumber:LastRunInfoStore.kt$LastRunInfoStore$3 MaxLineLength:LastRunInfo.kt$LastRunInfo$return "LastRunInfo(consecutiveLaunchCrashes=$consecutiveLaunchCrashes, crashed=$crashed, crashedDuringLaunch=$crashedDuringLaunch)" MaxLineLength:ThreadState.kt$ThreadState$"[${allThreads.size - maxThreadCount} threads omitted as the maxReportedThreads limit ($maxThreadCount) was exceeded]" + NestedBlockDepth:JsonHelper.kt$JsonHelper$ fun jsonToLong(value: Any?): Long? ProtectedMemberInFinalClass:ConfigInternal.kt$ConfigInternal$protected val plugins = HashSet<Plugin>() ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun isAnr(event: Event): Boolean ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun shouldDiscardClass(): Boolean @@ -39,7 +47,9 @@ SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get battery status") } SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get locationStatus") } SwallowedException:DeviceIdFilePersistence.kt$DeviceIdFilePersistence$catch (exc: OverlappingFileLockException) { Thread.sleep(FILE_LOCK_WAIT_MS) } + SwallowedException:JsonHelperTest.kt$JsonHelperTest$catch (e: IllegalArgumentException) { didThrow = true } SwallowedException:PluginClient.kt$PluginClient$catch (exc: ClassNotFoundException) { logger.d("Plugin '$clz' is not on the classpath - functionality will not be enabled.") null } + ThrowsCount:JsonHelper.kt$JsonHelper$ fun jsonToLong(value: Any?): Long? TooManyFunctions:ConfigInternal.kt$ConfigInternal : CallbackAwareMetadataAwareUserAwareFeatureFlagAware TooManyFunctions:DeviceDataCollector.kt$DeviceDataCollector TooManyFunctions:EventInternal.kt$EventInternal : FeatureFlagAwareStreamableMetadataAwareUserAware diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagEventMapper.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagEventMapper.kt index d850562671..62d5825a30 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagEventMapper.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagEventMapper.kt @@ -184,31 +184,7 @@ internal class BugsnagEventMapper( } internal fun convertStacktrace(trace: List>): Stacktrace { - return Stacktrace(trace.map { convertStackframe(it) }) - } - - internal fun convertStackframe(frame: Map): Stackframe { - val copy: MutableMap = frame.toMutableMap() - val lineNumber = frame["lineNumber"] as? Number - copy["lineNumber"] = lineNumber?.toLong() - - (frame["frameAddress"] as? String)?.let { - copy["frameAddress"] = java.lang.Long.decode(it) - } - - (frame["symbolAddress"] as? String)?.let { - copy["symbolAddress"] = java.lang.Long.decode(it) - } - - (frame["loadAddress"] as? String)?.let { - copy["loadAddress"] = java.lang.Long.decode(it) - } - - (frame["isPC"] as? Boolean)?.let { - copy["isPC"] = it - } - - return Stackframe(copy) + return Stacktrace(trace.map { Stackframe(it) }) } internal fun deserializeSeverityReason( diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/NativeStackframe.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/NativeStackframe.kt index 3e82416a85..30b40be440 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/NativeStackframe.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/NativeStackframe.kt @@ -1,5 +1,6 @@ package com.bugsnag.android +import com.bugsnag.android.internal.JsonHelper import java.io.IOException /** @@ -59,9 +60,9 @@ class NativeStackframe internal constructor( writer.name("method").value(method) writer.name("file").value(file) writer.name("lineNumber").value(lineNumber) - writer.name("frameAddress").value(frameAddress) - writer.name("symbolAddress").value(symbolAddress) - writer.name("loadAddress").value(loadAddress) + frameAddress?.let { writer.name("frameAddress").value(JsonHelper.ulongToHex(frameAddress)) } + symbolAddress?.let { writer.name("symbolAddress").value(JsonHelper.ulongToHex(symbolAddress)) } + loadAddress?.let { writer.name("loadAddress").value(JsonHelper.ulongToHex(loadAddress)) } writer.name("codeIdentifier").value(codeIdentifier) writer.name("isPC").value(isPC) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Stackframe.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/Stackframe.kt index 7ac08889bd..ed43d5b7e9 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Stackframe.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Stackframe.kt @@ -1,5 +1,6 @@ package com.bugsnag.android +import com.bugsnag.android.internal.JsonHelper import java.io.IOException /** @@ -103,13 +104,13 @@ class Stackframe : JsonStream.Streamable { internal constructor(json: Map) { method = json["method"] as? String file = json["file"] as? String - lineNumber = json["lineNumber"] as? Number + lineNumber = JsonHelper.jsonToLong(json["lineNumber"]) inProject = json["inProject"] as? Boolean columnNumber = json["columnNumber"] as? Number - frameAddress = (json["frameAddress"] as? Number)?.toLong() - symbolAddress = (json["symbolAddress"] as? Number)?.toLong() - loadAddress = (json["loadAddress"] as? Number)?.toLong() - codeIdentifier = (json["codeIdentifier"] as? String) + frameAddress = JsonHelper.jsonToLong(json["frameAddress"]) + symbolAddress = JsonHelper.jsonToLong(json["symbolAddress"]) + loadAddress = JsonHelper.jsonToLong(json["loadAddress"]) + codeIdentifier = json["codeIdentifier"] as? String isPC = json["isPC"] as? Boolean @Suppress("UNCHECKED_CAST") @@ -128,9 +129,9 @@ class Stackframe : JsonStream.Streamable { writer.name("columnNumber").value(columnNumber) - frameAddress?.let { writer.name("frameAddress").value(it) } - symbolAddress?.let { writer.name("symbolAddress").value(it) } - loadAddress?.let { writer.name("loadAddress").value(it) } + frameAddress?.let { writer.name("frameAddress").value(JsonHelper.ulongToHex(frameAddress)) } + symbolAddress?.let { writer.name("symbolAddress").value(JsonHelper.ulongToHex(symbolAddress)) } + loadAddress?.let { writer.name("loadAddress").value(JsonHelper.ulongToHex(loadAddress)) } codeIdentifier?.let { writer.name("codeIdentifier").value(it) } isPC?.let { writer.name("isPC").value(it) } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/JsonHelper.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/JsonHelper.kt index 8493d47895..6e69f71c6c 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/JsonHelper.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/JsonHelper.kt @@ -76,4 +76,68 @@ internal object JsonHelper { throw IOException("Could not deserialize from $file", ex) } } + + /** + * Convert a long that technically contains an unsigned long value into its (unsigned) hex string equivalent. + * Negative values are interpreted as if the sign bit is the high bit of an unsigned integer. + * + * Returns null if null is passed in. + */ + fun ulongToHex(value: Long?): String? { + return if (value == null) { + null + } else if (value >= 0) { + "0x%x".format(value) + } else { + return "0x%x%02x".format(value.ushr(8), value.and(0xff)) + } + } + + /** + * Convert a JSON-decoded value into a long. Accepts numeric types, or numeric encoded strings + * (e.g. "1234", "0xb1ff"). + * + * Returns null if null or an empty string is passed in. + */ + fun jsonToLong(value: Any?): Long? { + return when (value) { + null -> null + is Number -> value.toLong() + is String -> { + if (value.length == 0) { + null + } else { + try { + java.lang.Long.decode(value) + } catch (e: NumberFormatException) { + // Check if the value overflows a long, and correct for it. + if (value.startsWith("0x")) { + // All problematic hex values (e.g. 0x8000000000000000) have 18 characters + if (value.length != 18) { + throw e + } + // Decode all but the last byte, then shift and add it. + // This overflows and gives the "correct" signed result. + val headLength = value.length - 2 + java.lang.Long.decode(value.substring(0, headLength)) + .shl(8) + .or(value.substring(headLength, value.length).toLong(16)) + } else { + // The first problematic decimal value (9223372036854775808) has 19 digits + if (value.length < 19) { + throw e + } + // Decode all but the last 3 chars, then multiply and add them. + // This overflows and gives the "correct" signed result. + val headLength = value.length - 3 + java.lang.Long.decode(value.substring(0, headLength)) * + 1000 + + java.lang.Long.decode(value.substring(headLength, value.length)) + } + } + } + } + else -> throw IllegalArgumentException("Cannot convert " + value + " to long") + } + } } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/StackframeSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/StackframeSerializationTest.kt index c68a68953f..57901ffde5 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/StackframeSerializationTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/StackframeSerializationTest.kt @@ -43,14 +43,12 @@ internal class StackframeSerializationTest { @Parameter lateinit var testCase: Pair - private val eventMapper = BugsnagEventMapper(NoopLogger) - @Test fun testJsonSerialisation() = verifyJsonMatches(testCase.first, testCase.second) @Test fun testJsonDeserialisation() = verifyJsonParser(testCase.first, testCase.second) { - eventMapper.convertStackframe(it) + Stackframe(it) } } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/internal/JsonHelperTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/internal/JsonHelperTest.kt new file mode 100644 index 0000000000..7ea3e4d493 --- /dev/null +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/internal/JsonHelperTest.kt @@ -0,0 +1,47 @@ +package com.bugsnag.android.internal + +import org.junit.Assert.assertEquals +import org.junit.Test + +internal class JsonHelperTest { + fun assertBidirectional(longValue: Long?, stringValue: String?) { + assertEquals(stringValue, JsonHelper.ulongToHex(longValue)) + assertEquals(longValue, JsonHelper.jsonToLong(stringValue)) + } + + fun assertDecode(stringValue: String?, longValue: Long?) { + assertEquals(longValue, JsonHelper.jsonToLong(stringValue)) + } + + @Test + fun jsonLongConversions() { + assertBidirectional(null, null) + assertBidirectional(0, "0x0") + assertBidirectional(1, "0x1") + assertBidirectional(0x7fffffffffffffff, "0x7fffffffffffffff") + assertBidirectional(-1, "0xffffffffffffffff") + assertBidirectional(-0x7fffffffffffffff, "0x8000000000000001") + assertBidirectional(-0x7fffffffffffffff - 1, "0x8000000000000000") + + assertDecode("", null) + assertDecode("0", 0) + assertDecode("1", 1) + assertDecode("-1", -1) + assertDecode("9223372036854775807", 9223372036854775807) + assertDecode("-9223372036854775807", -9223372036854775807) + assertDecode("-9223372036854775808", -9223372036854775807 - 1) + assertDecode("9223372036854775808", -9223372036854775807 - 1) + assertDecode("0x8000000000000000", -9223372036854775807 - 1) + assertDecode("18446744073709551615", -1) + assertDecode("0xffffffffffffffff", -1) + + var didThrow = false + try { + JsonHelper.jsonToLong(false) + } catch (e: IllegalArgumentException) { + didThrow = true + } finally { + assert(didThrow, { "Expected to throw IllegalArgumentException" }) + } + } +} diff --git a/bugsnag-android-core/src/test/resources/native_stackframe_serialization_0.json b/bugsnag-android-core/src/test/resources/native_stackframe_serialization_0.json index fd0e2aecd0..ce8505cea0 100644 --- a/bugsnag-android-core/src/test/resources/native_stackframe_serialization_0.json +++ b/bugsnag-android-core/src/test/resources/native_stackframe_serialization_0.json @@ -2,9 +2,9 @@ "method": "aMethod", "file": "aFile", "lineNumber": 1, - "frameAddress": 2, - "symbolAddress": 3, - "loadAddress": 4, + "frameAddress": "0x2", + "symbolAddress": "0x3", + "loadAddress": "0x4", "isPC": true, "type": "c" } diff --git a/bugsnag-android-core/src/test/resources/stackframe_serialization_1.json b/bugsnag-android-core/src/test/resources/stackframe_serialization_1.json index a9fe2d7c6d..d9b7fe26de 100644 --- a/bugsnag-android-core/src/test/resources/stackframe_serialization_1.json +++ b/bugsnag-android-core/src/test/resources/stackframe_serialization_1.json @@ -2,8 +2,8 @@ "method": "aMethod", "file": "aFile", "lineNumber": 1, - "frameAddress": 2, - "symbolAddress": 3, - "loadAddress": 4, + "frameAddress": "0x2", + "symbolAddress": "0x3", + "loadAddress": "0x4", "type": "c" } diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV4Tests.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV4Tests.kt index 3e6eca92ee..7decba88b2 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV4Tests.kt +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV4Tests.kt @@ -118,19 +118,19 @@ class EventMigrationV4Tests : EventMigrationTest() { "type" to "c", "stacktrace" to listOf( mapOf( - "frameAddress" to 454379L, + "frameAddress" to "0x6eeeb", "lineNumber" to 0L, - "loadAddress" to 2367523L, - "symbolAddress" to 776L, + "loadAddress" to "0x242023", + "symbolAddress" to "0x308", "method" to "makinBacon", "file" to "lib64/libfoo.so", "isPC" to true ), mapOf( - "frameAddress" to 342334L, + "frameAddress" to "0x5393e", "lineNumber" to 0L, - "loadAddress" to 0L, - "symbolAddress" to 0L, + "loadAddress" to "0x0", + "symbolAddress" to "0x0", "method" to "0x5393e" // test address to method hex ) ) diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV5Tests.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV5Tests.kt index 4af268043e..4ca7240772 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV5Tests.kt +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV5Tests.kt @@ -119,19 +119,19 @@ class EventMigrationV5Tests : EventMigrationTest() { "type" to "c", "stacktrace" to listOf( mapOf( - "frameAddress" to 454379L, + "frameAddress" to "0x6eeeb", "lineNumber" to 0L, - "loadAddress" to 2367523L, - "symbolAddress" to 776L, + "loadAddress" to "0x242023", + "symbolAddress" to "0x308", "method" to "makinBacon", "file" to "lib64/libfoo.so", "isPC" to true ), mapOf( - "frameAddress" to 342334L, + "frameAddress" to "0x5393e", "lineNumber" to 0L, - "loadAddress" to 0L, - "symbolAddress" to 0L, + "loadAddress" to "0x0", + "symbolAddress" to "0x0", "method" to "0x5393e" // test address to method hex ) ) diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV6Tests.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV6Tests.kt index 9a7e2ae6fd..4d8afdf46b 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV6Tests.kt +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV6Tests.kt @@ -118,19 +118,19 @@ class EventMigrationV6Tests : EventMigrationTest() { "type" to "c", "stacktrace" to listOf( mapOf( - "frameAddress" to 454379L, + "frameAddress" to "0x6eeeb", "lineNumber" to 0L, - "loadAddress" to 2367523L, - "symbolAddress" to 776L, + "loadAddress" to "0x242023", + "symbolAddress" to "0x308", "method" to "makinBacon", "file" to "lib64/libfoo.so", "isPC" to true ), mapOf( - "frameAddress" to 342334L, + "frameAddress" to "0x5393e", "lineNumber" to 0L, - "loadAddress" to 0L, - "symbolAddress" to 0L, + "loadAddress" to "0x0", + "symbolAddress" to "0x0", "method" to "0x5393e" // test address to method hex ) ) diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV7Tests.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV7Tests.kt index 961ce551c5..9a4a6d146d 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV7Tests.kt +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV7Tests.kt @@ -118,19 +118,19 @@ class EventMigrationV7Tests : EventMigrationTest() { "type" to "c", "stacktrace" to listOf( mapOf( - "frameAddress" to 454379L, + "frameAddress" to "0x6eeeb", "lineNumber" to 0L, - "loadAddress" to 2367523L, - "symbolAddress" to 776L, + "loadAddress" to "0x242023", + "symbolAddress" to "0x308", "method" to "makinBacon", "file" to "lib64/libfoo.so", "isPC" to true ), mapOf( - "frameAddress" to 342334L, + "frameAddress" to "0x5393e", "lineNumber" to 0L, - "loadAddress" to 0L, - "symbolAddress" to 0L, + "loadAddress" to "0x0", + "symbolAddress" to "0x0", "method" to "0x5393e" // test address to method hex ) ) diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV8Tests.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV8Tests.kt index 13c630d12b..10994749ef 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV8Tests.kt +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV8Tests.kt @@ -135,19 +135,19 @@ class EventMigrationV8Tests : EventMigrationTest() { "type" to "c", "stacktrace" to listOf( mapOf( - "frameAddress" to 4294967294L, + "frameAddress" to "0xfffffffe", "lineNumber" to 4194967233L, - "loadAddress" to 2367523L, - "symbolAddress" to 776L, + "loadAddress" to "0x242023", + "symbolAddress" to "0x308", "method" to "makinBacon", "file" to "lib64/libfoo.so", "isPC" to true ), mapOf( - "frameAddress" to 3011142731L, + "frameAddress" to "0xb37a644b", "lineNumber" to 0L, - "loadAddress" to 0L, - "symbolAddress" to 0L, + "loadAddress" to "0x0", + "symbolAddress" to "0x0", "method" to "0xb37a644b" // test address to method hex ) ) diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV9Tests.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV9Tests.kt index e4626449e7..1c41667c2a 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV9Tests.kt +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV9Tests.kt @@ -135,19 +135,19 @@ class EventMigrationV9Tests : EventMigrationTest() { "type" to "c", "stacktrace" to listOf( mapOf( - "frameAddress" to 4294967294L, + "frameAddress" to "0xfffffffe", "lineNumber" to 4194967233L, - "loadAddress" to 2367523L, - "symbolAddress" to 776L, + "loadAddress" to "0x242023", + "symbolAddress" to "0x308", "method" to "makinBacon", "file" to "lib64/libfoo.so", "isPC" to true ), mapOf( - "frameAddress" to 3011142731L, + "frameAddress" to "0xb37a644b", "lineNumber" to 0L, - "loadAddress" to 0L, - "symbolAddress" to 0L, + "loadAddress" to "0x0", + "symbolAddress" to "0x0", "method" to "0xb37a644b" // test address to method hex ) ) diff --git a/bugsnag-plugin-android-ndk/src/androidTest/resources/exception_serialization.json b/bugsnag-plugin-android-ndk/src/androidTest/resources/exception_serialization.json index 130c2d3a8a..4bb42ad10e 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/resources/exception_serialization.json +++ b/bugsnag-plugin-android-ndk/src/androidTest/resources/exception_serialization.json @@ -1 +1 @@ -{"stacktrace":[{"frameAddress":536870912,"symbolAddress":369098752,"loadAddress":301989888,"lineNumber":52,"isPC":true,"file":"foo.c","method":"bar()"}],"errorClass":"signal","message":"whoops something went wrong","type":"c"} \ No newline at end of file +{"stacktrace":[{"frameAddress":"0x20000000","symbolAddress":"0x16000000","loadAddress":"0x12000000","lineNumber":52,"isPC":true,"file":"foo.c","method":"bar()"}],"errorClass":"signal","message":"whoops something went wrong","type":"c"} \ No newline at end of file diff --git a/bugsnag-plugin-android-ndk/src/androidTest/resources/stackframe_serialization.json b/bugsnag-plugin-android-ndk/src/androidTest/resources/stackframe_serialization.json index cf85931a12..f62a8babb8 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/resources/stackframe_serialization.json +++ b/bugsnag-plugin-android-ndk/src/androidTest/resources/stackframe_serialization.json @@ -1 +1 @@ -[{"frameAddress":536870912,"symbolAddress":369098752,"loadAddress":301989888,"lineNumber":52,"file":"foo.c","method":"bar()"}] \ No newline at end of file +[{"frameAddress":"0x20000000","symbolAddress":"0x16000000","loadAddress":"0x12000000","lineNumber":52,"file":"foo.c","method":"bar()"}] \ No newline at end of file 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 0da1a547bf..446004d8a2 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 @@ -246,13 +246,20 @@ void bsg_serialize_error(bsg_error exc, JSON_Object *exception, } } +static void set_hex_number(JSON_Object *frame, const char *name, + unsigned long value) { + char hex_str[20]; + sprintf(hex_str, "0x%lx", value); + json_object_set_string(frame, name, hex_str); +} + void bsg_serialize_stackframe(bugsnag_stackframe *stackframe, bool is_pc, JSON_Array *stacktrace) { JSON_Value *frame_val = json_value_init_object(); JSON_Object *frame = json_value_get_object(frame_val); - json_object_set_number(frame, "frameAddress", (*stackframe).frame_address); - json_object_set_number(frame, "symbolAddress", (*stackframe).symbol_address); - json_object_set_number(frame, "loadAddress", (*stackframe).load_address); + set_hex_number(frame, "frameAddress", (*stackframe).frame_address); + set_hex_number(frame, "symbolAddress", (*stackframe).symbol_address); + set_hex_number(frame, "loadAddress", (*stackframe).load_address); json_object_set_number(frame, "lineNumber", (*stackframe).line_number); if (is_pc) { // only necessary to set to true, false is the default value and omitting @@ -263,10 +270,7 @@ void bsg_serialize_stackframe(bugsnag_stackframe *stackframe, bool is_pc, json_object_set_string(frame, "file", (*stackframe).filename); } if (strlen((*stackframe).method) == 0) { - char *frame_address = calloc(1, sizeof(char) * 32); - sprintf(frame_address, "0x%lx", (unsigned long)(*stackframe).frame_address); - json_object_set_string(frame, "method", frame_address); - free(frame_address); + set_hex_number(frame, "method", (*stackframe).frame_address); } else { json_object_set_string(frame, "method", (*stackframe).method); } 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 ac84f6245c..8376cc97d5 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 @@ -11,6 +11,7 @@ #include #include + #define SERIALIZE_TEST_FILE "/data/data/com.bugsnag.android.ndk.test/cache/foo.crash" bugsnag_breadcrumb *init_breadcrumb(const char *name, const char *message, bugsnag_breadcrumb_type type); @@ -956,9 +957,9 @@ TEST test_exception_to_json(void) { ASSERT(stacktrace != NULL); ASSERT_EQ(2, json_array_get_count(stacktrace)); ASSERT(strcmp("makinBacon", json_object_get_string(json_array_get_object(stacktrace, 0), "method")) == 0); - ASSERT_EQ(454379, json_object_get_number(json_array_get_object(stacktrace, 0), "frameAddress")); + ASSERT_STR_EQ("0x6eeeb", json_object_get_string(json_array_get_object(stacktrace, 0), "frameAddress")); ASSERT(strcmp("0x5393e", json_object_get_string(json_array_get_object(stacktrace, 1), "method")) == 0); - ASSERT_EQ(342334, json_object_get_number(json_array_get_object(stacktrace, 1), "frameAddress")); + ASSERT_STR_EQ("0x5393e", json_object_get_string(json_array_get_object(stacktrace, 1), "frameAddress")); json_value_free(root_value); PASS(); } diff --git a/features/smoke_tests/unhandled.feature b/features/smoke_tests/unhandled.feature index c725451577..8ea97b62a5 100644 --- a/features/smoke_tests/unhandled.feature +++ b/features/smoke_tests/unhandled.feature @@ -124,10 +124,10 @@ Feature: Unhandled smoke tests And the event stacktrace identifies the program counter And the event "exceptions.0.stacktrace.0.method" is not null And the event "exceptions.0.stacktrace.0.file" is not null - And the error payload field "events.0.exceptions.0.stacktrace.0.frameAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.symbolAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.loadAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.lineNumber" is greater than 0 + And the error payload field "events.0.exceptions.0.stacktrace.0.frameAddress" is not null + And the error payload field "events.0.exceptions.0.stacktrace.0.symbolAddress" is not null + And the error payload field "events.0.exceptions.0.stacktrace.0.loadAddress" is not null + And the error payload field "events.0.exceptions.0.stacktrace.0.lineNumber" is not null # App data And the event binary arch field is valid From 788338f6db622534714840c4f1f300dd3749ad59 Mon Sep 17 00:00:00 2001 From: Karl Stenerud Date: Thu, 7 Jul 2022 09:32:59 +0200 Subject: [PATCH 2/6] Preserve ordering of feature flags --- CHANGELOG.md | 3 + .../java/com/bugsnag/android/FeatureFlags.kt | 23 +-- .../android/FeatureFlagsSerializationTest.kt | 4 +- .../feature_flags_serialization_0.json | 6 +- .../feature_flags_serialization_1.json | 6 +- .../src/main/jni/featureflags.c | 169 ++++++++---------- .../src/test/cpp/test_featureflags.c | 23 +-- 7 files changed, 109 insertions(+), 125 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c75df32246..1a03db7a3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Enhancements +* Feature flags are now kept in and trimmed in order of insertion or modification rather than in alphabetical order. + [#1718](https://github.com/bugsnag/bugsnag-android/pull/1718) + * Complex metadata (nested structures such as maps & lists) added in Java/Kotlin is now fully preserved in NDK errors [#1715](https://github.com/bugsnag/bugsnag-android/pull/1715) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/FeatureFlags.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/FeatureFlags.kt index bce01bff5d..97f3ed47d6 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/FeatureFlags.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/FeatureFlags.kt @@ -1,39 +1,40 @@ package com.bugsnag.android import java.io.IOException -import java.util.concurrent.ConcurrentHashMap internal class FeatureFlags( - internal val store: MutableMap = ConcurrentHashMap() + internal val store: MutableMap = mutableMapOf() ) : JsonStream.Streamable, FeatureFlagAware { private val emptyVariant = "__EMPTY_VARIANT_SENTINEL__" - override fun addFeatureFlag(name: String) { - store[name] = emptyVariant + @Synchronized override fun addFeatureFlag(name: String) { + addFeatureFlag(name, null) } - override fun addFeatureFlag(name: String, variant: String?) { + @Synchronized override fun addFeatureFlag(name: String, variant: String?) { + store.remove(name) store[name] = variant ?: emptyVariant } - override fun addFeatureFlags(featureFlags: Iterable) { + @Synchronized override fun addFeatureFlags(featureFlags: Iterable) { featureFlags.forEach { (name, variant) -> addFeatureFlag(name, variant) } } - override fun clearFeatureFlag(name: String) { + @Synchronized override fun clearFeatureFlag(name: String) { store.remove(name) } - override fun clearFeatureFlags() { + @Synchronized override fun clearFeatureFlags() { store.clear() } @Throws(IOException::class) override fun toStream(stream: JsonStream) { + val storeCopy = synchronized(this) { store.toMap() } stream.beginArray() - store.forEach { (name, variant) -> + storeCopy.forEach { (name, variant) -> stream.beginObject() stream.name("featureFlag").value(name) if (variant != emptyVariant) { @@ -44,9 +45,9 @@ internal class FeatureFlags( stream.endArray() } - fun toList(): List = store.entries.map { (name, variant) -> + @Synchronized fun toList(): List = store.entries.map { (name, variant) -> FeatureFlag(name, variant.takeUnless { it == emptyVariant }) } - fun copy() = FeatureFlags(store.toMutableMap()) + @Synchronized fun copy() = FeatureFlags(store.toMutableMap()) } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/FeatureFlagsSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/FeatureFlagsSerializationTest.kt index 770c57d807..7fe98148aa 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/FeatureFlagsSerializationTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/FeatureFlagsSerializationTest.kt @@ -17,14 +17,14 @@ internal class FeatureFlagsSerializationTest { ) private fun basic() = FeatureFlags().apply { - addFeatureFlag("demo_mode") addFeatureFlag("sample_group", "a") + addFeatureFlag("demo_mode") addFeatureFlag("view_mode", "modern") } private fun overrideVariants() = FeatureFlags().apply { - addFeatureFlag("demo_mode") addFeatureFlag("sample_group", "a") + addFeatureFlag("demo_mode") addFeatureFlag("sample_group", "b") } diff --git a/bugsnag-android-core/src/test/resources/feature_flags_serialization_0.json b/bugsnag-android-core/src/test/resources/feature_flags_serialization_0.json index 0cf85a47a1..8acee3ddc6 100644 --- a/bugsnag-android-core/src/test/resources/feature_flags_serialization_0.json +++ b/bugsnag-android-core/src/test/resources/feature_flags_serialization_0.json @@ -4,10 +4,10 @@ "variant": "a" }, { - "featureFlag": "view_mode", - "variant": "modern" + "featureFlag": "demo_mode" }, { - "featureFlag": "demo_mode" + "featureFlag": "view_mode", + "variant": "modern" } ] diff --git a/bugsnag-android-core/src/test/resources/feature_flags_serialization_1.json b/bugsnag-android-core/src/test/resources/feature_flags_serialization_1.json index 662fbe4a0a..08127d1ab1 100644 --- a/bugsnag-android-core/src/test/resources/feature_flags_serialization_1.json +++ b/bugsnag-android-core/src/test/resources/feature_flags_serialization_1.json @@ -1,9 +1,9 @@ [ { - "featureFlag": "sample_group", - "variant": "b" + "featureFlag": "demo_mode" }, { - "featureFlag": "demo_mode" + "featureFlag": "sample_group", + "variant": "b" } ] diff --git a/bugsnag-plugin-android-ndk/src/main/jni/featureflags.c b/bugsnag-plugin-android-ndk/src/main/jni/featureflags.c index dbbf485d65..f077d5f238 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/featureflags.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/featureflags.c @@ -11,131 +11,110 @@ extern "C" { /* * Implementation notes: * - * We store Feature Flags in a dynamically allocated array, sorted by the - * feature flag 'name' string, allowing us to binary-search the array for - * duplicates. + * We store Feature Flags in a dynamically allocated array, maintaining the + * insertion order. Modifying an existing entry causes a reinsertion + * (moving it to the end of the array). + * + * Searches are linear, but the impact should be negligible up to tens of + * thousands of entries. * * This provides a reasonable compromise between speed and size, since the array * is no larger than the number of feature flags (not counting malloc padding) - * and is unlikely to be modified often. It also keeps testing simple, as the - * keys will always be in a known order. + * and is unlikely to be modified often. * * We resize the array using 'realloc' and 'memmove' to try and keep the * overhead reasonable. */ -static int feature_flag_index(const bugsnag_event *env, const char *name) { - // simple binary search for a feature-flag by name - int low = 0; - int high = env->feature_flag_count - 1; - - while (low <= high) { - int mid = (low + high) >> 1; - int cmp = strcmp(env->feature_flags[mid].name, name); - - if (cmp < 0) { - low = mid + 1; - } else if (cmp > 0) { - high = mid - 1; - } else { - return mid; // found it +static const int INDEX_NOT_FOUND = -1; + +static int index_of_flag_named(const bugsnag_event *const event, + const char *const name) { + for (int i = 0; i < event->feature_flag_count; i++) { + if (strcmp(event->feature_flags[i].name, name) == 0) { + return i; } } - - return -(low + 1); + return INDEX_NOT_FOUND; } -static void *grow_array_for_index(void *array, const size_t element_count, - const size_t element_size, - const unsigned int index) { - void *new_array = realloc(array, (element_count + 1) * element_size); - - if (!new_array) { - return NULL; +static void remove_at_index_and_compact(bugsnag_event *const event, + const int index) { + if (event->feature_flag_count > 1 && index < event->feature_flag_count - 1) { + memmove(&event->feature_flags[index], &event->feature_flags[index + 1], + (event->feature_flag_count - index) * + sizeof(event->feature_flags[0])); } - - // if we need to: shift the "end" of the array by 1 element so 'index' has a - // space - memmove(new_array + ((index + 1) * element_size), - new_array + (index * element_size), - (element_count - index) * element_size); - - return new_array; } -void bsg_set_feature_flag(bugsnag_event *event, const char *name, - const char *variant) { - int expected_index = feature_flag_index(event, name); - - if (expected_index >= 0) { - // feature flag already exists, so we overwrite the variant - bsg_feature_flag *flag = &event->feature_flags[expected_index]; - - // make sure we release the existing variant, if one exists - free(flag->variant); +static void free_flag_contents(bsg_feature_flag *const flag) { + free(flag->name); + free(flag->variant); +} - if (variant) { - // make a copy of the variant, so that the JVM can have it's memory back - flag->variant = strdup(variant); - } else { - flag->variant = NULL; - } +static void set_flag_variant(bsg_feature_flag *const flag, + const char *const variant) { + if (variant == NULL) { + flag->variant = NULL; } else { - int new_flag_index = -expected_index - 1; - - // this is a new feature flag, we need to insert it - which means we also - // need a new array - bsg_feature_flag *new_flags = - grow_array_for_index(event->feature_flags, event->feature_flag_count, - sizeof(bsg_feature_flag), new_flag_index); - - // we cannot grow the feature flag array, so we return - if (!new_flags) { - return; - } - - new_flags[new_flag_index].name = strdup(name); - - if (variant) { - new_flags[new_flag_index].variant = strdup(variant); - } else { - new_flags[new_flag_index].variant = NULL; - } - - event->feature_flags = new_flags; - event->feature_flag_count = event->feature_flag_count + 1; + flag->variant = strdup(variant); } } -void bsg_clear_feature_flag(bugsnag_event *event, const char *name) { - int flag_index = feature_flag_index(event, name); +static void insert_new(bugsnag_event *const event, const char *const name, + const char *const variant) { + bsg_feature_flag *new_flags = + realloc(event->feature_flags, (event->feature_flag_count + 1) * + sizeof(event->feature_flags[0])); + if (!new_flags) { + return; + } + event->feature_flags = new_flags; - if (flag_index < 0) { - // no such feature flag - early exit + bsg_feature_flag *flag = &new_flags[event->feature_flag_count]; + flag->name = strdup(name); + if (flag->name == NULL) { return; } + set_flag_variant(flag, variant); - bsg_feature_flag *flag = &event->feature_flags[flag_index]; + event->feature_flag_count++; +} - // release the memory held for name and possibly the variant - free(flag->name); - free(flag->variant); +static void modify_at_index_and_reinsert(bugsnag_event *const event, + const int index, + const char *const variant) { + bsg_feature_flag flag = event->feature_flags[index]; + free(flag.variant); + set_flag_variant(&flag, variant); - // pack the array elements down to fill in the "gap" - // we don't resize the array down by one, that gets handled when elements are - // added - memmove( - &event->feature_flags[flag_index], &event->feature_flags[flag_index + 1], - (event->feature_flag_count - flag_index - 1) * sizeof(bsg_feature_flag)); + remove_at_index_and_compact(event, index); + event->feature_flags[event->feature_flag_count - 1] = flag; +} - // mark the array as having one-less flag - event->feature_flag_count = event->feature_flag_count - 1; +void bsg_set_feature_flag(bugsnag_event *event, const char *const name, + const char *const variant) { + const int index = index_of_flag_named(event, name); + if (index == INDEX_NOT_FOUND) { + insert_new(event, name, variant); + } else { + modify_at_index_and_reinsert(event, index, variant); + } +} + +void bsg_clear_feature_flag(bugsnag_event *const event, + const char *const name) { + const int index = index_of_flag_named(event, name); + if (index != INDEX_NOT_FOUND) { + free_flag_contents(&event->feature_flags[index]); + remove_at_index_and_compact(event, index); + event->feature_flag_count--; + } } -void bsg_free_feature_flags(bugsnag_event *event) { +void bsg_free_feature_flags(bugsnag_event *const event) { for (int index = 0; index < event->feature_flag_count; index++) { - free(event->feature_flags[index].name); - free(event->feature_flags[index].variant); + free_flag_contents(&event->feature_flags[index]); } free(event->feature_flags); diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/test_featureflags.c b/bugsnag-plugin-android-ndk/src/test/cpp/test_featureflags.c index a6d1e0cea7..582095b3c4 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/test_featureflags.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/test_featureflags.c @@ -6,19 +6,19 @@ TEST test_set_feature_flag(void) { bsg_set_feature_flag(event, "sample_group", "a"); bsg_set_feature_flag(event, "demo_mode", NULL); - bsg_set_feature_flag(event, "demo_mode", "yes"); bsg_set_feature_flag(event, "zzz", NULL); + bsg_set_feature_flag(event, "demo_mode", "yes"); ASSERT_EQ(3, event->feature_flag_count); - ASSERT_STR_EQ("demo_mode", event->feature_flags[0].name); - ASSERT_STR_EQ("yes", event->feature_flags[0].variant); + ASSERT_STR_EQ("sample_group", event->feature_flags[0].name); + ASSERT_STR_EQ("a", event->feature_flags[0].variant); - ASSERT_STR_EQ("sample_group", event->feature_flags[1].name); - ASSERT_STR_EQ("a", event->feature_flags[1].variant); + ASSERT_STR_EQ("zzz", event->feature_flags[1].name); + ASSERT_EQ(NULL, event->feature_flags[1].variant); - ASSERT_STR_EQ("zzz", event->feature_flags[2].name); - ASSERT_EQ(NULL, event->feature_flags[2].variant); + ASSERT_STR_EQ("demo_mode", event->feature_flags[2].name); + ASSERT_STR_EQ("yes", event->feature_flags[2].variant); bsg_free_feature_flags(event); free(event); @@ -38,12 +38,13 @@ TEST test_clear_feature_flag(void) { bsg_clear_feature_flag(event, "no_such_flag"); ASSERT_EQ(3, event->feature_flag_count); - bsg_clear_feature_flag(event, "sample_group"); bsg_clear_feature_flag(event, "demo_mode"); - ASSERT_EQ(1, event->feature_flag_count); - ASSERT_STR_EQ("remaining", event->feature_flags[0].name); - ASSERT_STR_EQ("flag", event->feature_flags[0].variant); + ASSERT_EQ(2, event->feature_flag_count); + ASSERT_STR_EQ("sample_group", event->feature_flags[0].name); + ASSERT_STR_EQ("a", event->feature_flags[0].variant); + ASSERT_STR_EQ("remaining", event->feature_flags[1].name); + ASSERT_STR_EQ("flag", event->feature_flags[1].variant); bsg_free_feature_flags(event); free(event); From 51be4a714dcea4ec83605e441273dfd5ff804f10 Mon Sep 17 00:00:00 2001 From: Karl Stenerud Date: Wed, 13 Jul 2022 13:52:16 +0200 Subject: [PATCH 3/6] Allow feature flags to be accessed from the event object during callbacks --- CHANGELOG.md | 7 +++++++ .../src/main/java/com/bugsnag/android/Event.java | 9 +++++++++ .../mazerunner/scenarios/OnSendCallbackScenario.kt | 6 ++++++ features/full_tests/onsend_callback.feature | 2 ++ 4 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12167c32b..270d795c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## TBD + +### Enhancements + +* Feature flags can now be accessed in the onSend callbacks. + [#1720](https://github.com/bugsnag/bugsnag-android/pull/1720) + ## 5.24.0 (2022-06-30) ### Enhancements diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Event.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Event.java index e3c9fa7d4e..4d815b6323 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Event.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Event.java @@ -91,6 +91,15 @@ public List getBreadcrumbs() { return impl.getBreadcrumbs(); } + /** + * A list of feature flags active at the time of the event. + * See {@link FeatureFlag} for details of the data available. + */ + @NonNull + public List getFeatureFlags() { + return impl.getFeatureFlags().toList(); + } + /** * Information set by the notifier about your app can be found in this field. These values * can be accessed and amended if necessary. diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/OnSendCallbackScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/OnSendCallbackScenario.kt index 2f7d02045f..10e44b86d1 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/OnSendCallbackScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/OnSendCallbackScenario.kt @@ -1,6 +1,7 @@ package com.bugsnag.android.mazerunner.scenarios import android.content.Context +import com.bugsnag.android.Bugsnag import com.bugsnag.android.Configuration import com.bugsnag.android.OnSendCallback import java.lang.RuntimeException @@ -14,6 +15,8 @@ internal class OnSendCallbackScenario( init { config.addOnSend( OnSendCallback { event -> + event.clearFeatureFlag("deleteMe") + event.addFeatureFlag(event.featureFlags[0].name, "b") event.addMetadata("mazerunner", "onSendCallback", "true") event.apiKey = "99999999999999909999999999999999" true @@ -24,6 +27,9 @@ internal class OnSendCallbackScenario( override fun startScenario() { super.startScenario() + Bugsnag.addFeatureFlag("fromStartup", "a") + Bugsnag.addFeatureFlag("deleteMe") + if (eventMetadata != "start-only") { throw RuntimeException("Unhandled Error") } diff --git a/features/full_tests/onsend_callback.feature b/features/full_tests/onsend_callback.feature index 72fd0776b2..55860db063 100644 --- a/features/full_tests/onsend_callback.feature +++ b/features/full_tests/onsend_callback.feature @@ -12,6 +12,8 @@ Feature: OnSend Callbacks can alter Events before upload And the error payload field "apiKey" equals "99999999999999909999999999999999" And the exception "message" equals "Unhandled Error" And the event "metaData.mazerunner.onSendCallback" equals "true" + And the event "featureFlags.0.featureFlag" equals "fromStartup" + And the event "featureFlags.0.variant" equals "b" Scenario: Handled exception altered by OnSendCallback When I run "HandledOnSendCallbackScenario" From c78b5d5bf1f7a2567c8d0427085635901d620165 Mon Sep 17 00:00:00 2001 From: Karl Stenerud Date: Wed, 13 Jul 2022 14:08:26 +0200 Subject: [PATCH 4/6] Update CHANGELOG.md Co-authored-by: Jason --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 270d795c55..c8689dabcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Enhancements -* Feature flags can now be accessed in the onSend callbacks. +* Feature flags can now be accessed in the onSend and onError callbacks. [#1720](https://github.com/bugsnag/bugsnag-android/pull/1720) ## 5.24.0 (2022-06-30) From 6bd6ca4ba855f914705610ad44067f0d47273a00 Mon Sep 17 00:00:00 2001 From: Tom Longridge Date: Mon, 18 Jul 2022 14:57:11 +0100 Subject: [PATCH 5/6] docs(changelog): correct changelog entry incorrectly against 5.24.0 --- CHANGELOG.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8689dabcd..7949362eaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,16 +4,15 @@ ### Enhancements -* Feature flags can now be accessed in the onSend and onError callbacks. +* Feature flags can now be accessed in the onSend and onError callbacks [#1720](https://github.com/bugsnag/bugsnag-android/pull/1720) +* Feature flags are now kept in and trimmed in order of insertion or modification rather than in alphabetical order + [#1718](https://github.com/bugsnag/bugsnag-android/pull/1718) ## 5.24.0 (2022-06-30) ### Enhancements -* Feature flags are now kept in and trimmed in order of insertion or modification rather than in alphabetical order. - [#1718](https://github.com/bugsnag/bugsnag-android/pull/1718) - * Complex metadata (nested structures such as maps & lists) added in Java/Kotlin is now fully preserved in NDK errors [#1715](https://github.com/bugsnag/bugsnag-android/pull/1715) * Configuration.discardClasses now applies to NDK errors From 6ab443a17d38e0625803d44cb3ac4abe79420e2a Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 19 Jul 2022 08:21:22 +0100 Subject: [PATCH 6/6] v5.25.0 --- CHANGELOG.md | 2 +- .../src/main/java/com/bugsnag/android/Notifier.kt | 2 +- examples/sdk-app-example/app/build.gradle | 2 +- gradle.properties | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7949362eaf..9461434007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## TBD +## 5.25.0 (2022-07-19) ### 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 b58d92a025..c3da369f8f 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 = "5.24.0", + var version: String = "5.25.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 15e8395d25..b48acd4f40 100644 --- a/examples/sdk-app-example/app/build.gradle +++ b/examples/sdk-app-example/app/build.gradle @@ -38,7 +38,7 @@ android { } dependencies { - implementation "com.bugsnag:bugsnag-android:5.24.0" + implementation "com.bugsnag:bugsnag-android:5.25.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" diff --git a/gradle.properties b/gradle.properties index 14a551a3b9..aa8c0a4eb4 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=5.24.0 +VERSION_NAME=5.25.0 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git