CVE-2026-14383
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TF_BUILTINsrc/builtins/builtins-iterator-gen.cc |
modified |
Files Changed
src/baseline/baseline-compiler.ccsrc/builtins/builtins-iterator-gen.ccsrc/builtins/builtins-iterator-gen.hsrc/builtins/iterator.tq
Patch
From 02a725e2a0762f39266fdac2ba2869c71af37ed2 Mon Sep 17 00:00:00 2001
From: Marja Hölttä <marja@chromium.org>
Date: Tue, 12 May 2026 11:25:04 +0200
Subject: [PATCH] [for-of] Use a single return value
Technically we return two values, "value" and "done". But when "done" is
true, there's no value. We can thus only return "value" and use the hole
to signal that done is true.
This also fixes a bunch of open bugs.
Fixed: 492410546
Bug: 408061015
Change-Id: I2757aff95743d53e785fb9386e0b7018c8324005
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7826150
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#107253}
---
diff --git a/src/baseline/baseline-compiler.cc b/src/baseline/baseline-compiler.cc
index 28e4166..bdf8087 100644
--- a/src/baseline/baseline-compiler.cc
+++ b/src/baseline/baseline-compiler.cc
@@ -2661,14 +2661,10 @@
Register next = scratch_scope.AcquireScratch();
__ LoadRegister(object, RegisterOperand(0));
__ LoadRegister(next, RegisterOperand(1));
- // Pass the output register slot as an argument, so that the builtin
- // is responsible for writing into the slots.
- Register out_reg_address = scratch_scope.AcquireScratch();
- basm_.RegisterFrameAddress(RegisterOperand(2), out_reg_address);
+
CallBuiltin<Builtin::kForOfNextBaseline>(object, // object
next, // next
- out_reg_address, // out_reg
- FeedbackSlotAsSmi(3)); // call_slot
+ FeedbackSlotAsSmi(2)); // call_slot
}
void BaselineCompiler::VisitGetIterator() {
diff --git a/src/builtins/builtins-iterator-gen.cc b/src/builtins/builtins-iterator-gen.cc
index bf08cb0..d567daf 100644
--- a/src/builtins/builtins-iterator-gen.cc
+++ b/src/builtins/builtins-iterator-gen.cc
@@ -524,63 +524,32 @@
Return(iterator);
}
-void IteratorBuiltinsAssembler::StoreValueAndDoneInRegisterPair(
- TNode<Context> context, TNode<Object> value, TNode<Object> done,
- TNode<Smi> value_done_reg) {
- // ForOfNext writes to a register pair. Since this is an eager deopt
- // continuation, we need to manually update the registers in the parent
- // interpreted frame.
- TNode<IntPtrT> reg_index = SmiToIntPtr(value_done_reg);
- TNode<IntPtrT> base_offset =
- IntPtrConstant(InterpreterFrameConstants::kRegisterFileFromFp);
- TNode<IntPtrT> value_offset =
- IntPtrSub(base_offset, TimesSystemPointerSize(reg_index));
- TNode<IntPtrT> done_offset = IntPtrSub(
- base_offset,
- TimesSystemPointerSize(IntPtrAdd(reg_index, IntPtrConstant(1))));
-
- auto parent_frame_pointer = LoadParentFramePointer();
- StoreFullTaggedNoWriteBarrier(parent_frame_pointer, value_offset, value);
- StoreFullTaggedNoWriteBarrier(parent_frame_pointer, done_offset, done);
-}
-
-// ForOfNextResultDeoptContinuation is used for the lazy deoptimization on
-// next() call and the eager deoptimization of loading done value.
TF_BUILTIN(ForOfNextResultDeoptContinuation, IteratorBuiltinsAssembler) {
auto context = Parameter<Context>(Descriptor::kContext);
- auto value_done_reg = Parameter<Smi>(Descriptor::kValueDoneReg);
auto result_object = Parameter<Object>(Descriptor::kResultObject);
Label is_jsreceiver(this), if_notjsreceiver(this, Label::kDeferred);
BranchIfJSReceiver(result_object, &is_jsreceiver, &if_notjsreceiver);
BIND(&is_jsreceiver);
- TVARIABLE(Object, var_value);
- TVARIABLE(Object, var_done);
-
- Label if_done(this), if_not_done(this), end(this, &var_value);
- var_done =
+ TNode<Object> var_done =
GetProperty(context, CAST(result_object), factory()->done_string());
- BranchIfToBooleanIsTrue(var_done.value(), &if_done, &if_not_done);
+
+ Label if_done(this), if_not_done(this);
+ BranchIfToBooleanIsTrue(var_done, &if_done, &if_not_done);
BIND(&if_done);
{
- var_value = UndefinedConstant();
- Goto(&end);
+ Return(TheHoleConstant());
}
BIND(&if_not_done);
{
- var_value =
+ TNode<Object> value =
GetProperty(context, CAST(result_object), factory()->value_string());
- Goto(&end);
+ Return(value);
}
- BIND(&end);
- StoreValueAndDoneInRegisterPair(context, var_value.value(), var_done.value(),
- value_done_reg);
- Return(var_value.value());
-
BIND(&if_notjsreceiver);
CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context, result_object);
Unreachable();
@@ -590,51 +559,36 @@
auto context = Parameter<Context>(Descriptor::kContext);
auto result_object = Parameter<Object>(Descriptor::kResultObject);
auto done = Parameter<Object>(Descriptor::kDone);
- auto value_done_reg = Parameter<Smi>(Descriptor::kValueDoneReg);
- TVARIABLE(Object, var_value);
- Label if_done(this), if_not_done(this), end(this, &var_value);
+ Label if_done(this), if_not_done(this);
BranchIfToBooleanIsTrue(done, &if_done, &if_not_done);
BIND(&if_done);
{
- var_value = UndefinedConstant();
- Goto(&end);
+ Return(TheHoleConstant());
}
BIND(&if_not_done);
{
- var_value =
+ TNode<Object> value =
GetProperty(context, CAST(result_object), factory()->value_string());
- Goto(&end);
+ Return(value);
}
-
- BIND(&end);
- StoreValueAndDoneInRegisterPair(context, var_value.value(), done,
- value_done_reg);
- Return(var_value.value());
}
TF_BUILTIN(ForOfNextLoadValueEagerDeoptContinuation,
IteratorBuiltinsAssembler) {
auto context = Parameter<Context>(Descriptor::kContext);
auto result_object = Parameter<Object>(Descriptor::kResultObject);
- auto value_done_reg = Parameter<Smi>(Descriptor::kValueDoneReg);
TNode<Object> value =
GetProperty(context, CAST(result_object), factory()->value_string());
- StoreValueAndDoneInRegisterPair(context, value, FalseConstant(),
- value_done_reg);
Return(value);
}
TF_BUILTIN(ForOfNextLoadValueLazyDeoptContinuation, IteratorBuiltinsAssembler) {
- auto context = Parameter<Context>(Descriptor::kContext);
auto value = Parameter<Object>(Descriptor::kValue);
- auto value_done_reg = Parameter<Smi>(Descriptor::kValueDoneReg);
- StoreValueAndDoneInRegisterPair(context, value, FalseConstant(),
- value_done_reg);
Return(value);
}
diff --git a/src/builtins/builtins-iterator-gen.h b/src/builtins/builtins-iterator-gen.h
index 4aee1bf..21f79ca 100644
--- a/src/builtins/builtins-iterator-gen.h
+++ b/src/builtins/builtins-iterator-gen.h
@@ -97,9 +97,7 @@
TNode<JSArray> FastIterableToList(TNode<Context> context,
TNode<JSAny> iterable, Label* slow);
- void StoreValueAndDoneInRegisterPair(TNode<Context> context,
- TNode<Object> value, TNode<Object> done,
- TNode<Smi> value_done_reg);
+ void StoreRegister(TNode<Smi> reg, TNode<Object> value);
};
} // namespace internal
diff --git a/src/builtins/iterator.tq b/src/builtins/iterator.tq
index 87ad17e..537a84f 100644
--- a/src/builtins/iterator.tq
+++ b/src/builtins/iterator.tq
@@ -129,40 +129,22 @@
return CreateAsyncFromSyncIterator(context, syncIterator);
}
-struct ForOfNextResult {
- value: Object;
- done: Object;
-}
-
Regression Test / PoC
diff --git a/test/mjsunit/es6/for-of-array-iterator-optimization-maglev-lazy-next-call-done-deopt.js b/test/mjsunit/es6/for-of-array-iterator-optimization-maglev-lazy-next-call-done-deopt.js
new file mode 100644
index 0000000..126a35e
--- /dev/null
+++ b/test/mjsunit/es6/for-of-array-iterator-optimization-maglev-lazy-next-call-done-deopt.js
@@ -0,0 +1,50 @@
+// 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 --maglev --no-stress-maglev
+// Flags: --for-of-optimization
+
+function lazy_next() {
+ this.count++;
+ if (this.count === 4) {
+ // Trigger a lazy deopt in the caller frame upon return!
+ %DeoptimizeFunction(testForOfLazyDeoptNextCall);
+ }
+ if (this.count === 4) {
+ return {done: true};
+ }
+ return {value: this.count * 10, done: false}; // Sums to 60
+}
+// Keep next() unoptimized to ensure a clean call/return boundary
+// that correctly exercises the lazy deopt continuation.
+%NeverOptimizeFunction(lazy_next);
+
+const iterable = {
+ [Symbol.iterator]() {
+ return {
+ count: 0,
+ next: lazy_next
+ };
+ }
+};
+
+function testForOfLazyDeoptNextCall() {
+ let sum = 0;
+ for (const x of iterable) {
+ sum += x;
+ }
+ return sum;
+}
+
+%PrepareFunctionForOptimization(testForOfLazyDeoptNextCall);
+testForOfLazyDeoptNextCall();
+testForOfLazyDeoptNextCall();
+
+%OptimizeMaglevOnNextCall(testForOfLazyDeoptNextCall);
+testForOfLazyDeoptNextCall();
+
+const result = testForOfLazyDeoptNextCall();
+
+assertUnoptimized(testForOfLazyDeoptNextCall);
+assertEquals(60, result);
diff --git a/test/mjsunit/regress/regress-492410546.js b/test/mjsunit/regress/regress-492410546.js
new file mode 100644
index 0000000..b3ab96a
--- /dev/null
+++ b/test/mjsunit/regress/regress-492410546.js
@@ -0,0 +1,35 @@
+// 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 --for-of-optimization --turbolev-future --turbolev
+
+const resultWithGetter = {};
+Object.defineProperty(resultWithGetter, 'value', {
+ get: function () { }, // effectively returns undefined
+});
+
+const myIterable = {
+ [Symbol.iterator]() {
+ return {
+ next() {
+ return Object.create(resultWithGetter);
+ }
+ };
+ }
+};
+
+function foo() {
+ try {
+ for (const v of myIterable) {
+ String.prototype.endsWith.apply();
+ }
+ } catch (e) {}
+}
+%PrepareFunctionForOptimization(foo);
+
+// Both calls are needed to repro this bug.
+foo();
+foo();
+%OptimizeFunctionOnNextCall(foo);
+foo();
diff --git a/test/unittests/interpreter/bytecode-array-builder-unittest.cc b/test/unittests/interpreter/bytecode-array-builder-unittest.cc
index 2597754..4a775c4 100644
--- a/test/unittests/interpreter/bytecode-array-builder-unittest.cc
+++ b/test/unittests/interpreter/bytecode-array-builder-unittest.cc
@@ -253,7 +253,7 @@
// Emit Iterator-protocol operations
builder.GetIterator(reg, load_slot.ToInt(), call_slot.ToInt());
- builder.ForOfNext(reg, reg, pair, 1);
+ builder.ForOfNext(reg, reg, 1);
// Emit load / store lookup slots.
builder.LoadLookupSlot(name, TypeofMode::kNotInside)
diff --git a/test/unittests/interpreter/bytecode_expectations/ForOfOptimization.golden b/test/unittests/interpreter/bytecode_expectations/ForOfOptimization.golden
index b46c6dc..0a1e388 100644
--- a/test/unittests/interpreter/bytecode_expectations/ForOfOptimization.golden
+++ b/test/unittests/interpreter/bytecode_expectations/ForOfOptimization.golden
@@ -12,7 +12,7 @@
"
frame size: 13
parameter count: 1
-bytecode array length: 107
+bytecode array length: 110
bytecodes: [
/* 48 S> */ B(CreateArrayLiteral), U8(0:ARRAY_BOILERPLATE_DESCRIPTION_TYPE), FBV(0), Flag8(0x25),
B(Star4),
@@ -27,13 +27,17 @@
B(Mov), R(context), R(9),
B(LdaTrue),
B(Star5),
- /* 43 s> */ B(ForOfNext), R(3), R(2), R(4), FBV(7),
- B(Ldar), R(5),
- B(JumpIfToBooleanTrue), U8(14),
- /* 43 S> */ B(Mov), R(4), R(1),
+ /* 43 s> */ B(ForOfNext), R(3), R(2), FBV(7),
+ B(Star4),
+ B(LdaTheHole),
+ B(TestReferenceEqual), R(4),
+ B(JumpIfTrue), U8(16),
+ B(LdaFalse),
+ /* 43 S> */ B(Star5),
+ B(Mov), R(4), R(1),
/* 43 S> */ B(Mov), R(1), R(0),
B(Ldar), R(0),
- /* 34 E> */ B(JumpLoop), U8(19), I8(0), FBV(13),
+ /* 34 E> */ B(JumpLoop), U8(22), I8(0), FBV(13),
B(LdaSmi), I8(-1),
B(Star7),
B(Star6),
@@ -77,8 +81,8 @@
INTERNALIZED_ONE_BYTE_STRING_TYPE ["return"],
]
handlers: [
- [22, 45, 51],
- [64, 83, 85],
+ [22, 48, 54],
+ [67, 86, 88],
]
---
@@ -87,7 +91,7 @@
"
frame size: 13
parameter count: 1
-bytecode array length: 117
+bytecode array length: 120
bytecodes: [
/* 58 S> */ B(CreateArrayLiteral), U8(0:ARRAY_BOILERPLATE_DESCRIPTION_TYPE), FBV(0), Flag8(0x25),
B(Star3),
@@ -106,13 +110,17 @@
B(Mov), R(context), R(9),
B(LdaTrue),
B(Star5),
- /* 43 s> */ B(ForOfNext), R(3), R(2), R(4), FBV(11),
- B(Ldar), R(5),
- B(JumpIfToBooleanTrue), U8(14),
- /* 43 S> */ B(Mov), R(4), R(1),
+ /* 43 s> */ B(ForOfNext), R(3), R(2), FBV(11),
+ B(Star4),
+ B(LdaTheHole),
+ B(TestReferenceEqual), R(4),
+ B(JumpIfTrue), U8(16),
+ B(LdaFalse),
+ /* 43 S> */ B(Star5),
+ B(Mov), R(4), R(1),
/* 43 S> */ B(Mov), R(1), R(0),
B(Ldar), R(0),
- /* 34 E> */ B(JumpLoop), U8(19), I8(0), FBV(17),
+ /* 34 E> */ B(JumpLoop), U8(22), I8(0), FBV(17),
B(LdaSmi), I8(-1),
B(Star7),
B(Star6),
@@ -157,8 +165,8 @@
INTERNALIZED_ONE_BYTE_STRING_TYPE ["return"],
]
handlers: [
- [32, 55, 61],
- [74, 93, 95],
+ [32, 58, 64],
+ [77, 96, 98],
]
---
@@ -167,7 +175,7 @@
"
frame size: 13
parameter count: 1
-bytecode array length: 117
+bytecode array length: 120
bytecodes: [
/* 58 S> */ B(CreateArrayLiteral), U8(0:ARRAY_BOILERPLATE_DESCRIPTION_TYPE), FBV(0), Flag8(0x25),
B(Star3),
@@ -186,13 +194,17 @@
B(Mov), R(context), R(9),
B(LdaTrue),
B(Star5),
- /* 43 s> */ B(ForOfNext), R(3), R(2), R(4), FBV(11),
- B(Ldar), R(5),
- B(JumpIfToBooleanTrue), U8(14),
- /* 43 S> */ B(Mov), R(4), R(1),
+ /* 43 s> */ B(ForOfNext), R(3), R(2), FBV(11),
+ B(Star4),
+ B(LdaTheHole),
+ B(TestReferenceEqual), R(4),
+ B(JumpIfTrue), U8(16),
+ B(LdaFalse),
+ /* 43 S> */ B(Star5),
+ B(Mov), R(4), R(1),
/* 43 S> */ B(Mov), R(1), R(0),
B(Ldar), R(0),
- /* 34 E> */ B(JumpLoop), U8(19), I8(0), FBV(17),
+ /* 34 E> */ B(JumpLoop), U8(22), I8(0), FBV(17),
B(LdaSmi), I8(-1),
B(Star7),
B(Star6),
@@ -237,7 +249,7 @@
INTERNALIZED_ONE_BYTE_STRING_TYPE ["return"],
]
handlers: [
- [32, 55, 61],
- [74, 93, 95],
+ [32, 58, 64],
+ [77, 96, 98],
]
Original Bug Report
DCHECK failure in (maglev_value) != nullptr in turbolev-graph-builder.cc
Detailed Report: https://clusterfuzz.com/testcase?key=6662523457568768
Fuzzer: ochang_js_fuzzer Job Type: linux32_d8_dbg Platform Id: linux
Crash Type: DCHECK failure Crash Address: Crash State: (maglev_value) != nullptr in turbolev-graph-builder.cc
Sanitizer: address (ASAN)
Regressed: https://clusterfuzz.com/revisions?job=linux32_d8_dbg&range=105738:105739
Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=6662523457568768
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.