-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1801 from bugsnag/PLAT-9577/fix-corrupt-feature-f…
…lags Fix rare cases of corrupt feature flags in NDK reports
- Loading branch information
Showing
14 changed files
with
833 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
224 changes: 224 additions & 0 deletions
224
...oid-ndk/src/androidTest/java/com/bugsnag/android/ndk/migrations/EventMigrationV12Tests.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
package com.bugsnag.android.ndk.migrations | ||
|
||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertNotEquals | ||
import org.junit.Assert.fail | ||
import org.junit.Test | ||
|
||
/** Migration v12 added telemetry data */ | ||
class EventMigrationV12Tests : EventMigrationTest() { | ||
|
||
@Test | ||
/** check notifier and api key, since they aren't included in event JSON */ | ||
fun testMigrationPayloadInfo() { | ||
val infoFile = createTempFile() | ||
|
||
val info = migratePayloadInfo(infoFile.absolutePath) | ||
|
||
assertEquals( | ||
mapOf( | ||
"apiKey" to "5d1e5fbd39a74caa1200142706a90b20", | ||
"notifierName" to "Test Library", | ||
"notifierURL" to "https://example.com/test-lib", | ||
"notifierVersion" to "2.0.11" | ||
), | ||
parseJSON(info) | ||
) | ||
} | ||
|
||
@Test | ||
fun testMigrateEventToLatest() { | ||
val eventFile = createTempFile() | ||
|
||
migrateEvent(eventFile.absolutePath) | ||
assertNotEquals(0, eventFile.length()) | ||
|
||
val output = parseJSON(eventFile) | ||
|
||
assertEquals( | ||
"00000000000m0r3.61ee9e6e099d3dd7448f740d395768da6b2df55d5.m4g1c", | ||
output["context"] | ||
) | ||
assertEquals( | ||
"a1d34088a096987361ee9e6e099d3dd7448f740d395768da6b2df55d5160f33", | ||
output["groupingHash"] | ||
) | ||
assertEquals("info", output["severity"]) | ||
|
||
// app | ||
assertEquals( | ||
mapOf( | ||
"binaryArch" to "mips", | ||
"buildUUID" to "1234-9876-adfe", | ||
"duration" to 81395165021L, | ||
"durationInForeground" to 81395165010L, | ||
"id" to "com.example.PhotoSnapPlus", | ||
"inForeground" to true, | ||
"isLaunching" to true, | ||
"releaseStage" to "リリース", | ||
"type" to "red", | ||
"version" to "2.0.52", | ||
"versionCode" to 8139512718L | ||
), | ||
output["app"] | ||
) | ||
|
||
// breadcrumbs | ||
val crumbs = output["breadcrumbs"] | ||
if (crumbs is List<Any?>) { | ||
assertEquals(50, crumbs.size) | ||
crumbs.forEachIndexed { index, crumb -> | ||
assertEquals( | ||
mapOf( | ||
"type" to "state", | ||
"name" to "mission $index", | ||
"timestamp" to "2021-12-08T19:43:50.014Z", | ||
"metaData" to mapOf( | ||
"message" to "Now we know what they mean by 'advanced' tactical training." | ||
) | ||
), | ||
crumb | ||
) | ||
} | ||
} else { | ||
fail("breadcrumbs is not a list of crumb objects?!") | ||
} | ||
|
||
// device | ||
assertEquals( | ||
mapOf( | ||
"cpuAbi" to listOf("mipsx"), | ||
"id" to "ffffa", | ||
"locale" to "en_AU#Melbun", | ||
"jailbroken" to true, | ||
"manufacturer" to "HI-TEC™", | ||
"model" to "🍨", | ||
"orientation" to "sideup", | ||
"osName" to "BOX BOX", | ||
"osVersion" to "98.7", | ||
"runtimeVersions" to mapOf( | ||
"osBuild" to "beta1-2", | ||
"androidApiLevel" to "32" | ||
), | ||
"time" to "2021-12-08T19:43:50Z", | ||
"totalMemory" to 3839512576L | ||
), | ||
output["device"] | ||
) | ||
|
||
// feature flags | ||
assertEquals( | ||
listOf( | ||
mapOf( | ||
"featureFlag" to "bluebutton", | ||
"variant" to "on" | ||
), | ||
mapOf( | ||
"featureFlag" to "redbutton", | ||
"variant" to "off" | ||
), | ||
mapOf("featureFlag" to "nobutton"), | ||
mapOf( | ||
"featureFlag" to "switch", | ||
"variant" to "left" | ||
) | ||
), | ||
output["featureFlags"] | ||
) | ||
|
||
// exceptions | ||
assertEquals( | ||
listOf( | ||
mapOf( | ||
"errorClass" to "SIGBUS", | ||
"message" to "POSIX is serious about oncoming traffic", | ||
"type" to "c", | ||
"stacktrace" to listOf( | ||
mapOf( | ||
"frameAddress" to "0xfffffffe", | ||
"lineNumber" to 4194967233L, | ||
"loadAddress" to "0x242023", | ||
"symbolAddress" to "0x308", | ||
"method" to "makinBacon", | ||
"file" to "lib64/libfoo.so", | ||
"isPC" to true | ||
), | ||
mapOf( | ||
"frameAddress" to "0xb37a644b", | ||
"lineNumber" to 0L, | ||
"loadAddress" to "0x0", | ||
"symbolAddress" to "0x0", | ||
"method" to "0xb37a644b" // test address to method hex | ||
) | ||
) | ||
) | ||
), | ||
output["exceptions"] | ||
) | ||
|
||
// metadata | ||
assertEquals( | ||
mapOf( | ||
"app" to mapOf( | ||
"activeScreen" to "Menu", | ||
"weather" to "rain" | ||
), | ||
"metrics" to mapOf( | ||
"experimentX" to false, | ||
"subject" to "percy", | ||
"counter" to 47.5.toBigDecimal() | ||
) | ||
), | ||
output["metaData"] | ||
) | ||
|
||
// session info | ||
assertEquals( | ||
mapOf( | ||
"id" to "aaaaaaaaaaaaaaaa", | ||
"startedAt" to "2031-07-09T11:08:21+00:00", | ||
"events" to mapOf( | ||
"handled" to 5L, | ||
"unhandled" to 2L | ||
) | ||
), | ||
output["session"] | ||
) | ||
|
||
// threads | ||
val threads = output["threads"] | ||
if (threads is List<Any?>) { | ||
assertEquals(8, threads.size) | ||
threads.forEachIndexed { index, thread -> | ||
assertEquals( | ||
mapOf( | ||
"name" to "Thread #$index", | ||
"state" to "paused-$index", | ||
"id" to 1000L + index, | ||
"type" to "c" | ||
), | ||
thread | ||
) | ||
} | ||
} else { | ||
fail("threads is not a list of thread objects?!") | ||
} | ||
|
||
// user | ||
assertEquals( | ||
mapOf( | ||
"email" to "fenton@io.example.com", | ||
"name" to "Fenton", | ||
"id" to "fex01" | ||
), | ||
output["user"] | ||
) | ||
} | ||
|
||
/** Migrate an event to the latest format, writing JSON to tempFilePath */ | ||
external fun migrateEvent(tempFilePath: String) | ||
|
||
/** Migrate notifier and apiKey info to a bespoke structure (apiKey and | ||
* notifier are not included in event info written to disk) */ | ||
external fun migratePayloadInfo(tempFilePath: String): String | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#include "seqlock.h" | ||
|
||
/* | ||
* Similar to a Sequence Lock (seqlock) in the Linux Kernel, implemented as a | ||
* pure-spinning lock. | ||
* | ||
* When the value of the lock (counter) is odd then a write is in progress. This | ||
* allows us to also perform optimistic-reads without waiting for the lock. | ||
* | ||
* This implementation does *not* support blocking reads, and will not block | ||
* optimistic reads when a write is in-progress. This allows for reads of | ||
* invalid/stale data within a signal handler boundary. | ||
*/ | ||
|
||
#ifndef spin | ||
|
||
#if (defined(__i386__) || defined(__amd64__)) | ||
#define spin() __builtin_ia32_pause() | ||
#elif (defined(__aarch64__) || defined(__arm__)) | ||
#define spin() asm inline("yield") | ||
#else | ||
#define spin() | ||
#endif | ||
|
||
#endif // #ifndef spin | ||
|
||
void bsg_seqlock_init(bsg_seqlock_t *lock) { atomic_init(lock, 0uLL); } | ||
|
||
void bsg_seqlock_acquire_write(bsg_seqlock_t *lock) { | ||
atomic_thread_fence(memory_order_acquire); | ||
bsg_seqlock_status_t current = atomic_load(lock); | ||
|
||
for (;;) { | ||
if (bsg_seqlock_is_write_locked(current)) { | ||
spin(); | ||
} else if (atomic_compare_exchange_strong(lock, ¤t, current + 1)) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
void bsg_seqlock_release_write(bsg_seqlock_t *lock) { | ||
bsg_seqlock_status_t current = atomic_load(lock); | ||
|
||
for (;;) { | ||
if (!bsg_seqlock_is_write_locked(current)) { | ||
break; | ||
} else if (atomic_compare_exchange_strong(lock, ¤t, current + 1)) { | ||
break; | ||
} | ||
} | ||
|
||
atomic_thread_fence(memory_order_release); | ||
} | ||
|
||
bsg_seqlock_status_t bsg_seqlock_optimistic_read(bsg_seqlock_t *lock) { | ||
bsg_seqlock_status_t status = atomic_load(lock); | ||
// optimistic reads are never valid during a write, and we return 0 in these | ||
// cases validate will always return `false` in these cases | ||
return bsg_seqlock_is_write_locked(status) ? 0 : status; | ||
} | ||
|
||
bool bsg_seqlock_validate(bsg_seqlock_t *lock, bsg_seqlock_status_t expected) { | ||
atomic_thread_fence(memory_order_acquire); | ||
return atomic_load(lock) == expected; | ||
} |
Oops, something went wrong.