Skip to content

Commit b969f32

Browse files
natebiggsCommit Queue
authored andcommitted
[dart2wasm] Store exception/stacktrace for nested catch blocks.
The async state machine uses a single state in the heap to store the current exception and stacktrace when within a catch block in case they need to be rethrown. When catch blocks are nested this state can be overwritten. With this new change, before entering a new catch we store the current value of the exception/stacktrace in a local that we can restore after exiting the associated catch blocks. A wasm catch can represent multiple Dart catch blocks so we only need to store the state per wasm catch. Fixes: #59981 Change-Id: I738084fdecdf5aceac65c5d3698e40b791175171 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/405920 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Nate Biggs <[email protected]>
1 parent e893d11 commit b969f32

File tree

2 files changed

+149
-1
lines changed

2 files changed

+149
-1
lines changed

pkg/dart2wasm/lib/state_machine.dart

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ class _YieldFinder extends RecursiveVisitor {
145145
addTarget(c, _StateTargetPlacement.Inner);
146146
recurse(c.body);
147147
}
148+
addTarget(node.catches.last, _StateTargetPlacement.After);
148149
addTarget(node, _StateTargetPlacement.After);
149150
}
150151

@@ -237,6 +238,10 @@ class ExceptionHandlerStack {
237238

238239
final StateMachineCodeGenerator codeGen;
239240

241+
/// Holds exceptions and stacktraces from nested catch blocks so that the
242+
/// enclosing ones can be restored when an inner block is exited.
243+
final List<(w.Local, w.Local)> _previousCatchLocals = [];
244+
240245
ExceptionHandlerStack(this.codeGen);
241246

242247
void _pushTryCatch(TryCatch node) {
@@ -317,6 +322,14 @@ class ExceptionHandlerStack {
317322
final exceptionLocal =
318323
b.addLocal(codeGen.translator.topInfo.nonNullableType);
319324

325+
final previousException =
326+
b.addLocal(codeGen.translator.topInfo.nullableType);
327+
328+
final previousStackTrace =
329+
b.addLocal(codeGen.translator.stackTraceInfo.repr.nullableType);
330+
331+
_previousCatchLocals.add((previousException, previousStackTrace));
332+
320333
void generateCatchBody() {
321334
// Set continuations of finalizers that can be reached by this `catch`
322335
// (or `catch_all`) as "rethrow".
@@ -328,6 +341,12 @@ class ExceptionHandlerStack {
328341
}
329342
}
330343

344+
// Store the current exception in case we enter a nested catch block.
345+
codeGen.getSuspendStateCurrentException();
346+
b.local_set(previousException);
347+
codeGen.getSuspendStateCurrentStackTrace();
348+
b.local_set(previousStackTrace);
349+
331350
// Set the untyped "current exception" variable. Catch blocks will do the
332351
// type tests as necessary using this variable and set their exception
333352
// and stack trace locals.
@@ -377,6 +396,22 @@ class ExceptionHandlerStack {
377396

378397
_tryBlockNumHandlers.clear();
379398
}
399+
400+
void _finalizeCatchBlocks(Object? debug) {
401+
if (_previousCatchLocals.isEmpty) return;
402+
403+
final (previousException, previousStackTrace) =
404+
_previousCatchLocals.removeLast();
405+
406+
final b = codeGen.b;
407+
408+
// Restore the exception and stacktrace from the enclosing try/catch
409+
// if there is one.
410+
codeGen
411+
.setSuspendStateCurrentException(() => b.local_get(previousException));
412+
codeGen.setSuspendStateCurrentStackTrace(
413+
() => b.local_get(previousStackTrace));
414+
}
380415
}
381416

382417
/// Represents an exception handler (`catch` or `finally`).
@@ -976,6 +1011,8 @@ abstract class StateMachineCodeGenerator extends AstCodeGenerator {
9761011
exceptionHandlers._terminateTryBlocks();
9771012
exceptionHandlers._pop();
9781013

1014+
final afterCatch = afterTargets[node.catches.last]!;
1015+
9791016
void emitCatchBlock(Catch catch_, Catch? nextCatch, bool emitGuard) {
9801017
if (emitGuard) {
9811018
getSuspendStateCurrentException();
@@ -1023,7 +1060,7 @@ abstract class StateMachineCodeGenerator extends AstCodeGenerator {
10231060

10241061
catchVariableStack.removeLast();
10251062

1026-
_jumpToTarget(after);
1063+
_jumpToTarget(afterCatch);
10271064
}
10281065

10291066
for (int catchIdx = 0; catchIdx < node.catches.length; catchIdx += 1) {
@@ -1054,6 +1091,12 @@ abstract class StateMachineCodeGenerator extends AstCodeGenerator {
10541091
b.ref_as_non_null();
10551092
b.throw_(translator.getExceptionTag(b.module));
10561093

1094+
emitTargetLabel(afterCatch);
1095+
1096+
exceptionHandlers._finalizeCatchBlocks(node.location);
1097+
1098+
_jumpToTarget(after);
1099+
10571100
emitTargetLabel(after);
10581101
}
10591102

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:expect/expect.dart';
6+
7+
void exception1(String e) {
8+
throw e;
9+
}
10+
11+
void exception2(String e) {
12+
throw e;
13+
}
14+
15+
Future<void> doThrow1() async {
16+
try {
17+
exception1('outer');
18+
} on Object {
19+
try {
20+
exception2('inner');
21+
} on Object {
22+
// ignore
23+
}
24+
rethrow;
25+
}
26+
}
27+
28+
Future<void> doThrow2() async {
29+
try {
30+
exception2('outer');
31+
} on Object {
32+
try {
33+
exception1('inner');
34+
} on Object {
35+
try {
36+
exception1('more inner');
37+
} on Object {
38+
// ignore
39+
}
40+
}
41+
rethrow;
42+
}
43+
}
44+
45+
Future<void> doThrow3() async {
46+
try {
47+
exception1('outer');
48+
} on Object {
49+
try {
50+
// don't throw
51+
} on Object {
52+
try {
53+
// also don't throw
54+
} on Object {
55+
// ignore
56+
}
57+
}
58+
rethrow;
59+
}
60+
}
61+
62+
Future<void> doThrow4() async {
63+
try {
64+
exception1('outer');
65+
} on Object {
66+
try {
67+
exception2('inner');
68+
} on bool {}
69+
rethrow;
70+
}
71+
}
72+
73+
void main() async {
74+
try {
75+
await doThrow1();
76+
Expect.fail('should throw');
77+
} catch (e, s) {
78+
Expect.equals(e, 'outer');
79+
Expect.isTrue('$s'.contains('exception1'));
80+
}
81+
82+
try {
83+
await doThrow2();
84+
Expect.fail('should throw');
85+
} catch (e, s) {
86+
Expect.equals(e, 'outer');
87+
Expect.isTrue('$s'.contains('exception2'));
88+
}
89+
90+
try {
91+
await doThrow3();
92+
Expect.fail('should throw');
93+
} catch (e, s) {
94+
Expect.equals(e, 'outer');
95+
Expect.isTrue('$s'.contains('exception1'));
96+
}
97+
98+
try {
99+
await doThrow4();
100+
Expect.fail('should throw');
101+
} catch (e, s) {
102+
Expect.equals(e, 'inner');
103+
Expect.isTrue('$s'.contains('exception2'));
104+
}
105+
}

0 commit comments

Comments
 (0)