CVE-2024-27808
Overview
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.
Attack Path
- Build polymorphic inline caches Run JS that creates Getter/Setter or Proxy-object inline caches, so AccessCases hold CallLinkInfo tied to a JITStubRoutine.
- 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.
- Trigger GC weak visiting Cause garbage collection so forEachDependentCell/doesCalls/visitWeak walk the AccessCase’s now-dangling CallLinkInfo*.
- 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifSource/JavaScriptCore/bytecode/AccessCase.cpp |
modified | |
forEachDependentCellSource/JavaScriptCore/bytecode/AccessCase.cpp |
modified | |
ifSource/JavaScriptCore/bytecode/InlineCacheCompiler.cpp |
modified | |
forSource/JavaScriptCore/bytecode/InlineCacheCompiler.cpp |
modified |
Files Changed
JSTests/stress/decouple-calllinkinfo-from-access-case.jsSource/JavaScriptCore/bytecode/AccessCase.cppSource/JavaScriptCore/bytecode/AccessCase.hSource/JavaScriptCore/bytecode/GetByStatus.cppSource/JavaScriptCore/bytecode/GetterSetterAccessCase.cppSource/JavaScriptCore/bytecode/GetterSetterAccessCase.hSource/JavaScriptCore/bytecode/InlineCacheCompiler.cppSource/JavaScriptCore/bytecode/InlineCacheCompiler.hSource/JavaScriptCore/bytecode/ProxyObjectAccessCase.cppSource/JavaScriptCore/bytecode/ProxyObjectAccessCase.hSource/JavaScriptCore/bytecode/PutByStatus.cppSource/JavaScriptCore/bytecode/StructureStubInfo.cppSource/JavaScriptCore/bytecode/StructureStubInfo.hSource/JavaScriptCore/jit/GCAwareJITStubRoutine.cppSource/JavaScriptCore/jit/GCAwareJITStubRoutine.hSource/JavaScriptCore/jit/JITStubRoutine.cppSource/JavaScriptCore/jit/JITStubRoutine.h
Audit Directions
- Raw compiled-code pointers in feedback dataAudit AccessCase and other IC-feedback structures for raw pointers into compiled code (CallLinkInfo, stub routines) whose lifetime they do not own.
- Destruction-ordering assumptionsGrep JSC for lifetime assumptions that CodeBlock is destroyed before its JITStubRoutines; delayed destruction can violate them.
Patch
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)
Regression Test / PoC
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 { }