← All issues

JSC `using` Declarations (Explicit Resource Management)

4ca7559

Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp

+void BytecodeGenerator::emitAddDisposableResource(DisposeCapability& disposeCapability, RegisterID* value, DisposeMethodHint hint, RegisterID* disposeMethod)
+{
+ auto& slot = disposeCapability.resources.last();
+ emitMove(slot.value.get(), value);
+ if (hint == DisposeMethodHint::SyncDispose) {
+ emitGetDisposeMethod(slot.method.get(), value);
+ } else {
+ emitGetAsyncDisposeMethod(slot.method.get(), value);
+ }
+}
+
+void BytecodeGenerator::emitDisposeResources(DisposeCapability& disposeCapability, RegisterID* completionTypeRegister, RegisterID* completionValueRegister)
+{
+ // Iterates slots in reverse order, catches disposal errors,
+ // chains via SuppressedError(newError, pendingError)
+ ...
+}

Source/JavaScriptCore/parser/Parser.cpp

+// 'using' lookahead disambiguation
+if (isIdent && *m_token.m_data.ident == m_vm.propertyNames->using
+ && !m_token.m_data.escaped) {
+ SavePoint savePoint = createSavePoint(context);
+ next();
+ bool isUsingDeclaration = matchSpecIdentifier()
+ && !(!peekTokenIsColon() && match(IDENT)
+ && *m_token.m_data.ident == m_vm.propertyNames->of);
+ restoreSavePoint(context, savePoint);
+ if (isUsingDeclaration)
+ return parseVariableDeclaration(...);
+}

This commit implements using declarations from the TC39 Explicit Resource Management proposal (Stage 4) in JavaScriptCore — the JavaScript equivalent of C++ RAII or Python with statements. When a using binding goes out of scope, JSC automatically calls Symbol.dispose on the resource.

The implementation spans three major areas. The parser gains multi-token lookahead logic to disambiguate using as a keyword versus a plain identifier — using is not a reserved word in JavaScript, so using x = ... (declaration) must be distinguished from using as a variable name. The disambiguation handles several edge cases including for (using of expr) (must parse as plain for-of per the spec's [lookahead ≠ using of] restriction) and escaped \u0075sing (must be treated as an identifier, not a keyword).

The bytecode compiler models disposal as a synthetic finally block. Disposal slots are pre-allocated before the try body so that partial initialization — an initializer that throws midway through a list — still runs cleanup for successfully initialized resources. Disposal runs in reverse declaration order, and if multiple disposals throw, errors are chained into a SuppressedError rather than dropped.

Scope entry
  │
  ├─ Pre-allocate slots (slot[i].method = undefined)  ← before try
  │
  ├─ try block
  │     ├─ slot[0].value = resource1; slot[0].method = getDisposeMethod(resource1)
  │     └─ slot[1].value = resource2; slot[1].method = getDisposeMethod(resource2)
  │
  └─ finally block (reverse order)
        ├─ call slot[1].method(slot[1].value)  → on throw: SuppressedError(newErr, pendingErr)
        ├─ call slot[0].method(slot[0].value)  → on throw: SuppressedError(newErr, pendingErr)
        └─ if disposeThrew → throw pendingError
           else if body threw → rethrow originalError

await using is deferred to a follow-up patch.

This is a non-trivial new JavaScript language feature with complex parser disambiguation, new scope tracking, and error-chaining semantics. The parser lookahead alone — distinguishing using x = ... from using as an identifier, handling escaped unicode, and interacting with for-of head parsing — introduces subtle edge cases. The disposal-as-finally-block model adds new bytecode patterns that interact with exception handling, generators, and scope exit in ways that are historically fertile ground for JIT and bytecode compiler bugs.

🔒

Pattern-based audit directions for variant discovery

Subscribe to read more