← All issues

[5] [JSC] Defer GC across direct eval cache key construction

Severity: High | Component: JSC interpreter | 32f1bfb

DirectEvalCodeCache::CacheLookupKey가 rope fiber에서 추출한 raw StringImpl*를 보유하는 구간에 DeferGC를 삽입한 fix입니다. 이 조치 없이 parser/profiler 할당 중 GC가 발생하면, 참조되지 않은 fiber가 sweep됩니다. 결과적으로 cache key와 이후의 삽입 과정이 해제된 메모리를 역참조하는 상황이 생기는데, 이것이 High로 평가된 이유입니다.

JSC::eval()은 cache-miss 컴파일 경로 전체를 DeferGC로 감쌉니다. key는 기존과 동일하게 programStr.data.impl()로 구성되며, deferral이 key 구성부터 cache 삽입까지의 모든 GC-safepoint 구간에서 rope fiber가 해제되지 않도록 보호합니다.

Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp

+ DeferGC deferGC(vm);
CacheLookupKey cacheKey { programStr.data.impl(), callerBytecodeIndex };
...
// profiler hook, LiteralParser, collectClosureVariablesUnderTDZ, parser, 삽입

Raw StringImpl* lifetime escape: eval cache key가 참조하는 rope fiber의 유일한 root는 스택 위의 rope이며, GC-safepoint parser 할당이 이 구간에 걸쳐 있습니다.

DeferGC는 key 구성부터 cache 삽입까지의 cache-miss 분기 전체를 감쌉니다. StringImpl에 대한 reference는 직접 획득하지 않으며, deferral이 참조되지 않은 fiber가 남아있는 동안 sweep을 억제하는 방식입니다.

DirectEvalCodeCache(StringImpl*, BytecodeIndex) 쌍을 key로 컴파일된 DirectEvalExecutable을 캐싱합니다. 같은 호출 지점에서 eval(sameString)이 반복될 때 비용을 줄이기 위한 구조입니다.

JSString::value()StringViewWithUnderlyingString을 반환하는데, rope인 경우 이 과정에서 rope가 resolve됩니다. 반환된 view는 독립적인 RefPtr<StringImpl>을 갖지 않고, rope의 fiber JSString 중 하나의 내용을 직접 가리키는 형태로 존재합니다.

패치 이전에는 cacheKey.implprogramStr.data.impl()에서 가져온 raw StringImpl*였습니다. resolve 이후에는 rope의 내부 layout이 변경될 수 있는데, rope flattening이나 substring sharing이 발생하면 key에 StringImpl을 보유하는 fiber가 스택 위의 rope에서 도달 불가능해집니다.

cache-miss 분기에서는 이후 여러 단계가 순서대로 이어집니다. source-profiler hook, sloppy-JSON eval fast path를 위한 LiteralParser 할당, collectClosureVariablesUnderTDZ, 전체 parser 할당, 그리고 cache 삽입이 차례로 실행되며, 이 모든 단계가 잠재적인 GC safepoint에 해당합니다.

🔒

The ownership story behind this cache key — and why a string that looks rooted on the stack can be freed mid-eval — is dissected in depth, along with the escalation potential beyond a crash.

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

🔒

Four reusable audit patterns identified covering raw-pointer cache keys, `JSString::value()` lifetime assumptions, rope mutation as a GC-reachability change, and `DeferGC` as a sentinel for unsafe pointers — each with concrete grep starting points.

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