dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.22k stars 1.57k forks source link

dart2js minified stack trace is not "de-minifed" in browser by sourcemap #55061

Closed daniel-v closed 7 months ago

daniel-v commented 8 months ago

Compiling dart web app with dart2js while retaining sourcemaps, I get minified stack traces in the browser instead of being presented where and what caused the error.

dart info

``` If providing this information as part of reporting a bug, please review the information below to ensure it only contains things you're comfortable posting publicly. #### General info - Dart 3.2.6 (stable) (Wed Jan 24 13:41:58 2024 +0000) on "linux_x64" - on linux / Linux 6.7.5-200.fc39.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Feb 17 17:20:08 UTC 2024 - locale is en_US.UTF-8 #### Project info - sdk constraint: '>=2.17.0 <4.0.0' - dependencies: sentry, source_map_stack_trace, source_maps, stack_trace, web - dev_dependencies: build_runner, build_web_compilers, lints #### Process info | Memory | CPU | Elapsed time | Command line | | -----: | ---: | -----------: | ------------------------------------------------------------------------------- | | 481 MB | 0.2% | 02:49:51 | dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.82.0 | | 712 MB | 0.9% | 02:19:26 | dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.82.0 | ```

Current situation

Running a simple dart web app

void main() {
  throw StateError('Please let me know where this happened');
}

I'm presented with minifed stack trace in the browser

js_helper.dart:1196 Uncaught Bad state: Please let me know where this happened
    at Object.a (http://localhost:8080/main.dart.js:129:18)
    at bZ (http://localhost:8080/main.dart.js:1041:14)
    at http://localhost:8080/main.dart.js:1190:6
    at http://localhost:8080/main.dart.js:1184:55
    at dartProgram (http://localhost:8080/main.dart.js:1187:84)
    at http://localhost:8080/main.dart.js:1190:15
build.yaml

```yaml targets: $default: builders: build_web_compilers|dart_source_cleanup: release_options: enabled: false build_web_compilers:entrypoint: options: dart2js_args: - -O3 ```

Command pub run build_runner serve web -r.

Expected output

Similar to:

/_internal/js_runtime/lib/js_helper.dart 1196:37  wrapException
/main.dart 7:5                                    main

I got this output by copying generated sourcemap file to proper location and running the following program:

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

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

void main() {
  var mapContents = File('bin/main.dart.js.map').readAsStringSync();
  var mapping = SingleMapping.fromJson(jsonDecode(mapContents));
  var stt = Trace.parseV8(trace);  
  var dartTrace = mapStackTrace(mapping, stt, minified: true);
  print(dartTrace);
}

const trace = r'''js_helper.dart:1196 Uncaught Bad state: Please let me know where this happened
    at Object.a (http://localhost:8080/main.dart.js:129:18)
    at bZ (http://localhost:8080/main.dart.js:1041:14)
    at http://localhost:8080/main.dart.js:1190:6
    at http://localhost:8080/main.dart.js:1184:55
    at dartProgram (http://localhost:8080/main.dart.js:1187:84)
    at http://localhost:8080/main.dart.js:1190:15
''';

Running this program, I seem to be able to resolve

  1. location that threw (main.dart:7:5)
  2. name of bZ minified symbol (main)

I would expect I get to do the same in chrome, firefox and in any other browser.

What am I missing here? Sourcemaps seem to be generated fine and there is tooling in Dart ecosystem to do what browsers should.

sigmundch commented 7 months ago

Good question @daniel-v, unfortunately I believe this is the expected behavior from browsers. They provide the original stack and will not change semantics of what is presented to the program. It's possible that the browser's debugger do deobfsucate. For example, if you pause on exceptions in Chrome Devtools, the side panel may show the deobfuscated frames.

During dev-time development DDC does a trick to rewrite the stack by using a stack-trace-mapper package, this is called by the DDC runtime when exceptions are thrown to provide a more readable stack for developers. Personally I don't like this approach because it can change the semantics of the program. Dart2js leaves the semantics alone and instead provides additional tools to help deobfuscate stack traces offline, outside the app.

Many of our internal customers work with obfuscated stack traces from apps running in production by processing them in a server using deobfuscation tools dart2js provides (sorry these tools are not very well documented at the moment).

Also worth noting that dart2js does many optimizations that are hard for browsers to deal with. For example, we use function inlining heavily. To help expand stack traces with inlining, dart2js produces source-maps with extensions added to help incorporate inlined frames data (see docs). These extensions are understood by our deobfuscation tools, but not by the browsers today.

Lastly, another trick worth trying is using some of the dart2js flags. Switching optimization levels is one trick that sometimes works, but unfortunately sometimes issues are related to the optimizations. For example, -O3 and -O4 remove checks that are present in -02 and -O1. Often I recommend using --no-minify. This flag can be applied in addition to all other existing flags, but it will generate more readable names in the output (closer to what you had written in your program). This is the closest we have to a "profile" mode, where all optimizations are still performed but only the encoding of the output is different.

I'll close this issue as "working-as-intended" since there isn't much we plan to do here, but I hope the info above is helpful to you.

daniel-v commented 7 months ago

Very much so, @sigmundch! Thank you for taking the time to write this out.