Skip to content

Commit 64d9caa

Browse files
shickscopybara-github
authored andcommitted
Ensure AsyncContext is correctly exited when a for-await loop is abruptly exited (via break or continue).
The simple solution is to just move the context swap at the end of the loop body into a `finally` block, so that it runs even if the loop exits via a different code path. Note that if the abrupt exit is a `return`, then swap runs twice, but this is okay since the exit operation is idempotent. PiperOrigin-RevId: 715856725
1 parent 2d27dff commit 64d9caa

File tree

2 files changed

+31
-14
lines changed

2 files changed

+31
-14
lines changed

src/com/google/javascript/jscomp/InstrumentAsyncContext.java

+12-6
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,11 @@ void instrumentForAwaitOf(NodeTraversal t, Node n, Node parent) {
178178
// becomes
179179
// for await (const x of swap(expr())) { // exit
180180
// swap(0, 1); // reenter
181-
// use(x);
182-
// swap(); // exit
181+
// try {
182+
// use(x);
183+
// } finally {
184+
// swap(); // exit
185+
// }
183186
// }
184187
// swap(0, 1); // reenter
185188

@@ -191,10 +194,13 @@ void instrumentForAwaitOf(NodeTraversal t, Node n, Node parent) {
191194

192195
Node body = n.getLastChild();
193196
checkState(body.isBlock()); // NOTE: IRFactory normalizes non-block `for` bodies
194-
195-
// Add reenter/exit calls to start/end of block
196-
body.addChildToFront(IR.exprResult(createReenter().srcrefTreeIfMissing(arg)));
197-
body.addChildToBack(IR.exprResult(createExit().srcrefTreeIfMissing(arg)));
197+
body.replaceWith(placeholder);
198+
placeholder.replaceWith(
199+
IR.block(
200+
IR.exprResult(createReenter().srcrefTreeIfMissing(arg)),
201+
IR.tryFinally(
202+
body, //
203+
IR.block(IR.exprResult(createExit().srcrefTreeIfMissing(arg))))));
198204

199205
// Add reenter call after for-await-of
200206
if (!parent.isBlock()) {

test/com/google/javascript/jscomp/InstrumentAsyncContextTest.java

+19-8
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ public void testAwait() {
124124
}
125125

126126
// BEFORE: for await (const x of EXPR) { BODY }
127-
// AFTER: for await (const x of swap(EXPR)) { swap(0, 1); BODY; swap() } swap(0, 1)
127+
// AFTER: for await (const x of swap(EXPR)) {
128+
// swap(0, 1); try { BODY } finally { swap() }
129+
// } swap(0, 1)
128130
//
129131
// Explanation:
130132
// * each iteration of the for-await loop is effectively an await; the top of the loop body is
@@ -153,8 +155,11 @@ public void testForAwait() {
153155
" try {",
154156
" for await (const x of $jscomp$swapContext(gen())) {",
155157
" $jscomp$swapContext(0, 1);",
156-
" use(x);",
157-
" $jscomp$swapContext();",
158+
" try {",
159+
" use(x);",
160+
" } finally {",
161+
" $jscomp$swapContext();",
162+
" }",
158163
" }",
159164
" $jscomp$swapContext(0, 1);",
160165
" } finally {",
@@ -187,8 +192,11 @@ public void testForAwaitNotInBlock() {
187192
" if (1) {",
188193
" for await (const x of $jscomp$swapContext(gen())) {",
189194
" $jscomp$swapContext(0, 1);",
190-
" use(x);",
191-
" $jscomp$swapContext();",
195+
" try {",
196+
" use(x);",
197+
" } finally {",
198+
" $jscomp$swapContext();",
199+
" }",
192200
" }",
193201
" $jscomp$swapContext(0, 1);",
194202
" }",
@@ -220,9 +228,12 @@ public void testForAwaitMixedWithAwait() {
220228
" $jscomp$swapContext(",
221229
" $jscomp$swapContext(await $jscomp$swapContext(gen()), 1))) {",
222230
" $jscomp$swapContext(0, 1);",
223-
" $jscomp$swapContext(",
224-
" await $jscomp$swapContext(use(x)), 1);",
225-
" $jscomp$swapContext();",
231+
" try {",
232+
" $jscomp$swapContext(",
233+
" await $jscomp$swapContext(use(x)), 1);",
234+
" } finally {",
235+
" $jscomp$swapContext();",
236+
" }",
226237
" }",
227238
" $jscomp$swapContext(0, 1);",
228239
" } finally {",

0 commit comments

Comments
 (0)