Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ class SentryFlutterPlugin :
"removeExtra" -> removeExtra(call.argument("key"), result)
"setTag" -> setTag(call.argument("key"), call.argument("value"), result)
"removeTag" -> removeTag(call.argument("key"), result)
"nativeCrash" -> crash()
"setReplayConfig" -> setReplayConfig(call, result)
"captureReplay" -> captureReplay(result)
else -> result.notImplemented()
Expand Down Expand Up @@ -291,6 +290,15 @@ class SentryFlutterPlugin :
@JvmStatic
fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay

@Suppress("unused") // Used by native/jni bindings
@JvmStatic
fun nativeCrash() {
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")
val mainThread = Looper.getMainLooper().thread
mainThread.uncaughtExceptionHandler?.uncaughtException(mainThread, exception)
mainThread.join(NATIVE_CRASH_WAIT_TIME)
}

@Suppress("unused") // Used by native/jni bindings
@JvmStatic
fun getDisplayRefreshRate(): Int? {
Expand Down Expand Up @@ -455,13 +463,6 @@ class SentryFlutterPlugin :
"debug_file" to debugFile,
)

private fun crash() {
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")
val mainThread = Looper.getMainLooper().thread
mainThread.uncaughtExceptionHandler?.uncaughtException(mainThread, exception)
mainThread.join(NATIVE_CRASH_WAIT_TIME)
}

private fun Double.adjustReplaySizeToBlockSize(): Double {
val remainder = this % VIDEO_BLOCK_SIZE
return if (remainder <= VIDEO_BLOCK_SIZE / 2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
collectProfile(call, result)
#endif

case "pauseAppHangTracking":
pauseAppHangTracking(result)

case "resumeAppHangTracking":
resumeAppHangTracking(result)

case "nativeCrash":
crash()

case "captureReplay":
#if canImport(UIKit) && !SENTRY_NO_UIKIT && (os(iOS) || os(tvOS))
PrivateSentrySDKOnly.captureReplay()
Expand Down Expand Up @@ -431,20 +422,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
result(nil)
}

private func pauseAppHangTracking(_ result: @escaping FlutterResult) {
SentrySDK.pauseAppHangTracking()
result("")
}

private func resumeAppHangTracking(_ result: @escaping FlutterResult) {
SentrySDK.resumeAppHangTracking()
result("")
}

private func crash() {
SentrySDK.crash()
}

// MARK: - Objective-C interoperability
//
// Group of methods exposed to the Objective-C runtime via `@objc`.
Expand Down Expand Up @@ -541,6 +518,18 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
#endif
}

@objc public class func nativeCrash() {
SentrySDK.crash()
}

@objc public class func pauseAppHangTracking() {
SentrySDK.pauseAppHangTracking()
}

@objc public class func resumeAppHangTracking() {
SentrySDK.resumeAppHangTracking()
}

@objc(loadDebugImagesAsBytes:)
public class func loadDebugImagesAsBytes(instructionAddresses: Set<String>) -> NSData? {
var debugImages: [DebugMeta] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
+ (nullable NSData *)fetchNativeAppStartAsBytes;
+ (nullable NSData *)loadContextsAsBytes;
+ (nullable NSData *)loadDebugImagesAsBytes:(NSSet<NSString *> *)instructionAddresses;
+ (void)nativeCrash;
+ (void)pauseAppHangTracking;
+ (void)resumeAppHangTracking;
@end
#endif
30 changes: 30 additions & 0 deletions packages/flutter/lib/src/native/cocoa/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,19 @@ late final _sel_fetchNativeAppStartAsBytes =
late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes");
late final _sel_loadDebugImagesAsBytes_ =
objc.registerName("loadDebugImagesAsBytes:");
late final _sel_nativeCrash = objc.registerName("nativeCrash");
final _objc_msgSend_1pl9qdv = objc.msgSendPointer
.cast<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<objc.ObjCObject>,
ffi.Pointer<objc.ObjCSelector>)>>()
.asFunction<
void Function(
ffi.Pointer<objc.ObjCObject>, ffi.Pointer<objc.ObjCSelector>)>();
late final _sel_pauseAppHangTracking =
objc.registerName("pauseAppHangTracking");
late final _sel_resumeAppHangTracking =
objc.registerName("resumeAppHangTracking");

/// SentryFlutterPlugin
class SentryFlutterPlugin extends objc.NSObject {
Expand Down Expand Up @@ -1186,6 +1199,23 @@ class SentryFlutterPlugin extends objc.NSObject {
: objc.NSData.castFromPointer(_ret, retain: true, release: true);
}

/// nativeCrash
static void nativeCrash() {
_objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_nativeCrash);
}

/// pauseAppHangTracking
static void pauseAppHangTracking() {
_objc_msgSend_1pl9qdv(
_class_SentryFlutterPlugin, _sel_pauseAppHangTracking);
}

/// resumeAppHangTracking
static void resumeAppHangTracking() {
_objc_msgSend_1pl9qdv(
_class_SentryFlutterPlugin, _sel_resumeAppHangTracking);
}

/// init
SentryFlutterPlugin init() {
objc.checkOsVersionInternal('SentryFlutterPlugin.init',
Expand Down
19 changes: 19 additions & 0 deletions packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,23 @@ class SentryNativeCocoa extends SentryNativeChannel {
return NativeAppStart.fromJson(json);
},
);

@override
void nativeCrash() {
cocoa.SentryFlutterPlugin.nativeCrash();
}

@override
void pauseAppHangTracking() {
tryCatchSync('pauseAppHangTracking', () {
cocoa.SentryFlutterPlugin.pauseAppHangTracking();
});
}

@override
void resumeAppHangTracking() {
tryCatchSync('resumeAppHangTracking', () {
cocoa.SentryFlutterPlugin.resumeAppHangTracking();
});
}
}
47 changes: 47 additions & 0 deletions packages/flutter/lib/src/native/java/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,29 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject {
.object<ReplayIntegration?>(const $ReplayIntegration$NullableType());
}

static final _id_nativeCrash = _class.instanceMethodId(
r'nativeCrash',
r'()V',
);

static final _nativeCrash = jni$_.ProtectedJniExtensions.lookup<
jni$_.NativeFunction<
jni$_.JThrowablePtr Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
)>>('globalEnv_CallVoidMethod')
.asFunction<
jni$_.JThrowablePtr Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
)>();

/// from: `public final void nativeCrash()`
void nativeCrash() {
_nativeCrash(reference.pointer, _id_nativeCrash as jni$_.JMethodIDPtr)
.check();
}

static final _id_getDisplayRefreshRate = _class.instanceMethodId(
r'getDisplayRefreshRate',
r'()Ljava/lang/Integer;',
Expand Down Expand Up @@ -1816,6 +1839,30 @@ class SentryFlutterPlugin extends jni$_.JObject {
.object<ReplayIntegration?>(const $ReplayIntegration$NullableType());
}

static final _id_nativeCrash = _class.staticMethodId(
r'nativeCrash',
r'()V',
);

static final _nativeCrash = jni$_.ProtectedJniExtensions.lookup<
jni$_.NativeFunction<
jni$_.JThrowablePtr Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
)>>('globalEnv_CallStaticVoidMethod')
.asFunction<
jni$_.JThrowablePtr Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
)>();

/// from: `static public final void nativeCrash()`
static void nativeCrash() {
_nativeCrash(
_class.reference.pointer, _id_nativeCrash as jni$_.JMethodIDPtr)
.check();
}

static final _id_getDisplayRefreshRate = _class.staticMethodId(
r'getDisplayRefreshRate',
r'()Ljava/lang/Integer;',
Expand Down
15 changes: 15 additions & 0 deletions packages/flutter/lib/src/native/java/sentry_native_java.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ class SentryNativeJava extends SentryNativeChannel {
});
}

@override
void nativeCrash() {
native.SentryFlutterPlugin.Companion.nativeCrash();
}

@override
void pauseAppHangTracking() {
assert(false, 'pauseAppHangTracking is not supported on Android.');
}

@override
void resumeAppHangTracking() {
assert(false, 'resumeAppHangTracking is not supported on Android.');
}

@override
Future<void> close() async {
await _replayRecorder?.stop();
Expand Down
16 changes: 11 additions & 5 deletions packages/flutter/lib/src/native/sentry_native_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,21 @@ class SentryNativeChannel
}

@override
Future<void> pauseAppHangTracking() =>
channel.invokeMethod('pauseAppHangTracking');
FutureOr<void> pauseAppHangTracking() {
assert(false,
'pauseAppHangTracking should not be used through method channels.');
}

@override
Future<void> resumeAppHangTracking() =>
channel.invokeMethod('resumeAppHangTracking');
FutureOr<void> resumeAppHangTracking() {
assert(false,
'resumeAppHangTracking should not be used through method channels.');
}

@override
Future<void> nativeCrash() => channel.invokeMethod('nativeCrash');
FutureOr<void> nativeCrash() {
assert(false, 'nativeCrash should not be used through method channels.');
}

@override
bool get supportsReplay => false;
Expand Down
47 changes: 33 additions & 14 deletions packages/flutter/test/sentry_native_channel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -263,30 +263,49 @@ void main() {
});

test('pauseAppHangTracking', () async {
when(channel.invokeMethod('pauseAppHangTracking'))
.thenAnswer((_) => Future.value());

await sut.pauseAppHangTracking();
if (mockPlatform.isAndroid) {
// Android doesn't support app hang tracking, so it should hit the assertion
expect(() => sut.pauseAppHangTracking(), throwsAssertionError);
} else {
// iOS/macOS should throw FFI exceptions in tests
final matcher = _nativeUnavailableMatcher(
mockPlatform,
includeLookupSymbol: true,
includeFailedToLoadClassException: true,
);
expect(() => sut.pauseAppHangTracking(), matcher);
}

verify(channel.invokeMethod('pauseAppHangTracking'));
verifyZeroInteractions(channel);
});

test('resumeAppHangTracking', () async {
when(channel.invokeMethod('resumeAppHangTracking'))
.thenAnswer((_) => Future.value());

await sut.resumeAppHangTracking();
if (mockPlatform.isAndroid) {
// Android doesn't support app hang tracking, so it should hit the assertion
expect(() => sut.resumeAppHangTracking(), throwsAssertionError);
} else {
// iOS/macOS should throw FFI exceptions in tests
final matcher = _nativeUnavailableMatcher(
mockPlatform,
includeLookupSymbol: true,
includeFailedToLoadClassException: true,
);
expect(() => sut.resumeAppHangTracking(), matcher);
}

verify(channel.invokeMethod('resumeAppHangTracking'));
verifyZeroInteractions(channel);
});

test('nativeCrash', () async {
when(channel.invokeMethod('nativeCrash'))
.thenAnswer((_) => Future.value());
final matcher = _nativeUnavailableMatcher(
mockPlatform,
includeLookupSymbol: true,
includeFailedToLoadClassException: true,
);

await sut.nativeCrash();
expect(() => sut.nativeCrash(), matcher);

verify(channel.invokeMethod('nativeCrash'));
verifyZeroInteractions(channel);
});

test('setReplayConfig', () async {
Expand Down
Loading