← All issues

[5] DFG ValueRepReduction type confusion via MultiGetByOffset

Severity: High | Component: JSC DFG JIT — ValueRepReductionPhase | 460528e

JIT 컴파일된 코드 내에서 비수치(non-numeric) JSValue의 bit pattern이 unboxed double로 재해석되는 type confusion이 observable effect로 드러나기 때문에 High로 평가되었습니다. 조작된 polymorphic property access를 통해 해당 경로에 도달할 수 있으며, MultiGetByOffset constant case에 대한 per-case validation이 누락되어 있음이 diff에서 직접 확인됩니다 (confidence 0.92).

double에 대한 ValueRepReduction은 MultiGetByOffset case의 constant를 즉시 double로 변환해야 합니다. 이 patch는 순수하게 double로 변환할 수 없는 MultiGetByOffset constant를 후보에서 제외합니다.

Source/JavaScriptCore/dfg/DFGValueRepReductionPhase.cpp

case MultiGetByOffset:
case GetByOffset: {
- if (node->child1().useKind() == RealNumberUse || node->child1().useKind() == NumberUse) {
- if (node->child1()->origin.exitOK)
- candidates.add(node->child1().node());
+ if (node->child1().useKind() != RealNumberUse && node->child1().useKind() != NumberUse)
break;
- }
+ if (!node->child1()->origin.exitOK)
+ break;
+ if (node->child1()->op() == MultiGetByOffset) {
+ bool isCandidate = true;
+ MultiGetByOffsetData& data = node->child1()->multiGetByOffsetData();
+ for (unsigned i = 0; i < data.cases.size(); ++i) {
+ GetByOffsetMethod& method = data.cases[i].method();
+ if (method.kind() == GetByOffsetMethod::Constant && !method.constant()->value().toNumberFromPrimitive()) {
+ isCandidate = false;
+ break;
+ }
+ }
+ if (!isCandidate)
+ break;
+ }
+ candidates.add(node->child1().node());
break;
}

MultiGetByOffset node를 double 변환 후보로 등록하기 전에 validation loop가 추가되었습니다. 새로 추가된 코드는 MultiGetByOffsetData의 모든 case를 순회하면서, Constant 종류의 case 값이 toNumberFromPrimitive()를 통해 순수하게 double로 변환될 수 있는지 확인합니다. 하나라도 변환에 실패하면 해당 node는 후보에서 제외됩니다. 수정 이전에는 MultiGetByOffsetGetByOffset과 동일하게 취급되어, per-case constant validation 없이 후보로 등록되었습니다.

Polymorphic property load를 unboxed double representation으로 확정하기 전, per-case constant 변환 가능성 검증이 누락된 패턴.

DFG ValueRepReductionPhase는 SSA 형태의 DFG IR 위에서 동작하며, output을 boxed JSValue에서 unboxed machine representation(double, int52, int32)으로 변환할 수 있는 node를 식별합니다. 이를 통해 hot path에서 boxing/unboxing overhead를 줄일 수 있습니다.

MultiGetByOffset은 polymorphic inline cache를 나타내는 DFG node로, 여러 object 구조에 적중하도록 profiling된 property access를 표현합니다. 각 case는 property를 로드하는 방식을 지정하는데, 고정 offset의 object slot에서 읽어오거나 inline constant(GetByOffsetMethod::Constant)로 반환하는 두 가지 형태가 있습니다. toNumberFromPrimitive()는 JSValue를 순수하게 numeric 값으로 변환하는 시도를 수행합니다. 숫자나 numeric string에는 유효한 double을 반환하지만, Symbol, BigInt, 변환이 불가능한 object에 대해서는 실패합니다.

  JS property access         DFG IR                    ValueRepReduction
  ─────────────────     ──────────────────        ──────────────────────────
  obj.x                 MultiGetByOffset           Is every case safely
  (profiled: 3           ├─ case S1: offset 16     convertible to double?
   structures)           ├─ case S2: Constant(42)    42 → 42.0   ✓
                         └─ case S3: Constant(sym)   Symbol → ✗  REJECT

수정 이전에는 node를 boxed JSValue에서 unboxed double로 변환할 수 있는지 결정할 때, convertValueRepsToUnboxed<DoubleRepUse>()MultiGetByOffsetGetByOffset과 동일하게 취급했습니다. MultiGetByOffset은 여러 structure case에 걸쳐 dispatch하며, 그 중 일부는 inline constant를 반환할 수 있습니다. 해당 phase가 이러한 node를 double 후보로 표시하면, JIT는 이후 node의 output을 unboxed double로 즉시 변환합니다. constant case 중 하나가 순수하게 double로 변환할 수 없는 값(Symbol, BigInt 등)을 담고 있다면, 이 즉각적인 변환은 잘못된 bit-pattern 재해석을 유발합니다. 즉, raw JSValue encoding이 double로 처리되는 상황이 발생합니다.

이 버그의 근본 원인은 의미론적으로 근본적인 차이가 있음에도 불구하고 MultiGetByOffsetGetByOffset이 공유 case 구문으로 묶여 있었다는 점에 있습니다. MultiGetByOffsetGetByOffset에는 없는 inline constant case를 포함합니다.

🔒

Explores how polymorphic property access interacts with value representation narrowing and what type confusion primitive this could yield

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

🔒

Multiple audit patterns identified across DFG optimization phases, with concrete search targets for variant discovery

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