a11aeeb500
Dart's `throw` expression does not correspond exactly to JavaScript's `throw` statement. To implement Dart semantics, and to reduce code size, dart2js uses the functions `wrapException` and `throwExpression` from the dart2js runtime. As a result, these functions appear in the stack trace (one or both).
A consequence of this is that a fixed prefix of an error stack is less useful, as potentially interesting frames are forced out of the prefix by these 'noise' frames.
This change ensures only one of the helpers on the stack trace.
It is also possible to get rid of the helper frame by an annotation.
The annotation `@pragma('dart2js:stack-starts-at-throw')` causes the stack to be captured in the current JavaScript function rather than in a helper. The cost is more code at the call site, about 12 bytes per `throw` expression in minified code. The annotation can be placed on a method, class or library, and applies to all `throw` expression in the scope of the annotated element.
This change uses the annotation to remove noise frames from type errors and some other errors in the runtime.
Change-Id: If15184a5963fb054199177bb4526b32f25e53fe9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406684
Reviewed-by: Mayank Patke <fishythefish@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
171 lines
5.7 KiB
Dart
171 lines
5.7 KiB
Dart
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:expect/async_helper.dart';
|
|
import 'package:compiler/src/commandline_options.dart';
|
|
import 'package:compiler/src/dart2js.dart' as entry;
|
|
|
|
import 'package:sourcemap_testing/src/stacktrace_helper.dart';
|
|
|
|
import 'package:compiler/src/util/memory_compiler.dart';
|
|
|
|
void main(List<String> args) {
|
|
ArgParser argParser = ArgParser(allowTrailingOptions: true);
|
|
argParser.addFlag('write-js', defaultsTo: false);
|
|
argParser.addFlag('print-js', defaultsTo: false);
|
|
argParser.addFlag('verbose', abbr: 'v', defaultsTo: false);
|
|
argParser.addFlag('continued', abbr: 'c', defaultsTo: false);
|
|
ArgResults argResults = argParser.parse(args);
|
|
Directory dataDir = Directory.fromUri(Platform.script.resolve('stacktrace'));
|
|
asyncTest(() async {
|
|
bool continuing = false;
|
|
await for (FileSystemEntity entity in dataDir.list()) {
|
|
String name = entity.uri.pathSegments.last;
|
|
if (!name.endsWith('.dart')) continue;
|
|
if (argResults.rest.isNotEmpty &&
|
|
!argResults.rest.contains(name) &&
|
|
!continuing) {
|
|
continue;
|
|
}
|
|
print('----------------------------------------------------------------');
|
|
print('Checking ${entity.uri}');
|
|
print('----------------------------------------------------------------');
|
|
String annotatedCode = await File.fromUri(entity.uri).readAsString();
|
|
await testAnnotatedCode(
|
|
annotatedCode,
|
|
verbose: argResults['verbose'],
|
|
printJs: argResults['print-js'],
|
|
writeJs: argResults['write-js'],
|
|
inlineData: name.contains('_inlining'),
|
|
);
|
|
if (argResults['continued']) {
|
|
continuing = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
const String kernelMarker = 'kernel.';
|
|
|
|
Future testAnnotatedCode(
|
|
String code, {
|
|
bool printJs = false,
|
|
bool writeJs = false,
|
|
bool verbose = false,
|
|
bool inlineData = false,
|
|
}) async {
|
|
Test test = processTestCode(code, [kernelMarker]);
|
|
print(test.code);
|
|
print('---from kernel------------------------------------------------------');
|
|
await runTest(
|
|
test,
|
|
kernelMarker,
|
|
printJs: printJs,
|
|
writeJs: writeJs,
|
|
verbose: verbose,
|
|
inlineData: inlineData,
|
|
);
|
|
}
|
|
|
|
Future runTest(
|
|
Test test,
|
|
String config, {
|
|
bool printJs = false,
|
|
bool writeJs = false,
|
|
bool verbose = false,
|
|
bool inlineData = false,
|
|
List<String> options = const <String>[],
|
|
}) async {
|
|
List<LineException> testAfterExceptions = <LineException>[];
|
|
if (config == kernelMarker) {
|
|
for (LineException exception in afterExceptions) {
|
|
testAfterExceptions.add(exception);
|
|
}
|
|
} else {
|
|
testAfterExceptions = afterExceptions;
|
|
}
|
|
return testStackTrace(
|
|
test,
|
|
config,
|
|
(String input, String output) async {
|
|
List<String> arguments = [
|
|
'-o$output',
|
|
'--platform-binaries=$sdkPlatformBinariesPath',
|
|
'--libraries-spec=$sdkLibrariesSpecificationPath',
|
|
'--packages=${Platform.packageConfig}',
|
|
Flags.testMode,
|
|
input,
|
|
]..addAll(options);
|
|
print("Compiling dart2js ${arguments.join(' ')}");
|
|
CompilationResult compilationResult = await entry.internalMain(arguments);
|
|
return compilationResult.isSuccess;
|
|
},
|
|
jsPreambles:
|
|
(input, output) => [
|
|
'$sdkPath/_internal/js_runtime/lib/preambles/d8.js',
|
|
],
|
|
afterExceptions: testAfterExceptions,
|
|
beforeExceptions: beforeExceptions,
|
|
verbose: verbose,
|
|
printJs: printJs,
|
|
writeJs: writeJs,
|
|
stackTraceLimit: 100,
|
|
expandDart2jsInliningData: inlineData,
|
|
);
|
|
}
|
|
|
|
/// Lines allowed before the intended stack trace. Typically from helper
|
|
/// methods.
|
|
const List<LineException> beforeExceptions = const [
|
|
const LineException('wrapException', 'js_helper.dart'),
|
|
const LineException('throwExpression', 'js_helper.dart'),
|
|
const LineException('throw_', 'js_helper.dart'),
|
|
];
|
|
|
|
/// Lines allowed after the intended stack trace. Typically from the event
|
|
/// queue.
|
|
const List<LineException> afterExceptions = const [
|
|
const LineException('_asyncStartSync', 'async_patch.dart'),
|
|
const LineException('_wrapJsFunctionForAsync', 'async_patch.dart'),
|
|
const LineException(
|
|
'_wrapJsFunctionForAsync.<anonymous function>',
|
|
'async_patch.dart',
|
|
),
|
|
const LineException(
|
|
'_awaitOnObject.<anonymous function>',
|
|
'async_patch.dart',
|
|
),
|
|
const LineException('_asyncAwait.<anonymous function>', 'async_patch.dart'),
|
|
const LineException('_asyncStart.<anonymous function>', 'async_patch.dart'),
|
|
const LineException('_RootZone.runUnary', 'zone.dart'),
|
|
const LineException('_FutureListener.handleValue', 'future_impl.dart'),
|
|
const LineException(
|
|
'_Future._asyncCompleteWithValue.<anonymous function>',
|
|
'future_impl.dart',
|
|
),
|
|
const LineException('_Future._completeWithValue', 'future_impl.dart'),
|
|
const LineException(
|
|
'_Future._propagateToListeners.handleValueCallback',
|
|
'future_impl.dart',
|
|
),
|
|
const LineException('_Future._propagateToListeners', 'future_impl.dart'),
|
|
const LineException(
|
|
'_Future._addListener.<anonymous function>',
|
|
'future_impl.dart',
|
|
),
|
|
const LineException('_microtaskLoop', 'schedule_microtask.dart'),
|
|
const LineException('_startMicrotaskLoop', 'schedule_microtask.dart'),
|
|
const LineException(
|
|
'_AsyncRun._scheduleImmediateJsOverride.internalCallback',
|
|
'async_patch.dart',
|
|
),
|
|
const LineException('invokeClosure.<anonymous function>', 'js_helper.dart'),
|
|
const LineException('invokeClosure', 'js_helper.dart'),
|
|
const LineException('convertDartClosureToJS', 'js_helper.dart'),
|
|
];
|