From 586d7d26492d8edb74aba74047a51370fc992f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Fri, 31 May 2024 12:35:59 +0000 Subject: [PATCH] Parse PlatformException from details instead of message (#2052) --- CHANGELOG.md | 1 + ...id_platform_exception_event_processor.dart | 26 ++- ...atform_exception_event_processor_test.dart | 178 +++++++++++------- flutter/test/jvm/jvm_exception_test.dart | 41 ++++ 4 files changed, 171 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db67a7706d..de3a68db32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Change app start span description from `Cold start` to `Cold Start` and `Warm start` to `Warm Start` ([#2076](https://github.com/getsentry/sentry-dart/pull/2076)) +- Parse `PlatformException` from details instead of message ([#2052](https://github.com/getsentry/sentry-dart/pull/2052)) ### Dependencies diff --git a/flutter/lib/src/event_processor/android_platform_exception_event_processor.dart b/flutter/lib/src/event_processor/android_platform_exception_event_processor.dart index bffc6707b7..d939f4b90e 100644 --- a/flutter/lib/src/event_processor/android_platform_exception_event_processor.dart +++ b/flutter/lib/src/event_processor/android_platform_exception_event_processor.dart @@ -21,8 +21,8 @@ class AndroidPlatformExceptionEventProcessor implements EventProcessor { return event; } - final plaformException = event.throwable; - if (plaformException is! PlatformException) { + final platformException = event.throwable; + if (platformException is! PlatformException) { return event; } @@ -31,18 +31,24 @@ class AndroidPlatformExceptionEventProcessor implements EventProcessor { final packageInfo = await PackageInfo.fromPlatform(); final nativeStackTrace = - _tryParse(plaformException.stacktrace, packageInfo.packageName); - final messageStackTrace = - _tryParse(plaformException.message, packageInfo.packageName); + _tryParse(platformException.stacktrace, packageInfo.packageName); - if (nativeStackTrace == null && messageStackTrace == null) { + final details = platformException.details; + String? detailsString; + if (details is String) { + detailsString = details; + } + final detailsStackTrace = + _tryParse(detailsString, packageInfo.packageName); + + if (nativeStackTrace == null && detailsStackTrace == null) { return event; } return _processPlatformException( event, nativeStackTrace, - messageStackTrace, + detailsStackTrace, ); } catch (e, stackTrace) { _options.logger( @@ -71,18 +77,18 @@ class AndroidPlatformExceptionEventProcessor implements EventProcessor { SentryEvent _processPlatformException( SentryEvent event, List>? nativeStackTrace, - List>? messageStackTrace, + List>? detailsStackTrace, ) { final threads = _markDartThreadsAsNonCrashed(event.threads); final jvmExceptions = [ ...?nativeStackTrace?.map((e) => e.key), - ...?messageStackTrace?.map((e) => e.key) + ...?detailsStackTrace?.map((e) => e.key) ]; var jvmThreads = [ ...?nativeStackTrace?.map((e) => e.value), - ...?messageStackTrace?.map((e) => e.value), + ...?detailsStackTrace?.map((e) => e.value), ]; if (jvmThreads.isNotEmpty) { diff --git a/flutter/test/android_platform_exception_event_processor_test.dart b/flutter/test/android_platform_exception_event_processor_test.dart index 060ada138c..4085d8215c 100644 --- a/flutter/test/android_platform_exception_event_processor_test.dart +++ b/flutter/test/android_platform_exception_event_processor_test.dart @@ -25,21 +25,22 @@ void main() { }); group(AndroidPlatformExceptionEventProcessor, () { - test('exception is correctly parsed', () async { + test('platform exception with details and stackTrace is correctly parsed', + () async { final platformExceptionEvent = await fixture.processor - .apply(fixture.eventWithPlatformStackTrace, Hint()); + .apply(fixture.eventWithPlatformDetailsAndStackTrace, Hint()); final exceptions = platformExceptionEvent!.exceptions!; expect(exceptions.length, 3); - final platformException = exceptions[1]; + final platformException_1 = exceptions[1]; - expect(platformException.type, 'IllegalArgumentException'); + expect(platformException_1.type, 'IllegalArgumentException'); expect( - platformException.value, + platformException_1.value, "Unsupported value: '[Ljava.lang.StackTraceElement;@ba6feed' of type 'class [Ljava.lang.StackTraceElement;'", ); - expect(platformException.stackTrace!.frames.length, 18); + expect(platformException_1.stackTrace!.frames.length, 18); final platformException_2 = exceptions[2]; @@ -51,37 +52,47 @@ void main() { expect(platformException_2.stackTrace!.frames.length, 18); }); - test('platform exception is correctly parsed', () async { + test('platform exception with details correctly parsed', () async { final platformExceptionEvent = await fixture.processor - .apply(fixture.eventWithFailingPlatformStackTrace, Hint()); + .apply(fixture.eventWithPlatformDetails, Hint()); final exceptions = platformExceptionEvent!.exceptions!; - expect(exceptions.length, 3); + expect(exceptions.length, 2); - final platformException = exceptions[1]; + final platformException_1 = exceptions[1]; - expect(platformException.type, 'PlatformException'); + expect(platformException_1.type, 'Resources\$NotFoundException'); + expect(platformException_1.module, 'android.content.res'); expect( - platformException.value, - "PlatformException(getNotificationChannelsError, Unable to find resource ID #0x7f14000d, android.content.res.Resources\$NotFoundException: Unable to find resource ID #0x7f14000d", + platformException_1.value, + "Unable to find resource ID #0x7f14000d", ); - expect(platformException.stackTrace!.frames.length, 20); + expect(platformException_1.stackTrace!.frames.length, 19); + }); - final platformException_2 = exceptions[2]; + test('platform exception with stackTrace correctly parsed', () async { + final platformExceptionEvent = await fixture.processor + .apply(fixture.eventWithPlatformStackTrace, Hint()); + + final exceptions = platformExceptionEvent!.exceptions!; + expect(exceptions.length, 2); - expect(platformException_2.type, 'PlatformException'); + final platformException_1 = exceptions[1]; + + expect(platformException_1.type, 'IllegalArgumentException'); + expect(platformException_1.module, 'java.lang'); expect( - platformException_2.value, - "PlatformException(getNotificationChannelsError, Unable to find resource ID #0x7f14000d, android.content.res.Resources\$NotFoundException: Unable to find resource ID #0x7f14000d", + platformException_1.value, + "Not supported, use openfile", ); - expect(platformException_2.stackTrace!.frames.length, 20); + expect(platformException_1.stackTrace!.frames.length, 22); }); test( 'Dart thread is current and not crashed if Android exception is present', () async { final platformExceptionEvent = await fixture.processor - .apply(fixture.eventWithPlatformStackTrace, Hint()); + .apply(fixture.eventWithPlatformDetailsAndStackTrace, Hint()); final exceptions = platformExceptionEvent!.exceptions!; expect(exceptions.length, 3); @@ -92,7 +103,7 @@ void main() { test('platformexception has Android thread attached', () async { final platformExceptionEvent = await fixture.processor - .apply(fixture.eventWithPlatformStackTrace, Hint()); + .apply(fixture.eventWithPlatformDetailsAndStackTrace, Hint()); final exceptions = platformExceptionEvent!.exceptions!; expect(exceptions.length, 3); @@ -109,10 +120,11 @@ void main() { test('platformexception has no Android thread attached if disabled', () async { fixture.options.attachThreads = false; - final threadCount = fixture.eventWithPlatformStackTrace.threads?.length; + final threadCount = + fixture.eventWithPlatformDetailsAndStackTrace.threads?.length; final platformExceptionEvent = await fixture.processor - .apply(fixture.eventWithPlatformStackTrace, Hint()); + .apply(fixture.eventWithPlatformDetailsAndStackTrace, Hint()); final exceptions = platformExceptionEvent!.exceptions!; expect(exceptions.length, 3); @@ -122,7 +134,7 @@ void main() { test('does nothing if no PlatformException is there', () async { final exception = fixture.options.exceptionFactory - .getSentryException(testPlatformException); + .getSentryException(detailsAndStackTracePlatformException); final event = SentryEvent( exceptions: [exception], @@ -136,10 +148,10 @@ void main() { }); test('does nothing if PlatformException has no stackTrace', () async { - final platformExceptionEvent = await fixture.processor - .apply(fixture.eventWithoutPlatformStackTrace, Hint()); + final platformExceptionEvent = + await fixture.processor.apply(fixture.eventWithPlatformEmpty, Hint()); - expect(fixture.eventWithoutPlatformStackTrace, platformExceptionEvent); + expect(fixture.eventWithPlatformEmpty, platformExceptionEvent); }); }); } @@ -148,33 +160,44 @@ class Fixture { late AndroidPlatformExceptionEventProcessor processor = AndroidPlatformExceptionEventProcessor(options); - late SentryException withPlatformStackTrace = options.exceptionFactory - .getSentryException(testPlatformException) + late SentryException withPlatformDetailsAndStackTrace = options + .exceptionFactory + .getSentryException(detailsAndStackTracePlatformException) .copyWith(threadId: 1); - late SentryException withoutPlatformStackTrace = options.exceptionFactory - .getSentryException(emptyPlatformException) + late SentryEvent eventWithPlatformDetailsAndStackTrace = SentryEvent( + exceptions: [withPlatformDetailsAndStackTrace], + throwable: detailsAndStackTracePlatformException, + threads: [dartThread], + ); + + late SentryException withPlatformDetails = options.exceptionFactory + .getSentryException(detailsPlatformException) .copyWith(threadId: 1); - late SentryEvent eventWithPlatformStackTrace = SentryEvent( - exceptions: [withPlatformStackTrace], - throwable: testPlatformException, + late SentryEvent eventWithPlatformDetails = SentryEvent( + exceptions: [withPlatformDetails], + throwable: detailsPlatformException, threads: [dartThread], ); - late SentryEvent eventWithoutPlatformStackTrace = SentryEvent( - exceptions: [withoutPlatformStackTrace], - throwable: emptyPlatformException, + late SentryException withPlatformStackTrace = options.exceptionFactory + .getSentryException(stackTracePlatformException) + .copyWith(threadId: 1); + + late SentryEvent eventWithPlatformStackTrace = SentryEvent( + exceptions: [withPlatformDetails], + throwable: stackTracePlatformException, threads: [dartThread], ); - late SentryException withFailingPlatformStackTrace = options.exceptionFactory - .getSentryException(failingPlatformException) + late SentryException withPlatformEmpty = options.exceptionFactory + .getSentryException(emptyPlatformException) .copyWith(threadId: 1); - late SentryEvent eventWithFailingPlatformStackTrace = SentryEvent( - exceptions: [withFailingPlatformStackTrace], - throwable: failingPlatformException, + late SentryEvent eventWithPlatformEmpty = SentryEvent( + exceptions: [withPlatformEmpty], + throwable: emptyPlatformException, threads: [dartThread], ); @@ -189,30 +212,14 @@ class Fixture { ..attachThreads = true; } -final testPlatformException = PlatformException( +final detailsAndStackTracePlatformException = PlatformException( code: 'error', - details: + message: "Unsupported value: '[Ljava.lang.StackTraceElement;@fa902f1' of type 'class [Ljava.lang.StackTraceElement;'", - message: _jvmStackTrace, + details: _jvmStackTrace, stacktrace: _jvmStackTrace, ); -final emptyPlatformException = PlatformException( - code: 'error', - details: - "Unsupported value: '[Ljava.lang.StackTraceElement;@fa902f1' of type 'class [Ljava.lang.StackTraceElement;'", - message: null, - stacktrace: null, -); - -final failingPlatformException = PlatformException( - code: 'error', - details: - "PlatformException: PlatformException(getNotificationChannelsError, Unable to find resource ID #0x7f14000d, android.content.res.Resources\$NotFoundException: Unable to find resource ID #0x7f14000d", - message: _failingStackTrace, - stacktrace: _failingStackTrace, -); - const _jvmStackTrace = """java.lang.IllegalArgumentException: Unsupported value: '[Ljava.lang.StackTraceElement;@ba6feed' of type 'class [Ljava.lang.StackTraceElement;' at io.flutter.plugin.common.StandardMessageCodec.writeValue(StandardMessageCodec.java:292) @@ -234,8 +241,11 @@ const _jvmStackTrace = at com.android.internal.os.RuntimeInit\$MethodAndArgsCaller.run(RuntimeInit.java:556) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1037)"""; -const _failingStackTrace = - """PlatformException: PlatformException(getNotificationChannelsError, Unable to find resource ID #0x7f14000d, android.content.res.Resources\$NotFoundException: Unable to find resource ID #0x7f14000d +final detailsPlatformException = PlatformException( + code: 'getNotificationChannelsError', + message: 'Unable to find resource ID #0x7f14000d', + details: + """android.content.res.Resources\$NotFoundException: Unable to find resource ID #0x7f14000d at android.content.res.ResourcesImpl.getResourceEntryName(ResourcesImpl.java:493) at android.content.res.Resources.getResourceEntryName(Resources.java:2441) at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.getMappedNotificationChannel(FlutterLocalNotificationsPlugin.java:170) @@ -254,5 +264,43 @@ const _failingStackTrace = at android.app.ActivityThread.main(ActivityThread.java:9821) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit\$MethodAndArgsCaller.run(RuntimeInit.java:586) - at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1201) -, null)"""; + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1201)""", + stacktrace: null, +); + +final stackTracePlatformException = PlatformException( + code: "error", + message: "Not supported, use openfile", + details: null, + stacktrace: """java.lang.IllegalArgumentException: Not supported, use openfile + at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172) + at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:153) + at android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:814) + at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:2043) + at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1981) + at io.flutter.plugin.platform.f.q(PlatformPlugin.java:57) + at io.flutter.plugin.platform.f.c(PlatformPlugin.java:1) + at io.flutter.plugin.platform.f\$a.g(PlatformPlugin.java:3) + at lb.j\$a.onMethodCall(PlatformChannel.java:294) + at mb.j\$a.a(MethodChannel.java:18) + at za.c.l(DartMessenger.java:19) + at za.c.m(DartMessenger.java:41) + at za.c.i(Unknown Source:0) + at za.b.run(Unknown Source:12) + at android.os.Handler.handleCallback(Handler.java:958) + at android.os.Handler.dispatchMessage(Handler.java:99) + at android.os.Looper.loopOnce(Looper.java:230) + at android.os.Looper.loop(Looper.java:319) + at android.app.ActivityThread.main(ActivityThread.java:8893) + at java.lang.reflect.Method.invoke(Native Method) + at com.android.internal.os.RuntimeInit\$MethodAndArgsCaller.run(RuntimeInit.java:608) + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)""", +); + +final emptyPlatformException = PlatformException( + code: 'error', + message: + "Unsupported value: '[Ljava.lang.StackTraceElement;@fa902f1' of type 'class [Ljava.lang.StackTraceElement;'", + details: null, + stacktrace: null, +); diff --git a/flutter/test/jvm/jvm_exception_test.dart b/flutter/test/jvm/jvm_exception_test.dart index de8a3b4e29..9f3f0d2ef9 100644 --- a/flutter/test/jvm/jvm_exception_test.dart +++ b/flutter/test/jvm/jvm_exception_test.dart @@ -86,6 +86,25 @@ void main() { expect(exception.stackTrace[0].lineNumber, 292); }); + test('parse other Flutter Android PlatformException', () { + final exception = JvmException.parse(otherFlutterAndroidPlatformException); + expect( + exception.description, + "Unable to find resource ID #0x7f14000d", + ); + expect(exception.thread, null); + expect(exception.type, 'android.content.res.Resources\$NotFoundException'); + expect(exception.stackTrace.length, 19); + expect(exception.causes, null); + expect(exception.suppressed, null); + + expect( + exception.stackTrace[0].className, 'android.content.res.ResourcesImpl'); + expect(exception.stackTrace[0].method, 'getResourceEntryName'); + expect(exception.stackTrace[0].fileName, 'ResourcesImpl.java'); + expect(exception.stackTrace[0].lineNumber, 493); + }); + test('parse drops empty frames', () { final exception = JvmException.parse(platformExceptionWithEmptyStackFrames); expect(exception.stackTrace.length, 13); @@ -205,6 +224,28 @@ java.lang.IllegalArgumentException: Unsupported value: '[Ljava.lang.StackTraceEl at com.android.internal.os.RuntimeInit\$MethodAndArgsCaller.run(RuntimeInit.java:556) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1037)'''; +const otherFlutterAndroidPlatformException = ''' +android.content.res.Resources\$NotFoundException: Unable to find resource ID #0x7f14000d + at android.content.res.ResourcesImpl.getResourceEntryName(ResourcesImpl.java:493) + at android.content.res.Resources.getResourceEntryName(Resources.java:2441) + at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.getMappedNotificationChannel(FlutterLocalNotificationsPlugin.java:170) + at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.getNotificationChannels(FlutterLocalNotificationsPlugin.java:32) + at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.onMethodCall(FlutterLocalNotificationsPlugin.java:399) + at be.j\$a.a(MethodChannel.java:18) + at pd.c.l(DartMessenger.java:19) + at pd.c.m(DartMessenger.java:42) + at pd.c.h(Unknown Source:0) + at pd.b.run(Unknown Source:12) + at android.os.Handler.handleCallback(Handler.java:966) + at android.os.Handler.dispatchMessage(Handler.java:110) + at android.os.Looper.loopOnce(Looper.java:205) + at android.os.Looper.loop(Looper.java:293) + at android.app.ActivityThread.loopProcess(ActivityThread.java:9832) + at android.app.ActivityThread.main(ActivityThread.java:9821) + at java.lang.reflect.Method.invoke(Native Method) + at com.android.internal.os.RuntimeInit\$MethodAndArgsCaller.run(RuntimeInit.java:586) + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1201)'''; + const platformExceptionWithEmptyStackFrames = ''' java.lang.RuntimeException: Catch this platform exception! at io.sentry.samples.flutter.MainActivity\$configureFlutterEngine\$1.onMethodCall(MainActivity.kt:40)