diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart index 9a8e1dc9080..315c0b92564 100644 --- a/pkg/dart2wasm/lib/async.dart +++ b/pkg/dart2wasm/lib/async.dart @@ -1307,7 +1307,7 @@ class AsyncCodeGenerator extends CodeGenerator { // try-finally. Would that be more efficient? b.local_get(exceptionLocal); b.local_get(stackTraceLocal); - b.throw_(translator.exceptionTag); + call(translator.errorThrow.reference); b.unreachable(); return expectedType; diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index d9e46de84ae..ac72ae4344e 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -244,6 +244,8 @@ mixin KernelNodes { late final Class errorClass = index.getClass("dart:core", "Error"); late final Field errorClassStackTraceField = index.getField("dart:core", "Error", "_stackTrace"); + late final Procedure errorThrow = + index.getProcedure("dart:core", "Error", "_throw"); late final Procedure errorThrowWithCurrentStackTrace = index.getProcedure("dart:core", "Error", "_throwWithCurrentStackTrace"); diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart index e8c3d9b37e3..b8d896959b4 100644 --- a/pkg/dart2wasm/lib/transformers.dart +++ b/pkg/dart2wasm/lib/transformers.dart @@ -427,6 +427,9 @@ class _WasmTransformer extends Transformer { // Try-catch-finally around the body to call `controller.addError` and // `controller.close`. final exceptionVar = VariableDeclaration(null, isSynthesized: true); + final stackTraceVar = VariableDeclaration(null, + isSynthesized: true, + type: coreTypes.stackTraceRawType(Nullability.nonNullable)); final Procedure controllerAddErrorProc = coreTypes.index .getProcedure('dart:async', 'StreamController', 'addError'); final FunctionType controllerAddErrorType = @@ -442,11 +445,12 @@ class _WasmTransformer extends Transformer { [ Catch( exceptionVar, + stackTrace: stackTraceVar, ExpressionStatement(InstanceInvocation( InstanceAccessKind.Instance, VariableGet(controller), Name('addError'), - Arguments([VariableGet(exceptionVar)]), + Arguments([VariableGet(exceptionVar), VariableGet(stackTraceVar)]), interfaceTarget: controllerAddErrorProc, functionType: controllerAddErrorType, )), diff --git a/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries.dart.strong.transformed.expect b/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries.dart.strong.transformed.expect index d1c1827fe8c..73b40fc228d 100644 --- a/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries.dart.strong.transformed.expect @@ -71,6 +71,7 @@ abstract class _B&Object&Error extends core::Object implements core::Error /*isA } @#C4 @#C10 + @#C12 external static method /* from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/errors_patch.dart */ _throw(core::Object error, core::StackTrace stackTrace) → Never; } class B extends self::_B&Object&Error { @@ -90,4 +91,6 @@ constants { #C8 = "vm:external-name" #C9 = "Error_throwWithStackTrace" #C10 = core::pragma {name:#C8, options:#C9} + #C11 = "wasm:entry-point" + #C12 = core::pragma {name:#C11, options:#C2} } diff --git a/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries.dart.weak.transformed.expect b/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries.dart.weak.transformed.expect index d1c1827fe8c..73b40fc228d 100644 --- a/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries.dart.weak.transformed.expect +++ b/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries.dart.weak.transformed.expect @@ -71,6 +71,7 @@ abstract class _B&Object&Error extends core::Object implements core::Error /*isA } @#C4 @#C10 + @#C12 external static method /* from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/errors_patch.dart */ _throw(core::Object error, core::StackTrace stackTrace) → Never; } class B extends self::_B&Object&Error { @@ -90,4 +91,6 @@ constants { #C8 = "vm:external-name" #C9 = "Error_throwWithStackTrace" #C10 = core::pragma {name:#C8, options:#C9} + #C11 = "wasm:entry-point" + #C12 = core::pragma {name:#C11, options:#C2} } diff --git a/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries_legacy.dart.strong.transformed.expect b/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries_legacy.dart.strong.transformed.expect index 316a17ed7ce..dd4cd2aa97b 100644 --- a/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries_legacy.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries_legacy.dart.strong.transformed.expect @@ -63,6 +63,7 @@ abstract class _B&Object&Error extends core::Object implements core::Error /*isA } @#C4 @#C10 + @#C12 external static method /* from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/errors_patch.dart */ _throw(core::Object error, core::StackTrace stackTrace) → Never; } class B extends self::_B&Object&Error { @@ -82,4 +83,6 @@ constants { #C8 = "vm:external-name" #C9 = "Error_throwWithStackTrace" #C10 = core::pragma {name:#C8, options:#C9} + #C11 = "wasm:entry-point" + #C12 = core::pragma {name:#C11, options:#C2} } diff --git a/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries_legacy.dart.weak.transformed.expect b/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries_legacy.dart.weak.transformed.expect index 316a17ed7ce..dd4cd2aa97b 100644 --- a/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries_legacy.dart.weak.transformed.expect +++ b/pkg/front_end/testcases/class_modifiers/mixin/mixin_class_core_libraries_legacy.dart.weak.transformed.expect @@ -63,6 +63,7 @@ abstract class _B&Object&Error extends core::Object implements core::Error /*isA } @#C4 @#C10 + @#C12 external static method /* from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/errors_patch.dart */ _throw(core::Object error, core::StackTrace stackTrace) → Never; } class B extends self::_B&Object&Error { @@ -82,4 +83,6 @@ constants { #C8 = "vm:external-name" #C9 = "Error_throwWithStackTrace" #C10 = core::pragma {name:#C8, options:#C9} + #C11 = "wasm:entry-point" + #C12 = core::pragma {name:#C11, options:#C2} } diff --git a/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect index 248c2332703..eaa994c45e0 100644 --- a/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect @@ -81,7 +81,7 @@ static method asyncMethod(asy::Stream stream) → asy::Stream throw error; + Never throwWithStack(Error error, StackTrace stack) => + Error.throwWithStackTrace(error, stack); + dynamic throwNSM(dynamic c) => c * 4; + + _testSync("sync", throwError, throwWithStack, throwNSM); +} + +void testSyncStar() { + Iterable throwErrorStream(Error error) sync* { + yield throw error; + } + + Iterable throwWithStackStream(Error error, StackTrace stack) sync* { + yield Error.throwWithStackTrace(error, stack); + } + + Iterable throwNSMStream(dynamic c) sync* { + yield c * 4; + } + + Never throwError(Error error) => throwErrorStream(error).first; + Never throwWithStack(Error error, StackTrace stack) => + throwWithStackStream(error, stack).first; + dynamic throwNSM(dynamic c) => throwNSMStream(c).first; + + _testSync("sync*", throwError, throwWithStack, throwNSM); +} + +Future testAsync() async { + Future throwError(Error error) async => throw error; + Future throwErrorWithStack(Error error, StackTrace stack) async => + Error.throwWithStackTrace(error, stack); + Future throwNSM(dynamic c) async => c * 4; + + return _testAsync("async", throwError, throwErrorWithStack, throwNSM); +} + +Future testAsyncStar() async { + Stream throwErrorStream(Error error) async* { + yield throw error; + } + + Future throwError(Error error) => throwErrorStream(error).first; + + Stream throwErrorWithStackStream( + Error error, StackTrace stack) async* { + yield Error.throwWithStackTrace(error, stack); + } + + Future throwErrorWithStack(Error error, StackTrace stack) => + throwErrorWithStackStream(error, stack).first; + + Stream throwNSMStream(dynamic c) async* { + yield c * 4; + } + + Future throwNSM(dynamic c) => throwNSMStream(c).first; + + return _testAsync("async*", throwError, throwErrorWithStack, throwNSM); +} + +void _testSync( + String functionKind, + Never Function(Error) throwError, + Never Function(Error, StackTrace) throwWithStack, + dynamic Function(dynamic) throwNSM) { + // Checks that an error first thrown with [firstStack] as [Error.stackTrace], + // will keep that stack trace if thrown again asynchronously. + void testErrorSet(String throwKind, Error error, StackTrace firstStack) { + var desc = "$functionKind $throwKind"; + // Was thrown with [stackTrace] as stack trace. + Expect.isNotNull(error.stackTrace, "$desc throw - did not set .stackTrace"); + Expect.stringEquals(firstStack.toString(), error.stackTrace.toString(), + "$desc, caught stack/set stack - not same"); + // Throw same error again, using `throw`, with different stack. + try { + throwError(error); + } on Error catch (e, s) { + var redesc = "$functionKind throw again"; + Expect.identical(error, e, "$redesc - not same error"); + Expect.notEquals(firstStack.toString(), s.toString(), + "$redesc, set stack/new stack - not different"); + // Did not overwrite existing `error.stackTrace`. + Expect.equals(firstStack.toString(), e.stackTrace.toString(), + "$redesc - changed .stackTrace"); + } + + // Throw same error again using `Error.throwWithStackTrace`. + var stack2 = StackTrace.fromString("stack test string 2"); + try { + throwWithStack(error, stack2); + } on Error catch (e, s) { + var redesc = "$functionKind E.tWST again"; + Expect.identical(error, e, "$redesc - not same error"); + Expect.equals(stack2.toString(), s.toString(), + "$redesc, thrown stack/caught stack - not same"); + Expect.notEquals(firstStack.toString(), s.toString(), + "$redesc, first stack/new stack - not different"); + // Did not overwrite existing `error.stackTrace`. + Expect.equals(firstStack.toString(), e.stackTrace.toString(), + "$redesc - changed .stackTrace"); + } + } + + { + // System thrown error. + try { + throwNSM(NoMult()); + Expect.fail("Did not throw"); + } on NoSuchMethodError catch (e, s) { + testErrorSet("throwNSM", e, s); + } + } + + { + // User thrown error, explicit `throw`. + var error = StateError("error test string"); + Expect.isNull(error.stackTrace); + try { + throwError(error); + } on Error catch (e, s) { + Expect.identical(error, e, + "$functionKind throw: thrown error/caught error - not same"); + testErrorSet("throw", e, s); + } + } + + { + // Thrown using `Error.throwWithStackTrace`. + var error = StateError("error test string"); + Expect.isNull(error.stackTrace); + var stack = StackTrace.fromString("stack test string"); + try { + throwWithStack(error, stack); + } on Error catch (e, s) { + Expect.identical(error, e, + "$functionKind E.tWST: thrown error/caught error - not same"); + Expect.stringEquals(stack.toString(), s.toString(), + "$functionKind E.tWST: thrown stack/caught stack - not same"); + testErrorSet("E.tWST", e, s); + } + } +} + +Future _testAsync( + String functionKind, + Future Function(Error) throwError, + Future Function(Error, StackTrace) throwWithStack, + Future Function(dynamic) throwNSM) async { + // Checks that an error first thrown with [firstStack] as [Error.stackTrace], + // will keep that stack trace if thrown again asynchronously. + Future testErrorSet( + String throwKind, Error error, StackTrace firstStack) async { + var desc = "$functionKind $throwKind"; + // Was thrown with [stackTrace] as stack trace. + Expect.isNotNull(error.stackTrace, "$desc throw - did not set .stackTrace"); + Expect.stringEquals(firstStack.toString(), error.stackTrace.toString(), + "$desc, caught stack/set stack - not same"); + // Throw same error again, using `throw`, with different stack. + try { + await throwError(error); + } on Error catch (e, s) { + var redesc = "$functionKind throw again"; + Expect.identical(error, e, "$redesc - not same error"); + if (functionKind != "async*") { + // An async* throw happens asynchronously, so its stack trace + // can be just the same short stack from the event loop every time. + Expect.notEquals(firstStack.toString(), s.toString(), + "$redesc, set stack/new stack - not different"); + } + // Did not overwrite existing `error.stackTrace`. + Expect.equals(firstStack.toString(), e.stackTrace.toString(), + "$redesc - changed .stackTrace"); + } + + // Throw same error again using `Error.throwWithStackTrace`. + var stack2 = StackTrace.fromString("stack test string 2"); + try { + await throwWithStack(error, stack2); + } on Error catch (e, s) { + var redesc = "$functionKind E.tWST again"; + Expect.identical(error, e, "$redesc - not same error"); + Expect.equals(stack2.toString(), s.toString(), + "$redesc, thrown stack/caught stack - not same"); + if (functionKind != "async*") { + Expect.notEquals(firstStack.toString(), s.toString(), + "$redesc, first stack/new stack - not different"); + } + // Did not overwrite existing `error.stackTrace`. + Expect.equals(firstStack.toString(), e.stackTrace.toString(), + "$redesc - changed .stackTrace"); + } + } + + asyncStart(); + + { + // System thrown error. + try { + await throwNSM(NoMult()); + Expect.fail("Did not throw"); + } on NoSuchMethodError catch (e, s) { + await testErrorSet("throwNSM", e, s); + } + } + + { + // User thrown error, explicit `throw`. + var error = StateError("error test string"); + Expect.isNull(error.stackTrace); + try { + await throwError(error); + } on Error catch (e, s) { + Expect.identical(error, e, + "$functionKind throw: thrown error/caught error - not same"); + await testErrorSet("throw", e, s); + } + } + + { + // Thrown using `Error.throwWithStackTrace`. + var error = StateError("error test string"); + Expect.isNull(error.stackTrace); + var stack = StackTrace.fromString("stack test string"); + try { + await throwWithStack(error, stack); + } on Error catch (e, s) { + Expect.identical(error, e, + "$functionKind E.tWST: thrown error/caught error - not same"); + Expect.stringEquals(stack.toString(), s.toString(), + "$functionKind E.tWST: thrown stack/caught stack - not same"); + await testErrorSet("E.tWST", e, s); + } + } + asyncEnd(); +} + +// Has no `operator *`, forcing a dynamic `NoSuchMethodError` +// when used in `c * 4`. +class NoMult {}