dart-lang / source_maps

A package to programmatically manipulate source maps.
https://pub.dev/packages/source_maps
BSD 3-Clause "New" or "Revised" License
16 stars 14 forks source link

Source-mapped Stack Trace Doesn't Match Chrome #18

Open todbachman-wf opened 8 years ago

todbachman-wf commented 8 years ago

We are having trouble with the reliability of applying source maps to stack traces. One of the things I'm finding is Mapping.spanFor sometimes returns null when I'd have expected to get a specific line in my code. It's possible that the way we are using source_maps is at fault, so if that's the case please let me know!

Here's a simple example that demonstrates the problem. It renders a single button that throws an exception when clicked. It catches the exception and applies the source map to the exception and then rethrows the exception so we can let Chrome handle it too.

pubspec.yaml:

name: stack_trace_parser

dependencies:
  browser: ">=0.10.0 <0.11.0"
  source_maps: ^0.10.1
  stack_trace: ^1.6.6

transformers:
- $dart2js:
    sourceMaps: true

web/index.html:

<html>
<head>
    <title>Stack Trace Parsing</title>
</head>
<body>
    <script type="application/dart" src="main.dart"></script>
    <script src="/packages/browser/dart.js"></script>
</body>
</html>

web/main.dart

import 'dart:async';
import 'dart:html';

import 'package:stack_trace_parser/stack_trace_parser.dart';

Future main() async {
  await loadSourceMap();
  addElements();
}

void addElements() {
  querySelector('body')
    ..append(new ButtonElement()
      ..text = 'Throw Exception'
      ..onClick.listen(_handleExceptionButton));
}

void _handleExceptionButton(_) {
  try {
    throw new IntegerDivisionByZeroException();
  } catch (e, stackTrace) {
    print(mapStackTrace(stackTrace));
    throw e;
  }
}

lib/stack_trace_parser.dart:

import 'dart:async';
import 'dart:html';

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

Mapping _sourceMap;

Future loadSourceMap() async {
  _sourceMap = parse(await HttpRequest.getString('main.dart.js.map'));
}

StackTrace mapStackTrace(StackTrace stackTrace) {
  var frames = new Trace.from(stackTrace).frames.map((Frame frame) {
    var span = _sourceMap.spanFor(frame.line - 1, (frame.column ?? 1) - 1);
    if (span == null) {
      return frame;
    }
    return new Frame(
        span.sourceUrl, span.start.line + 1, span.start.column + 1, span.text);
  });
  return new Trace(frames);
}

Steps to generate stack traces:

  1. Open Chrome
  2. Open the dev tools
  3. Edit dev tools settings and ensure Enable JavaScript source maps is checked
  4. pub serve
  5. Navigate to localhost:8080 in Chrome
  6. Click the button labeled Throw Exception
  7. Switch to the dev tools Console
  8. You should see the output from us applying the source map followed by Chrome handling the exception and applying the source map itself: image

Notice that our output differs on the second line of the stack trace from that of Chrome. In our case Mapping.spanFor returned null and we just passed the stack trace frame through unchanged. On the other hand, Chrome was able to use the source map to identify a line number in the original source file, main.dart (although the actual line number is off by two 😉 ).

Am I using the source_maps API correctly? If so, it seems like there might be an issue here in the source map parser.

todbachman-wf commented 8 years ago

FYI -- I've submitted a separate issue to the Dart SDK because it appears line numbers reported by Chrome in stack traces don't line up with the correct locations in source files. https://github.com/dart-lang/sdk/issues/27600 I suppose it's possible the two issues could be related.