CVE-2025-43457
Overview
Background
- Butterfly
- the out-of-line storage block that holds a JavaScript array’s indexed elements and its
Butterfly_publicLength. - Object allocation sinking
- a DFG optimization that delays or elides an allocation and reconstructs it at an OSR exit via
Materialize*andPhantom*nodes. - Abstract Interpreter (AI)
- the DFG’s abstract-value engine that proves per-node types and structures, feeding constant folding and speculation removal.
- Indexing type
- the tag (
ALL_INT32_INDEXING_TYPES,ALL_DOUBLE_INDEXING_TYPES,ALL_CONTIGUOUS_INDEXING_TYPES) that dictates how a butterfly’s raw element cells are physically interpreted.
Root Cause Analysis
The old NewArrayWithConstantSize node conflated two distinct heap operations — allocating a butterfly and allocating the wrapping JS array object — into a single NodeResultJS node whose only size record was a frozen constant (m_opInfo2), and whose clobberize def published ArrayLengthLoc/Butterfly_publicLength as that frozen constant. When object allocation sinking sank such an array and rematerialized it at an OSR exit through MaterializeNewArrayWithConstantSize/PhantomNewArrayWithConstantSize, the AI never verified that the values used to repopulate the materialized butterfly actually matched the array’s indexingType, so an ALL_DOUBLE_INDEXING_TYPES butterfly could be filled with a non-number JSValue (or a boxed value written where raw doubles are expected), producing a type-confused butterfly whose elements are reinterpreted as raw doubles or pointers.
The fix splits the operation into NewButterflyWithSize (NodeResultStorage) and NewArrayWithButterfly (NodeResultJS) so the butterfly is a first-class SSA value the sinking phase can track independently, and it adds a RELEASE_ASSERT(isSubtypeSpeculation(...)) in the AI’s MaterializeNewButterflyWithSize case that requires every materialization child to be a subtype of the validTypes implied by the indexingType. It also stops publishing a frozen-constant length in clobberize, instead defing Butterfly_publicLength from node->child1().node(), keeping the length definition faithful to the real size input. Together these changes make the storage allocation, its length, and its element-type contract explicit and checkable, closing the window in which a sunk/materialized array could be rebuilt with mismatched element representations.
indexingType, allowing element-representation confusion; the fix promotes the butterfly to its own NodeResultStorage node and enforces isSubtypeSpeculation on every materialization operand.Attack Path
- Create a small constant-size typed array
Craft JS that triggers
convertToNewArrayWithConstantSize(nowconvertToNewArrayWithButterfly) for a double- or contiguous-indexed array belowMIN_ARRAY_STORAGE_CONSTRUCTION_LENGTH. - Force allocation sinking
Structure control flow so the DFG sinks the array allocation and represents it as a
Phantom/Materializepair rather than a live allocation. - Poison the materialization operands
Arrange the sunk state so the values slotted into the butterfly do not match the array’s
indexingType, exploiting the absent element-type check. - Trigger OSR exit Cause a speculation failure so the array is rematerialized from the mismatched operands, yielding a type-confused butterfly.
- Corrupt memory Read or write the confused elements to reinterpret doubles as pointers (or vice versa), gaining out-of-bounds and use-after-free primitives.
Impact Assessment
Files Changed
JSTests/stress/array-allocation-elimination-closure-capture.jsJSTests/stress/array-allocation-elimination-conditional-usage.jsJSTests/stress/array-allocation-elimination-cross-function.jsSource/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.hSource/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cppSource/JavaScriptCore/dfg/DFGClobberize.hSource/JavaScriptCore/dfg/DFGCloneHelper.hSource/JavaScriptCore/dfg/DFGConstantFoldingPhase.cppSource/JavaScriptCore/dfg/DFGDoesGC.cppSource/JavaScriptCore/dfg/DFGFixupPhase.cppSource/JavaScriptCore/dfg/DFGLoopUnrollingPhase.cppSource/JavaScriptCore/dfg/DFGMayExit.cppSource/JavaScriptCore/dfg/DFGNode.cppSource/JavaScriptCore/dfg/DFGNode.hSource/JavaScriptCore/dfg/DFGNodeType.hSource/JavaScriptCore/dfg/DFGObjectAllocationSinkingPhase.cppSource/JavaScriptCore/dfg/DFGObjectMaterializationData.hSource/JavaScriptCore/dfg/DFGOperations.cppSource/JavaScriptCore/dfg/DFGOperations.hSource/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cppSource/JavaScriptCore/dfg/DFGPromotedHeapLocation.hSource/JavaScriptCore/dfg/DFGSafeToExecute.hSource/JavaScriptCore/dfg/DFGSpeculativeJIT.cppSource/JavaScriptCore/dfg/DFGSpeculativeJIT.hSource/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cppSource/JavaScriptCore/dfg/DFGSpeculativeJIT64.cppSource/JavaScriptCore/dfg/DFGStoreBarrierInsertionPhase.cppSource/JavaScriptCore/dfg/DFGUseKind.hSource/JavaScriptCore/dfg/DFGValidate.cppSource/JavaScriptCore/ftl/FTLCapabilities.cppSource/JavaScriptCore/ftl/FTLExitTimeObjectMaterialization.cppSource/JavaScriptCore/ftl/FTLLowerDFGToB3.cppSource/JavaScriptCore/ftl/FTLOperations.cppSource/JavaScriptCore/runtime/IndexingType.cppSource/JavaScriptCore/runtime/IndexingType.h
Audit Directions
- Conflated allocation nodesAudit any DFG node that bundles storage and object allocation in one op; ensure the butterfly is a distinct SSA value so sinking and materialization cannot desynchronize.
- Unvalidated materialization operandsFor every
Materialize*case in the AI, confirm operandSpeculatedTypes are asserted against the target’sindexingType/structure before the butterfly or object is reconstructed. - Frozen-constant heap `def`sReview
clobberizedefs that publishButterfly_publicLengthor element locations as frozen constants; verify the definition tracks the real dynamic input rather than a compile-time value that a later phase could rely on unsafely.
Patch
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
index 751179787d0c..19f700a8773f 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
@@ -3725,10 +3725,30 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
break;
}
- case NewArrayWithConstantSize:
- case MaterializeNewArrayWithConstantSize:
+ case MaterializeNewButterflyWithSize: {
+ SpeculatedType validTypes = [&]() {
+ switch (node->indexingType()) {
+ case ALL_INT32_INDEXING_TYPES: return SpecInt32Only;
+ case ALL_DOUBLE_INDEXING_TYPES: return SpecBytecodeNumber;
+ case ALL_CONTIGUOUS_INDEXING_TYPES: return SpecBytecodeTop;
+ default: break;
+ }
+ RELEASE_ASSERT_NOT_REACHED();
+ }();
+ for (unsigned i = 1; i < node->numChildren(); ++i)
+ RELEASE_ASSERT(isSubtypeSpeculation(forNode(m_graph.varArgChild(node, i)).m_type, validTypes));
+
+ [[fallthrough]];
+ }
+ case NewButterflyWithSize:
+ // We don't represent storage/butterflies in AI.
+ clearForNode(node);
+ break;
+
+ case NewArrayWithButterfly: {
setForNode(node, m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(node->indexingMode()));
break;
+ }
case NewTypedArray: {
switch (node->child1().useKind()) {
@@ -3990,7 +4010,8 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
}
case PhantomNewObject:
- case PhantomNewArrayWithConstantSize:
+ case PhantomNewButterflyWithSize:
+ case PhantomNewArrayWithButterfly:
case PhantomNewFunction:
case PhantomNewGeneratorFunction:
case PhantomNewAsyncGeneratorFunction:
diff --git a/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
index 87bca15efbd3..21b451f8d2cc 100644
--- a/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
@@ -525,7 +525,6 @@ private:
case NewTypedArray:
case NewTypedArrayBuffer:
case NewArrayWithSize:
- case NewArrayWithConstantSize:
case NewArrayWithSpecies:
case NewArrayWithSizeAndStructure: {
// Negative zero is not observable. NaN versus undefined are only observable
diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.h
index 61ffe85f1df6..a6f985602b29 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberize.h
+++ b/Source/JavaScriptCore/dfg/DFGClobberize.h
@@ -1924,12 +1924,20 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
write(HeapObjectCount);
return;
- case NewArrayWithConstantSize:
- case PhantomNewArrayWithConstantSize:
- case MaterializeNewArrayWithConstantSize:
+ case NewButterflyWithSize:
+ case PhantomNewArrayWithButterfly:
+ case PhantomNewButterflyWithSize:
+ case MaterializeNewButterflyWithSize: {
read(HeapObjectCount);
write(HeapObjectCount);
- def(HeapLocation(ArrayLengthLoc, Butterfly_publicLength, node), LazyNode(graph.freeze(jsNumber(node->newArraySize()))));
+ // FIXME: In this phase we say the Array is where the length of the array is def'd but this differs from ObjectAllocationSinking.
+ return;
+ }
+
+ case NewArrayWithButterfly:
+ read(HeapObjectCount);
+ write(HeapObjectCount);
+ def(HeapLocation(ArrayLengthLoc, Butterfly_publicLength, node), LazyNode(node->child1().node()));
return;
case NewTypedArray:
diff --git a/Source/JavaScriptCore/dfg/DFGCloneHelper.h b/Source/JavaScriptCore/dfg/DFGCloneHelper.h
index f2150b6cfc3f..2a55975a54f9 100644
--- a/Source/JavaScriptCore/dfg/DFGCloneHelper.h
+++ b/Source/JavaScriptCore/dfg/DFGCloneHelper.h
@@ -288,7 +288,7 @@ BasicBlock* CloneHelper::cloneBlock(BasicBlock* const block, const CustomizeSucc
CLONE_STATUS(MultiPutByOffset, Common) \
CLONE_STATUS(NewArray, Common) \
CLONE_STATUS(NewArrayBuffer, Common) \
- CLONE_STATUS(NewArrayWithConstantSize, Common) \
+ CLONE_STATUS(NewArrayWithButterfly, Common) \
CLONE_STATUS(NewArrayWithSize, Common) \
CLONE_STATUS(NewArrayWithSpread, Common) \
CLONE_STATUS(NewFunction, Common) \
diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
index 28a3847a266b..97fb681b53a4 100644
--- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
@@ -1106,10 +1106,14 @@ private:
if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) {
if (node->child1().useKind() == Int32Use && node->child1()->isInt32Constant()) {
int32_t length = node->child1()->asInt32();
- if (length >= 0
+ if (0 <= length
&& length < MIN_ARRAY_STORAGE_CONSTRUCTION_LENGTH
- && isNewArrayWithConstantSizeIndexingType(node->indexingType())) {
- node->convertToNewArrayWithConstantSize(m_graph, length);
+ && !hasAnyArrayStorage(node->indexingType())) {
+ m_interpreter.execute(indexInBlock); // Push CFA over this node after we get the state before.
+ alreadyHandled = true; // Don't allow the default constant folder to do things to this.
+
+ Node* butterfly = m_insertionSet.insertNode(indexInBlock, SpecNone, NewButterflyWithSize, node->origin, OpInfo(node->indexingType()), node->child1());
+ node->convertToNewArrayWithButterfly(m_graph, butterfly);
changed = true;
}
}
@@ -1676,7 +1680,8 @@ private:
}
case PhantomNewObject:
- case PhantomNewArrayWithConstantSize:
+ case PhantomNewArrayWithButterfly:
+ case PhantomNewButterflyWithSize:
case PhantomNewFunction:
case PhantomNewGeneratorFunction:
case PhantomNewAsyncGeneratorFunction:
diff --git a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
index 8c617adc8a2f..47ec5e1b340d 100644
--- a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
@@ -225,7 +225,8 @@ bool doesGC(Graph& graph, Node* node)
case CheckBadValue:
case BottomValue:
case PhantomNewObject:
- case PhantomNewArrayWithConstantSize:
+ case PhantomNewArrayWithButterfly:
+ case PhantomNewButterflyWithSize:
case PhantomNewFunction:
case PhantomNewGeneratorFunction:
case PhantomNewAsyncFunction:
@@ -413,8 +414,9 @@ bool doesGC(Graph& graph, Node* node)
case NewArrayWithSpread:
case NewInternalFieldObject:
case Spread:
+ case NewButterflyWithSize:
case NewArrayWithSize:
- case NewArrayWithConstantSize:
+ case NewArrayWithButterfly:
case NewArrayWithSpecies:
case NewArrayWithSizeAndStructure:
case NewArrayBuffer:
@@ -440,7 +442,7 @@ bool doesGC(Graph& graph, Node* node)
case EnumeratorNextUpdatePropertyName:
case EnumeratorNextUpdateIndexAndMode:
case MaterializeNewObject:
- case MaterializeNewArrayWithConstantSize:
+ case MaterializeNewButterflyWithSize:
case MaterializeNewInternalFieldObject:
case MaterializeCreateActivation:
case SetFunctionName:
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index b471fd112937..a672363d2dc0 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -2064,11 +2064,6 @@ private:
break;
}
- case NewArrayWithConstantSize: {
- watchHavingABadTime(node);
- break;
- }
-
case NewArrayWithSpecies: {
ArrayMode arrayMode = node->arrayMode().refine(m_graph, node, node->child2()->prediction(), ArrayMode::unusedIndexSpeculatedType);
node->setArrayMode(arrayMode);
@@ -2664,8 +2659,11 @@ private:
case Int52Constant:
case Identity: // This should have been cleaned up.
case BooleanToNumber:
+ case NewArrayWithButterfly:
+ case NewButterflyWithSize:
case PhantomNewObject:
- case PhantomNewArrayWithConstantSize:
+ case PhantomNewButterflyWithSize:
+ case PhantomNewArrayWithButterfly:
case PhantomNewFunction:
case PhantomNewGeneratorFunction:
case PhantomNewAsyncGeneratorFunction:
@@ -2687,7 +2685,7 @@ private:
case CheckStructureOrEmpty:
case CheckArrayOrEmpty:
case MaterializeNewObject:
- case MaterializeNewArrayWithConstantSize:
+ case MaterializeNewButterflyWithSize:
case MaterializeCreateActivation:
case MaterializeNewInternalFieldObject:
case PutStack:
diff --git a/Source/JavaScriptCore/dfg/DFGLoopUnrollingPhase.cpp b/Source/JavaScriptCore/dfg/DFGLoopUnrollingPhase.cpp
index c34e4908c0d2..324fb26db1c8 100644
--- a/Source/JavaScriptCore/dfg/DFGLoopUnrollingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGLoopUnrollingPhase.cpp
@@ -863,7 +863,8 @@ bool LoopUnrollingPhase::LoopData::isMaterialNode(Graph& graph, Node* node)
case ZombieHint:
case ExitOK:
case PhantomNewObject:
- case PhantomNewArrayWithConstantSize:
+ case PhantomNewArrayWithButterfly:
+ case PhantomNewButterflyWithSize:
case PhantomNewFunction:
case PhantomNewGeneratorFunction:
case PhantomNewAsyncGeneratorFunction:
diff --git a/Source/JavaScriptCore/dfg/DFGMayExit.cpp b/Source/JavaScriptCore/dfg/DFGMayExit.cpp
index 47f59267376f..f1a39bc8e1fd 100644
--- a/Source/JavaScriptCore/dfg/DFGMayExit.cpp
+++ b/Source/JavaScriptCore/dfg/DFGMayExit.cpp
@@ -71,8 +71,9 @@ ExitMode mayExitImpl(Graph& graph, Node* node, StateType& state)
case BottomValue:
case PutHint:
case PhantomNewObject:
- case PhantomNewArrayWithConstantSize:
+ case PhantomNewArrayWithButterfly:
case PhantomNewInternalFieldObject:
+ case PhantomNewButterflyWithSize:
case PutStack:
case KillStack:
case GetStack:
@@ -172,7 +173,7 @@ ExitMode mayExitImpl(Graph& graph, Node* node, StateType& state)
case CreateActivation:
case MaterializeCreateActivation:
case MaterializeNewObject:
- case MaterializeNewArrayWithConstantSize:
+ case MaterializeNewButterflyWithSize:
case MaterializeNewInternalFieldObject:
case NewFunction:
case NewGeneratorFunction:
@@ -184,7 +185,8 @@ ExitMode mayExitImpl(Graph& graph, Node* node, StateType& state)
case NewRegExp:
case NewMap:
case NewSet:
- case NewArrayWithConstantSize:
+ case NewArrayWithButterfly:
+ case NewButterflyWithSize:
case ToNumber:
case ToNumeric:
case ToObject:
diff --git a/Source/JavaScriptCore/dfg/DFGNode.cpp b/Source/JavaScriptCore/dfg/DFGNode.cpp
index da08861b5e5e..1cea7ccd0aea 100644
--- a/Source/JavaScriptCore/dfg/DFGNode.cpp
+++ b/Source/JavaScriptCore/dfg/DFGNode.cpp
@@ -268,12 +268,14 @@ void Node::convertToNewArrayWithSize()
m_opInfo = indexingType;
}
-void Node::convertToNewArrayWithConstantSize(Graph&, uint32_t size)
+void Node::convertToNewArrayWithButterfly(Graph&, Node* butterfly)
{
ASSERT(op() == NewArrayWithSize);
- ASSERT(size < MIN_ARRAY_STORAGE_CONSTRUCTION_LENGTH);
- setOpAndDefaultFlags(NewArrayWithConstantSize);
- m_opInfo2 = size;
+ IndexingType indexingType = this->indexingType();
+ setOpAndDefaultFlags(NewArrayWithButterfly);
+ ASSERT(child1()->asInt32() < MIN_ARRAY_STORAGE_CONSTRUCTION_LENGTH);
+ children.child2() = Edge(butterfly);
+ ASSERT_UNUSED(indexingType, indexingType == this->indexingType());
}
void Node::convertToNewArrayWithSizeAndStructure(Graph& graph, RegisteredStructure structure)
diff --git a/Source/JavaScriptCore/dfg/DFGNode.h b/Source/JavaScriptCore/dfg/DFGNode.h
index abf85a660a2c..51c69a82eabc 100644
--- a/Source/JavaScriptCore/dfg/DFGNode.h
+++ b/Source/JavaScriptCore/dfg/DFGNode.h
@@ -727,17 +727,22 @@ public:
children.setChild1(index);
}
- void convertToPhantomNewArrayWithConstantSize()
+ void convertToPhantomNewArrayWithButterfly()
{
- ASSERT(m_op == NewArrayWithConstantSize);
- m_op = PhantomNewArrayWithConstantSize;
- m_flags &= ~NodeHasVarArgs;
- m_flags |= NodeMustGenerate;
- // No need to clear the infos, as the indexing type and array
- // size are still required for materialization when an OSR exit occurs.
- children = AdjacencyList();
+ ASSERT(m_op == NewArrayWithButterfly);
+ setOpAndDefaultFlags(PhantomNewArrayWithButterfly);
+ // OpInfo/children shouldn't change and are needed for OSR exit.
+ ASSERT(child1().useKind() == Int32Use);
}
+ void convertToPhantomNewButterflyWithSize()
+ {
+ ASSERT(m_op == NewButterflyWithSize);
+ setOpAndDefaultFlags(PhantomNewButterflyWithSize);
+ // OpInfo/children shouldn't change and are needed for OSR exit.
+ ASSERT(child1().useKind() == Int32Use);
+ }
+
void convertToPhantomNewObject()
{
ASSERT(m_op == NewObject);
@@ -916,7 +921,7 @@ public:
void convertToNewArrayBuffer(FrozenValue* immutableButterfly);
void convertToNewArrayWithSize();
- void convertToNewArrayWithConstantSize(Graph&, uint32_t);
+ void convertToNewArrayWithButterfly(Graph&, Node* butterfly);
void convertToNewArrayWithSizeAndStructure(Graph&, RegisteredStructure);
void convertToNewBoundFunction(FrozenValue*);
@@ -1454,32 +1459,16 @@ public:
return newArrayBufferData().vectorLengthHint;
}
- unsigned hasNewArraySize()
- {
- switch (op()) {
- case NewArrayWithConstantSize:
- case PhantomNewArrayWithConstantSize:
- case MaterializeNewArrayWithConstantSize:
- return true;
- default:
- return false;
- }
- }
-
- unsigned newArraySize()
- {
- ASSERT(hasNewArraySize());
- return op() == MaterializeNewArrayWithConstantSize ? objectMaterializationData().m_newArraySize : m_opInfo2.as<unsigned>();
- }
-
bool hasIndexingType()
{
switch (op()) {
case NewArray:
case NewArrayWithSize:
- case NewArrayWithConstantSize:
- case PhantomNewArrayWithConstantSize:
- case MaterializeNewArrayWithConstantSize:
+ case NewArrayWithButterfly:
+ case NewButterflyWithSize:
+ case PhantomNewArrayWithButterfly:
+ case PhantomNewButterflyWithSize:
+ case MaterializeNewButterflyWithSize:
case NewArrayBuffer:
case PhantomNewArrayBuffer:
case NewArrayWithSpecies:
@@ -2514,7 +2503,7 @@ public:
{
switch (op()) {
case MaterializeNewObject:
- case MaterializeNewArrayWithConstantSize:
+ case MaterializeNewButterflyWithSize:
case MaterializeNewInternalFieldObject:
case MaterializeCreateActivation:
return true;
@@ -2602,7 +2591,8 @@ public:
bool isPhantomAllocation()
{
switch (op()) {
- case PhantomNewArrayWithConstantSize:
+ case PhantomNewArrayWithButterfly:
+ case PhantomNewButterflyWithSize:
case PhantomNewObject:
case PhantomDirectArguments:
case PhantomCreateRest:
diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h
index 6bbcca98750c..8c7751814389 100644
--- a/Source/JavaScriptCore/dfg/DFGNodeType.h
+++ b/Source/JavaScriptCore/dfg/DFGNodeType.h
@@ -403,13 +403,15 @@ namespace JSC { namespace DFG {
macro(NewObject, NodeResultJS) \
macro(NewGenerator, NodeResultJS) \
macro(NewAsyncGenerator, NodeResultJS) \
+ /* FIXME: A lot of these could likely be consolidated but there's some subtle differences between them, particularly when having a bad time. */ \
macro(NewArray, NodeResultJS | NodeHasVarArgs) \
macro(NewArrayWithSpread, NodeResultJS | NodeHasVarArgs) \
macro(NewArrayWithSpecies, NodeResultJS | NodeMustGenerate) \
macro(NewArrayWithSize, NodeResultJS | NodeMustGenerate) \
- macro(NewArrayWithConstantSize, NodeResultJS | NodeMustGenerate) \
macro(NewArrayWithSizeAndStructure, NodeResultJS | NodeMustGenerate) \
macro(NewArrayBuffer, NodeResultJS) \
+ macro(NewArrayWithButterfly, NodeResultJS) \
+ macro(NewButterflyWithSize, NodeResultStorage) \
macro(NewInternalFieldObject, NodeResultJS) \
macro(NewTypedArray, NodeResultJS | NodeMustGenerate) \
macro(NewTypedArrayBuffer, NodeResultJS | NodeMustGenerate) \
@@ -425,8 +427,9 @@ namespace JSC { namespace DFG {
\
... (file diff truncated)
Regression Test / PoC
diff --git a/JSTests/stress/array-allocation-elimination-closure-capture.js b/JSTests/stress/array-allocation-elimination-closure-capture.js
new file mode 100644
index 000000000000..c8235d83e04a
--- /dev/null
+++ b/JSTests/stress/array-allocation-elimination-closure-capture.js
@@ -0,0 +1,207 @@
+function assert(condition, message) {
+ if (!condition)
+ throw new Error(message || "Assertion failed");
+}
+
+// Test array allocation with closure capture scenarios
+
+// Function that creates a closure value
+function createClosureValue(multiplier) {
+ return function(x) {
+ return x * multiplier;
+ };
+}
+
+// Function that allocates an array with fixed size
+function allocateArrayForClosure(baseValue) {
+ const arr = new Array(5);
+ for (let i = 0; i < 5; i++) {
+ arr[i] = baseValue + i;
+ }
+ return arr;
+}
+
+// Function that uses array and captures closure
+function processWithClosure(closureFn, arr) {
+ let result = 0;
+ for (let i = 0; i < arr.length; i++) {
+ result += closureFn(arr[i]);
+ }
+ return result;
+}
+
+// Complex scenario: array allocation, closure capture, and usage all in different functions
+function complexClosureTest(multiplier, baseValue) {
+ const closureFn = createClosureValue(multiplier);
+ const arr = allocateArrayForClosure(baseValue);
+ return processWithClosure(closureFn, arr);
+}
+
+// Scenario where closure modifies array behavior
+function closureModifiesArray(capturedValue) {
+ return function(baseValue) {
+ const arr = new Array(4);
+ for (let i = 0; i < 4; i++) {
+ arr[i] = (baseValue + i) + capturedValue;
+ }
+
+ // Array usage within the closure
+ let sum = 0;
+ for (let i = 0; i < arr.length; i++) {
+ sum += arr[i] * 2;
+ }
+ return sum;
+ };
+}
+
+// Nested closure with array
+function nestedClosureWithArray(outerValue) {
+ return function(middleValue) {
+ return function(baseValue) {
+ const arr = new Array(3);
+ for (let i = 0; i < 3; i++) {
+ arr[i] = baseValue + i + outerValue + middleValue;
+ }
+
+ // Process array using captured values
+ let product = 1;
+ for (let i = 0; i < arr.length; i++) {
+ product *= (arr[i] % 10) || 1; // avoid zero
+ }
+ return product;
+ };
+ };
+}
+
+let escapedArray = null;
+// Array that sometimes escapes through closure
+function conditionalEscapeViaClosure(shouldEscape) {
+
+ return function(baseValue) {
+ const arr = new Array(4);
+ for (let i = 0; i < 4; i++) {
+ arr[i] = baseValue + i * 2;
+ }
+
+ if (shouldEscape) {
+ escapedArray = arr;
+ return arr.length;
+ } else {
+ // Use array locally
+ let sum = 0;
+ for (let i = 0; i < arr.length; i++) {
+ sum += arr[i];
+ }
+ return sum;
+ }
+ };
+}
+
+// Closure that captures array from outer scope
+function captureArrayInClosure(baseValue) {
+ const arr = new Array(6);
+ for (let i = 0; i < 6; i++) {
+ arr[i] = baseValue + i;
+ }
+
+ // Return closure that uses the captured array
+ return function(multiplier) {
+ let result = 0;
+ for (let i = 0; i < arr.length; i++) {
+ result += arr[i] * multiplier;
+ }
+ return result;
+ };
+}
+
+// Multiple arrays with closure interactions
+function multiArrayClosure(factor1, factor2) {
+ return function(baseValue) {
+ const arr1 = new Array(3);
+ const arr2 = new Array(3);
+
+ for (let i = 0; i < 3; i++) {
+ arr1[i] = (baseValue + i) * factor1;
+ arr2[i] = (baseValue + i) * factor2;
+ }
+
+ // Combine arrays using closure variables
+ let combined = 0;
+ for (let i = 0; i < 3; i++) {
+ combined += arr1[i] + arr2[i];
+ }
+ return combined;
+ };
+}
+
+noInline(complexClosureTest);
+noInline(closureModifiesArray);
+noInline(nestedClosureWithArray);
+noInline(conditionalEscapeViaClosure);
+noInline(captureArrayInClosure);
+noInline(multiArrayClosure);
+
+// Run tests
+for (let i = 0; i < testLoopCount; i++) {
+ const baseValue = i % 8;
+ const multiplier = 1 + (i % 3); // multipliers 1-3
+
+ // Test complex closure scenario (array size 5)
+ let result1 = complexClosureTest(multiplier, baseValue);
+ let expected1 = 0;
+ for (let j = 0; j < 5; j++) {
+ expected1 += (baseValue + j) * multiplier;
+ }
+ assert(result1 === expected1, `complexClosureTest(${multiplier}, ${baseValue}) failed: got ${result1}, expected ${expected1}`);
+
+ // Test closure that modifies array behavior (array size 4)
+ const closureFn = closureModifiesArray(i % 5);
+ let result2 = closureFn(baseValue);
+ let expected2 = 0;
+ for (let j = 0; j < 4; j++) {
+ expected2 += (baseValue + j + (i % 5)) * 2;
+ }
+ assert(result2 === expected2, `closureModifiesArray test failed: got ${result2}, expected ${expected2}`);
+
+ // Test nested closure (array size 3)
+ const nestedFn = nestedClosureWithArray(i % 3)(i % 4);
+ let result3 = nestedFn(baseValue);
+ let expected3 = 1;
+ for (let j = 0; j < 3; j++) {
+ let val = (baseValue + j + (i % 3) + (i % 4)) % 10;
+ if (val !== 0) expected3 *= val;
+ }
+ assert(result3 === expected3, `nestedClosureWithArray test failed: got ${result3}, expected ${expected3}`);
+
+ // Test conditional escape via closure - non-escaping case (array size 4)
+ const condEscapeFn = conditionalEscapeViaClosure(false);
+ let result4 = condEscapeFn(baseValue);
+ let expected4 = 0;
+ for (let j = 0; j < 4; j++) {
+ expected4 += baseValue + j * 2;
+ }
+ assert(result4 === expected4, `conditionalEscapeViaClosure(false) test failed: got ${result4}, expected ${expected4}`);
+
+ // Test conditional escape via closure - escaping case (array size 4)
+ const condEscapeFn2 = conditionalEscapeViaClosure(true);
+ let result5 = condEscapeFn2(baseValue);
+ assert(result5 === 4, `conditionalEscapeViaClosure(true) test failed: got ${result5}, expected 4`);
+
+ // Test capture array in closure (array size 6)
+ const capturedFn = captureArrayInClosure(baseValue);
+ let result6 = capturedFn(multiplier);
+ let expected6 = 0;
+ for (let j = 0; j < 6; j++) {
+ expected6 += (baseValue + j) * multiplier;
+ }
+ assert(result6 === expected6, `captureArrayInClosure test failed: got ${result6}, expected ${expected6}`);
+
+ // Test multiple arrays with closure (arrays size 3 each)
+ const multiArrayFn = multiArrayClosure(2, 3);
+ let result7 = multiArrayFn(baseValue);
+ let expected7 = 0;
+ for (let j = 0; j < 3; j++) {
+ expected7 += (baseValue + j) * 2 + (baseValue + j) * 3; // factor1 + factor2
+ }
+ assert(result7 === expected7, `multiArrayClosure test failed: got ${result7}, expected ${expected7}`);
+}
diff --git a/JSTests/stress/array-allocation-elimination-conditional-usage.js b/JSTests/stress/array-allocation-elimination-conditional-usage.js
new file mode 100644
index 000000000000..9875274dbee5
--- /dev/null
+++ b/JSTests/stress/array-allocation-elimination-conditional-usage.js
@@ -0,0 +1,108 @@
+function assert(condition, message) {
+ if (!condition)
+ throw new Error(message || "Assertion failed");
+}
+
+// Test conditional array usage - some branches use array, others don't
+function testConditionalArrayUsage(useArray, writeValue) {
+ const arr = new Array(5);
+
+ if (useArray) {
+ // Branch that uses the array
+ arr[0] = writeValue;
+ arr[2] = writeValue + 10;
+ arr[4] = writeValue + 20;
+
+ let sum = 0;
+ for (let i = 0; i < 5; i++) {
+ if (arr[i] !== undefined) {
+ sum += arr[i];
+ }
+ }
+ return sum;
+ } else {
+ // Branch that doesn't use the array at all
+ return writeValue * 3 + 30; // Same result as the sum above when writeValue = 0
+ }
+}
+
+// Test with nested conditionals
+function testNestedConditionalArray(outer, inner, value) {
+ const arr = new Array(3);
+
+ if (outer) {
+ arr[0] = value;
+
+ if (inner) {
+ arr[1] = value * 2;
+ arr[2] = value * 3;
+ return arr[0] + arr[1] + arr[2]; // value * 6
+ } else {
+ // Only use first element
+ return arr[0]; // value
+ }
+ } else {
+ // Don't use array at all
+ return inner ? value * 6 : value;
+ }
+}
+
+// Test with early return that skips array usage
+function testEarlyReturnArray(shouldReturn, value) {
+ const arr = new Array(4);
+
+ if (shouldReturn) {
+ return value * 42;
+ }
+
+ // This array usage should be eliminated when shouldReturn is true
+ arr[0] = value;
+ arr[1] = value + 1;
+ arr[3] = value + 3;
+
+ return arr[0] + arr[1] + arr[3]; // value * 3 + 4
+}
+
+noInline(testConditionalArrayUsage);
+noInline(testNestedConditionalArray);
+noInline(testEarlyReturnArray);
+
+// Run tests to trigger optimization
+for (let i = 0; i < testLoopCount; i++) {
+ // Test the branch that uses the array
+ let result1 = testConditionalArrayUsage(true, i % 100);
+ let expected1 = (i % 100) + (i % 100 + 10) + (i % 100 + 20);
+ assert(result1 === expected1, `testConditionalArrayUsage(true, ${i % 100}) failed: got ${result1}, expected ${expected1}`);
+
+ // Test the branch that doesn't use the array
+ let result2 = testConditionalArrayUsage(false, i % 100);
+ let expected2 = (i % 100) * 3 + 30;
+ assert(result2 === expected2, `testConditionalArrayUsage(false, ${i % 100}) failed: got ${result2}, expected ${expected2}`);
+
... (truncated)