Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentCompiler
Bug ClassType Confusion
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIPlausible
The mapped commit may not contain the whole fix.
Reviewer notes: Diff truncated: visible hunks show only deopt-continuation scaffolding and a temp-copy comment, not the COW/changed-elements guards. Mechanism rests on the commit message; commit is a feature reland bundling the fix.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker502439789
Fix commit66a3f1e94d4b (v8/v8) +1284/-0
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-14 (per vendor advisory)
Fix landed2026-04-27 (public git)
Disclosed2026-06-02
Patch gap36 days — fix public in source before the advisory

Background

Array.prototype.sort inlining
The optimizing compilers now replace a sort(comparefn) call on a small PACKED array with an inline insertion sort emitted directly into the Maglev/Turbofan graph, via IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeSort.
`comparefn` re-entrancy
The user-supplied comparator is arbitrary JavaScript that runs on every comparison and can mutate the receiver (change its elements kind, transition COW elements, push/pop to force a right-trim, or return a non-number), all mid-sort.
Deopt continuation
A builtin (ArraySortNoopEagerDeoptContinuation / ArraySortNoopLazyDeoptContinuation) that resumes execution after the optimized frame bails out; here it simply re-Calls the generic sort on the receiver.
Elements-kind / backing-store invariant
Inlined array code assumes the receiver’s elements kind and FixedArray backing pointer observed at reduction time still hold when the generated loads/stores execute.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Diff truncated: visible hunks show only deopt-continuation scaffolding and a temp-copy comment, not the COW/changed-elements guards. Mechanism rests on the commit message; commit is a feature reland bundling the fix.

The reland introduces an inlined insertion sort in ReduceArrayPrototypeSort that reads the receiver’s elements, sorts while invoking comparefn, and copies the result back. Because comparefn is arbitrary JS that runs between element accesses, the elements kind and the concrete FixedArray the inlined code was specialized for can change underneath it: the comparator can transition COW elements, change the elements array, or push/pop to exhaust slack and trigger a right/left trim that reallocates or aliases the backing store (as the regress-crbug-502944996, -b and -503180648 tests exercise). The original inlining did not consistently snapshot the elements or guard these transitions, so the generated code could keep operating on a stale/wrong-typed backing pointer — a type/backing-store confusion and, per -503180648, a write through a stale pre-trim FixedArray pointer. The comment now states the mitigation: the sort runs on a temp array copy so comparefn side effects on the receiver do not affect ordering and are overwritten by copy-back, matching the spec’s SortIndexedProperties snapshot semantics.

The fix adds this temp-copy snapshot plus explicit guards (“Guard against corruption due to COW elements”, “Guard against corruption due to changed elements”) and deopt continuations (ArraySortNoop*DeoptContinuation) that safely restart the generic sort because the receiver is left unmodified.

Key insight
The inlined sort trusted that a re-entrant comparefn could not change the receiver’s elements kind or backing store between the loads and stores it emitted; the fix sorts a temporary copy and adds COW/changed-elements guards so the specialized code never dereferences a stale or wrong-typed FixedArray.

Attack Path

  1. Enter the inlined fast path Get a function that calls array.sort(comparefn) on a small PACKED array optimized by Maglev/Turbofan so the call is reduced to the inline insertion sort (length <= kMaxInlineSortLength).
  2. Mutate mid-sort from the comparator From comparefn, transition the receiver’s elements (COW copy-on-write, change the elements array, or push/pop to force a right-/left-trim that reallocates or aliases the backing store), as in regress-crbug-502944996 and -503180648.
  3. Break the backing-store invariant The inlined code, specialized for the original elements kind/backing pointer, continues to load or store against the now-stale or wrong-typed FixedArray, yielding type confusion or a write through a stale pre-trim pointer (inferred as the concrete corruption; the shown diff adds the snapshot/guards rather than the vulnerable sink).
  4. Corrupt heap state The out-of-invariant store lands in a differently-typed/relocated backing store, giving a memory-corruption primitive (inferred).

Impact Assessment

An attacker running script in the renderer can trigger type confusion / a stale-backing-store write via a mutating sort comparator once the function is optimized, corrupting V8 heap objects; further escalation to a read/write primitive or RCE is (inferred) and not demonstrated by the diff. Preconditions are only reachable JavaScript and normal optimization warm-up on a small PACKED array. The advisory classifies this as Type Confusion in V8; it does not state exploited-in-the-wild.

Files Changed

  • src/builtins/array.tq
  • src/builtins/builtins.cc
  • src/compiler/js-call-reducer.cc

Audit Directions

  • Re-entrant callbacks in inlined array builtins
    Audit every Reduce* reduction that inlines an array builtin taking a user callback (sort, map, filter, reduce, forEach) for the assumption that elements kind and backing pointer survive across callback invocations; require re-loads or guards after each callback.
  • Trim/relocation aliasing
    Look for inlined code that caches a FixedArray pointer and then calls into JS that can push/pop/set length and trigger a right-/left-trim, aliasing or reallocating the backing store (regress-crbug-502944996, -503180648).
  • COW and elements-kind transitions
    Search for optimized paths that write to elements without re-checking for COW or a changed elements kind after a possible side-effecting call, especially newly-added inlinings behind kMax*Length fast paths.
From 66a3f1e94d4b681bff6476a876067a3c79a853f0 Mon Sep 17 00:00:00 2001
From: Jakob Linke <[email protected]>
Date: Mon, 27 Apr 2026 09:54:04 +0200
Subject: [PATCH] Reland: [compiler] Inline Array.prototype.sort in Maglev and Turbofan

For PACKED arrays with a provided comparefn and length <= 16, replace
the sort call with an inline insertion sort, avoiding the builtin-JS
transition overhead on every comparefn invocation inside TimSort. Arrays
with length > 16 take a runtime slow path to the generic sort builtin.
Maglev supports all packed kinds including PACKED_DOUBLE; the Turbofan
reduction handles PACKED_SMI and PACKED_ELEMENTS only.

Original cl:
https://chromium-review.googlesource.com/c/v8/v8/+/7594692

The reland additionally includes:

- Conditional TypeGuards to fix failures with --no-turbo-loop-variable.
- Guard against corruption due to COW elements.
- Guard against corruption due to changed elements.
- A fix for aborts in GetFloat64ForToNumber causing corrupted graph
  state.
- Regression tests for each.

Bug: 502439789, 502832780, 502923583, 502944996, 503098806
Bug: 503180648, 504070398
Change-Id: I48ecfc2fab6c6f1dcb8d248d2625f0d8e4c76e57
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7791329
Auto-Submit: Jakob Linke <[email protected]>
Commit-Queue: Jakob Linke <[email protected]>
Reviewed-by: Darius Mercadier <[email protected]>
Cr-Commit-Position: refs/heads/main@{#106837}
---

diff --git a/src/builtins/array.tq b/src/builtins/array.tq
index 17f5a29..16f0441 100644
--- a/src/builtins/array.tq
+++ b/src/builtins/array.tq
@@ -91,4 +91,19 @@
       LoadJSArrayElementsMap(ElementsKind::PACKED_ELEMENTS, nativeContext);
   return AllocateJSArray(map, array, Convert<Smi>(array.length));
 }
+
+// Deopt continuations for the inlined Array.prototype.sort.  The sort
+// operates on a temp array copy so the receiver is unmodified -- just
+// restart the generic sort.
+transitioning javascript builtin ArraySortNoopEagerDeoptContinuation(
+    js-implicit context: NativeContext, receiver: JSAny, target: JSFunction)(
+    comparefn: JSAny): JSAny {
+  return Call(context, target, receiver, comparefn);
+}
+
+transitioning javascript builtin ArraySortNoopLazyDeoptContinuation(
+    js-implicit context: NativeContext, receiver: JSAny, target: JSFunction)(
+    comparefn: JSAny, _result: JSAny): JSAny {
+  return Call(context, target, receiver, comparefn);
+}
 }
diff --git a/src/builtins/builtins.cc b/src/builtins/builtins.cc
index 38a41ab..b7c9e42 100644
--- a/src/builtins/builtins.cc
+++ b/src/builtins/builtins.cc
@@ -652,6 +652,8 @@
     case Builtin::kArrayFindIndexLoopAfterCallbackLazyDeoptContinuation:
     case Builtin::kArrayForEachLoopEagerDeoptContinuation:
     case Builtin::kArrayForEachLoopLazyDeoptContinuation:
+    case Builtin::kArraySortNoopEagerDeoptContinuation:
+    case Builtin::kArraySortNoopLazyDeoptContinuation:
     case Builtin::kArrayMapPreLoopLazyDeoptContinuation:
     case Builtin::kArrayMapLoopEagerDeoptContinuation:
     case Builtin::kArrayMapLoopLazyDeoptContinuation:
diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc
index bf7aeef..689d36f 100644
--- a/src/compiler/js-call-reducer.cc
+++ b/src/compiler/js-call-reducer.cc
@@ -37,6 +37,7 @@
 #include "src/ic/call-optimization.h"
 #include "src/objects/elements-kind.h"
 #include "src/objects/instance-type.h"
+#include "src/objects/js-array.h"
 #include "src/objects/js-function.h"
 #include "src/objects/objects-inl.h"
 #include "src/objects/ordered-hash-table.h"
@@ -123,6 +124,9 @@
                       TNode<Object> arg2, TNode<Object> arg3);
 
   // Javascript operators.
+  TNode<Object> JSCall2(TNode<Object> function, TNode<Object> this_arg,
+                        TNode<Object> arg0, TNode<Object> arg1,
+                        FrameState frame_state);
   TNode<Object> JSCall3(TNode<Object> function, TNode<Object> this_arg,
                         TNode<Object> arg0, TNode<Object> arg1,
                         TNode<Object> arg2, FrameState frame_state);
@@ -449,6 +453,10 @@
                                             const bool has_stability_dependency,
                                             ElementsKind kind,
                                             SharedFunctionInfoRef shared);
+  TNode<Object> ReduceArrayPrototypeSort(MapInference* inference,
+                                         const bool has_stability_dependency,
+                                         ElementsKind kind,
+                                         SharedFunctionInfoRef shared);
   TNode<Object> ReduceArrayPrototypeReduce(MapInference* inference,
                                            const bool has_stability_dependency,
                                            ElementsKind kind,
@@ -858,6 +866,24 @@
                                            arg0, arg1, arg2, arg3, context));
 }
 
+TNode<Object> JSCallReducerAssembler::JSCall2(TNode<Object> function,
+                                              TNode<Object> this_arg,
+                                              TNode<Object> arg0,
+                                              TNode<Object> arg1,
+                                              FrameState frame_state) {
+  JSCallNode n(node_ptr());
+  CallParameters const& p = n.Parameters();
+  return MayThrow(_ {
+    return AddNode<Object>(graph()->NewNode(
+        javascript()->Call(JSCallNode::ArityForArgc(2), p.frequency(),
+                           p.feedback(), ConvertReceiverMode::kAny,
+                           p.speculation_mode(),
+                           CallFeedbackRelation::kUnrelated),
+        function, this_arg, arg0, arg1, n.feedback_vector(), ContextInput(),
+        frame_state, effect(), control()));
+  });
+}
+
 TNode<Object> JSCallReducerAssembler::JSCall3(
     TNode<Object> function, TNode<Object> this_arg, TNode<Object> arg0,
     TNode<Object> arg1, TNode<Object> arg2, FrameState frame_state) {
@@ -1593,6 +1619,42 @@
 
 namespace {
 
+// ---- Array.prototype.sort inline insertion sort
+// --------------------------------
+
+struct SortFrameStateParams {
+  JSGraph* jsgraph;
+  SharedFunctionInfoRef shared;
+  TNode<Context> context;
+  TNode<Object> target;
+  FrameState outer_frame_state;
+  TNode<Object> receiver;
+  TNode<Object> comparefn;
+};
+
+// Deopt frame states for the inlined sort.  The sort operates on a temp
+// array copy so the receiver is unmodified — just restart the generic sort.
+FrameState SortNoopEagerFrameState(const SortFrameStateParams& params) {
+  Node* checkpoint_params[] = {params.receiver, params.comparefn};
+  return CreateJavaScriptBuiltinContinuationFrameState(
+      params.jsgraph, params.shared,
+      Builtin::kArraySortNoopEagerDeoptContinuation, params.target,
+      params.context, checkpoint_params, arraysize(checkpoint_params),
+      params.outer_frame_state, ContinuationFrameStateMode::EAGER);
+}
+
+FrameState SortNoopLazyFrameState(const SortFrameStateParams& params) {
+  Node* checkpoint_params[] = {params.receiver, params.comparefn};
+  return CreateJavaScriptBuiltinContinuationFrameState(
+      params.jsgraph, params.shared,
+      Builtin::kArraySortNoopLazyDeoptContinuation, params.target,
+      params.context, checkpoint_params, arraysize(checkpoint_params),
+      params.outer_frame_state, ContinuationFrameStateMode::LAZY);
+}
+
+// ---- Array.prototype.forEach
+// --------------------------------------------------
+
 struct ForEachFrameStateParams {
   JSGraph* jsgraph;
   SharedFunctionInfoRef shared;
@@ -1672,6 +1734,239 @@
   return UndefinedConstant();
 }
 
+TNode<Object> IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeSort(
+    MapInference* inference, const bool has_stability_dependency,
+    ElementsKind kind, SharedFunctionInfoRef shared) {
+  // Inline a small insertion sort directly into the Turbofan graph.
+  //
+  // Fast path (length <= kMaxInlineSortLength):
+  //   1. Copy receiver's elements into a temporary FixedArray.
+  //   2. Insertion-sort the temp array, calling comparefn for each comparison.
+  //   3. Copy sorted elements back into the receiver.
+  //   Using a temp array matches the spec's SortIndexedProperties snapshot
+  //   semantics: comparefn side effects on the receiver do not affect the
+  //   sort order and are overwritten by the copy-back.
+  //
+  // Slow path (length > kMaxInlineSortLength):
+  //   Call Array.prototype.sort with kDisallowSpeculation to prevent
+  //   re-reduction.
+  //
+  // On any deopt the kArraySortNoopLazy/EagerDeoptContinuation builtins
+  // restart the generic sort (the receiver is unmodified since the sort
+  // operates on a temp copy).
+  static constexpr int32_t kMaxInlineSortSize = JSArray::kMaxInlineSortLength;
+
+  FrameState outer_frame_state = FrameStateInput();
+  TNode<Context> context = ContextInput();
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/regress/regress-crbug-502439789.js b/test/mjsunit/regress/regress-crbug-502439789.js
new file mode 100644
index 0000000..cb8e696
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-502439789.js
@@ -0,0 +1,19 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax
+
+// Regression test for crbug.com/502439789. Turboshaft BuildGraph must not
+// fail the dominating_frame_state.valid() DCHECK when OSR-compiling a loop
+// that contains Array.prototype.sort.
+
+function test() {
+  const a = [[1, 2, 3]];
+  for (let i = 0; i < 5; i++) {
+    a.sort(Array);
+    %OptimizeOsr();
+  }
+}
+%PrepareFunctionForOptimization(test);
+test();
diff --git a/test/mjsunit/regress/regress-crbug-502832780.js b/test/mjsunit/regress/regress-crbug-502832780.js
new file mode 100644
index 0000000..29922d7
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-502832780.js
@@ -0,0 +1,24 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --jit-fuzzing
+
+// Regression test for crbug.com/502832780: MaglevGraphVerifier crash on a Phi
+// with a null input, caused by the inlined Array.prototype.sort reduction not
+// handling the abort path correctly when the comparefn's return value cannot be
+// converted to float64 (e.g. when comparefn returns a JSFunction).
+
+function main() {
+  for (let i = 0; i < 5; i++) {
+    const v0 = [];
+    function f1() {
+      return f1;
+    }
+    Math.max(f1);
+    v0.sort(f1);
+    %OptimizeOsr();
+  }
+}
+%PrepareFunctionForOptimization(main);
+main();
diff --git a/test/mjsunit/regress/regress-crbug-502923583.js b/test/mjsunit/regress/regress-crbug-502923583.js
new file mode 100644
index 0000000..6c5755a
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-502923583.js
@@ -0,0 +1,19 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --no-concurrent-osr --no-turbo-loop-variable
+
+// Regression test for crbug.com/502923583: Turbofan RepresentationChanger
+// crash in the inlined Array.prototype.sort reduction.  Without loop variable
+// analysis the outer loop Phi's type widens to Range(0, inf), gets kRepFloat64,
+// and cannot be converted to kRepWord64 for use as a FixedArray index.
+
+function main() {
+  for (let i = 0; i < 5; i++) {
+    %OptimizeOsr();
+    [1, 2].sort((a, b) => i);
+  }
+}
+%PrepareFunctionForOptimization(main);
+main();
diff --git a/test/mjsunit/regress/regress-crbug-502944996-b.js b/test/mjsunit/regress/regress-crbug-502944996-b.js
new file mode 100644
index 0000000..cbeb88c
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-502944996-b.js
@@ -0,0 +1,52 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax
+
+// Regression test for crbug.com/502944996. After a Maglev-compiled
+// Array.prototype.sort mutates array g's backing store (via the literal alias),
+// a deoptimization of split() must not yield two different values for the same
+// g[0] load that was eliminated by load-elimination before the deopt.
+
+function makeArr() { return [3, 1, 2]; }
+const g = makeArr();
+
+function poly(x) { return x + 1; }
+
+let before = 0, after = 0;
+function split(obj) {
+  before = g[0];
+  let b = poly(obj);
+  after = g[0];
+}
+
+%PrepareFunctionForOptimization(makeArr);
+%PrepareFunctionForOptimization(poly);
+%PrepareFunctionForOptimization(split);
+for (let i = 0; i < 100000; i++) split(42);
+%OptimizeFunctionOnNextCall(split);
+split(42);
+
+function trigger() {
+  let a = makeArr();
+  let first = true;
+  a.sort(function(x, y) {
+    if (first) {
+      first = false;
+      for (let i = 0; i < 100; i++) a.push(0);
+      for (let i = 0; i < 100; i++) a.pop();
+    }
+    return x - y;
+  });
+}
+
+%PrepareFunctionForOptimization(trigger);
+for (let i = 0; i < 10; i++) trigger();
+%OptimizeMaglevOnNextCall(trigger);
+trigger();
+
+// Deopt split() by passing a non-number. The two g[0] loads in split must
+// agree: load elimination must not forward a pre-deopt value across the deopt.
+split("x");
+assertEquals(before, after);
diff --git a/test/mjsunit/regress/regress-crbug-502944996.js b/test/mjsunit/regress/regress-crbug-502944996.js
new file mode 100644
index 0000000..64af05d
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-502944996.js
@@ -0,0 +1,36 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax
+
+// Regression test for crbug.com/502944996. Maglev-compiled Array.prototype.sort
+// must not mutate the backing store of the array literal in the callee (makeArr)
+// when the comparator push/pops to exhaust slack capacity and triggers a
+// right-trim, which aliases the literal's FixedArray.
+
+function makeArr() { return [3, 1, 2]; }
+
+function trigger() {
+  let a = makeArr();
+  let first = true;
+  a.sort(function(x, y) {
+    if (first) {
+      first = false;
+      for (let i = 0; i < 100; i++) a.push(0);
+      for (let i = 0; i < 100; i++) a.pop();
+    }
+    return x - y;
+  });
+}
+
+%PrepareFunctionForOptimization(makeArr);
+%PrepareFunctionForOptimization(trigger);
+for (let i = 0; i < 10; i++) trigger();
+%OptimizeMaglevOnNextCall(trigger);
+trigger();
+
+// The literal in makeArr must not have been sorted in-place.
+assertEquals(3, makeArr()[0]);
+assertEquals(1, makeArr()[1]);
+assertEquals(2, makeArr()[2]);
diff --git a/test/mjsunit/regress/regress-crbug-503098806.js b/test/mjsunit/regress/regress-crbug-503098806.js
new file mode 100644
index 0000000..b6fc843
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-503098806.js
@@ -0,0 +1,37 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax
+// Flags: --stress-concurrent-inlining --stress-concurrent-inlining-attach-code
+// Flags: --no-maglev-overwrite-budget
+
+// Regression test for crbug.com/503098806: Turboshaft graph builder crash
+// (dominating_frame_state.valid()) in the inlined Array.prototype.sort
+// reduction when the comparefn contains a try-catch.
+//
+// This crash is timing-dependent (concurrent compilation race).  Increase
+// probability of hitting the race with multiple optimization passes.
+
+(function () {
+  let throwing = false;
+  function cmp(a, b) {
+    try {
+      if (throwing) throw 'caught';
+      return a - b;
+    } catch (e) {}
+  }
+  function f() {
+    return [4, 13].sort(cmp);
+  }
+  throwing = true;
+  throwing = false;
+
+  // Multiple optimization attempts to increase race probability.
+  for (let i = 0; i < 5; i++) {
+    %PrepareFunctionForOptimization(f);
+    f();
+    %OptimizeFunctionOnNextCall(f);
+    f();
+  }
+})();
diff --git a/test/mjsunit/regress/regress-crbug-503180648.js b/test/mjsunit/regress/regress-crbug-503180648.js
new file mode 100644
index 0000000..87bb8dd
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-503180648.js
@@ -0,0 +1,48 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax
+
+// Regression test for crbug.com/503180648. Optimized Array.prototype.sort
+// must not write through a stale pre-left-trim FixedArray pointer when a
+// comparator grows the receiver past kMaxCopyElements and then shifts it.
+
+let victim;
+let did_mutate;
+
+function makeLargeCapacityPackedSmiArray() {
+  const a = [];
+  for (let i = 0; i < 236; i++) a.push(1000 + i);
+  // Leaves PACKED_SMI_ELEMENTS with length 16 but large backing capacity.
+  // (Direct length assignment would right-trim instead.)
+  a.splice(16);
+  return a;
+}
+
+function sortVictim(a) {
+  did_mutate = false;
+  victim = a;
+  // Inline closure so the sort reducer sees a statically-known comparefn via
+  // JSCreateClosure/CheckClosure.
+  victim.sort(function(a, b) {
+    if (!did_mutate) {
+      did_mutate = true;
+      // No reallocation: backing capacity already covers this.
+      for (let i = 0; i < 220; i++) victim.push(2000 + i);
+      // Forces LeftTrimFixedArray because new_length > kMaxCopyElements.
+      victim.shift();
+      // Restore map/length so the inline-sort exit guards pass.
+      victim.length = 16;
+    }
+    return a - b;
+  });
+  return victim.length;
+}
+
+%PrepareFunctionForOptimization(sortVictim);
+sortVictim(makeLargeCapacityPackedSmiArray());
+sortVictim(makeLargeCapacityPackedSmiArray());
+%OptimizeFunctionOnNextCall(sortVictim);
+sortVictim(makeLargeCapacityPackedSmiArray());
+%CollectGarbage(0);
diff --git a/test/mjsunit/regress/regress-crbug-503557995-b.js b/test/mjsunit/regress/regress-crbug-503557995-b.js
new file mode 100644
index 0000000..730d84d
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-503557995-b.js
@@ -0,0 +1,61 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Regression test for crbug.com/503557995.  The inlined Array.prototype.sort
+// must keep the heap consistent when the comparator truncates the receiver,
+// allocates a separate "groom" array, and then re-grows the receiver.  A bug
+// here surfaces as a heap-verifier failure (Map check) during a later
+// scavenge.
+
+'use strict';
+
+let current = null;
+let flip = false;
+let groom = null;
+let seq = 0;
+
... (truncated)
Loading diff…

Original Bug Report

reported by [email protected]

DCHECK failure in dominating_frame_state.valid() in graph-builder.cc

Detailed Report: https://clusterfuzz.com/testcase?key=6685440564297728

Fuzzer: None Job Type: linux_asan_d8_dbg Platform Id: linux

Crash Type: DCHECK failure Crash Address: Crash State: dominating_frame_state.valid() in graph-builder.cc v8::internal::compiler::turboshaft::GraphBuilder::Process v8::internal::compiler::turboshaft::BuildGraph

Sanitizer: address (ASAN)

Regressed: https://clusterfuzz.com/revisions?job=linux_asan_d8_dbg&range=106415:106416

Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=6685440564297728

Issue filed automatically.

To reproduce this, please build the target in this report and run it against the reproducer testcase. Please use the GN arguments provided at bottom of this report when building the binary.

If you have trouble reproducing, please also export the environment variables listed under “[Environment]” in the crash stacktrace.

If you have any feedback on reproducing test cases, let us know at https://forms.gle/Yh3qCYFveHj6E5jz5 so we can improve.

View on issue tracker