From a863aeba6893cabbb05ac5a8fa692fee0d62f60e Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 4 Sep 2023 16:41:19 +0200 Subject: [PATCH 1/6] filter out empty stack frames --- flutter/lib/src/jvm/jvm_exception.dart | 7 ++++-- flutter/test/jvm/jvm_exception_test.dart | 29 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/flutter/lib/src/jvm/jvm_exception.dart b/flutter/lib/src/jvm/jvm_exception.dart index adaf71cac1..a26a15af4a 100644 --- a/flutter/lib/src/jvm/jvm_exception.dart +++ b/flutter/lib/src/jvm/jvm_exception.dart @@ -256,8 +256,11 @@ class JvmException { frames.add(trimmed); } - final thisExceptionFrames = - thisException.map((e) => JvmFrame.parse(e)).toList(growable: false); + final thisExceptionFrames = thisException + .map((e) => JvmFrame.parse(e)) + .where( + (e) => e.originalFrame.trim() != 'at' && e.originalFrame.isNotEmpty) + .toList(growable: false); final suppressedExceptions = supressed .map((e) => JvmException.parse(e.join(_newLine))) diff --git a/flutter/test/jvm/jvm_exception_test.dart b/flutter/test/jvm/jvm_exception_test.dart index 3202da097d..c5632d9894 100644 --- a/flutter/test/jvm/jvm_exception_test.dart +++ b/flutter/test/jvm/jvm_exception_test.dart @@ -85,6 +85,17 @@ void main() { expect(exception.stackTrace[0].fileName, 'StandardMessageCodec.java'); expect(exception.stackTrace[0].lineNumber, 292); }); + + test('parse drops frames with `at ` and empty original frame', () { + final exception = + JvmException.parse(platformExceptionWithEmptyLastStackFrame); + expect(exception.stackTrace.length, 13); + expect(exception.stackTrace.last.className, + 'com.android.internal.os.ZygoteInit'); + expect(exception.stackTrace.last.fileName, 'ZygoteInit.java'); + expect(exception.stackTrace.last.method, 'main'); + expect(exception.stackTrace.last.lineNumber, 936); + }); } const javaExceptionWithCauses = ''' @@ -194,3 +205,21 @@ java.lang.IllegalArgumentException: Unsupported value: '[Ljava.lang.StackTraceEl at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit\$MethodAndArgsCaller.run(RuntimeInit.java:556) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1037)'''; + +const platformExceptionWithEmptyLastStackFrame = ''' +java.lang.RuntimeException: Catch this platform exception! + at io.sentry.samples.flutter.MainActivity\$configureFlutterEngine\$1.onMethodCall(MainActivity.kt:40) + at io.flutter.plugin.common.MethodChannel\$IncomingMethodCallHandler.onMessage(MethodChannel.java:258) + at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295) + at io.flutter.embedding.engine.dart.DartMessenger.lambda\$dispatchMessageToQueue\$0\$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:322) + at io.flutter.embedding.engine.dart.DartMessenger\$\$ExternalSyntheticLambda0.run(Unknown Source:12) + at android.os.Handler.handleCallback(Handler.java:942) + at android.os.Handler.dispatchMessage(Handler.java:99) + at android.os.Looper.loopOnce(Looper.java:201) + at android.os.Looper.loop(Looper.java:288) + at android.app.ActivityThread.main(ActivityThread.java:7872) + at java.lang.reflect.Method.invoke + at com.android.internal.os.RuntimeInit\$MethodAndArgsCaller.run(RuntimeInit.java:548) + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) + at + '''; From 98d58d5abf5aa5f002e52e23f09d825cd6a1d392 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 4 Sep 2023 17:45:58 +0200 Subject: [PATCH 2/6] check empty first --- flutter/lib/src/jvm/jvm_exception.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/src/jvm/jvm_exception.dart b/flutter/lib/src/jvm/jvm_exception.dart index a26a15af4a..707d4753b8 100644 --- a/flutter/lib/src/jvm/jvm_exception.dart +++ b/flutter/lib/src/jvm/jvm_exception.dart @@ -259,7 +259,7 @@ class JvmException { final thisExceptionFrames = thisException .map((e) => JvmFrame.parse(e)) .where( - (e) => e.originalFrame.trim() != 'at' && e.originalFrame.isNotEmpty) + (e) => e.originalFrame.isNotEmpty && e.originalFrame.trim() != 'at' ) .toList(growable: false); final suppressedExceptions = supressed From ee4d4b2e251fdb586798072c2deea25e3d41c709 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 4 Sep 2023 17:53:31 +0200 Subject: [PATCH 3/6] format, update test expectations --- dio/test/dio_event_processor_test.dart | 24 +++++++++---------- dio/test/failed_request_interceptor_test.dart | 2 +- dio/test/mocks.dart | 2 +- flutter/lib/src/jvm/jvm_exception.dart | 2 +- flutter/test/jvm/jvm_exception_test.dart | 7 +++--- sqflite/test/mocks/mocks.dart | 3 ++- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/dio/test/dio_event_processor_test.dart b/dio/test/dio_event_processor_test.dart index af45d0c681..3cd060ea8e 100644 --- a/dio/test/dio_event_processor_test.dart +++ b/dio/test/dio_event_processor_test.dart @@ -67,7 +67,7 @@ void main() { throwable: throwable, exceptions: [ fixture.sentryError(throwable), - fixture.sentryError(dioError) + fixture.sentryError(dioError), ], ); final processedEvent = sut.apply(event) as SentryEvent; @@ -96,7 +96,7 @@ void main() { throwable: throwable, exceptions: [ fixture.sentryError(throwable), - fixture.sentryError(dioError) + fixture.sentryError(dioError), ], ); final processedEvent = sut.apply(event) as SentryEvent; @@ -125,7 +125,7 @@ void main() { throwable: throwable, exceptions: [ fixture.sentryError(throwable), - fixture.sentryError(dioError) + fixture.sentryError(dioError), ], ); final processedEvent = sut.apply(event) as SentryEvent; @@ -177,7 +177,7 @@ void main() { throwable: throwable, exceptions: [ fixture.sentryError(throwable), - fixture.sentryError(dioError) + fixture.sentryError(dioError), ], ); final processedEvent = sut.apply(event) as SentryEvent; @@ -207,7 +207,7 @@ void main() { data: 'foobar', headers: Headers.fromMap(>{ 'foo': ['bar'], - 'set-cookie': ['foo=bar'] + 'set-cookie': ['foo=bar'], }), requestOptions: request, isRedirect: true, @@ -219,7 +219,7 @@ void main() { throwable: throwable, exceptions: [ fixture.sentryError(throwable), - fixture.sentryError(dioError) + fixture.sentryError(dioError), ], ); final processedEvent = sut.apply(event) as SentryEvent; @@ -248,7 +248,7 @@ void main() { response: Response( data: 'foobar', headers: Headers.fromMap(>{ - 'foo': ['bar'] + 'foo': ['bar'], }), requestOptions: request, isRedirect: true, @@ -260,7 +260,7 @@ void main() { throwable: throwable, exceptions: [ fixture.sentryError(throwable), - fixture.sentryError(dioError) + fixture.sentryError(dioError), ], ); final processedEvent = sut.apply(event) as SentryEvent; @@ -320,7 +320,7 @@ void main() { throwable: throwable, exceptions: [ fixture.sentryError(throwable), - fixture.sentryError(dioError) + fixture.sentryError(dioError), ], ); final processedEvent = sut.apply(event) as SentryEvent; @@ -338,7 +338,7 @@ void main() { final dataByType = { ResponseType.plain: ['plain'], ResponseType.bytes: [ - [1337] + [1337], ], ResponseType.json: [ 9001, @@ -347,7 +347,7 @@ void main() { true, ['list'], {'map-key': 'map-value'}, - ] + ], }; for (final entry in dataByType.entries) { @@ -375,7 +375,7 @@ void main() { throwable: throwable, exceptions: [ fixture.sentryError(throwable), - fixture.sentryError(dioError) + fixture.sentryError(dioError), ], ); final processedEvent = sut.apply(event) as SentryEvent; diff --git a/dio/test/failed_request_interceptor_test.dart b/dio/test/failed_request_interceptor_test.dart index 80eede8803..e7fb5a63e1 100644 --- a/dio/test/failed_request_interceptor_test.dart +++ b/dio/test/failed_request_interceptor_test.dart @@ -113,7 +113,7 @@ class Fixture { FailedRequestInterceptor getSut({ List failedRequestStatusCodes = const [ - SentryStatusCode.defaultRange() + SentryStatusCode.defaultRange(), ], List failedRequestTargets = const ['.*'], }) { diff --git a/dio/test/mocks.dart b/dio/test/mocks.dart index 83454cbfb3..5ed6dcd00a 100644 --- a/dio/test/mocks.dart +++ b/dio/test/mocks.dart @@ -42,7 +42,7 @@ final fakeEvent = SentryEvent( type: 'navigation', data: {'screen': 'MainActivity', 'state': 'created'}, level: SentryLevel.info, - ) + ), ], contexts: Contexts( operatingSystem: const SentryOperatingSystem( diff --git a/flutter/lib/src/jvm/jvm_exception.dart b/flutter/lib/src/jvm/jvm_exception.dart index 707d4753b8..eeb465522c 100644 --- a/flutter/lib/src/jvm/jvm_exception.dart +++ b/flutter/lib/src/jvm/jvm_exception.dart @@ -259,7 +259,7 @@ class JvmException { final thisExceptionFrames = thisException .map((e) => JvmFrame.parse(e)) .where( - (e) => e.originalFrame.isNotEmpty && e.originalFrame.trim() != 'at' ) + (e) => e.originalFrame.isNotEmpty && e.originalFrame.trim() != 'at') .toList(growable: false); final suppressedExceptions = supressed diff --git a/flutter/test/jvm/jvm_exception_test.dart b/flutter/test/jvm/jvm_exception_test.dart index c5632d9894..e5a2f4a222 100644 --- a/flutter/test/jvm/jvm_exception_test.dart +++ b/flutter/test/jvm/jvm_exception_test.dart @@ -40,7 +40,7 @@ void main() { 'Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]'); expect(thirdCause.thread, null); expect(thirdCause.type, 'java.sql.SQLException'); - expect(thirdCause.stackTrace.length, 6); + expect(thirdCause.stackTrace.length, 5); expect(thirdCause.causes, null); expect(thirdCause.suppressed, null); }); @@ -87,8 +87,7 @@ void main() { }); test('parse drops frames with `at ` and empty original frame', () { - final exception = - JvmException.parse(platformExceptionWithEmptyLastStackFrame); + final exception = JvmException.parse(platformExceptionWithEmptyStackFrames); expect(exception.stackTrace.length, 13); expect(exception.stackTrace.last.className, 'com.android.internal.os.ZygoteInit'); @@ -206,7 +205,7 @@ 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 platformExceptionWithEmptyLastStackFrame = ''' +const platformExceptionWithEmptyStackFrames = ''' java.lang.RuntimeException: Catch this platform exception! at io.sentry.samples.flutter.MainActivity\$configureFlutterEngine\$1.onMethodCall(MainActivity.kt:40) at io.flutter.plugin.common.MethodChannel\$IncomingMethodCallHandler.onMessage(MethodChannel.java:258) diff --git a/sqflite/test/mocks/mocks.dart b/sqflite/test/mocks/mocks.dart index 0a3115df74..3f0af8274c 100644 --- a/sqflite/test/mocks/mocks.dart +++ b/sqflite/test/mocks/mocks.dart @@ -33,7 +33,8 @@ ISentrySpan startTransactionShim( DatabaseExecutor, ], customMocks: [ - MockSpec(fallbackGenerators: {#startTransaction: startTransactionShim}) + MockSpec( + fallbackGenerators: {#startTransaction: startTransactionShim}), ], ) void main() {} From 4aa7eec2b26bb55eec64d9de5c5c72cb3b84013d Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 4 Sep 2023 17:55:08 +0200 Subject: [PATCH 4/6] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8af73e66a5..761d341a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Normalize data properties of `SentryUser` and `Breadcrumb` before sending over method channel ([#1591](https://github.com/getsentry/sentry-dart/pull/1591)) - Fixing memory leak issue in SentryFlutterPlugin (Android Plugin) ([#1588](https://github.com/getsentry/sentry-dart/pull/1588)) +- Discard empty stack frames ([#1625](https://github.com/getsentry/sentry-dart/pull/1625)) ### Dependencies From 27e1631b041bdd73f13afa836e9ee8c67f444d49 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 11 Sep 2023 11:05:18 +0200 Subject: [PATCH 5/6] fun fix & format --- flutter/lib/src/jvm/jvm_exception.dart | 3 +-- flutter/test/jvm/jvm_exception_test.dart | 4 ++-- flutter/test/sentry_navigator_observer_test.dart | 1 + sqflite/lib/src/sentry_database.dart | 15 +++++++++++---- sqflite/test/mocks/mocks.dart | 3 ++- sqflite/test/sentry_batch_test.dart | 8 ++++++-- sqflite/test/sentry_database_test.dart | 4 +++- 7 files changed, 26 insertions(+), 12 deletions(-) diff --git a/flutter/lib/src/jvm/jvm_exception.dart b/flutter/lib/src/jvm/jvm_exception.dart index eeb465522c..756f219b12 100644 --- a/flutter/lib/src/jvm/jvm_exception.dart +++ b/flutter/lib/src/jvm/jvm_exception.dart @@ -257,9 +257,8 @@ class JvmException { } final thisExceptionFrames = thisException + .where((line) => line.trim().isNotEmpty) .map((e) => JvmFrame.parse(e)) - .where( - (e) => e.originalFrame.isNotEmpty && e.originalFrame.trim() != 'at') .toList(growable: false); final suppressedExceptions = supressed diff --git a/flutter/test/jvm/jvm_exception_test.dart b/flutter/test/jvm/jvm_exception_test.dart index e5a2f4a222..de8a3b4e29 100644 --- a/flutter/test/jvm/jvm_exception_test.dart +++ b/flutter/test/jvm/jvm_exception_test.dart @@ -86,7 +86,7 @@ void main() { expect(exception.stackTrace[0].lineNumber, 292); }); - test('parse drops frames with `at ` and empty original frame', () { + test('parse drops empty frames', () { final exception = JvmException.parse(platformExceptionWithEmptyStackFrames); expect(exception.stackTrace.length, 13); expect(exception.stackTrace.last.className, @@ -220,5 +220,5 @@ java.lang.RuntimeException: Catch this platform exception! at java.lang.reflect.Method.invoke at com.android.internal.os.RuntimeInit\$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) - at + '''; diff --git a/flutter/test/sentry_navigator_observer_test.dart b/flutter/test/sentry_navigator_observer_test.dart index d3871b4390..d4c2499efc 100644 --- a/flutter/test/sentry_navigator_observer_test.dart +++ b/flutter/test/sentry_navigator_observer_test.dart @@ -756,6 +756,7 @@ class _MockHub extends MockHub { @override final options = SentryOptions(dsn: fakeDsn); + @override late final scope = Scope(options); @override diff --git a/sqflite/lib/src/sentry_database.dart b/sqflite/lib/src/sentry_database.dart index 71147c3464..d148eb7ded 100644 --- a/sqflite/lib/src/sentry_database.dart +++ b/sqflite/lib/src/sentry_database.dart @@ -58,8 +58,11 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database { @internal Hub? hub, }) : _hub = hub ?? HubAdapter(), dbName = p.basenameWithoutExtension(_database.path), - super(_database, - hub: hub, dbName: p.basenameWithoutExtension(_database.path)) { + super( + _database, + hub: hub, + dbName: p.basenameWithoutExtension(_database.path), + ) { // ignore: invalid_use_of_internal_member final options = _hub.options; options.sdk.addIntegration('SentrySqfliteTracing'); @@ -132,8 +135,12 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database { setDatabaseAttributeData(span, dbName); Future newAction(Transaction txn) async { - final executor = SentryDatabaseExecutor(txn, - parentSpan: span, hub: _hub, dbName: dbName); + final executor = SentryDatabaseExecutor( + txn, + parentSpan: span, + hub: _hub, + dbName: dbName, + ); final sentrySqfliteTransaction = SentrySqfliteTransaction(executor, hub: _hub, dbName: dbName); diff --git a/sqflite/test/mocks/mocks.dart b/sqflite/test/mocks/mocks.dart index 3f0af8274c..bcafb94302 100644 --- a/sqflite/test/mocks/mocks.dart +++ b/sqflite/test/mocks/mocks.dart @@ -34,7 +34,8 @@ ISentrySpan startTransactionShim( ], customMocks: [ MockSpec( - fallbackGenerators: {#startTransaction: startTransactionShim}), + fallbackGenerators: {#startTransaction: startTransactionShim}, + ), ], ) void main() {} diff --git a/sqflite/test/sentry_batch_test.dart b/sqflite/test/sentry_batch_test.dart index ce6f59e0d5..e3b24d811a 100644 --- a/sqflite/test/sentry_batch_test.dart +++ b/sqflite/test/sentry_batch_test.dart @@ -296,7 +296,9 @@ SELECT * FROM Product'''; final span = fixture.tracer.children.last; expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); expect( - span.data[SentryDatabase.dbNameKey], (db as SentryDatabase).dbName); + span.data[SentryDatabase.dbNameKey], + (db as SentryDatabase).dbName, + ); await db.close(); }); @@ -313,7 +315,9 @@ SELECT * FROM Product'''; final span = fixture.tracer.children.last; expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); expect( - span.data[SentryDatabase.dbNameKey], (db as SentryDatabase).dbName); + span.data[SentryDatabase.dbNameKey], + (db as SentryDatabase).dbName, + ); await db.close(); }); diff --git a/sqflite/test/sentry_database_test.dart b/sqflite/test/sentry_database_test.dart index e05bd2367c..5c954426bb 100644 --- a/sqflite/test/sentry_database_test.dart +++ b/sqflite/test/sentry_database_test.dart @@ -112,7 +112,9 @@ void main() { expect(insertSpan.context.parentSpanId, trSpan.context.spanId); expect(insertSpan.status, SpanStatus.ok()); expect( - insertSpan.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + insertSpan.data[SentryDatabase.dbSystemKey], + SentryDatabase.dbSystem, + ); expect(insertSpan.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( From e74241a00b8f5dc0773959301931e812a1058624 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 11 Sep 2023 11:30:57 +0200 Subject: [PATCH 6/6] Add comment describing issue --- flutter/lib/src/jvm/jvm_exception.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flutter/lib/src/jvm/jvm_exception.dart b/flutter/lib/src/jvm/jvm_exception.dart index 756f219b12..352092b1ed 100644 --- a/flutter/lib/src/jvm/jvm_exception.dart +++ b/flutter/lib/src/jvm/jvm_exception.dart @@ -257,6 +257,8 @@ class JvmException { } final thisExceptionFrames = thisException + // Sometimes stringified exceptions from the native side have + // empty lines. Discard those! .where((line) => line.trim().isNotEmpty) .map((e) => JvmFrame.parse(e)) .toList(growable: false);