← 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(...);
+}

TC39 Explicit Resource Management 제안(Stage 4)의 using 선언을 JavaScriptCore에 구현한 commit입니다. C++의 RAII, Python의 with 문과 동등한 JavaScript 기능으로, using 바인딩이 스코프를 벗어나는 시점에 JSC가 자동으로 해당 리소스의 Symbol.dispose를 호출합니다.

구현은 크게 세 영역에 걸쳐 있습니다. 먼저 파서에는 using을 키워드로 볼지, 일반 식별자로 볼지 판단하기 위한 multi-token lookahead 로직이 추가되었습니다. using은 JavaScript의 예약어가 아니기 때문에, using x = ... 형태의 선언과 변수명으로 쓰인 using을 구분해야 합니다. 이 과정에서 몇 가지 edge case가 처리됩니다. for (using of expr)는 스펙의 [lookahead ≠ using of] 제약에 따라 일반 for-of로 파싱해야 합니다. 또한 \u0075sing처럼 escape된 형태는 키워드가 아닌 식별자로 처리해야 합니다.

바이트코드 컴파일러는 disposal을 합성된 finally 블록으로 모델링합니다. Disposal slot은 try 블록 진입 전에 미리 할당되어 있어, partial initialization — 초기화 목록 중간에 예외가 발생하는 상황 — 에서도 이미 초기화된 리소스의 정리 작업은 빠짐없이 실행됩니다. 또한 disposal은 선언의 역순으로 진행되며, 여러 disposal에서 예외가 발생하면 오류를 버리는 대신 SuppressedError로 연결해 보존합니다.

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은 후속 패치로 미루어졌습니다.

새로운 JavaScript 언어 기능이지만, 구현이 단순하지 않습니다. Parser disambiguation이 복잡하고, 새로운 scope 추적 방식이 도입되며, error-chaining 시맨틱까지 포함됩니다. 파서의 lookahead만 하더라도 섬세한 edge case가 생겨납니다. using x = ...과 식별자로 쓰인 using의 구분, escape된 unicode 처리, for-of head 파싱과의 상호작용 — 각각이 서로 다른 처리를 필요로 합니다. 한편 disposal-as-finally-block 모델은 새로운 bytecode 패턴을 추가합니다. 이 패턴은 exception handling, generator, scope exit과 상호작용하는데, 이런 조합은 역사적으로 JIT 및 bytecode 컴파일러 버그가 빈번하게 발생해온 영역입니다.

🔒

이 취약점 패턴의 변종을 찾기 위한 구체적인 탐색 방향이 포함되어 있습니다

더 확인하려면 구독해 주세요