getsentry / sentry-dart

Sentry SDK for Dart and Flutter
https://sentry.io/for/flutter/
MIT License
761 stars 239 forks source link

Stack traces from Flutter web app don't deobfuscate function names #1430

Closed blaugold closed 1 year ago

blaugold commented 1 year ago

Continuing from here: https://github.com/getsentry/sentry-dart/issues/897#issuecomment-1539666926

Platform

Flutter Web

Obfuscation

Disabled

Debug Info

Disabled

Doctor

Flutter 3.7.12 • channel stable • https://github.com/flutter/flutter.git Framework • revision 4d9e56e694 (3 weeks ago) • 2023-04-17 21:47:46 -0400 Engine • revision 1a65d409c7 Tools • Dart 2.19.6 • DevTools 2.20.1

Version

7.5.1

Steps to Reproduce

  1. flutter create sentry_flutter_web_stacktraces_repro

  2. cd sentry_flutter_web_stacktraces_repro

  3. flutter pub add sentry_flutter

  4. flutter pub add dev:sentry_dart_plugin

  5. Configure sentry_dart_plugin:

    sentry:
     upload_debug_symbols: false
     upload_source_maps: true
     org: ...
     project: ...
     auth_token: ...
     commits: false
  6. Replace lib/main.dart with the following:

    void main() {
     SentryFlutter.init(
       (options) {
         options.dsn = '...';
       },
       appRunner: () => runApp(const MainApp()),
     );
    }
    
    class MainApp extends StatelessWidget {
     const MainApp({super.key});
    
     @override
     Widget build(BuildContext context) {
       return MaterialApp(
         home: Scaffold(
           body: Center(
             child: ElevatedButton(
               onPressed: () {
                 throw Exception('Test exception');
               },
               child: const Text('Throw exception'),
             ),
           ),
         ),
       );
     }
    }
  7. Configure DSN in main.dart

  8. flutter build web --source-maps

  9. dart run sentry_dart_plugin

  10. cd build/web

  11. Start a web server to serve the files, e.g. caddy file-server

  12. Open the web app in a browser, wherever it is served, e.g. http://localhost.

  13. Click on the "Throw exception" button

Expected Result

The stack trace for error events sent to sentry should contain deobfuscated funtion names.

Actual Result

While the correct line and column numbers have been recovered, function names are not deobufscated:

minified:Gi: Exception: Test exception
  at A.c(org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart:1128:37)
  at A.Tt.prototype.$0(../../../lib/main.dart:24:15)
  at A.v6.prototype.Em(../../../../../../fvm/versions/stable/packages/flutter/lib/src/widgets/framework.dart:921:26)
  at A.akv(org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart:2105:9)
  at A.cN.prototype.EC(../../../../../../fvm/versions/stable/packages/flutter/lib/src/gestures/recognizer.dart:253:16)
  at A.cN.prototype.fG(../../../../../../fvm/versions/stable/packages/flutter/lib/src/gestures/recognizer.dart:239:6)
  at A.fA.prototype.Eo(../../../../../../fvm/versions/stable/packages/flutter/lib/src/gestures/tap.dart:627:11)
  at A.xl.prototype.zd(../../../../../../fvm/versions/stable/packages/flutter/lib/src/gestures/tap.dart:306:5)
  at A.xl.prototype.El(../../../../../../fvm/versions/stable/packages/flutter/lib/src/gestures/tap.dart:239:7)
  at A.nF.prototype.i4(../../../../../../fvm/versions/stable/packages/flutter/lib/src/gestures/recognizer.dart:615:9)

Link to an example event: https://gabriel-59.sentry.io/issues/4170223115/events/234cd5801ab546e3a8812316462ebda4/?project=4505149812834304

Are you willing to submit a PR?

None

marandaneto commented 1 year ago

Thanks @blaugold , will test it out.

marandaneto commented 1 year ago

Image:

Screenshot 2023-05-10 at 10 40 13

JSON event:

event.txt

Uploaded bundle:

Screenshot 2023-05-10 at 10 58 35

Bundles are actually here: https://sentry-sdks.sentry.io/settings/projects/sentry-flutter/source-maps/artifact-bundles/13d8dc56-b866-5275-8cb8-618012f1b6d5/

Either the generated source maps changed something or there's a regression in our system, I will check this internally.

marandaneto commented 1 year ago

The processing team is looking at this.

marandaneto commented 1 year ago

@loewenheim should we close this as done? re https://github.com/getsentry/symbolic/pull/786

loewenheim commented 1 year ago

Actually I meant to post the corrected stacktrace here for feedback, thanks for reminding me :)

With the linked symbolic change, we now get this stacktrace for the provided example program:

Frame #0 Function: wrapException File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart Line/Column: 1128:37 Frame #1 Module: lib/main Function: MainApp.build. File: ../../../lib/main.dart Line/Column: 23:15 Frame #2 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/widgets/framework Function: _InkResponseState.handleTap File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/widgets/framework.dart Line/Column: 921:26 Frame #3 Function: A.aku File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart Line/Column: 2105:9 Frame #4 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/recognizer Function: GestureRecognizer.invokeCallback File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/recognizer.dart Line/Column: 253:16 Frame #5 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/recognizer Function: A.cN.prototype.fG File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/recognizer.dart Line/Column: 239:6 Frame #6 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/tap Function: TapGestureRecognizer.handleTapUp File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/tap.dart Line/Column: 627:11 Frame #7 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/tap Function: BaseTapGestureRecognizer._checkUp File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/tap.dart Line/Column: 306:5 Frame #8 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/tap Function: BaseTapGestureRecognizer.handlePrimaryPointer File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/tap.dart Line/Column: 239:7 Frame #9 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/recognizer Function: PrimaryPointerGestureRecognizer.handleEvent File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/recognizer.dart Line/Column: 615:9 Frame #10 Function: A.aku File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart Line/Column: 2115:9 Frame #11 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/pointer_router Function: PointerRouter._dispatch File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/pointer_router.dart Line/Column: 98:7 Frame #12 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/pointer_router Function: PointerRouter._dispatchEventToRoutes. File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/pointer_router.dart Line/Column: 143:9 Frame #13 Function: JsLinkedHashMap.keys.dn.prototype.P File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/linked_hash_map.dart Line/Column: 190:7 Frame #14 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/pointer_router Function: PointerRouter._dispatchEventToRoutes File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/pointer_router.dart Line/Column: 141:5 Frame #15 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/pointer_router Function: PointerRouter.route File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/pointer_router.dart Line/Column: 127:7 Frame #16 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding Function: GestureBinding.handleEvent File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding.dart Line/Column: 460:5 Frame #17 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding Function: GestureBinding.dispatchEvent File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding.dart Line/Column: 440:15 Frame #18 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/rendering/binding Function: _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding.dispatchEvent File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/rendering/binding.dart Line/Column: 336:11 Frame #19 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding Function: GestureBinding._handlePointerEventImmediately File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding.dart Line/Column: 395:7 Frame #20 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding Function: GestureBinding.handlePointerEvent File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding.dart Line/Column: 357:5 Frame #21 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding Function: GestureBinding._flushPointerEventQueue File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding.dart Line/Column: 314:7 Frame #22 Module: usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding Function: GestureBinding._handlePointerDataPacket File: /usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter/lib/src/gestures/binding.dart Line/Column: 295:7 Frame #23 Function: A.aku File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart Line/Column: 2115:9 Frame #24 Function: A.a6K File: org-dartlang-sdk:///lib/async/zone.dart Line/Column: 1414:12 Frame #25 Function: _rootRunUnary File: org-dartlang-sdk:///lib/async/zone.dart Line/Column: 1404:3 Frame #26 Function: _CustomZone.runUnary File: org-dartlang-sdk:///lib/async/zone.dart Line/Column: 1306:34 Frame #27 Function: _CustomZone.runUnaryGuarded File: org-dartlang-sdk:///lib/async/zone.dart Line/Column: 1216:7 Frame #28 Module: usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/platform_dispatcher Function: invoke1 File: /usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/platform_dispatcher.dart Line/Column: 1185:9 Frame #29 Module: usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/platform_dispatcher Function: PointerBinding._onPointerData File: /usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/platform_dispatcher.dart Line/Column: 243:5 Frame #30 Function: A.aku File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart Line/Column: 2115:9 Frame #31 Module: usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/pointer_binding Function: _PointerAdapter.setup. File: /usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/pointer_binding.dart Line/Column: 689:20 Frame #32 Module: usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/pointer_binding Function: _PointerAdapter._addPointerEventListener. File: /usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/pointer_binding.dart Line/Column: 620:7 Frame #33 Module: usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/pointer_binding Function: _BaseAdapter.addEventListener.loggedHandler File: /usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/pointer_binding.dart Line/Column: 303:9 Frame #34 Module: usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/pointer_binding Function: _Listener.register..a3C.prototype.$1 File: /usr/local/Caskroom/flutter/3.7.12/flutter/bin/cache/flutter_web_sdk/lib/_engine/engine/pointer_binding.dart Line/Column: 188:73 Frame #35 Function: Primitives.applyFunction File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart Line/Column: 851:11 Frame #36 Function: Function.apply File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/core_patch.dart Line/Column: 84:23 Frame #37 Function: _callDartFunctionFast File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_patch.dart Line/Column: 545:3 Frame #38 Function: _convertDartFunctionFast File: org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_patch.dart Line/Column: 523:1 Errors during symbolication Path: http://localhost/main.dart.js Error: Missing source contents for source file org-dartlang-sdk:///lib/_internal/js_runtime/lib/core_patch.dart and sourcemap file http://localhost/main.dart.js.map Path: http://localhost/main.dart.js Error: Missing source contents for source file org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart and sourcemap file http://localhost/main.dart.js.map Path: http://localhost/main.dart.js Error: Missing source contents for source file org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_patch.dart and sourcemap file http://localhost/main.dart.js.map Path: http://localhost/main.dart.js Error: Missing source contents for source file org-dartlang-sdk:///lib/_internal/js_runtime/lib/linked_hash_map.dart and sourcemap file http://localhost/main.dart.js.map Path: http://localhost/main.dart.js Error: Missing source contents for source file org-dartlang-sdk:///lib/async/zone.dart and sourcemap file http://localhost/main.dart.js.map

As you can see, most function names are now resolved, but we don't have source contents for all files.

marandaneto commented 1 year ago

@loewenheim is this deployed and live already? If so, @blaugold mind testing once more? Thanks!

loewenheim commented 1 year ago

It's not live yet, I'll get it ready ASAP

loewenheim commented 1 year ago

Closed this prematurely. The fix is now live.

blaugold commented 1 year ago

Thanks for the swift fix! For synchronous code, I now get the correct function names. 🎉 Unfortunately, it does not solve the issue for async code. 😅

If you change the function that throws the exception to the code below, the throwing frame is reported like this: at $async$$0(../../../lib/main.dart:25:15).

onPressed: () async {
  await Future(() {});
  throw Exception('Test exception');
},

Here is an example of how the source_map_stack_trace package can be used to unminify even async code:

import 'dart:convert';
import 'dart:io';

import 'package:source_map_stack_trace/source_map_stack_trace.dart';
import 'package:source_maps/source_maps.dart';

const stackTraceString = r'''
minified:GR: Exception: Test exception
    at Object.b (main.dart.js:4293:3)
    at <fn> (main.dart.js:56371:16)
    at a45.a (main.dart.js:5603:62)
    at a45.$2 (main.dart.js:31015:14)
    at a2X.$1 (main.dart.js:31009:21)
    at Object.a3Z (main.dart.js:5858:19)
    at Ed.<fn> (main.dart.js:57768:68)
    at Gh.jD (main.dart.js:31819:12)
    at a0i.$0 (main.dart.js:31350:11)
    at Object.p3 (main.dart.js:5729:40)
''';

void main() {
  final mapping = SingleMapping.fromJson(jsonDecode(File('build/web/main.dart.js.map').readAsStringSync()));
  final stackTrace = StackTrace.fromString(stackTraceString);
  final mappedStackTrace = mapStackTrace(mapping, stackTrace, minified: true);
  print(mappedStackTrace.toString());
}

And this is its output:

org-dartlang-sdk:///dart-sdk/lib/_internal/js_runtime/lib/js_helper.dart 1195:37   wrapException
../../../lib/main.dart 25:15                                                       MainApp.build.<anonymous function>
org-dartlang-sdk:///dart-sdk/lib/_internal/js_runtime/lib/async_patch.dart 306:19  _wrapJsFunctionForAsync
org-dartlang-sdk:///dart-sdk/lib/_internal/js_runtime/lib/async_patch.dart 331:23  _wrapJsFunctionForAsync.<anonymous function>
org-dartlang-sdk:///dart-sdk/lib/_internal/js_runtime/lib/async_patch.dart 282:19  _awaitOnObject.<anonymous function>
org-dartlang-sdk:///dart-sdk/lib/async/zone.dart 1407:46                           _rootRunUnary
org-dartlang-sdk:///dart-sdk/lib/async/zone.dart 1405:3                            _rootRunUnary[function-entry$5]
org-dartlang-sdk:///dart-sdk/lib/async/zone.dart 1307:34                           _CustomZone.runUnary
org-dartlang-sdk:///dart-sdk/lib/async/future_impl.dart 112:29                     _FutureListener.handleValue
org-dartlang-sdk:///dart-sdk/lib/async/future_impl.dart 813:13                     _Future._propagateToListeners
blaugold commented 1 year ago

Looking at how source_map_stack_trace and source_maps work, there does not seem to be anything special, as far as I can tell with my limited knowledge of source maps.

For reference, here is how source maps are parsed and here how a location is looked up.

blaugold commented 1 year ago

Running the source map from the example through symbolic I can see that a useful function name is available in the name field of a source location.

#[test]
fn dart_debug() {
    let minified = std::fs::read_to_string(fixture("sourcemapcache/dart/main.dart.js")).unwrap();
    let map = std::fs::read_to_string(fixture("sourcemapcache/dart/main.dart.js.map")).unwrap();

    let writer = SourceMapCacheWriter::new(&minified, &map).unwrap();

    let mut buf = vec![];
    writer.serialize(&mut buf).unwrap();

    let cache = SourceMapCache::parse(&buf).unwrap();

    let sl = cache.lookup(SourcePosition::new(56371, 16)).unwrap();
    println!("{:?}", sl);
}
   Compiling symbolic-sourcemapcache v12.1.5 (/Users/terwesten/dev/symbolic/symbolic-sourcemapcache)
    Finished test [unoptimized + debuginfo] target(s) in 0.40s
     Running tests/integration.rs (target/debug/deps/integration-9650d2080c26a134)

running 1 test
SourceLocation { file: Some(File { name: Some("../../../lib/main.dart"), source: None, line_offsets: [LineOffset(0)] }), line: 25, column: 13, name: Some("MainApp.build.<anonymous function>"), scope: NamedScope("$async$$0") }
test dart_debug ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 1.91s

Would it be a problem to use the mapped name for function names of Dart stack traces when available instead of scope?

marandaneto commented 1 year ago

@loewenheim can you take a look at this use case as well?

marandaneto commented 1 year ago

Thanks @blaugold for debugging this with us. I've raised the issue https://github.com/getsentry/symbolic/issues/791 Let's track the linked issue there, closing this one since the original bug is resolved.