diff --git a/pkg/compiler/lib/src/io/source_map_builder.dart b/pkg/compiler/lib/src/io/source_map_builder.dart index 14dec0271e6f0..0e88362ff4f56 100644 --- a/pkg/compiler/lib/src/io/source_map_builder.dart +++ b/pkg/compiler/lib/src/io/source_map_builder.dart @@ -211,48 +211,44 @@ class SourceMapBuilder { void writeMinifiedNames(Map minifiedNames, IndexMap nameMap, StringBuffer buffer) { bool first = true; - buffer.write('{'); + buffer.write('"'); minifiedNames.forEach((String minifiedName, String name) { if (!first) buffer.write(','); - buffer.write('"'); - writeJsonEscapedCharsOn(minifiedName, buffer); - buffer.write('"'); - buffer.write(':'); + // minifiedNames are valid JS identifiers so they don't need to be escaped + buffer.write(minifiedName); + buffer.write(','); buffer.write(nameMap[name]); first = false; }); - buffer.write('}'); + buffer.write('"'); } void writeFrames( IndexMap uriMap, IndexMap nameMap, StringBuffer buffer) { - bool first = true; - buffer.write('['); + var offsetEncoder = DeltaEncoder(); + var uriEncoder = DeltaEncoder(); + var lineEncoder = DeltaEncoder(); + var columnEncoder = DeltaEncoder(); + var nameEncoder = DeltaEncoder(); + buffer.write('"'); frames.forEach((int offset, List entries) { - if (!first) buffer.write(','); - buffer.write('['); - buffer.write(offset); for (var entry in entries) { - buffer.write(','); + offsetEncoder.encode(buffer, offset); if (entry.isPush) { SourceLocation location = entry.pushLocation; - buffer.write('['); - buffer.write(uriMap[location.sourceUri]); - buffer.write(','); - buffer.write(location.line - 1); - buffer.write(','); - buffer.write(location.column - 1); - buffer.write(','); - buffer.write(nameMap[entry.inlinedMethodName]); - buffer.write(']'); + uriEncoder.encode(buffer, uriMap[location.sourceUri]); + lineEncoder.encode(buffer, location.line - 1); + columnEncoder.encode(buffer, location.column - 1); + nameEncoder.encode(buffer, nameMap[entry.inlinedMethodName]); } else { - buffer.write(entry.isEmptyPop ? 0 : -1); + // ; and , are not used by VLQ so we can distinguish them in the + // encoding, this is the same reason they are used in the mappings + // field. + buffer.write(entry.isEmptyPop ? ";" : ","); } } - buffer.write(']'); - first = false; }); - buffer.write(']'); + buffer.write('"'); } /// Returns the source map tag to put at the end a .js file in [fileUri] to diff --git a/pkg/dart2js_tools/lib/src/dart2js_mapping.dart b/pkg/dart2js_tools/lib/src/dart2js_mapping.dart index 2a62e6e232f63..2ba789c0e8e6b 100644 --- a/pkg/dart2js_tools/lib/src/dart2js_mapping.dart +++ b/pkg/dart2js_tools/lib/src/dart2js_mapping.dart @@ -9,6 +9,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:source_maps/source_maps.dart'; +import 'package:source_maps/src/vlq.dart'; import 'util.dart'; @@ -35,48 +36,16 @@ class Dart2jsMapping { if (extensions == null) return; var minifiedNames = extensions['minified_names']; if (minifiedNames != null) { - minifiedNames['global'].forEach((minifiedName, id) { - globalNames[minifiedName] = sourceMap.names[id]; - }); - minifiedNames['instance'].forEach((minifiedName, id) { - instanceNames[minifiedName] = sourceMap.names[id]; - }); + _extractMinifedNames(minifiedNames['global'], sourceMap, globalNames); + _extractMinifedNames(minifiedNames['instance'], sourceMap, instanceNames); } - List jsonFrames = extensions['frames']; + String jsonFrames = extensions['frames']; if (jsonFrames != null) { - for (List values in jsonFrames) { - if (values.length < 2) { - warn("warning: incomplete frame data: $values"); - continue; - } - int offset = values[0]; - List entries = frames[offset] ??= []; - if (entries.length > 0) { - warn("warning: duplicate entries for $offset"); - continue; - } - for (int i = 1; i < values.length; i++) { - var current = values[i]; - if (current == -1) { - entries.add(new FrameEntry.pop(false)); - } else if (current == 0) { - entries.add(new FrameEntry.pop(true)); - } else { - if (current is List) { - if (current.length == 4) { - entries.add(new FrameEntry.push(sourceMap.urls[current[0]], - current[1], current[2], sourceMap.names[current[3]])); - } else { - warn("warning: unexpected entry $current"); - } - } else { - warn("warning: unexpected entry $current"); - } - } - } - } + new _FrameDecoder(jsonFrames).parseFrames(frames, sourceMap); } } + + Dart2jsMapping.json(Map json) : this(parseJson(json), json); } class FrameEntry { @@ -131,3 +100,63 @@ Dart2jsMapping parseMappingFor(Uri uri) { var json = jsonDecode(sourcemapFile.readAsStringSync()); return new Dart2jsMapping(parseJson(json), json); } + +class _FrameDecoder implements Iterator { + final String _internal; + final int _length; + int index = -1; + _FrameDecoder(this._internal) : _length = _internal.length; + + // Iterator API is used by decodeVlq to consume VLQ entries. + bool moveNext() => ++index < _length; + + String get current => + (index >= 0 && index < _length) ? _internal[index] : null; + + bool get hasTokens => index < _length - 1 && _length > 0; + + int _readDelta() => decodeVlq(this); + + void parseFrames(Map> frames, SingleMapping sourceMap) { + var offset = 0; + var uriId = 0; + var nameId = 0; + var line = 0; + var column = 0; + while (hasTokens) { + offset += _readDelta(); + List entries = frames[offset] ??= []; + var marker = _internal[index + 1]; + if (marker == ';') { + entries.add(new FrameEntry.pop(true)); + index++; + continue; + } else if (marker == ',') { + entries.add(new FrameEntry.pop(false)); + index++; + continue; + } else { + uriId += _readDelta(); + var uri = sourceMap.urls[uriId]; + line += _readDelta(); + column += _readDelta(); + nameId += _readDelta(); + var name = sourceMap.names[nameId]; + entries.add(new FrameEntry.push(uri, line, column, name)); + } + } + } +} + +_extractMinifedNames(String encodedInput, SingleMapping sourceMap, + Map minifiedNames) { + List input = encodedInput.split(','); + if (input.length % 2 != 0) { + warn("expected an even number of entries"); + } + for (int i = 0; i < input.length; i += 2) { + String minifiedName = input[i]; + int id = int.tryParse(input[i + 1]); + minifiedNames[minifiedName] = sourceMap.names[id]; + } +} diff --git a/pkg/sourcemap_testing/lib/src/stacktrace_helper.dart b/pkg/sourcemap_testing/lib/src/stacktrace_helper.dart index 6f968a152f492..2e2f18b295639 100644 --- a/pkg/sourcemap_testing/lib/src/stacktrace_helper.dart +++ b/pkg/sourcemap_testing/lib/src/stacktrace_helper.dart @@ -10,6 +10,7 @@ import 'package:expect/expect.dart'; import 'package:source_maps/source_maps.dart'; import 'package:source_maps/src/utils.dart'; import 'package:source_span/source_span.dart'; +import 'package:dart2js_tools/src/dart2js_mapping.dart'; import 'annotated_code_helper.dart'; @@ -431,25 +432,6 @@ class LineException { const LineException(this.methodName, this.fileName); } -class FrameEntry { - final String callUri; - final int callLine; - final int callColumn; - final String inlinedMethodName; - final bool isEmpty; - FrameEntry.push( - this.callUri, this.callLine, this.callColumn, this.inlinedMethodName) - : isEmpty = false; - FrameEntry.pop(this.isEmpty) - : callUri = null, - callLine = null, - callColumn = null, - inlinedMethodName = null; - - bool get isPush => callUri != null; - bool get isPop => callUri == null; -} - /// Search backwards in [sources] for a function declaration that includes the /// [start] offset. TargetEntry findEnclosingFunction( @@ -466,44 +448,5 @@ TargetEntry findEnclosingFunction( Map> _loadInlinedFrameData( SingleMapping mapping, String sourceMapText) { var json = jsonDecode(sourceMapText); - var frames = >{}; - var extensions = json['x_org_dartlang_dart2js']; - if (extensions == null) return null; - List jsonFrames = extensions['frames']; - if (jsonFrames == null) return null; - - for (List values in jsonFrames) { - if (values.length < 2) { - print("warning: incomplete frame data: $values"); - continue; - } - - int offset = values[0]; - List entries = frames[offset] ??= []; - if (entries.length > 0) { - print("warning: duplicate entries for $offset"); - continue; - } - - for (int i = 1; i < values.length; i++) { - var current = values[i]; - if (current == -1) { - entries.add(new FrameEntry.pop(false)); - } else if (current == 0) { - entries.add(new FrameEntry.pop(true)); - } else { - if (current is List) { - if (current.length == 4) { - entries.add(new FrameEntry.push(mapping.urls[current[0]], - current[1], current[2], mapping.names[current[3]])); - } else { - print("warning: unexpected entry $current"); - } - } else { - print("warning: unexpected entry $current"); - } - } - } - } - return frames; + return Dart2jsMapping(mapping, json).frames; } diff --git a/tests/compiler/dart2js/sourcemaps/minified_names_test.dart b/tests/compiler/dart2js/sourcemaps/minified_names_test.dart index 6b12c541299dc..70a764fa4a950 100644 --- a/tests/compiler/dart2js/sourcemaps/minified_names_test.dart +++ b/tests/compiler/dart2js/sourcemaps/minified_names_test.dart @@ -9,6 +9,7 @@ import 'dart:convert'; import 'package:args/args.dart'; import 'package:async_helper/async_helper.dart'; import 'package:compiler/src/commandline_options.dart'; +import 'package:dart2js_tools/src/dart2js_mapping.dart'; import '../helpers/d8_helper.dart'; import 'package:expect/expect.dart'; @@ -115,25 +116,23 @@ checkExpectation(MinifiedNameTest test, bool minified) async { var sourceMap = '${result.outputPath}.map'; var json = jsonDecode(await new File(sourceMap).readAsString()); - var extensions = json['x_org_dartlang_dart2js']; - Expect.isNotNull(extensions, "Source-map doesn't contain dart2js extensions"); - var minifiedNames = extensions['minified_names']; - Expect.isNotNull(minifiedNames, "Source-map doesn't contain minified-names"); + var mapping = Dart2jsMapping.json(json); + Expect.isTrue(mapping.globalNames.isNotEmpty, + "Source-map doesn't contain minified-names"); var actualName; if (test.isGlobal) { - var index = minifiedNames['global'][name]; - Expect.isNotNull(index, "'$name' not in global name map"); - actualName = json['names'][index]; + actualName = mapping.globalNames[name]; + Expect.isNotNull(actualName, "'$name' not in global name map"); } else if (test.isInstance) { - var index = minifiedNames['instance'][name]; // In non-minified mode some errors show the original name // rather than the selector name (e.g. m1 instead of m1$0 in a - // NoSuchMethodError), and because of that `index` may be null. + // NoSuchMethodError), and because of that the name might not be on the + // table. // // TODO(sigmund): consider making all errors show the internal name, or // include a marker to make it easier to distinguish. - actualName = index == null ? name : json['names'][index]; + actualName = mapping.instanceNames[name] ?? name; } else { Expect.fail('unexpected'); }