← All issues

JSC `ArrayIsArray` DFG Intrinsic

b7c9035

Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp

+void SpeculativeJIT::compileArrayIsArray(Node* node)
+{
+ JSValueOperand value(this, node->child1());
+ GPRTemporary result(this);
+ JSValueRegs valueRegs = value.jsValueRegs();
+ GPRReg resultGPR = result.gpr();
+
+ Jump isNotCell = branchIfNotCell(valueRegs);
+ static_assert(DerivedArrayType == ArrayType + 1, ...);
+ load8(Address(valueRegs.payloadGPR(), JSCell::typeInfoTypeOffset()), resultGPR);
+ sub32(TrustedImm32(ArrayType), resultGPR);
+ Jump isArrayOrDerived = branch32(BelowOrEqual, resultGPR, TrustedImm32(DerivedArrayType - ArrayType));
+ Jump isProxy = branch32(Equal, resultGPR, TrustedImm32(ProxyObjectType - ArrayType));
+ isNotCell.link(this);
+ move(TrustedImm32(0), resultGPR);
+ Jump done = jump();
+ isArrayOrDerived.link(this);
+ move(TrustedImm32(1), resultGPR);
+ addSlowPathGenerator(slowPathCall(isProxy, this, operationArrayIsArray, resultGPR,
+ LinkableConstant::globalObject(*this, node), valueRegs));
+ done.link(this);
+ unblessedBooleanResult(resultGPR, node);
+}

JSC의 inline cache 시스템은 관찰된 object shape을 기반으로 fast property access stub을 speculative하게 컴파일합니다. 기존 Array.isArray 구현은 IsCellWithType(CellUse) 기반의 JS builtin이었으며, DFG speculative compiler가 입력을 항상 GC-managed cell로 가정하도록 만드는 방식이었습니다. TypeCheckHoistingPhase가 이 cell check를 함수 진입 시점으로 끌어올리면서, undefined·null·boolean 등 non-cell 값이 단 한 번만 전달되어도 BadType OSR exit가 발생합니다. 컴파일된 코드 단위 전체가 jettison되는 셈입니다. typed 입력과 untyped 입력을 혼용하는 hot code는 이 때문에 조용히 deoptimization되고 있었습니다.

Before:
  call Array.isArray(v)
    └─► JS builtin → IsCellWithType(CellUse)
          └─► TypeCheckHoistingPhase hoists cell check to entry
                └─► v = undefined / null / boolean → BadType OSR exit → DFG jettison

After:
  call Array.isArray(v)
    └─► ArrayIsArray DFG node (UntypedUse)
          ├── non-cell (int/double/bool/null/undefined) ──► false (inline)
          ├── ArrayType / DerivedArrayType             ──► true  (inline)
          ├── ProxyObjectType                          ──► isArraySlow() (C++ slow path)
          └── other cell                               ──► false (inline)

이 commit은 builtin을 C++ host function으로 교체했습니다. 새로운 ArrayIsArray DFG/FTL IR 노드는 UntypedUse를 사용하며, ES spec의 모든 케이스를 inline으로 처리합니다. non-cell은 false, ArrayTypeDerivedArrayType은 true, ProxyObjectType은 slow path, 그 외 cell은 false로 각각 분기됩니다. 입력 타입이 정적으로 알려진 경우에는 abstract interpreter가 constant-fold를 수행할 수 있습니다.

mixed type으로 Array.isArray를 호출하는 일반적인 경우, 이 변경으로 deoptimization이 완전히 제거됩니다. array임이 이미 알려진 입력에 대한 constant-folding도 그대로 유지되는 점이 중요합니다. 범위 검사는 ArrayType을 뺀 뒤 DerivedArrayType - ArrayType을 기준으로 BelowOrEqual 비교를 수행하는 방식으로, 간결하게 구현되어 있습니다. 다만 해당 JSType 상수들이 연속적으로 배치되어 있다는 static_assert에 의존한다는 점에 주의가 필요합니다.

🔒

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

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