Skip to content

Commit

Permalink
[dart2wasm] Handle JS exceptions in await exprs, finally blocks
Browse files Browse the repository at this point in the history
Fixes #54024.

Change-Id: I5c92f12a453a550ce76b340116f57b329ffa97f7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/335740
Reviewed-by: Jackson Gardner <jacksongardner@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
  • Loading branch information
osa1 authored and Commit Queue committed Dec 11, 2023
1 parent f9d6459 commit 41ee6a5
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 22 deletions.
40 changes: 29 additions & 11 deletions pkg/dart2wasm/lib/async.dart
Original file line number Diff line number Diff line change
Expand Up @@ -781,23 +781,41 @@ class AsyncCodeGenerator extends CodeGenerator {
b.return_();
b.end(); // masterLoop

b.catch_(translator.exceptionTag);

final stackTraceLocal =
addLocal(translator.stackTraceInfo.repr.nonNullableType);
b.local_set(stackTraceLocal);

final exceptionLocal = addLocal(translator.topInfo.nonNullableType);

void callCompleteError() {
b.local_get(suspendStateLocal);
b.struct_get(
asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
b.ref_as_non_null();
b.local_get(exceptionLocal);
b.local_get(stackTraceLocal);
call(translator.completerCompleteError.reference);
b.return_();
}

// Handle Dart exceptions.
b.catch_(translator.exceptionTag);
b.local_set(stackTraceLocal);
b.local_set(exceptionLocal);
callCompleteError();

b.local_get(suspendStateLocal);
b.struct_get(
asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
b.ref_as_non_null();
b.local_get(exceptionLocal);
b.local_get(stackTraceLocal);
call(translator.completerCompleteError.reference);
b.return_();
// Handle JS exceptions.
b.catch_all();

// Create a generic JavaScript error.
call(translator.javaScriptErrorFactory.reference);
b.local_set(exceptionLocal);

// JS exceptions won't have a Dart stack trace, so we attach the current
// Dart stack trace.
call(translator.stackTraceCurrent.reference);
b.local_set(stackTraceLocal);

callCompleteError();

b.end(); // end try

Expand Down
28 changes: 17 additions & 11 deletions pkg/dart2wasm/lib/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1293,7 +1293,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
// execution after the finalizer (no throws, returns, or breaks).
w.Label tryFinallyBlock = b.block();

// Create one block for each wrapping label
// Create one block for each wrapping label.
for (final labelBlocks in breakFinalizers.values.toList().reversed) {
labelBlocks.add(b.block());
}
Expand All @@ -1305,29 +1305,36 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>

w.Label tryBlock = b.try_();
visitStatement(node.body);

final bool mustHandleReturn =
returnFinalizers.removeLast().mustHandleReturn;
b.catch_(translator.exceptionTag);

// `break` statements in the current finalizer and the rest will not run
// the current finalizer, update the `break` targets
// the current finalizer, update the `break` targets.
final removedBreakTargets = <LabeledStatement, w.Label>{};
for (final breakFinalizerEntry in breakFinalizers.entries) {
removedBreakTargets[breakFinalizerEntry.key] =
breakFinalizerEntry.value.removeLast();
}

// Run finalizer on exception
// Handle Dart exceptions.
b.catch_(translator.exceptionTag);
visitStatement(node.finalizer);
b.rethrow_(tryBlock);

// Handle JS exceptions.
b.catch_all();
visitStatement(node.finalizer);
b.rethrow_(tryBlock);
b.end(); // end tryBlock.

// Run finalizer on normal execution (no breaks, throws, or returns)
b.end(); // tryBlock

// Run finalizer on normal execution (no breaks, throws, or returns).
visitStatement(node.finalizer);
b.br(tryFinallyBlock);
b.end(); // end returnFinalizerBlock.
b.end(); // returnFinalizerBlock

// Run finalizer on `return`
// Run the finalizer on `return`.
if (mustHandleReturn) {
visitStatement(node.finalizer);
if (returnFinalizers.isNotEmpty) {
Expand All @@ -1341,15 +1348,14 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
}
}

// Generate finalizers for `break`s in the `try` block
// Generate finalizers for `break`s in the `try` block.
for (final removedBreakTargetEntry in removedBreakTargets.entries) {
b.end();
visitStatement(node.finalizer);
b.br(breakFinalizers[removedBreakTargetEntry.key]!.last);
}

// Terminate `tryFinallyBlock`
b.end();
b.end(); // tryFinallyBlock
}

@override
Expand Down
83 changes: 83 additions & 0 deletions tests/web/wasm/js_exceptions_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2023, 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:js_interop';
import 'dart:_wasm';

import 'package:expect/expect.dart';

// Catch JS exceptions in try-catch and try-finally, in sync and async
// functions. Also in `await`.
void main() async {
defineThrowJSException();

jsExceptionCatch();
jsExceptionFinally();
jsExceptionCatchAsync();
jsExceptionFinallyAsync();
}

@JS()
external void eval(String code);

@JS()
external void throwJSException();

bool runtimeTrue() => int.parse('1') == 1;

void defineThrowJSException() {
eval(r'''
globalThis.throwJSException = function() {
throw "Hi from JS";
}
''');
}

// Catch a JS exception in `catch`.
void jsExceptionCatch() {
try {
throwJSException();
} catch (e) {
return;
}
Expect.fail("Exception not caught");
}

// Catch a JS exception in `finally`.
void jsExceptionFinally() {
if (runtimeTrue()) {
try {
throwJSException();
} finally {
return;
}
}
Expect.fail("Exception not caught");
}

Future<void> throwJSExceptionAsync() async {
return throwJSException();
}

// Catch a JS exception throw by `await` in `catch`.
Future<void> jsExceptionCatchAsync() async {
try {
await throwJSExceptionAsync();
} catch (e) {
return;
}
Expect.fail("Exception not caught");
}

// Catch a JS exception thrown by `await` in `finally`.
Future<void> jsExceptionFinallyAsync() async {
if (runtimeTrue()) {
try {
await throwJSExceptionAsync();
} finally {
return;
}
}
Expect.fail("Exception not caught");
}

0 comments on commit 41ee6a5

Please sign in to comment.