`for await` closes the iterator at the wrong times (3 cases left after #1743 / #1649)
I maintainer di solito rispondono entro 2 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- c, javascript
- Ambito
- compilers
Direzione di ricerca
Start by tracing QuickJS-ng's for await bytecode and iterator-close handling; the issue does not name source files or tests, so locate the relevant implementation and regression tests first. Reproduce cases 6–8 from the issue, then check that the tests pass with the reported behavior: case 6 must not read value when done is true, case 7 must await return(), and case 8 must close the iterator on generator return.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Reproduction
async function run(next) {
let called = false, threw = false;
const it = {
[Symbol.asyncIterator]() { return this; },
next,
return() { called = true; return {}; },
};
try { for await (const x of it) break; } catch { threw = true; }
return `return() called: ${called}, threw: ${threw}`;
}
// A. next() failed or finished: return() must not be called
console.log("1 next throws: ", await run(() => { throw 1; }));
console.log("2 next rejects: ", await run(() => Promise.reject(1)));
console.log("3 next returns a primitive: ", await run(() => 1));
console.log("4 done getter throws: ", await run(() => ({ get done() { throw 1; } })));
console.log("5 value getter throws: ", await run(() => ({ done: false, get value() { throw 1; } })));
console.log("6 done, value getter throws:", await run(() => ({ done: true, get value() { throw 1; } })));
// B. a throw in the body must await return()
const order = [];
const slow = {
[Symbol.asyncIterator]() { return this; },
next() { return { value: 1, done: false }; },
async return() { await null; order.push("return settled"); return {}; },
};
try { for await (const x of slow) throw 1; } catch { order.push("caught"); }
console.log("7", order.join(" -> "));
// C. gen.return() while suspended in a binding initializer must close the iterator
let outerClosed = false;
const outer = {
[Symbol.asyncIterator]() { return this; },
next() { return { value: [], done: false }; },
return() { outerClosed = true; return {}; },
};
async function* g() { for await (const [x = yield] of outer); }
const gen = g();
await gen.next();
await gen.return();
console.log("8 return() called after gen.return():", outerClosed);
Expected vs actual
| # | case | expected (V8 13.6, node 24) | QuickJS-ng master 6d46d07 | with #1743 or #1649 |
|---|---|---|---|---|
| 1 | next throws | return() not called |
❌ return() called |
✅ |
| 2 | next rejects | return() not called |
❌ return() called |
✅ |
| 3 | next returns a primitive | return() not called |
❌ return() called |
✅ |
| 4 | done getter throws |
return() not called |
❌ return() called |
✅ |
| 5 | value getter throws |
return() not called |
❌ return() called |
✅ |
| 6 | done, value getter throws |
return() not called, no error |
❌ return() called, error thrown |
❌ error thrown |
| 7 | throw in the body | caught after return() settles |
❌ caught before |
❌ caught before |
| 8 | gen.return() in binding initializer | return() called |
❌ return() not called |
❌ return() not called |
Raw output
V8:
1 next throws: return() called: false, threw: true
2 next rejects: return() called: false, threw: true
3 next returns a primitive: return() called: false, threw: true
4 done getter throws: return() called: false, threw: true
5 value getter throws: return() called: false, threw: true
6 done, value getter throws: return() called: false, threw: false
7 return settled -> caught
8 return() called after gen.return(): true
QuickJS-ng master:
1 next throws: return() called: true, threw: true
2 next rejects: return() called: true, threw: true
3 next returns a primitive: return() called: true, threw: true
4 done getter throws: return() called: true, threw: true
5 value getter throws: return() called: true, threw: true
6 done, value getter throws: return() called: true, threw: true
7 caught
8 return() called after gen.return(): false
With #1743 or #1649:
1 next throws: return() called: false, threw: true
2 next rejects: return() called: false, threw: true
3 next returns a primitive: return() called: false, threw: true
4 done getter throws: return() called: false, threw: true
5 value getter throws: return() called: false, threw: true
6 done, value getter throws: return() called: false, threw: true
7 caught
8 return() called after gen.return(): false
Spec
- 1–5:
? Call(next),? Await, the "not an Object" TypeError,? IteratorCompleteand? IteratorValueare plain abrupt completions. The iterator is not closed. - 6: when
doneis true the loop returns.valueis never read. - 7: an abrupt completion of the body goes through AsyncIteratorClose, which does
Await(Call(return, iterator))before rethrowing. - 8: the
yieldis part of the binding initialization; resuming it with a return completion is abrupt, so the iterator is closed.
Context
I was working on a fix for all 8 when I found #1649, and then #1743, which ports Bellard's fix (bellard/quickjs@56c47f7). Both fix 1–5 and the async-from-sync closeOnRejection step, but 6, 7 and 8 still fail with either applied, and also in bellard/quickjs itself (where 8 throws TypeError: cannot read property 'return' of undefined). So I wanted to get the remaining cases on record.
7 needs a bytecode catch handler around the binding and the body, since the current close runs from C and cannot await. 6 and 8 are small changes on their own. I have these three with regression tests and I'm happy to send them as a follow-up once one of those PRs lands, or fold them into one of them, whatever works best.
Versions
- QuickJS-ng: master 6d46d07 (0.17.0), 0.16.2
- V8: 13.6.233
- Lingua principale
- C
- Stelle
- 3.9k
- Fork
- 389
- Merge medio
- 4g 1h
- PR unite (30g)
- 24
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di quickjs-ng/quickjs
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
quickjs-ng/quickjs#1836 ·
I maintainer di solito rispondono entro 2 giorni
-
Array.prototype.find frees an element twice when interrupted between iterationsForse già presa @josefguenther l’ha presa 16 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 73/100
quickjs-ng/quickjs#1792 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
Object.prototype.propertyIsEnumerable converts `this` before the property keyForse già presa @SomSamantray l’ha presa 11 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
quickjs-ng/quickjs#1772 ·
I maintainer di solito rispondono entro 2 giorni
-
Deserialization corrupts data or returns `undefined` when a RegExp precedes a shared object referenceForse già presa @Sree24-ui l’ha presa 15 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
quickjs-ng/quickjs#1771 · 2 commenti ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 72/100
quickjs-ng/quickjs#1835 · 1 reazione ·
I maintainer di solito rispondono entro 2 giorni
Tutte le issue di quickjs-ng/quickjs
Issue simili
-
TLS: a stale OpenSSL error makes non-blocking reads fail with "Resource temporarily unavailable"Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 1-3 ore Idoneità per principianti 82/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
bigtreetech/BIGTREETECH-TouchScreenFirmware#3056 ·
I maintainer di solito rispondono entro 1 giorno
-
Allow pillow 13Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
[pico][astrocade] emu/apply.sh edits src/mame/midway/astrohome.cpp, which current MAME has in bally/Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
FujiNetWIFI/fujinet-firmware#2120 ·
I maintainer di solito rispondono entro 1 giorno