← All issues

Implement `await using` syntax from Explicit Resource Management proposal

92e5222

JSTests/stress/await-using-declaration-basic.js

async function testNull() {
var order = [];
var wasSync = true;
var promise = (async function() {
{
order.push("before");
await using x = null; // null resource: still forces one Await
order.push("after-decl");
}
order.push("after-block");
shouldBe(wasSync, false); // confirms execution suspended (awaited)
})();
wasSync = false;
await promise;
shouldBe(order.join(","), "before,after-decl,after-block");
}
async function testSyncFallback() {
var calledAsync = false;
var calledSync = false;
{
await using a = {
[Symbol.dispose]() { calledSync = true; } // no @@asyncDispose
};
}
shouldBe(calledAsync, false);
shouldBe(calledSync, true); // fell back to sync @@dispose
}

The Explicit Resource Management proposal adds using (sync) and await using (async) declarations to JavaScript — RAII-style resource cleanup where a resource's dispose method is automatically invoked on scope exit. JSC already had using; this commit adds the async variant await using, which requires Await suspend points during disposal. The implementation reuses JSC's Generatorification infrastructure that transforms async functions into yield-based state machines.

The bytecode compiler's "using scope" machinery emits a finally-like block that iterates UsingSlot entries in reverse order, calling each slot's dispose method. For async slots, a new @getAsyncDisposeMethod built-in looks up @@asyncDispose first, falling back to @@dispose with a wrapping closure that converts the sync result into a resolved promise. The spec mandates a subtle invariant: await using x = null must produce exactly one Await(undefined) — so the compiler emits a two-register needsAwait/hasAwaited protocol tracking whether an actual await has occurred during disposal, plus a per-slot reached boolean that separately tracks whether method lookup succeeded (so a throwing getter doesn't trigger a spurious await before propagating the error). The parser adds a two-token lookahead with savepoint restore for the await [no LineTerminator here] using production.

Disposal sequence for: { using a = r1; await using b = r2; }

  slot[1] (b, async, disposed first):
    @getAsyncDisposeMethod(b)
    ├─ method = undefined (null resource) → needsAwait = true
    └─ method found → call method(); Await(result); hasAwaited = true

  slot[0] (a, sync):
    Step 3.d: if (needsAwait && !hasAwaited) → Await(undefined)
    call a[@@dispose]()

  trailing Step 6: statically elided (last slot is sync)

This completes JSC's Explicit Resource Management support, introducing a non-trivial new async execution path with per-slot state machine logic inside scope exit finally blocks. The needsAwait/hasAwaited protocol, the reached flag semantics, and the @@asyncDispose@@dispose fallback wrapper each introduce subtle correctness requirements that interact with async generator lifecycles and exception propagation.

🔒

Multiple edge cases in the new async disposal state machine, method fallback path, and generator interaction are worth security investigation.

Subscribe to read more