From e40f655c2bbc1d43213e6b816e8ae950329a1278 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 23:21:42 +0000 Subject: [PATCH 01/16] build(deps): bump gradle/actions from 4.3.1 to 4.4.0 Bumps [gradle/actions](https://github.com/gradle/actions) from 4.3.1 to 4.4.0. - [Release notes](https://github.com/gradle/actions/releases) - [Commits](https://github.com/gradle/actions/compare/06832c7b30a0129d7fb559bcc6e43d26f6374244...8379f6a1328ee0e06e2bb424dadb7b159856a326) --- updated-dependencies: - dependency-name: gradle/actions dependency-version: 4.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4cf61a27bc..62558877fb 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -77,4 +77,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + - uses: gradle/actions/wrapper-validation@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 From b7e6f4aaad44fa7f88d413aad079a8d7a815858e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 23:23:34 +0000 Subject: [PATCH 02/16] build(deps): bump github/codeql-action from 3.28.16 to 3.28.18 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.28.18. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/28deaeda66b76a05916b6923827895f2b14ab387...ff0a06e83cb2de871e5a09832bc6a81e7276941f) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.28.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6d78438000..099957a752 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -64,7 +64,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 #v3.28.16 + uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -83,6 +83,6 @@ jobs: ./gradlew --no-daemon assemble - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 #v3.28.16 + uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4cf61a27bc..2a96c5c5a1 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -68,7 +68,7 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: sarif_file: results.sarif From 1f36865cfcf1d57112d9d180d0d20699a4dbb148 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 23:51:34 +0000 Subject: [PATCH 03/16] build(deps): bump ossf/scorecard-action from 2.4.1 to 2.4.2 Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.1 to 2.4.2. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/f49aabe0b5af0936a0987cfb85d86b75731b0186...05b42c624433fc40578a4040d5cf5e36ddca8cde) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-version: 2.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4cf61a27bc..ddb2a23672 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -37,7 +37,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif From 92a5f2cc5d32796189f818a5d61d0fb5d0cd0828 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 4 Jun 2025 16:07:34 +0100 Subject: [PATCH 04/16] fix(release): fix the publishing repository --- gradle/release.gradle | 7 ++---- scripts/docker-publish.sh | 47 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/gradle/release.gradle b/gradle/release.gradle index b2ba076769..3b2ab823b8 100644 --- a/gradle/release.gradle +++ b/gradle/release.gradle @@ -35,11 +35,8 @@ project.afterEvaluate { repositories { maven { - if (VERSION_NAME.contains("SNAPSHOT")) { - url "https://oss.sonatype.org/content/repositories/snapshots/" - } else { - url "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - } + name = 'ossrhStaging' + url = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/" credentials { username = project.hasProperty("NEXUS_USERNAME") ? "$NEXUS_USERNAME" : System.getenv("NEXUS_USERNAME") password = project.hasProperty("NEXUS_PASSWORD") ? "$NEXUS_PASSWORD" : System.getenv("NEXUS_PASSWORD") diff --git a/scripts/docker-publish.sh b/scripts/docker-publish.sh index 78be123a92..409d8dada3 100755 --- a/scripts/docker-publish.sh +++ b/scripts/docker-publish.sh @@ -10,5 +10,48 @@ echo "NEXUS_PASSWORD=$PUBLISH_PASS" >> ~/.gradle/gradle.properties echo "nexusUsername=$PUBLISH_USER" >> ~/.gradle/gradle.properties echo "nexusPassword=$PUBLISH_PASS" >> ~/.gradle/gradle.properties -/app/gradlew assembleRelease publish --no-daemon --max-workers=1 && \ - echo "Go to https://oss.sonatype.org/ to release the final artefact. For the full release instructions, please read https://github.com/bugsnag/bugsnag-android/blob/next/docs/RELEASING.md" +/app/gradlew assembleRelease publish --no-daemon --max-workers=1 + +# === Close Staging Repository === +echo "--- Closing staging repository" +echo "Fetching staging repositories..." + +REPOS_JSON=$(curl -s -u "$PUBLISH_USER:$PUBLISH_PASS" \ + "https://ossrh-staging-api.central.sonatype.com/manual/search/repositories") + +if [[ -z "$REPOS_JSON" ]]; then + echo "Failed to retrieve repository list. Check your credentials or network." >&2 + exit 1 +fi + +REPO_KEYS=($(echo "$REPOS_JSON" | jq -r '.repositories[] | select(.state == "open") | .key')) + +if [[ "${#REPO_KEYS[@]}" -eq 0 ]]; then + echo "No open repositories found." + exit 1 +elif [[ "${#REPO_KEYS[@]}" -gt 1 ]]; then + echo "Multiple open repositories found. Please specify which one to close:" + printf '%s\n' "${REPO_KEYS[@]}" + exit 1 +fi + +REPO_KEY="${REPO_KEYS[0]}" +echo "Closing repository $REPO_KEY..." + +URL="https://ossrh-staging-api.central.sonatype.com/manual/upload/repository/$REPO_KEY?publishing_type=user_managed" +RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST -u "$PUBLISH_USER:$PUBLISH_PASS" "$URL") + +BODY=$(echo "$RESPONSE" | sed -n '/^HTTP_STATUS:/!p') +STATUS=$(echo "$RESPONSE" | sed -n 's/^HTTP_STATUS://p') + +if [[ "$STATUS" != "200" ]]; then + echo "Failed to close repository. HTTP Status: $STATUS" + echo "$BODY" | jq -r + exit 1 +fi + +echo "Repository $REPO_KEY closed successfully." + +echo "Go to https://oss.sonatype.org/ to release the final artefact." +echo "For full release instructions, visit:" +echo "https://github.com/bugsnag/bugsnag-android/blob/next/docs/RELEASING.md" From 9260f098f12241b455944612a3efa01d6994ebeb Mon Sep 17 00:00:00 2001 From: jason Date: Thu, 5 Jun 2025 09:57:13 +0100 Subject: [PATCH 05/16] fix(exitinfo): move the optional call to `setProcessStateSummary` to a worker thread and swallow any exceptions --- CHANGELOG.md | 7 ++++ .../detekt-baseline.xml | 3 +- .../bugsnag/android/BugsnagExitInfoPlugin.kt | 38 ++++++++++++------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e9795c56..cf0890872a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## TBD + +### Bug fixes + +* The `bugsnag-plugin-android-exitinfo` plugin now calls `setProcessState` (if configured) on a background thread and swallows any rate-limiting errors, so that it does not block the main thread during startup + [#2197](https://github.com/bugsnag/bugsnag-android/pull/2197) + ## 6.14.0 (2025-06-04) ### Enhancements diff --git a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml index 3bd6751cd5..ea05089591 100644 --- a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml +++ b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml @@ -27,7 +27,8 @@ MaxLineLength:TraceParserTest.kt$TraceParserTest$"void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (android::AsyncWorker::*)(), android::AsyncWorker*> >(void*)" NestedBlockDepth:TraceParser.kt$TraceParser$private fun parseThreadAttributes(line: String) ReturnCount:TraceParser.kt$TraceParser$@VisibleForTesting internal fun parseNativeFrame(line: String): Stackframe? - SwallowedException:BugsnagExitInfoPlugin.kt$BugsnagExitInfoPlugin$e: Exception + SwallowedException:BugsnagExitInfoPlugin.kt$BugsnagExitInfoPlugin.SessionProcessStateSummaryCallback$e: Exception + SwallowedException:BugsnagExitInfoPlugin.kt$e: Exception SwallowedException:ExitInfoCallback.kt$ExitInfoCallback$exc: Throwable SwallowedException:ExitInfoPluginStore.kt$ExitInfoPluginStore$exc: Throwable diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt index cbf11895fa..be6368031a 100644 --- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt @@ -10,6 +10,7 @@ import android.os.Process import androidx.annotation.RequiresApi import com.bugsnag.android.ApplicationExitInfoMatcher.Companion.MATCH_ALL import com.bugsnag.android.ApplicationExitInfoMatcher.Companion.MAX_EXIT_INFO +import com.bugsnag.android.internal.TaskType @RequiresApi(Build.VERSION_CODES.R) class BugsnagExitInfoPlugin @JvmOverloads constructor( @@ -21,13 +22,7 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( @SuppressLint("VisibleForTests") override fun load(client: Client) { if (!configuration.disableProcessStateSummaryOverride) { - client.addOnSession( - OnSessionCallback { session: Session -> - val am = client.appContext.safeGetActivityManager() - am?.setProcessStateSummary(session.id.toByteArray()) - return@OnSessionCallback true - } - ) + client.addOnSession(SessionProcessStateSummaryCallback(client)) } val tombstoneEventEnhancer = TombstoneEventEnhancer( @@ -146,14 +141,29 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( } override fun unload() = Unit - - private fun Context.safeGetActivityManager(): ActivityManager? = try { - getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager - } catch (e: Exception) { - null - } - private fun Context.isPrimaryProcess(): Boolean { return Application.getProcessName() == packageName } + + private class SessionProcessStateSummaryCallback( + private val client: Client + ) : OnSessionCallback { + override fun onSession(session: Session): Boolean { + val am = client.appContext.safeGetActivityManager() ?: return true + client.bgTaskService.submitTask(TaskType.DEFAULT) { + try { + am.setProcessStateSummary(session.id.toByteArray()) + } catch (e: Exception) { + // this can be rate limited by the system, so we ignore it + } + } + return true + } + } +} + +internal fun Context.safeGetActivityManager(): ActivityManager? = try { + getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager +} catch (e: Exception) { + null } From 2f90458195eebd08a6a366b054037320e729f611 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 01:05:27 +0000 Subject: [PATCH 06/16] build(deps): bump github/codeql-action from 3.28.18 to 3.29.0 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.18 to 3.29.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ff0a06e83cb2de871e5a09832bc6a81e7276941f...ce28f5bb42b7a9f2c824e633a3f6ee835bab6858) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 099957a752..6baf898e73 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -64,7 +64,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18 + uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 #v3.29.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -83,6 +83,6 @@ jobs: ./gradlew --no-daemon assemble - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18 + uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 #v3.29.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4ad81efc72..26182644f1 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -68,7 +68,7 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: sarif_file: results.sarif From a1adf8038a6de2cf1601c365628723298811489e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:25:50 +0100 Subject: [PATCH 07/16] build(deps): bump gradle/actions from 4.4.0 to 4.4.1 (#2200) Bumps [gradle/actions](https://github.com/gradle/actions) from 4.4.0 to 4.4.1. - [Release notes](https://github.com/gradle/actions/releases) - [Commits](https://github.com/gradle/actions/compare/8379f6a1328ee0e06e2bb424dadb7b159856a326...ac638b010cf58a27ee6c972d7336334ccaf61c96) --- updated-dependencies: - dependency-name: gradle/actions dependency-version: 4.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 26182644f1..57a3c39468 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -77,4 +77,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: gradle/actions/wrapper-validation@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + - uses: gradle/actions/wrapper-validation@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 From 90f2638cd46c5de42257c305aff48a34f167462c Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 24 Jun 2025 15:40:16 +0100 Subject: [PATCH 08/16] fix(ndk): properly scope the build-id capturing in the stack_unwinder --- CHANGELOG.md | 2 ++ .../src/main/jni/utils/stack_unwinder.cpp | 26 ++++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf0890872a..99ffd467fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * The `bugsnag-plugin-android-exitinfo` plugin now calls `setProcessState` (if configured) on a background thread and swallows any rate-limiting errors, so that it does not block the main thread during startup [#2197](https://github.com/bugsnag/bugsnag-android/pull/2197) +* Improve the scoping of the build-id capturing in `bugsnag-plugin-android-ndk` to more reliably capture the build-id from the correct `.so` file + [#2203](https://github.com/bugsnag/bugsnag-android/pull/2203) ## 6.14.0 (2025-06-04) diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp index 411b699e33..3fe7e90e52 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp @@ -102,26 +102,28 @@ static void populate_code_identifier(const unwindstack::FrameData &frame, } auto shared_build_id = elf_fields->build_id_.load(); - std::string_view build_id; if (shared_build_id == nullptr) { auto elf = elf_fields->elf_.get(); if (elf == nullptr) { return; } - build_id = elf->GetBuildID(); - } else { - build_id = *shared_build_id; - } + auto build_id = elf->GetBuildID(); + if (build_id.empty()) { + return; + } - if (build_id.empty()) { - return; + // MapInfo.GetPrintableBuildID is *not* async-safe so we need our own + // safe hex encoder to copy BuildID into code_identifier. + bsg_hex_encode(dst_frame.code_identifier, build_id.data(), + build_id.length(), sizeof(dst_frame.code_identifier)); + } else if (!shared_build_id->empty()) { + std::string build_id = static_cast(*shared_build_id); + // MapInfo.GetPrintableBuildID is *not* async-safe so we need our own + // safe hex encoder to copy BuildID into code_identifier. + bsg_hex_encode(dst_frame.code_identifier, build_id.data(), + build_id.length(), sizeof(dst_frame.code_identifier)); } - - // MapInfo.GetPrintableBuildID is *not* async-safe so we need our own - // safe hex encoder to copy BuildID into code_identifier. - bsg_hex_encode(dst_frame.code_identifier, build_id.data(), build_id.length(), - sizeof(dst_frame.code_identifier)); } void bsg_unwinder_refresh(void) { From d1fda766ab1f90189dda441bed305a8fc728a7d5 Mon Sep 17 00:00:00 2001 From: Steve Kirkland Date: Tue, 24 Jun 2025 17:01:56 +0100 Subject: [PATCH 09/16] Allow e2e test requests to be forwarded to Bugsnag and InsightHub --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 08903779c8..9ad40dc121 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ x-common-environment: &common-environment BUILDKITE_RETRY_COUNT: BUILDKITE_STEP_KEY: MAZE_BUGSNAG_API_KEY: + MAZE_REPEATER_API_KEY: + MAZE_HUB_REPEATER_API_KEY: MAZE_NO_FAIL_FAST: MAZE_APPIUM_BUGSNAG_API_KEY: MAZE_SCENARIO_BUGSNAG_API_KEY: From 9958d9f35d01056c16453262d95d41f6bcc2f06b Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 11 Jun 2025 10:18:27 +0100 Subject: [PATCH 10/16] fix(startup): more startup processing to complete in the background --- bugsnag-android-core/detekt-baseline.xml | 2 +- .../android/AppDataCollectorForegroundTest.kt | 3 +- .../bugsnag/android/AppDataCollectorTest.kt | 15 +++--- .../java/com/bugsnag/android/FileStoreTest.kt | 3 +- .../com/bugsnag/android/AppDataCollector.kt | 11 +++-- .../bugsnag/android/DataCollectionModule.kt | 2 +- .../com/bugsnag/android/EventStorageModule.kt | 6 +-- .../java/com/bugsnag/android/EventStore.kt | 3 +- .../java/com/bugsnag/android/FileStore.kt | 7 +-- .../android/InternalReportDelegate.java | 10 ++-- .../java/com/bugsnag/android/SessionStore.kt | 3 +- .../AppDataCollectorSerializationTest.kt | 3 +- .../android/AppMetadataSerializationTest.kt | 3 +- .../bugsnag/android/EmptyEventCallbackTest.kt | 17 ++++--- .../com/bugsnag/android/EventFilenameTest.kt | 49 +++++++++++-------- .../bugsnag/android/EventStoreMaxLimitTest.kt | 17 ++++--- .../InternalEventPayloadDelegateTest.kt | 4 +- .../android/LaunchCrashDeliveryTest.kt | 17 ++++--- .../android/SessionStoreMaxLimitTest.kt | 17 ++++--- 19 files changed, 110 insertions(+), 82 deletions(-) diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index 843019fbd7..a44dcfc279 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -6,7 +6,7 @@ CyclomaticComplexMethod:ConfigInternal.kt$ConfigInternal$fun getConfigDifferences(): Map<String, Any> ImplicitDefaultLocale:Deliverable.kt$Deliverable$String.format("%02x", byte) LongParameterList:App.kt$App$( /** * The architecture of the running application binary */ var binaryArch: String?, /** * The package name of the application */ var id: String?, /** * The release stage set in [Configuration.releaseStage] */ var releaseStage: String?, /** * The version of the application set in [Configuration.version] */ var version: String?, /** The revision ID from the manifest (React Native apps only) */ var codeBundleId: String?, /** * The unique identifier for the build of the application set in [Configuration.buildUuid] */ buildUuid: Provider<String?>?, /** * The application type set in [Configuration#version] */ var type: String?, /** * The version code of the application set in [Configuration.versionCode] */ var versionCode: Number? ) - LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: SessionTracker, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val memoryTrimState: MemoryTrimState ) + LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: Provider<SessionTracker>, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val memoryTrimState: MemoryTrimState ) LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: Provider<String?>?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ var duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ var durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ var inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ var isLaunching: Boolean? ) LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: String?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ isLaunching: Boolean? ) LongParameterList:AppWithState.kt$AppWithState$( config: ImmutableConfig, binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, duration: Number?, durationInForeground: Number?, inForeground: Boolean?, isLaunching: Boolean? ) diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt index d57317c881..18f052ea8f 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt @@ -2,6 +2,7 @@ package com.bugsnag.android import android.content.Context import android.os.SystemClock +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -41,7 +42,7 @@ class AppDataCollectorForegroundTest { appContext, null, config, - sessionTracker, + ValueProvider(sessionTracker), null, launchCrashTracker, memoryTrimState diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorTest.kt index 09da647a6a..4207d15ed9 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorTest.kt @@ -6,6 +6,7 @@ import android.content.pm.PackageManager import android.os.Build import android.os.Process import androidx.test.core.app.ApplicationProvider +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -49,7 +50,7 @@ class AppDataCollectorTest { context, context.packageManager, client.immutableConfig, - client.sessionTracker, + ValueProvider(client.sessionTracker), am, client.launchCrashTracker, client.memoryTrimState @@ -70,7 +71,7 @@ class AppDataCollectorTest { context, context.packageManager, client.immutableConfig, - client.sessionTracker, + ValueProvider(client.sessionTracker), am, client.launchCrashTracker, client.memoryTrimState @@ -93,7 +94,7 @@ class AppDataCollectorTest { context, context.packageManager, client.immutableConfig, - client.sessionTracker, + ValueProvider(client.sessionTracker), am, client.launchCrashTracker, client.memoryTrimState @@ -115,7 +116,7 @@ class AppDataCollectorTest { context, packageManager, client.immutableConfig, - client.sessionTracker, + ValueProvider(client.sessionTracker), am, client.launchCrashTracker, client.memoryTrimState @@ -144,7 +145,7 @@ class AppDataCollectorTest { context, packageManager, client.immutableConfig, - client.sessionTracker, + ValueProvider(client.sessionTracker), am, client.launchCrashTracker, client.memoryTrimState @@ -171,7 +172,7 @@ class AppDataCollectorTest { context, packageManager, client.immutableConfig, - client.sessionTracker, + ValueProvider(client.sessionTracker), am, client.launchCrashTracker, client.memoryTrimState @@ -198,7 +199,7 @@ class AppDataCollectorTest { context, packageManager, client.immutableConfig, - client.sessionTracker, + ValueProvider(client.sessionTracker), am, client.launchCrashTracker, client.memoryTrimState diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/FileStoreTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/FileStoreTest.kt index 6c4733ac54..3363cf5b21 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/FileStoreTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/FileStoreTest.kt @@ -2,6 +2,7 @@ package com.bugsnag.android import android.app.Application import androidx.test.core.app.ApplicationProvider +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -48,6 +49,6 @@ internal class CustomFileStore( folder: File, maxStoreCount: Int, delegate: Delegate? -) : FileStore(folder, maxStoreCount, NoopLogger, delegate) { +) : FileStore(folder, maxStoreCount, NoopLogger, ValueProvider(delegate)) { override fun getFilename(obj: Any?) = "foo.json" } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/AppDataCollector.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/AppDataCollector.kt index c1eb409bc8..0347f2a428 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/AppDataCollector.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/AppDataCollector.kt @@ -24,6 +24,7 @@ import android.os.Build.VERSION_CODES import android.os.Process import android.os.SystemClock import com.bugsnag.android.internal.ImmutableConfig +import com.bugsnag.android.internal.dag.Provider /** * Collects various data on the application state @@ -32,7 +33,7 @@ internal class AppDataCollector( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, - private val sessionTracker: SessionTracker, + private val sessionTracker: Provider, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val memoryTrimState: MemoryTrimState @@ -54,7 +55,7 @@ internal class AppDataCollector( App(config, binaryArch, packageName, releaseStage, versionName, codeBundleId) fun generateAppWithState(): AppWithState { - val inForeground = sessionTracker.isInForeground + val inForeground = sessionTracker.get().isInForeground val durationInForeground = calculateDurationInForeground(inForeground) return AppWithState( @@ -118,7 +119,7 @@ internal class AppDataCollector( fun getAppDataMetadata(): MutableMap { val map = HashMap() map["name"] = appName - map["activeScreen"] = sessionTracker.contextActivity + map["activeScreen"] = sessionTracker.get().contextActivity map["lowMemory"] = memoryTrimState.isLowMemory map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription map["processImportance"] = getProcessImportance() @@ -168,7 +169,7 @@ internal class AppDataCollector( * * @return the duration in ms */ - internal fun calculateDurationInForeground(inForeground: Boolean? = sessionTracker.isInForeground): Long? { + internal fun calculateDurationInForeground(inForeground: Boolean? = sessionTracker.get().isInForeground): Long? { if (inForeground == null) { return null } @@ -176,7 +177,7 @@ internal class AppDataCollector( val nowMs = SystemClock.elapsedRealtime() var durationMs: Long = 0 - val sessionStartTimeMs: Long = sessionTracker.lastEnteredForegroundMs + val sessionStartTimeMs: Long = sessionTracker.get().lastEnteredForegroundMs if (inForeground && sessionStartTimeMs != 0L) { durationMs = nowMs - sessionStartTimeMs diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt index e8967de359..4b9edee875 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt @@ -34,7 +34,7 @@ internal class DataCollectionModule( ctx, ctx.packageManager, cfg, - trackerModule.sessionTracker.get(), + trackerModule.sessionTracker, systemServiceModule.activityManager, trackerModule.launchCrashTracker, memoryTrimState diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt index 2d519eb5b1..09996e232a 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt @@ -29,9 +29,9 @@ internal class EventStorageModule( cfg.logger, cfg, systemServiceModule.storageManager, - dataCollectionModule.appDataCollector.get(), + dataCollectionModule.appDataCollector, dataCollectionModule.deviceDataCollector, - trackerModule.sessionTracker.get(), + trackerModule.sessionTracker, notifier, bgTaskService ) else null @@ -43,7 +43,7 @@ internal class EventStorageModule( cfg.logger, notifier, bgTaskService, - delegate.getOrNull(), + delegate, callbackState ) } 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 5d68837b05..e11fe96ba6 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 @@ -9,6 +9,7 @@ import com.bugsnag.android.internal.BackgroundTaskService import com.bugsnag.android.internal.ForegroundDetector import com.bugsnag.android.internal.ImmutableConfig import com.bugsnag.android.internal.TaskType +import com.bugsnag.android.internal.dag.Provider import java.io.File import java.util.Calendar import java.util.Date @@ -27,7 +28,7 @@ internal class EventStore( logger: Logger, notifier: Notifier, bgTaskService: BackgroundTaskService, - delegate: Delegate?, + delegate: Provider?, callbackState: CallbackState ) : FileStore( File(config.persistenceDirectory.value, "bugsnag/errors"), diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt index f7ede678cb..aa49df6d54 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt @@ -1,6 +1,7 @@ package com.bugsnag.android import com.bugsnag.android.JsonStream.Streamable +import com.bugsnag.android.internal.dag.Provider import java.io.BufferedWriter import java.io.File import java.io.FileNotFoundException @@ -15,7 +16,7 @@ internal abstract class FileStore( val storageDir: File, private val maxStoreCount: Int, protected open val logger: Logger, - protected val delegate: Delegate? + protected val delegate: Provider? ) { internal fun interface Delegate { /** @@ -66,7 +67,7 @@ internal abstract class FileStore( out.write(content) } catch (exc: Exception) { val eventFile = File(filePath) - delegate?.onErrorIOFailure(exc, eventFile, "NDK Crash report copy") + delegate?.getOrNull()?.onErrorIOFailure(exc, eventFile, "NDK Crash report copy") IOUtils.deleteFile(eventFile, logger) } finally { try { @@ -100,7 +101,7 @@ internal abstract class FileStore( logger.w("Ignoring FileNotFoundException - unable to create file", exc) } catch (exc: Exception) { val eventFile = File(filename) - delegate?.onErrorIOFailure(exc, eventFile, "Crash report serialization") + delegate?.getOrNull()?.onErrorIOFailure(exc, eventFile, "Crash report serialization") IOUtils.deleteFile(eventFile, logger) } finally { IOUtils.closeQuietly(stream) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java index ad1d791bdf..f45c764a92 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java @@ -32,10 +32,10 @@ class InternalReportDelegate implements EventStore.Delegate { @Nullable final StorageManager storageManager; - final AppDataCollector appDataCollector; + final Provider appDataCollector; final Provider deviceDataCollector; final Context appContext; - final SessionTracker sessionTracker; + final Provider sessionTracker; final Notifier notifier; final BackgroundTaskService backgroundTaskService; @@ -43,9 +43,9 @@ class InternalReportDelegate implements EventStore.Delegate { Logger logger, ImmutableConfig immutableConfig, @Nullable StorageManager storageManager, - AppDataCollector appDataCollector, + Provider appDataCollector, Provider deviceDataCollector, - SessionTracker sessionTracker, + Provider sessionTracker, Notifier notifier, BackgroundTaskService backgroundTaskService) { this.logger = logger; @@ -101,7 +101,7 @@ void recordStorageCacheBehavior(Event event) { * This is intended for internal use only, and reports will not be visible to end-users. */ void reportInternalBugsnagError(@NonNull Event event) { - event.setApp(appDataCollector.generateAppWithState()); + event.setApp(appDataCollector.get().generateAppWithState()); event.setDevice(deviceDataCollector.get().generateDeviceWithState(new Date().getTime())); event.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "notifierName", notifier.getName()); diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt index e6e0ce6aa8..82241ca949 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt @@ -2,6 +2,7 @@ package com.bugsnag.android import com.bugsnag.android.SessionFilenameInfo.Companion.defaultFilename import com.bugsnag.android.SessionFilenameInfo.Companion.findTimestampInFilename +import com.bugsnag.android.internal.dag.Provider import java.io.File import java.util.Calendar import java.util.Comparator @@ -16,7 +17,7 @@ internal class SessionStore( maxPersistedSessions: Int, private val apiKey: String, logger: Logger, - delegate: Delegate? + delegate: Provider? ) : FileStore( File(bugsnagDir, "sessions"), maxPersistedSessions, diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorSerializationTest.kt index 93f33991a7..4f58d2b6fe 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorSerializationTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorSerializationTest.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import com.bugsnag.android.BugsnagTestUtils.convert +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -49,7 +50,7 @@ internal class AppDataCollectorSerializationTest { context, pm, convert(config), - sessionTracker, + ValueProvider(sessionTracker), am, launchCrashTracker, memoryTrimState diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppMetadataSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppMetadataSerializationTest.kt index 70cf84c87b..829468dcd8 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppMetadataSerializationTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppMetadataSerializationTest.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import com.bugsnag.android.internal.convertToImmutableConfig +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.Assert.assertNotNull import org.junit.Test import org.junit.runner.RunWith @@ -50,7 +51,7 @@ internal class AppMetadataSerializationTest { context, pm, convertToImmutableConfig(config, null, null, ApplicationInfo()), - sessionTracker, + ValueProvider(sessionTracker), am, launchCrashTracker, memoryTrimState diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/EmptyEventCallbackTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/EmptyEventCallbackTest.kt index 6c802250a3..13da2d8cd4 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/EmptyEventCallbackTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/EmptyEventCallbackTest.kt @@ -6,6 +6,7 @@ import com.bugsnag.android.FileStore.Delegate import com.bugsnag.android.internal.BackgroundTaskService import com.bugsnag.android.internal.ImmutableConfig import com.bugsnag.android.internal.convertToImmutableConfig +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -142,14 +143,16 @@ class EmptyEventCallbackTest { NoopLogger, Notifier(), backgroundTaskService, - object : Delegate { - override fun onErrorIOFailure( - exception: Exception?, - errorFile: File?, - context: String? - ) { + ValueProvider( + object : Delegate { + override fun onErrorIOFailure( + exception: Exception?, + errorFile: File?, + context: String? + ) { + } } - }, + ), CallbackState() ) } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFilenameTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFilenameTest.kt index 53742e044a..d042eeafb0 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFilenameTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFilenameTest.kt @@ -3,6 +3,7 @@ package com.bugsnag.android import com.bugsnag.android.EventStore.Companion.EVENT_COMPARATOR import com.bugsnag.android.FileStore.Delegate import com.bugsnag.android.internal.BackgroundTaskService +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull @@ -58,14 +59,16 @@ internal class EventFilenameTest { NoopLogger, Notifier(), BackgroundTaskService(), - object : Delegate { - override fun onErrorIOFailure( - exception: Exception?, - errorFile: File?, - context: String? - ) { + ValueProvider( + object : Delegate { + override fun onErrorIOFailure( + exception: Exception?, + errorFile: File?, + context: String? + ) { + } } - }, + ), CallbackState() ) @@ -87,14 +90,16 @@ internal class EventFilenameTest { NoopLogger, Notifier(), BackgroundTaskService(), - object : Delegate { - override fun onErrorIOFailure( - exception: Exception?, - errorFile: File?, - context: String? - ) { + ValueProvider( + object : Delegate { + override fun onErrorIOFailure( + exception: Exception?, + errorFile: File?, + context: String? + ) { + } } - }, + ), CallbackState() ) @@ -110,14 +115,16 @@ internal class EventFilenameTest { NoopLogger, Notifier(), BackgroundTaskService(), - object : Delegate { - override fun onErrorIOFailure( - exception: Exception?, - errorFile: File?, - context: String? - ) { + ValueProvider( + object : Delegate { + override fun onErrorIOFailure( + exception: Exception?, + errorFile: File?, + context: String? + ) { + } } - }, + ), CallbackState() ) diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventStoreMaxLimitTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventStoreMaxLimitTest.kt index 4bcf5f964f..5e21302fc2 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventStoreMaxLimitTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventStoreMaxLimitTest.kt @@ -6,6 +6,7 @@ import com.bugsnag.android.FileStore.Delegate import com.bugsnag.android.internal.BackgroundTaskService import com.bugsnag.android.internal.ImmutableConfig import com.bugsnag.android.internal.convertToImmutableConfig +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -84,14 +85,16 @@ class EventStoreMaxLimitTest { NoopLogger, Notifier(), BackgroundTaskService(), - object : Delegate { - override fun onErrorIOFailure( - exception: Exception?, - errorFile: File?, - context: String? - ) { + ValueProvider( + object : Delegate { + override fun onErrorIOFailure( + exception: Exception?, + errorFile: File?, + context: String? + ) { + } } - }, + ), CallbackState() ) } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt index d17dac578f..666e412131 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt @@ -53,9 +53,9 @@ internal class InternalEventPayloadDelegateTest { NoopLogger, config, storageManager, - appDataCollector, + ValueProvider(appDataCollector), ValueProvider(deviceDataCollector), - sessionTracker, + ValueProvider(sessionTracker), Notifier(), BackgroundTaskService() ) diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt index 401e8fd1fa..1a6fe613a8 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt @@ -4,6 +4,7 @@ import com.bugsnag.android.BugsnagTestUtils.generateConfiguration import com.bugsnag.android.BugsnagTestUtils.generateEvent import com.bugsnag.android.FileStore.Delegate import com.bugsnag.android.internal.BackgroundTaskService +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -161,14 +162,16 @@ class LaunchCrashDeliveryTest { NoopLogger, Notifier(), backgroundTaskService, - object : Delegate { - override fun onErrorIOFailure( - exception: Exception?, - errorFile: File?, - context: String? - ) { + ValueProvider( + object : Delegate { + override fun onErrorIOFailure( + exception: Exception?, + errorFile: File?, + context: String? + ) { + } } - }, + ), CallbackState() ) } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt index 1a3e4c6f24..b9a17648b3 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt @@ -5,6 +5,7 @@ import com.bugsnag.android.BugsnagTestUtils.generateSession import com.bugsnag.android.FileStore.Delegate import com.bugsnag.android.internal.ImmutableConfig import com.bugsnag.android.internal.convertToImmutableConfig +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -83,14 +84,16 @@ class SessionStoreMaxLimitTest { config.maxPersistedSessions, config.apiKey, NoopLogger, - object : Delegate { - override fun onErrorIOFailure( - exception: Exception?, - errorFile: File?, - context: String? - ) { + ValueProvider( + object : Delegate { + override fun onErrorIOFailure( + exception: Exception?, + errorFile: File?, + context: String? + ) { + } } - } + ) ) } } From 75693c825ef443a44e8635b07154a6126ab9e7f0 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 11 Jun 2025 14:32:05 +0100 Subject: [PATCH 11/16] fix(startup): allow the SessionTracker startup to complete later without blocking --- .../com/bugsnag/android/SessionTracker.java | 26 ++++++++++--------- .../java/com/bugsnag/android/TrackerModule.kt | 2 +- .../android/SessionTrackerPauseResumeTest.kt | 3 ++- .../bugsnag/android/SessionTrackerTest.java | 9 ++++--- 4 files changed, 22 insertions(+), 18 deletions(-) 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 25fb7058c4..82d1c80575 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 @@ -5,6 +5,7 @@ import com.bugsnag.android.internal.ForegroundDetector; import com.bugsnag.android.internal.ImmutableConfig; import com.bugsnag.android.internal.TaskType; +import com.bugsnag.android.internal.dag.Provider; import android.app.Activity; @@ -32,7 +33,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi private final ImmutableConfig configuration; private final CallbackState callbackState; private final Client client; - final SessionStore sessionStore; + final Provider sessionStore; private volatile Session currentSession = null; final BackgroundTaskService backgroundTaskService; final Logger logger; @@ -41,7 +42,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi SessionTracker(ImmutableConfig configuration, CallbackState callbackState, Client client, - SessionStore sessionStore, + Provider sessionStore, Logger logger, BackgroundTaskService backgroundTaskService) { this(configuration, callbackState, client, DEFAULT_TIMEOUT_MS, @@ -52,7 +53,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi CallbackState callbackState, Client client, long timeoutMs, - SessionStore sessionStore, + Provider sessionStore, Logger logger, BackgroundTaskService backgroundTaskService) { this.configuration = configuration; @@ -263,7 +264,7 @@ public void run() { * Attempts to flush session payloads stored on disk */ void flushStoredSessions() { - List storedFiles = sessionStore.findStoredFiles(); + List storedFiles = sessionStore.get().findStoredFiles(); for (File storedFile : storedFiles) { flushStoredSession(storedFile); @@ -282,27 +283,28 @@ void flushStoredSession(File storedFile) { } DeliveryStatus deliveryStatus = deliverSessionPayload(payload); + SessionStore store = sessionStore.get(); switch (deliveryStatus) { case DELIVERED: - sessionStore.deleteStoredFiles(Collections.singletonList(storedFile)); + store.deleteStoredFiles(Collections.singletonList(storedFile)); logger.d("Sent 1 new session to Bugsnag"); break; case UNDELIVERED: - if (sessionStore.isTooOld(storedFile)) { + if (store.isTooOld(storedFile)) { logger.w("Discarding historical session (from {" - + sessionStore.getCreationDate(storedFile) + + store.getCreationDate(storedFile) + "}) after failed delivery"); - sessionStore.deleteStoredFiles(Collections.singletonList(storedFile)); + store.deleteStoredFiles(Collections.singletonList(storedFile)); } else { - sessionStore.cancelQueuedFiles(Collections.singletonList(storedFile)); + store.cancelQueuedFiles(Collections.singletonList(storedFile)); logger.w("Leaving session payload for future delivery"); } break; case FAILURE: // drop bad data logger.w("Deleting invalid session tracking payload"); - sessionStore.deleteStoredFiles(Collections.singletonList(storedFile)); + store.deleteStoredFiles(Collections.singletonList(storedFile)); break; default: break; @@ -319,7 +321,7 @@ public void run() { }); } catch (RejectedExecutionException exception) { // This is on the current thread but there isn't much else we can do - sessionStore.write(session); + sessionStore.get().write(session); } } @@ -331,7 +333,7 @@ void deliverInMemorySession(Session session) { switch (deliveryStatus) { case UNDELIVERED: logger.w("Storing session payload for future delivery"); - sessionStore.write(session); + sessionStore.get().write(session); break; case FAILURE: logger.w("Dropping invalid session tracking payload"); diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt index 8eb6c47607..afc2be9779 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt @@ -26,7 +26,7 @@ internal class TrackerModule( config, callbackState, client, - storageModule.sessionStore.get(), + storageModule.sessionStore, config.logger, bgTaskService ) diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerPauseResumeTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerPauseResumeTest.kt index b2339cf096..1d82e5e354 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerPauseResumeTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerPauseResumeTest.kt @@ -5,6 +5,7 @@ import com.bugsnag.android.BugsnagTestUtils.generateConfiguration import com.bugsnag.android.BugsnagTestUtils.generateDevice import com.bugsnag.android.internal.BackgroundTaskService import com.bugsnag.android.internal.ImmutableConfig +import com.bugsnag.android.internal.dag.ValueProvider import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals @@ -60,7 +61,7 @@ internal class SessionTrackerPauseResumeTest { BugsnagTestUtils.generateImmutableConfig(), configuration.impl.callbackState, client, - sessionStore, + ValueProvider(sessionStore), NoopLogger, BackgroundTaskService() ) diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerTest.java b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerTest.java index 172ee3a22b..7dfe12a158 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerTest.java +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerTest.java @@ -10,6 +10,7 @@ import com.bugsnag.android.internal.BackgroundTaskService; import com.bugsnag.android.internal.ForegroundDetector; import com.bugsnag.android.internal.ImmutableConfig; +import com.bugsnag.android.internal.dag.ValueProvider; import androidx.annotation.NonNull; @@ -71,8 +72,8 @@ public void setUp() { configuration.setDelivery(BugsnagTestUtils.generateDelivery()); immutableConfig = BugsnagTestUtils.generateImmutableConfig(); sessionTracker = new SessionTracker(immutableConfig, - configuration.impl.callbackState, client, sessionStore, NoopLogger.INSTANCE, - bgTaskService); + configuration.impl.callbackState, client, new ValueProvider<>(sessionStore), + NoopLogger.INSTANCE, bgTaskService); configuration.setAutoTrackSessions(true); user = new User(null, null, null); @@ -179,7 +180,7 @@ public void testBasicInForeground() { public void testZeroSessionTimeout() { CallbackState callbackState = configuration.impl.callbackState; sessionTracker = new SessionTracker(immutableConfig, callbackState, client, - 0, sessionStore, NoopLogger.INSTANCE, bgTaskService); + 0, new ValueProvider<>(sessionStore), NoopLogger.INSTANCE, bgTaskService); long now = System.currentTimeMillis(); sessionTracker.onForegroundStatus(true, now); @@ -199,7 +200,7 @@ public void testZeroSessionTimeout() { public void testSessionTimeout() { CallbackState callbackState = configuration.impl.callbackState; sessionTracker = new SessionTracker(immutableConfig, callbackState, client, - 100, sessionStore, NoopLogger.INSTANCE, bgTaskService); + 100, new ValueProvider<>(sessionStore), NoopLogger.INSTANCE, bgTaskService); long now = System.currentTimeMillis(); sessionTracker.onForegroundStatus(true, now); From 31b6fcc78857c453b9220e2cf565352c7a4f09ab Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 20 Jun 2025 10:07:56 +0100 Subject: [PATCH 12/16] fix(root): add a process timeout to RootDetector --- CHANGELOG.md | 2 ++ bugsnag-android-core/detekt-baseline.xml | 1 + .../java/com/bugsnag/android/RootDetector.kt | 36 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ffd467fe..ba89ea7623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ [#2197](https://github.com/bugsnag/bugsnag-android/pull/2197) * Improve the scoping of the build-id capturing in `bugsnag-plugin-android-ndk` to more reliably capture the build-id from the correct `.so` file [#2203](https://github.com/bugsnag/bugsnag-android/pull/2203) +* Fixed a background ANR that could occur during startup if processes do not launch or run quickly enough + [#2202](https://github.com/bugsnag/bugsnag-android/pull/2202) ## 6.14.0 (2025-06-04) diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index a44dcfc279..ac410321aa 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -61,6 +61,7 @@ SwallowedException:ForegroundDetector.kt$ForegroundDetector$e: Exception SwallowedException:JsonHelperTest.kt$JsonHelperTest$e: IllegalArgumentException SwallowedException:PluginClient.kt$PluginClient$exc: ClassNotFoundException + SwallowedException:RootDetector.kt$RootDetector$ex: IllegalThreadStateException SwallowedException:SharedPrefMigrator.kt$SharedPrefMigrator$e: RuntimeException ThrowsCount:JsonHelper.kt$JsonHelper$fun jsonToLong(value: Any?): Long? TooManyFunctions:ConfigInternal.kt$ConfigInternal : CallbackAwareMetadataAwareUserAwareFeatureFlagAware diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/RootDetector.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/RootDetector.kt index c187121d5e..7be09c0b0a 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/RootDetector.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/RootDetector.kt @@ -1,9 +1,14 @@ package com.bugsnag.android +import android.os.Build +import android.os.SystemClock import androidx.annotation.VisibleForTesting import java.io.File import java.io.IOException import java.io.Reader +import java.lang.Thread +import java.util.concurrent.TimeUnit +import kotlin.math.min /** * Attempts to detect whether the device is rooted. Root detection errs on the side of false @@ -21,6 +26,9 @@ internal class RootDetector @JvmOverloads constructor( ) { companion object { + private const val PROCESS_TIMEOUT = 250L + private const val PROCESS_POLL_DELAY = 50L + private val BUILD_PROP_FILE = File("/system/build.prop") private val ROOT_INDICATORS = listOf( @@ -120,7 +128,20 @@ internal class RootDetector @JvmOverloads constructor( var process: Process? = null return try { process = processBuilder.start() + val processComplete = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + process.waitFor(PROCESS_TIMEOUT, TimeUnit.MILLISECONDS) + } else { + process.fallbackWaitFor(PROCESS_TIMEOUT) + } + + if (!processComplete) { + return false + } + process.inputStream.bufferedReader().use { it.isNotBlank() } + } catch (ignored: InterruptedException) { + Thread.currentThread().interrupt() // restore the interrupted status + false } catch (ignored: IOException) { false } finally { @@ -147,4 +168,19 @@ internal class RootDetector @JvmOverloads constructor( libraryLoaded -> performNativeRootChecks() else -> false } + + private fun Process.fallbackWaitFor(timeout: Long): Boolean { + val endTime = SystemClock.elapsedRealtime() + timeout + while (SystemClock.elapsedRealtime() < endTime) { + try { + exitValue() + return true + } catch (ex: IllegalThreadStateException) { + // Process is still running, wait a bit before checking again + Thread.sleep(min(PROCESS_POLL_DELAY, endTime - SystemClock.elapsedRealtime())) + } + } + + return false + } } From 0a1412e418fcf1b677ce916f89cf2713af73bf3d Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 27 Jun 2025 11:28:18 +0100 Subject: [PATCH 13/16] refactor(ndk): generate JNI linker tables in Gradle, and RegisterNatives in JNI_OnLoad instead of relying on name-based lazy-linking --- .../api/bugsnag-plugin-android-ndk.api | 1 - bugsnag-plugin-android-ndk/build.gradle.kts | 1 + .../src/main/CMakeLists.txt | 2 - ...exported_native_symbols-RelWithDebInfo.txt | 1 + .../com/bugsnag/android/ndk/NativeBridge.kt | 4 +- .../src/main/jni/bugsnag_ndk.c | 260 +++++++++--------- .../src/main/jni/jni_cache.c | 3 + .../src/main/jni/jni_cache.h | 3 + buildSrc/build.gradle.kts | 11 + .../gradle/GenerateJNILinkTableTransform.kt | 135 +++++++++ .../android/gradle/JNILinkTablePlugin.kt | 67 +++++ 11 files changed, 356 insertions(+), 132 deletions(-) create mode 100644 buildSrc/src/main/kotlin/com/bugsnag/android/gradle/GenerateJNILinkTableTransform.kt create mode 100644 buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt 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 4b7eea278f..1136fc1740 100644 --- a/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api +++ b/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api @@ -33,7 +33,6 @@ public final class com/bugsnag/android/ndk/NativeBridge : com/bugsnag/android/in public final fun updateContext (Ljava/lang/String;)V public final fun updateInForeground (ZLjava/lang/String;)V public final fun updateIsLaunching (Z)V - public final fun updateLastRunInfo (I)V public final fun updateLowMemory (ZLjava/lang/String;)V public final fun updateOrientation (Ljava/lang/String;)V public final fun updateUserEmail (Ljava/lang/String;)V diff --git a/bugsnag-plugin-android-ndk/build.gradle.kts b/bugsnag-plugin-android-ndk/build.gradle.kts index 51a08ab826..55dba4e1b3 100644 --- a/bugsnag-plugin-android-ndk/build.gradle.kts +++ b/bugsnag-plugin-android-ndk/build.gradle.kts @@ -1,5 +1,6 @@ plugins { loadDefaultPlugins() + id("bugsnag-ndk-table-plugin") } android { diff --git a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt index 7bcebcccf6..acb5079306 100644 --- a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt @@ -35,8 +35,6 @@ include_directories( jni/external/libunwindstack-ndk/include ) -target_include_directories(bugsnag-ndk PRIVATE ${BUGSNAG_DIR}/assets/include) - target_link_libraries( # Specifies the target library. bugsnag-ndk diff --git a/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt index 94204bd8fa..fda9392331 100644 --- a/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt +++ b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt @@ -6,6 +6,7 @@ global: __emutls_get_address; __gxx_personality_v0; Java_*; + JNI_OnLoad; extern "C++" { "std::get_terminate()"; "std::set_terminate(void (*)())"; 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 793eec21ef..dae23bd689 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 @@ -84,13 +84,12 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse external fun updateContext(context: String) external fun updateInForeground(inForeground: Boolean, activityName: String) external fun updateIsLaunching(isLaunching: Boolean) - external fun updateLastRunInfo(consecutiveLaunchCrashes: Int) external fun updateOrientation(orientation: String) external fun updateUserId(newValue: String) external fun updateUserEmail(newValue: String) external fun updateUserName(newValue: String) - external fun getSignalUnwindStackFunction(): Long external fun updateLowMemory(newValue: Boolean, memoryTrimLevelDescription: String) + external fun getSignalUnwindStackFunction(): Long external fun addFeatureFlag(name: String, variant: String?) external fun clearFeatureFlag(name: String) external fun clearFeatureFlags() @@ -139,7 +138,6 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse event.contextActivity ?: "" ) - is StateEvent.UpdateLastRunInfo -> updateLastRunInfo(event.consecutiveLaunchCrashes) is StateEvent.UpdateIsLaunching -> { updateIsLaunching(event.isLaunching) 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 a0dbdeed20..c19b1c4054 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c @@ -84,8 +84,7 @@ void bsg_finish_handling_crash() { bsg_global_env->handling_crash = false; } -JNIEXPORT void JNICALL Java_com_bugsnag_android_NdkPlugin_enableCrashReporting( - JNIEnv *env, jobject _this) { +static void JNI_NdkPlugin_enableCrashReporting(JNIEnv *env, jobject _this) { if (bsg_global_env == NULL) { BUGSNAG_LOG( "Attempted to enable crash reporting without first calling install()"); @@ -95,14 +94,12 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_NdkPlugin_enableCrashReporting( bsg_handler_install_cpp(bsg_global_env); } -JNIEXPORT void JNICALL Java_com_bugsnag_android_NdkPlugin_disableCrashReporting( - JNIEnv *env, jobject _this) { +static void JNI_NdkPlugin_disableCrashReporting(JNIEnv *env, jobject _this) { bsg_handler_uninstall_signal(); bsg_handler_uninstall_cpp(); } -JNIEXPORT jstring JNICALL -Java_com_bugsnag_android_NdkPlugin_getBinaryArch(JNIEnv *env, jobject _this) { +static jstring JNI_NdkPlugin_getBinaryArch(JNIEnv *env, jobject _this) { #if defined(__i386__) const char *binary_arch = "x86"; #elif defined(__x86_64__) @@ -117,9 +114,7 @@ Java_com_bugsnag_android_NdkPlugin_getBinaryArch(JNIEnv *env, jobject _this) { return bsg_safe_new_string_utf(env, binary_arch); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_enableCrashReporting(JNIEnv *env, - jobject _this) { +static void JNI_NativeBridge_enableCrashReporting(JNIEnv *env, jobject _this) { if (bsg_global_env == NULL) { BUGSNAG_LOG( "Attempted to enable crash reporting without first calling install()"); @@ -129,9 +124,7 @@ Java_com_bugsnag_android_ndk_NativeBridge_enableCrashReporting(JNIEnv *env, bsg_handler_install_cpp(bsg_global_env); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_disableCrashReporting(JNIEnv *env, - jobject _this) { +static void JNI_NativeBridge_disableCrashReporting(JNIEnv *env, jobject _this) { bsg_handler_uninstall_signal(); bsg_handler_uninstall_cpp(); } @@ -152,12 +145,14 @@ void bsg_update_next_run_info(bsg_environment *env) { launch_crashes, crashed_value); } -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, jstring _event_uuid, - jint consecutive_launch_crashes, jboolean auto_detect_ndk_crashes, - jint _api_level, jboolean is32bit, jint send_threads, - jint max_breadcrumbs) { +static void JNI_NativeBridge_install(JNIEnv *env, jobject _this, + jstring _api_key, jstring _event_path, + jstring _last_run_info_path, + jstring _event_uuid, + jint consecutive_launch_crashes, + jboolean auto_detect_ndk_crashes, + jint _api_level, jboolean is32bit, + jint send_threads, jint max_breadcrumbs) { if (!bsg_jni_cache_init(env)) { BUGSNAG_LOG("Could not init JNI jni_cache."); @@ -255,9 +250,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install( free(bugsnag_env); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_addHandledEvent(JNIEnv *env, - jobject _this) { +static void JNI_NativeBridge_addHandledEvent(JNIEnv *env, jobject _this) { bsg_environment *bsg_env = request_env_write_lock(); if (bsg_env == NULL) { return; @@ -270,9 +263,7 @@ Java_com_bugsnag_android_ndk_NativeBridge_addHandledEvent(JNIEnv *env, release_env_write_lock(); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_addUnhandledEvent(JNIEnv *env, - jobject _this) { +static void JNI_NativeBridge_addUnhandledEvent(JNIEnv *env, jobject _this) { bsg_environment *bsg_env = request_env_write_lock(); if (bsg_env == NULL) { return; @@ -285,9 +276,11 @@ Java_com_bugsnag_android_ndk_NativeBridge_addUnhandledEvent(JNIEnv *env, release_env_write_lock(); } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_startedSession( - JNIEnv *env, jobject _this, jstring session_id_, jstring start_date_, - jint handled_count, jint unhandled_count) { +static void JNI_NativeBridge_startedSession(JNIEnv *env, jobject _this, + jstring session_id_, + jstring start_date_, + jint handled_count, + jint unhandled_count) { if (session_id_ == NULL) { return; } @@ -306,8 +299,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_startedSession( bsg_safe_release_string_utf_chars(env, start_date_, started_at); } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_pausedSession( - JNIEnv *env, jobject _this) { +static void JNI_NativeBridge_pausedSession(JNIEnv *env, jobject _this) { bsg_environment *bsg_env = request_env_write_lock(); if (bsg_env == NULL) { return; @@ -320,9 +312,10 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_pausedSession( release_env_write_lock(); } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb( - JNIEnv *env, jobject _this, jstring name_, jint crumb_type, - jstring timestamp_, jobject metadata) { +static void JNI_NativeBridge_addBreadcrumb(JNIEnv *env, jobject _this, + jstring name_, jint crumb_type, + jstring timestamp_, + jobject metadata) { if (!bsg_jni_cache->initialized) { BUGSNAG_LOG("addBreadcrumb failed: JNI cache not initialized."); @@ -386,10 +379,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb( bsg_safe_release_string_utf_chars(env, timestamp_, timestamp); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateAppVersion(JNIEnv *env, - jobject _this, - jstring new_value) { +static void JNI_NativeBridge_updateAppVersion(JNIEnv *env, jobject _this, + jstring new_value) { char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value); if (value == NULL) { return; @@ -404,10 +395,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateAppVersion(JNIEnv *env, bsg_safe_release_string_utf_chars(env, new_value, value); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateBuildUUID(JNIEnv *env, - jobject _this, - jstring new_value) { +static void JNI_NativeBridge_updateBuildUUID(JNIEnv *env, jobject _this, + jstring new_value) { char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value); if (value == NULL) { return; @@ -421,8 +410,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateBuildUUID(JNIEnv *env, bsg_safe_release_string_utf_chars(env, new_value, value); } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateContext( - JNIEnv *env, jobject _this, jstring new_value) { +static void JNI_NativeBridge_updateContext(JNIEnv *env, jobject _this, + jstring new_value) { char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value); if (value == NULL) { return; @@ -439,9 +428,9 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateContext( } } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateInForeground( - JNIEnv *env, jobject _this, jboolean new_value, jstring activity_) { +static void JNI_NativeBridge_updateInForeground(JNIEnv *env, jobject _this, + jboolean new_value, + jstring activity_) { bsg_environment *bsg_env = request_env_write_lock(); if (bsg_env == NULL) { return; @@ -465,9 +454,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateInForeground( } } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateIsLaunching( - JNIEnv *env, jobject _this, jboolean new_value) { +static void JNI_NativeBridge_updateIsLaunching(JNIEnv *env, jobject _this, + jboolean new_value) { bsg_environment *bsg_env = request_env_write_lock(); if (bsg_env == NULL) { return; @@ -477,10 +465,10 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateIsLaunching( release_env_write_lock(); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateLowMemory( - JNIEnv *env, jobject _this, jboolean low_memory, - jstring memory_trim_level_description) { +static void +JNI_NativeBridge_updateLowMemory(JNIEnv *env, jobject _this, + jboolean low_memory, + jstring memory_trim_level_description) { char *memory_trim_level = (char *)bsg_safe_get_string_utf_chars(env, memory_trim_level_description); @@ -505,10 +493,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateLowMemory( } } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateOrientation(JNIEnv *env, - jobject _this, - jstring new_value) { +static void JNI_NativeBridge_updateOrientation(JNIEnv *env, jobject _this, + jstring new_value) { char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value); if (value == NULL) { return; @@ -526,9 +512,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateOrientation(JNIEnv *env, } } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateReleaseStage( - JNIEnv *env, jobject _this, jstring new_value) { +static void JNI_NativeBridge_updateReleaseStage(JNIEnv *env, jobject _this, + jstring new_value) { char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value); if (value == NULL) { return; @@ -545,8 +530,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateReleaseStage( } } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserId( - JNIEnv *env, jobject _this, jstring new_value) { +static void JNI_NativeBridge_updateUserId(JNIEnv *env, jobject _this, + jstring new_value) { char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value); if (value == NULL) { return; @@ -566,8 +551,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserId( } } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserName( - JNIEnv *env, jobject _this, jstring new_value) { +static void JNI_NativeBridge_updateUserName(JNIEnv *env, jobject _this, + jstring new_value) { char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value); if (value == NULL) { return; @@ -587,10 +572,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserName( } } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateUserEmail(JNIEnv *env, - jobject _this, - jstring new_value) { +static void JNI_NativeBridge_updateUserEmail(JNIEnv *env, jobject _this, + jstring new_value) { char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value); if (value == NULL) { return; @@ -610,9 +593,9 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateUserEmail(JNIEnv *env, } } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_addMetadataString( - JNIEnv *env, jobject _this, jstring tab_, jstring key_, jstring value_) { +static void JNI_NativeBridge_addMetadataString(JNIEnv *env, jobject _this, + jstring tab_, jstring key_, + jstring value_) { char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_); char *key = (char *)bsg_safe_get_string_utf_chars(env, key_); char *value = (char *)bsg_safe_get_string_utf_chars(env, value_); @@ -631,9 +614,9 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataString( bsg_safe_release_string_utf_chars(env, value_, value); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_addMetadataDouble( - JNIEnv *env, jobject _this, jstring tab_, jstring key_, jdouble value_) { +static void JNI_NativeBridge_addMetadataDouble(JNIEnv *env, jobject _this, + jstring tab_, jstring key_, + jdouble value_) { char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_); char *key = (char *)bsg_safe_get_string_utf_chars(env, key_); if (tab != NULL && key != NULL) { @@ -650,9 +633,9 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataDouble( bsg_safe_release_string_utf_chars(env, key_, key); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_addMetadataBoolean( - JNIEnv *env, jobject _this, jstring tab_, jstring key_, jboolean value_) { +static void JNI_NativeBridge_addMetadataBoolean(JNIEnv *env, jobject _this, + jstring tab_, jstring key_, + jboolean value_) { char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_); char *key = (char *)bsg_safe_get_string_utf_chars(env, key_); if (tab != NULL && key != NULL) { @@ -669,9 +652,9 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataBoolean( bsg_safe_release_string_utf_chars(env, key_, key); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_addMetadataOpaque( - JNIEnv *env, jobject _this, jstring tab_, jstring key_, jstring value_) { +static void JNI_NativeBridge_addMetadataOpaque(JNIEnv *env, jobject _this, + jstring tab_, jstring key_, + jstring value_) { char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_); char *key = (char *)bsg_safe_get_string_utf_chars(env, key_); char *value = (char *)bsg_safe_get_string_utf_chars(env, value_); @@ -690,10 +673,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataOpaque( bsg_safe_release_string_utf_chars(env, value_, value); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_clearMetadataTab(JNIEnv *env, - jobject _this, - jstring tab_) { +static void JNI_NativeBridge_clearMetadataTab(JNIEnv *env, jobject _this, + jstring tab_) { char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_); if (tab == NULL) { return; @@ -708,8 +689,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_clearMetadataTab(JNIEnv *env, bsg_safe_release_string_utf_chars(env, tab_, tab); } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_removeMetadata( - JNIEnv *env, jobject _this, jstring tab_, jstring key_) { +static void JNI_NativeBridge_removeMetadata(JNIEnv *env, jobject _this, + jstring tab_, jstring key_) { char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_); char *key = (char *)bsg_safe_get_string_utf_chars(env, key_); @@ -727,8 +708,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_removeMetadata( bsg_safe_release_string_utf_chars(env, key_, key); } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateMetadata( - JNIEnv *env, jobject _this, jobject metadata) { +static void JNI_NativeBridge_updateMetadata(JNIEnv *env, jobject _this, + jobject metadata) { if (!bsg_jni_cache->initialized) { BUGSNAG_LOG("updateMetadata failed: JNI cache not initialized."); return; @@ -742,14 +723,13 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateMetadata( release_env_write_lock(); } -JNIEXPORT jlong JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_getSignalUnwindStackFunction( - JNIEnv *env, jobject thiz) { +static jlong JNI_NativeBridge_getSignalUnwindStackFunction(JNIEnv *env, + jobject thiz) { return (jlong)bsg_unwind_crash_stack; } -JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addFeatureFlag( - JNIEnv *env, jobject thiz, jstring name_, jstring variant_) { +static void JNI_NativeBridge_addFeatureFlag(JNIEnv *env, jobject thiz, + jstring name_, jstring variant_) { char *name = (char *)bsg_safe_get_string_utf_chars(env, name_); char *variant = (char *)bsg_safe_get_string_utf_chars(env, variant_); @@ -768,10 +748,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addFeatureFlag( bsg_safe_release_string_utf_chars(env, variant_, variant); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlag(JNIEnv *env, - jobject thiz, - jstring name_) { +static void JNI_NativeBridge_clearFeatureFlag(JNIEnv *env, jobject thiz, + jstring name_) { char *name = (char *)bsg_safe_get_string_utf_chars(env, name_); @@ -788,9 +766,7 @@ Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlag(JNIEnv *env, bsg_safe_release_string_utf_chars(env, name_, name); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlags(JNIEnv *env, - jobject thiz) { +static void JNI_NativeBridge_clearFeatureFlags(JNIEnv *env, jobject thiz) { bsg_environment *bsg_env = request_env_write_lock(); if (bsg_env == NULL) { return; @@ -799,15 +775,12 @@ Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlags(JNIEnv *env, release_env_write_lock(); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_refreshSymbolTable(JNIEnv *env, - jobject thiz) { +static void JNI_NativeBridge_refreshSymbolTable(JNIEnv *env, jobject thiz) { bugsnag_refresh_symbol_table(); } -JNIEXPORT jobject JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_getCurrentCallbackSetCounts( - JNIEnv *env, jobject thiz) { +static jobject JNI_NativeBridge_getCurrentCallbackSetCounts(JNIEnv *env, + jobject thiz) { if (bsg_global_env == NULL || bsg_jni_cache == NULL) { return NULL; @@ -839,9 +812,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_getCurrentCallbackSetCounts( return counts; } -JNIEXPORT jobject JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_getCurrentNativeApiCallUsage( - JNIEnv *env, jobject thiz) { +static jobject JNI_NativeBridge_getCurrentNativeApiCallUsage(JNIEnv *env, + jobject thiz) { if (bsg_global_env == NULL || bsg_jni_cache == NULL) { return NULL; } @@ -869,10 +841,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_getCurrentNativeApiCallUsage( return map; } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_setStaticJsonData(JNIEnv *env, - jobject thiz, - jstring data_) { +static void JNI_NativeBridge_setStaticJsonData(JNIEnv *env, jobject thiz, + jstring data_) { if (bsg_global_env == NULL) { return; } @@ -900,10 +870,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_setStaticJsonData(JNIEnv *env, bsg_safe_release_string_utf_chars(env, data_, data); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_initCallbackCounts(JNIEnv *env, - jobject thiz, - jobject counts) { +static void JNI_NativeBridge_initCallbackCounts(JNIEnv *env, jobject thiz, + jobject counts) { jobject entrySet = bsg_safe_call_object_method(env, counts, bsg_jni_cache->Map_entrySet); jobject iterator = @@ -925,10 +893,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_initCallbackCounts(JNIEnv *env, } } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_notifyAddCallback(JNIEnv *env, - jobject thiz, - jstring callback_) { +static void JNI_NativeBridge_notifyAddCallback(JNIEnv *env, jobject thiz, + jstring callback_) { const char *callback = bsg_safe_get_string_utf_chars(env, callback_); if (!callback) { return; @@ -944,9 +910,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_notifyAddCallback(JNIEnv *env, bsg_safe_release_string_utf_chars(env, callback_, callback); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_notifyRemoveCallback( - JNIEnv *env, jobject thiz, jstring callback_) { +static void JNI_NativeBridge_notifyRemoveCallback(JNIEnv *env, jobject thiz, + jstring callback_) { const char *callback = bsg_safe_get_string_utf_chars(env, callback_); if (!callback) { return; @@ -962,12 +927,55 @@ Java_com_bugsnag_android_ndk_NativeBridge_notifyRemoveCallback( bsg_safe_release_string_utf_chars(env, callback_, callback); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_setInternalMetricsEnabled( - JNIEnv *env, jobject thiz, jboolean enabled) { +static void JNI_NativeBridge_setInternalMetricsEnabled(JNIEnv *env, + jobject thiz, + jboolean enabled) { bsg_set_internal_metrics_enabled(enabled); } +#include "NativeBridge_JNI.h" +#include "NdkPlugin_JNI.h" + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + // export all of the JNI functions and link them to the Java methods + JNIEnv *env = NULL; + jint result = -1; + + if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) { + goto error; + } + + if (env == NULL) { + goto error; + } + + if (!bsg_jni_cache_init(env)) { + BUGSNAG_LOG("Could not init JNI jni_cache."); + goto error; + } + + result = (*env)->RegisterNatives(env, bsg_jni_cache->NativeBridge, + NativeBridge_JNIMethods, + NativeBridge_JNI_METHODS_COUNT); + if (result < 0) { + BUGSNAG_LOG("Failed to register Bugsnag NativeBridge JNI methods"); + goto error; + } + + result = (*env)->RegisterNatives(env, bsg_jni_cache->NdkPlugin, + NdkPlugin_JNIMethods, + NdkPlugin_JNI_METHODS_COUNT); + if (result < 0) { + BUGSNAG_LOG("Failed to register Bugsnag NdkPlugin JNI methods"); + goto error; + } + + return JNI_VERSION_1_4; + +error: + return result; +} + #ifdef __cplusplus } #endif 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 15f86c7fcb..300a9c73ca 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c @@ -208,6 +208,9 @@ bool bsg_jni_cache_init(JNIEnv *env) { CACHE_ENUM_CONSTANT(ErrorType_C, "com/bugsnag/android/ErrorType", "C"); + CACHE_CLASS(NativeBridge, "com/bugsnag/android/ndk/NativeBridge"); + CACHE_CLASS(NdkPlugin, "com/bugsnag/android/NdkPlugin"); + pthread_key_create(&jni_cleanup_key, detach_java_env); bsg_jni_cache->initialized = true; 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 574b424d08..3528633637 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h @@ -80,6 +80,9 @@ typedef struct { jmethodID OpaqueValue_makeSafe; jobject ErrorType_C; + + jclass NativeBridge; + jclass NdkPlugin; } bsg_jni_cache_t; extern bsg_jni_cache_t *const bsg_jni_cache; diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index bac8645f5d..5db8d9742a 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -4,8 +4,19 @@ plugins { repositories { mavenCentral() + google() +} + +gradlePlugin { + plugins { + register("bugsnag-ndk-table-plugin") { + id = "bugsnag-ndk-table-plugin" + implementationClass = "com.bugsnag.android.gradle.JNILinkTablePlugin" + } + } } dependencies { compileOnly(gradleApi()) + implementation("com.android.tools.build:gradle-api:7.0.4") } diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/GenerateJNILinkTableTransform.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/GenerateJNILinkTableTransform.kt new file mode 100644 index 0000000000..aba304f9d4 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/GenerateJNILinkTableTransform.kt @@ -0,0 +1,135 @@ +package com.bugsnag.android.gradle + +import com.android.build.api.instrumentation.AsmClassVisitorFactory +import com.android.build.api.instrumentation.ClassContext +import com.android.build.api.instrumentation.ClassData +import com.android.build.api.instrumentation.InstrumentationParameters +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +class GenerateJNILinkTableTransform( + api: Int, + classVisitor: ClassVisitor?, + val outputDirectory: Directory +) : ClassVisitor(api, classVisitor) { + + private var className: String? = null + + private var nativeMethods: MutableList = mutableListOf() + + override fun visit( + version: Int, + access: Int, + name: String?, + signature: String?, + superName: String?, + interfaces: Array? + ) { + className = name?.substringAfterLast('/') + super.visit(version, access, name, signature, superName, interfaces) + } + + override fun visitEnd() { + // write the JNI link table for the class to header file + className?.let { className -> + if (nativeMethods.isNotEmpty()) { + generateLinkHeader(className, nativeMethods) + } + } + + super.visitEnd() + } + + private fun generateLinkHeader(className: String, methods: List) { + val headerFile = outputDirectory.file("${className}_JNI.h").asFile + headerFile.printWriter().use { writer -> + writer.println("// JNI Link Table for $className") + writer.println("#include ") + writer.println("#ifndef ${className}_JNI_H") + writer.println("#define ${className}_JNI_H") + writer.println() + + writer.println("#define ${className}_JNI_METHODS_COUNT ${methods.size}") + + writer.println("static const JNINativeMethod ${className}_JNIMethods[] = {"); + for (i in methods.indices) { + val method = methods[i] + + writer.print(" {\"") + writer.print(method.name) + writer.print("\", \"") + writer.print(method.descriptor) + writer.print("\", &JNI_") + writer.print(className) + writer.print('_') + writer.print(method.name) + writer.print("}") + + if (i < methods.size - 1) { + writer.println(",") + } else { + writer.println() + } + } + writer.println("};"); + + writer.println() + writer.println("#endif // ${className}_JNI_H") + } + } + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ): MethodVisitor { + if (access and Opcodes.ACC_NATIVE != 0 && name != null && descriptor != null) { + nativeMethods.add(JNIMethod(name, descriptor)) + } + + return super.visitMethod(access, name, descriptor, signature, exceptions) + } + + private data class JNIMethod( + val name: String, + val descriptor: String, + ) +} + +abstract class GenerateJNILinkTableTransformFactory : + AsmClassVisitorFactory { + override fun createClassVisitor( + classContext: ClassContext, + nextClassVisitor: ClassVisitor + ): ClassVisitor { + return GenerateJNILinkTableTransform( + Opcodes.ASM7, + nextClassVisitor, + parameters.get().outputDirectory.get() + ) + } + + override fun isInstrumentable(classData: ClassData): Boolean { + val className = classData.className + val packagePrefix = parameters.get().packagePrefix.get() + + // Check if the class is in the specified package + return className.startsWith(packagePrefix) + } +} + +interface GenerateJNILinkTableParameters : InstrumentationParameters { + @get:Input + val packagePrefix: Property + + @get:OutputDirectory + val outputDirectory: DirectoryProperty +} diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt new file mode 100644 index 0000000000..4da895b1aa --- /dev/null +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt @@ -0,0 +1,67 @@ +package com.bugsnag.android.gradle + +import com.android.build.api.instrumentation.InstrumentationScope +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.Variant +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.provider.Provider + +class JNILinkTablePlugin : Plugin { + override fun apply(project: Project) { + // Create a directory for generated headers + val generatedHeadersDir = project.layout.buildDirectory.dir("generated/jni-headers") + + // Get the Android components extension + val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) + + // Configure each variant + androidComponents.onVariants { variant -> + project.configureVariant(variant, generatedHeadersDir) + } + } + + private fun Project.configureVariant( + variant: Variant, + generatedHeadersDir: Provider + ) { + val externalNativeBuild = variant.externalNativeBuild ?: return + + // configure the variant to generate the headers from the class files + variant.transformClassesWith( + GenerateJNILinkTableTransformFactory::class.java, + InstrumentationScope.PROJECT + ) { parameters -> + parameters.packagePrefix.set("com.bugsnag.android") + parameters.outputDirectory.set(generatedHeadersDir) + } + + fixTaskOrdering(variant) + + // Get the physical directory + val headersDir = generatedHeadersDir.get().asFile + + // Ensure the directory exists + headersDir.mkdirs() + + // Configure CMake to include our generated headers directory + externalNativeBuild.cFlags.apply { + // Add the include directory to the CMake arguments + add("-I${headersDir.absolutePath}") + } + } + + private fun Project.fixTaskOrdering(variant: Variant) { + val variantName = variant.name.capitalize() + val transformTaskName = "transform${variantName}ClassesWithAsm" + // Ensure the CMake task runs after the classes are transformed + tasks.whenTaskAdded { + when (name) { + "buildCMakeDebug", "buildCMakeRelWithDebInfo" -> { + dependsOn(transformTaskName) + } + } + } + } +} \ No newline at end of file From 7c25eeaa28d19ada5b6e1195e7a2fc161307c389 Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 30 Jun 2025 16:37:53 +0100 Subject: [PATCH 14/16] chore(docs): renamed the link-table plugin to `bugsnag-jni-link-table-plugin` and added some docs to explain what it does --- CHANGELOG.md | 5 +++++ bugsnag-plugin-android-ndk/build.gradle.kts | 2 +- .../src/main/jni/bugsnag_ndk.c | 3 +++ buildSrc/build.gradle.kts | 4 ++-- .../com/bugsnag/android/gradle/JNILinkTablePlugin.kt | 12 ++++++++++++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ffd467fe..9bbfcf5eef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## TBD +### Enhancements + +* `bugsnag-plugin-android-ndk` no longer exports the `Java_` JNI functions, and instead uses the JNI `RegisterNatives` API to register the native methods. + [#2205](https://github.com/bugsnag/bugsnag-android/pull/2205) + ### Bug fixes * The `bugsnag-plugin-android-exitinfo` plugin now calls `setProcessState` (if configured) on a background thread and swallows any rate-limiting errors, so that it does not block the main thread during startup diff --git a/bugsnag-plugin-android-ndk/build.gradle.kts b/bugsnag-plugin-android-ndk/build.gradle.kts index 55dba4e1b3..089b80df0f 100644 --- a/bugsnag-plugin-android-ndk/build.gradle.kts +++ b/bugsnag-plugin-android-ndk/build.gradle.kts @@ -1,6 +1,6 @@ plugins { loadDefaultPlugins() - id("bugsnag-ndk-table-plugin") + id("bugsnag-jni-link-table-plugin") } android { 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 c19b1c4054..14f735d0c3 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c @@ -933,6 +933,9 @@ static void JNI_NativeBridge_setInternalMetricsEnabled(JNIEnv *env, bsg_set_internal_metrics_enabled(enabled); } +// These headers are included here to ensure that the JNI methods are +// declared (above) as static, avoiding the need to forward declare them + #include "NativeBridge_JNI.h" #include "NdkPlugin_JNI.h" diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 5db8d9742a..9f7408cab1 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -9,8 +9,8 @@ repositories { gradlePlugin { plugins { - register("bugsnag-ndk-table-plugin") { - id = "bugsnag-ndk-table-plugin" + register("bugsnag-jni-link-table-plugin") { + id = "bugsnag-jni-link-table-plugin" implementationClass = "com.bugsnag.android.gradle.JNILinkTablePlugin" } } diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt index 4da895b1aa..066f874171 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt @@ -8,7 +8,19 @@ import org.gradle.api.Project import org.gradle.api.file.Directory import org.gradle.api.provider.Provider +/** + * Generates C header files for use with [RegisterNatives](docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives) + * for the Bugsnag Android SDK. This plugin ensures that the Java/Kotlin classes with native methods + * have corresponding C functions as part of the build process. When the headers are included + * in the C or C++ code, any missing implementations will cause a build error. + */ class JNILinkTablePlugin : Plugin { + /* + * This plugin generates JNI headers for the Bugsnag Android SDK by registering an + * identity ASM transformer to AGP. The transformer processes the classes, but does not + * modify them. Instead it just captures the native method signatures to generate the headers. + */ + override fun apply(project: Project) { // Create a directory for generated headers val generatedHeadersDir = project.layout.buildDirectory.dir("generated/jni-headers") From 24eb1c161a7a22a08975de79b4403ffb733e6164 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:06:35 +0100 Subject: [PATCH 15/16] build(deps): bump github/codeql-action from 3.29.0 to 3.29.2 (#2207) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.0 to 3.29.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ce28f5bb42b7a9f2c824e633a3f6ee835bab6858...181d5eefc20863364f96762470ba6f862bdef56b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6baf898e73..2e96402a4a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -64,7 +64,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 #v3.29.0 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b #v3.29.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -83,6 +83,6 @@ jobs: ./gradlew --no-daemon assemble - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 #v3.29.0 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b #v3.29.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 57a3c39468..9487828c60 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -68,7 +68,7 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: sarif_file: results.sarif From 1999bab42b72e9c120927ef9fcd7bb32f47b5c64 Mon Sep 17 00:00:00 2001 From: YYChen01988 Date: Wed, 2 Jul 2025 09:18:16 +0100 Subject: [PATCH 16/16] release v6.15.0 --- CHANGELOG.md | 2 +- .../src/main/java/com/bugsnag/android/Notifier.kt | 2 +- gradle.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466f8baabf..12ac086608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## TBD +## 6.15.0 (2025-07-02) ### 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 69e15b30c3..a82c6a6439 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.14.0", + var version: String = "6.15.0", var url: String = "https://bugsnag.com" ) : JsonStream.Streamable { diff --git a/gradle.properties b/gradle.properties index 1706d5d1ef..3a15499332 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.14.0 +VERSION_NAME=6.15.0 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git