diff --git a/CHANGELOG.md b/CHANGELOG.md index 31992e0034..7e8355405f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Fixes + +- 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 - Flush logs if client/hub/sdk is closed ([#3335](https://github.com/getsentry/sentry-dart/pull/3335) 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..3697c6e618 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 @@ -194,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); }); @@ -219,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); @@ -237,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; @@ -306,8 +308,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)); @@ -380,46 +382,45 @@ class SentryNativeJava extends SentryNativeChannel { 0, // bitRate is currently not used ); - _nativeReplay ??= native.SentryFlutterPlugin.Companion - .privateSentryGetReplayIntegration(); + _nativeReplay ??= + native.SentryFlutterPlugin.privateSentryGetReplayIntegration(); _nativeReplay?.onConfigurationChanged(replayConfig); replayConfig.release(); }); } -JObject? _dartToJObject(Object? value, Arena arena) => switch (value) { +JObject? _dartToJObject(Object? value) => 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), + 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, Arena arena) { - final jlist = JList.array(JObject.nullableType)..releasedBy(arena); - - for (final value in values) { - final jObj = _dartToJObject(value, arena); - jlist.add(jObj); +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; + 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; + return jMap; } const _videoBlockSize = 16; 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..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 @@ -46,8 +46,7 @@ void initSentryAndroid({ ); replayCallbacks.use((cb) { - native.SentryFlutterPlugin.Companion - .setupReplay(androidOptions, cb); + native.SentryFlutterPlugin.setupReplay(androidOptions, cb); }); }, ), @@ -126,7 +125,9 @@ native.SentryOptions$BeforeSendReplayCallback createBeforeSendReplayCallback( return shouldRemove; }); - payload?.addAll(_dartToJMap(options.privacy.toJson(), arena)); + final jMap = _dartToJMap(options.privacy.toJson()); + payload?.addAll(jMap); + jMap.release(); } }); return sentryReplayEvent; @@ -151,8 +152,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) {