From f2c85c0ac3a508f7bce535637a422a4c7835cf20 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 19 Nov 2025 15:16:25 +0100 Subject: [PATCH 1/6] Dont use Companion in JNI --- .../src/native/java/android_replay_recorder.dart | 4 ++-- .../lib/src/native/java/sentry_native_java.dart | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/flutter/lib/src/native/java/android_replay_recorder.dart b/packages/flutter/lib/src/native/java/android_replay_recorder.dart index 0a99968a82..a7f31f7271 100644 --- a/packages/flutter/lib/src/native/java/android_replay_recorder.dart +++ b/packages/flutter/lib/src/native/java/android_replay_recorder.dart @@ -99,8 +99,8 @@ class _AndroidReplayHandler extends WorkerHandler { late final native.ReplayIntegration _nativeReplay; _AndroidReplayHandler(this._config) { - _nativeReplay = native.SentryFlutterPlugin.Companion - .privateSentryGetReplayIntegration()!; + _nativeReplay = + native.SentryFlutterPlugin.privateSentryGetReplayIntegration()!; } @override diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index b772e6f504..c339827333 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -74,8 +74,8 @@ class SentryNativeJava extends SentryNativeChannel { // NOTE: when instructionAddressSet is empty, loadDebugImagesAsBytes will return // all debug images as fallback. - imagesUtf8JsonBytes = native.SentryFlutterPlugin.Companion - .loadDebugImagesAsBytes(instructionAddressSet); + imagesUtf8JsonBytes = native.SentryFlutterPlugin.loadDebugImagesAsBytes( + instructionAddressSet); if (imagesUtf8JsonBytes == null) return null; final byteRange = @@ -112,8 +112,7 @@ class SentryNativeJava extends SentryNativeChannel { // is significantly faster because contexts can be large and contain many nested // objects. Local benchmarks show this method is ~4x faster than the alternative // approach of converting JNI objects to Dart objects one by one. - contextsUtf8JsonBytes = - native.SentryFlutterPlugin.Companion.loadContextsAsBytes(); + contextsUtf8JsonBytes = native.SentryFlutterPlugin.loadContextsAsBytes(); if (contextsUtf8JsonBytes == null) return null; final byteRange = @@ -136,8 +135,7 @@ class SentryNativeJava extends SentryNativeChannel { @override int? displayRefreshRate() => tryCatchSync('displayRefreshRate', () { - return native.SentryFlutterPlugin.Companion - .getDisplayRefreshRate() + return native.SentryFlutterPlugin.getDisplayRefreshRate() ?.intValue(releaseOriginal: true); }); @@ -150,7 +148,7 @@ class SentryNativeJava extends SentryNativeChannel { return null; } appStartUtf8JsonBytes = - native.SentryFlutterPlugin.Companion.fetchNativeAppStartAsBytes(); + native.SentryFlutterPlugin.fetchNativeAppStartAsBytes(); if (appStartUtf8JsonBytes == null) return null; final byteRange = @@ -166,7 +164,7 @@ class SentryNativeJava extends SentryNativeChannel { @override void nativeCrash() { - native.SentryFlutterPlugin.Companion.crash(); + native.SentryFlutterPlugin.crash(); } @override From a5843359f3b211745f55c15bea2b3ca4a54b873f Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 19 Nov 2025 15:25:56 +0100 Subject: [PATCH 2/6] Dont use Companion in JNI --- .../flutter/lib/src/native/java/sentry_native_java.dart | 8 ++++---- .../lib/src/native/java/sentry_native_java_init.dart | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index c339827333..e8d863f96d 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -304,8 +304,8 @@ class SentryNativeJava extends SentryNativeChannel { SentryId captureReplay() { final id = tryCatchSync('captureReplay', () { return using((arena) { - _nativeReplay ??= native.SentryFlutterPlugin.Companion - .privateSentryGetReplayIntegration(); + _nativeReplay ??= + native.SentryFlutterPlugin.privateSentryGetReplayIntegration(); // The passed parameter is `isTerminating` _nativeReplay?.captureReplay(false.toJBoolean()..releasedBy(arena)); @@ -378,8 +378,8 @@ class SentryNativeJava extends SentryNativeChannel { 0, // bitRate is currently not used ); - _nativeReplay ??= native.SentryFlutterPlugin.Companion - .privateSentryGetReplayIntegration(); + _nativeReplay ??= + native.SentryFlutterPlugin.privateSentryGetReplayIntegration(); _nativeReplay?.onConfigurationChanged(replayConfig); replayConfig.release(); diff --git a/packages/flutter/lib/src/native/java/sentry_native_java_init.dart b/packages/flutter/lib/src/native/java/sentry_native_java_init.dart index b0bf1ea417..6cfd12ab4a 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java_init.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java_init.dart @@ -46,8 +46,7 @@ void initSentryAndroid({ ); replayCallbacks.use((cb) { - native.SentryFlutterPlugin.Companion - .setupReplay(androidOptions, cb); + native.SentryFlutterPlugin.setupReplay(androidOptions, cb); }); }, ), @@ -151,8 +150,8 @@ native.ReplayRecorderCallbacks? createReplayRecorderCallbacks({ SentryId.fromId(replayIdString.toDartString(releaseOriginal: true)); owner._replayId = replayId; - owner._nativeReplay = native.SentryFlutterPlugin.Companion - .privateSentryGetReplayIntegration(); + owner._nativeReplay = + native.SentryFlutterPlugin.privateSentryGetReplayIntegration(); owner._replayRecorder = AndroidReplayRecorder.factory(options); await owner._replayRecorder!.start(); hub.configureScope((s) { From 2f2b393c2fc4ab6de9a4f6dd2cc3734fbdd05179 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 19 Nov 2025 15:26:40 +0100 Subject: [PATCH 3/6] Update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31992e0034..605f274c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Fixes + +- Dont use `Companion` in JNI calls ([#3354](https://github.com/getsentry/sentry-dart/pull/3354)) + - This potentially fixes segfault crashes related to JNI + ### Enhancements - Flush logs if client/hub/sdk is closed ([#3335](https://github.com/getsentry/sentry-dart/pull/3335) From e1145e0bb5b77744caf80c75464897d82d850134 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 19 Nov 2025 15:52:58 +0100 Subject: [PATCH 4/6] Fix JNI ref release --- CHANGELOG.md | 2 +- .../src/native/java/sentry_native_java.dart | 61 ++++++++++--------- .../native/java/sentry_native_java_init.dart | 2 +- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 605f274c50..7e8355405f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixes -- Dont use `Companion` in JNI calls ([#3354](https://github.com/getsentry/sentry-dart/pull/3354)) +- Dont use `Companion` in JNI calls and properly release JNI refs ([#3354](https://github.com/getsentry/sentry-dart/pull/3354)) - This potentially fixes segfault crashes related to JNI ### Enhancements diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index e8d863f96d..3d5a6382f8 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -192,10 +192,12 @@ class SentryNativeJava extends SentryNativeChannel { final nativeOptions = native.ScopesAdapter.getInstance()?.getOptions() ?..releasedBy(arena); if (nativeOptions == null) return; - final jMap = _dartToJMap(breadcrumb.toJson(), arena); + final jMap = _dartToJMap(breadcrumb.toJson()); final nativeBreadcrumb = native.Breadcrumb.fromMap(jMap, nativeOptions) ?..releasedBy(arena); + // release jMap directly after use + jMap.release(); if (nativeBreadcrumb == null) return; native.Sentry.addBreadcrumb$1(nativeBreadcrumb); }); @@ -217,9 +219,11 @@ class SentryNativeJava extends SentryNativeChannel { ?..releasedBy(arena); if (nativeOptions == null) return; - final nativeUser = native.User.fromMap( - _dartToJMap(user.toJson(), arena), nativeOptions) + final jMap = _dartToJMap(user.toJson()); + final nativeUser = native.User.fromMap(jMap, nativeOptions) ?..releasedBy(arena); + // release jMap directly after use + jMap.release(); if (nativeUser == null) return; native.Sentry.setUser(nativeUser); @@ -235,7 +239,7 @@ class SentryNativeJava extends SentryNativeChannel { run: (iScope) { using((arena) { final jKey = key.toJString()..releasedBy(arena); - final jVal = _dartToJObject(value, arena); + final jVal = _dartToJObject(value)?..releasedBy(arena); if (jVal == null) return; @@ -386,37 +390,36 @@ class SentryNativeJava extends SentryNativeChannel { }); } -JObject? _dartToJObject(Object? value, Arena arena) => switch (value) { - null => null, - String s => s.toJString()..releasedBy(arena), - bool b => b.toJBoolean()..releasedBy(arena), - int i => i.toJLong()..releasedBy(arena), - double d => d.toJDouble()..releasedBy(arena), - List l => _dartToJList(l, arena), - Map m => _dartToJMap(m, arena), - _ => null - }; - -JList _dartToJList(List values, Arena arena) { - final jlist = JList.array(JObject.nullableType)..releasedBy(arena); - - for (final value in values) { - final jObj = _dartToJObject(value, arena); - jlist.add(jObj); - } +JObject? _dartToJObject(Object? value) { + if (value == null) return null; + if (value is String) return value.toJString(); + if (value is bool) return value.toJBoolean(); + if (value is int) return value.toJLong(); + if (value is double) return value.toJDouble(); + if (value is List) return _dartToJList(value); + if (value is Map) return _dartToJMap(value); + return null; +} +JList _dartToJList(List values) { + final jlist = JList.array(JObject.nullableType); + for (final v in values) { + final j = _dartToJObject(v); + jlist.add(j); + j?.release(); + } return jlist; } -JMap _dartToJMap(Map json, Arena arena) { - final jmap = JMap.hash(JString.type, JObject.nullableType)..releasedBy(arena); - +JMap _dartToJMap(Map json) { + final jmap = JMap.hash(JString.type, JObject.nullableType); for (final entry in json.entries) { - final key = entry.key.toJString()..releasedBy(arena); - final value = _dartToJObject(entry.value, arena); - jmap[key] = value; + final jk = entry.key.toJString(); + final jv = _dartToJObject(entry.value); + jmap[jk] = jv; + jk.release(); + jv?.release(); } - return jmap; } diff --git a/packages/flutter/lib/src/native/java/sentry_native_java_init.dart b/packages/flutter/lib/src/native/java/sentry_native_java_init.dart index 6cfd12ab4a..01fd4b2b98 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java_init.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java_init.dart @@ -125,7 +125,7 @@ native.SentryOptions$BeforeSendReplayCallback createBeforeSendReplayCallback( return shouldRemove; }); - payload?.addAll(_dartToJMap(options.privacy.toJson(), arena)); + payload?.addAll(_dartToJMap(options.privacy.toJson())); } }); return sentryReplayEvent; From 69cd27016b3013098b0810022184b85699d739dd Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 20 Nov 2025 12:21:24 +0100 Subject: [PATCH 5/6] Styling change --- .../src/native/java/sentry_native_java.dart | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 3d5a6382f8..3697c6e618 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -390,37 +390,37 @@ class SentryNativeJava extends SentryNativeChannel { }); } -JObject? _dartToJObject(Object? value) { - if (value == null) return null; - if (value is String) return value.toJString(); - if (value is bool) return value.toJBoolean(); - if (value is int) return value.toJLong(); - if (value is double) return value.toJDouble(); - if (value is List) return _dartToJList(value); - if (value is Map) return _dartToJMap(value); - return null; -} +JObject? _dartToJObject(Object? value) => switch (value) { + null => null, + String s => s.toJString(), + bool b => b.toJBoolean(), + int i => i.toJLong(), + double d => d.toJDouble(), + List l => _dartToJList(l), + Map m => _dartToJMap(m), + _ => null + }; JList _dartToJList(List values) { - final jlist = JList.array(JObject.nullableType); + final jList = JList.array(JObject.nullableType); for (final v in values) { final j = _dartToJObject(v); - jlist.add(j); + jList.add(j); j?.release(); } - return jlist; + return jList; } JMap _dartToJMap(Map json) { - final jmap = JMap.hash(JString.type, JObject.nullableType); + final jMap = JMap.hash(JString.type, JObject.nullableType); for (final entry in json.entries) { final jk = entry.key.toJString(); final jv = _dartToJObject(entry.value); - jmap[jk] = jv; + jMap[jk] = jv; jk.release(); jv?.release(); } - return jmap; + return jMap; } const _videoBlockSize = 16; From 52f7999abf5cc0607b7421c2fd9a4c762f5786da Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 20 Nov 2025 12:50:37 +0100 Subject: [PATCH 6/6] Refactor privacy options handling in Sentry init --- .../flutter/lib/src/native/java/sentry_native_java_init.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java_init.dart b/packages/flutter/lib/src/native/java/sentry_native_java_init.dart index 01fd4b2b98..36642754be 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java_init.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java_init.dart @@ -125,7 +125,9 @@ native.SentryOptions$BeforeSendReplayCallback createBeforeSendReplayCallback( return shouldRemove; }); - payload?.addAll(_dartToJMap(options.privacy.toJson())); + final jMap = _dartToJMap(options.privacy.toJson()); + payload?.addAll(jMap); + jMap.release(); } }); return sentryReplayEvent;