Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

`for await` closes the iterator at the wrong times (3 cases left after #1743 / #1649)

Aperta
#1,747 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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

ForIn/OfBodyEvaluation:

  • 1–5: ? Call(next), ? Await, the "not an Object" TypeError, ? IteratorComplete and ? IteratorValue are plain abrupt completions. The iterator is not closed.
  • 6: when done is true the loop returns. value is never read.
  • 7: an abrupt completion of the body goes through AsyncIteratorClose, which does Await(Call(return, iterator)) before rethrowing.
  • 8: the yield is 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

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di quickjs-ng/quickjs

Tutte le issue di quickjs-ng/quickjs

Issue simili

Altre issue su C

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.