fix: Iterator.from 鸭子回退包装器创建时缓存 next 闭包——getter 恰触发一次,消费期零重读,getter 抛错透传

This commit is contained in:
2026-09-11 16:29:35 +00:00
parent ee32b2c682
commit f3533167b5
2 changed files with 128 additions and 17 deletions
+63 -17
View File
@@ -1200,7 +1200,8 @@ pub(crate) fn make_iterator_for_value_with_proto<H: VmHost>(
///
/// # 步骤
/// 1. 经迭代协议取内层迭代器(String/Array/Map/Set 直接作为内层,其余对象调用
/// `@@iterator` 或回退可调用的 `next`)。
/// `@@iterator` 或回退可调用的 `next`;鸭子回退路径把读到的 `next` 闭包
/// 缓存进包装器 `__next__` 槽,消费期不重触发 getter)。
/// 2. 包装成统一迭代器对象(带 `next` 与可选 `return`),供调用方逐个取元素。
///
/// # 返回值
@@ -1220,8 +1221,8 @@ pub(crate) fn try_make_iterator_inner<H: VmHost>(
pub(crate) fn try_make_iterator_inner_proto<H: VmHost>(
vm: &mut H, value: JsValue, bind_return: bool, wrapper_proto: Option<*mut JsObject>,
) -> Result<Option<JsValue>, JsValue> {
let inner = match get_iterator(vm, value) {
Ok(Some(inner)) => inner,
let (inner, cached_next) = match get_iterator(vm, value) {
Ok(Some(pair)) => pair,
Ok(None) => return Ok(None),
Err(err) => return Err(err),
};
@@ -1242,8 +1243,16 @@ pub(crate) fn try_make_iterator_inner_proto<H: VmHost>(
vm.set_or_create_prop_value(wrapper_obj, inner_si, inner);
vm.set_or_create_prop_value(wrapper_obj, index_si, JsValue::int(0));
let next_fn = make_native_function(vm, "next", iterator_wrapper_next::<H> as *const (), 0);
vm.set_or_create_prop_value(wrapper_obj, next_si, next_fn);
// 鸭子回退包装器:from() 时读到的 next 闭包缓存进 __next__ 槽(与 helper 包装器
// "只读一次"同模式),消费期直读槽不重触发 getter;非鸭子路径无槽,wrapper next
// 回退重读内层 next(既有行为)。
if let Some(next_fn) = cached_next {
let next_cache_si = vm.kernel_core().perm_interner().intern(NEXT_CACHE_PROP).0;
vm.set_or_create_prop_value(wrapper_obj, next_cache_si, next_fn);
}
let wrapper_next = make_native_function(vm, "next", iterator_wrapper_next::<H> as *const (), 0);
vm.set_or_create_prop_value(wrapper_obj, next_si, wrapper_next);
// for-of/解构的 IteratorClose 需要 return 方法:条件暴露(内层有可调用 return 时)。
// `yield*` 委托(bind_return=false)不绑定,转发时对内层延迟 GetMethod。
@@ -1291,7 +1300,8 @@ fn iterator_wrapper_return<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
/// 迭代器包装器的 `next` 方法:对 Array/String/Map/Set 直接按索引取值,
/// 其它对象委托其自身 `next`;底层抛出异常时透传原始值(不做二次包装)。
/// 其它对象优先用 `__next__` 槽(鸭子回退包装器创建时只读一次缓存的闭包),
/// 槽缺失时委托内层自身 `next`;底层抛出异常时透传原始值(不做二次包装)。
pub fn iterator_wrapper_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
if !this_val.is_object() {
@@ -1313,18 +1323,36 @@ pub fn iterator_wrapper_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
}
if inner.is_object() {
let inner_obj = unsafe { &*inner.as_js_object_ptr() };
let next_si = vm.kernel_core().perm_interner().intern("next").0;
let next = match vm.ordinary_get(inner_obj, next_si, inner) {
Ok(next) => next,
// 鸭子回退包装器优先读 `__next__` 槽(from() 时只读一次的缓存闭包,
// 消费期不重触发 getter);槽缺失或不可调用时回退重读内层 next
// (非鸭子路径既有行为)。
let next_cache_si = vm.kernel_core().perm_interner().intern(NEXT_CACHE_PROP).0;
let cached = match vm.ordinary_get(wrapper, next_cache_si, this_val) {
Ok(c) => c,
Err(err) => {
// GetMethod 的 next getter 抛错:透传原异常,不重新包装成 TypeError。
// `__next__` 槽读取抛错:透传原异常,不重新包装成 TypeError。
return match vm.take_uncaught_value() {
Some(original) => NativeResult::Err(original),
None => NativeResult::Err(crate::error::create_type_error(vm, &err)),
};
}
};
let next = if is_callable(cached) {
cached
} else {
let inner_obj = unsafe { &*inner.as_js_object_ptr() };
let next_si = vm.kernel_core().perm_interner().intern("next").0;
match vm.ordinary_get(inner_obj, next_si, inner) {
Ok(n) => n,
Err(err) => {
// GetMethod 的 next getter 抛错:透传原异常,不重新包装成 TypeError。
return match vm.take_uncaught_value() {
Some(original) => NativeResult::Err(original),
None => NativeResult::Err(crate::error::create_type_error(vm, &err)),
};
}
}
};
// 转发调用实参(`yield*` 委托的 next(v) 语义),缺省为 undefined。
let arg = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
return match vm.call_function_sync(next, inner, &[arg]) {
@@ -1382,14 +1410,23 @@ pub(crate) fn peek_iterator_method<H: VmHost>(vm: &mut H, value: JsValue) -> Res
Ok(false)
}
fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<JsValue>, JsValue> {
/// 经迭代协议取内层迭代器:内建集合直接作为内层(无缓存 next),`@@iterator`
/// 调用结果作为内层(无缓存 next),鸭子回退路径同时回传读到的 `next` 闭包
/// 供调用方缓存进包装器 `__next__` 槽(消费期不重触发 getter)。
///
/// # 返回值
/// - `Ok(Some((inner, next)))`:`inner` 为内层迭代器;鸭子回退时 `next` 为
/// 读到的闭包,其余路径为 `None`;
/// - `Ok(None)`:不可迭代;
/// - `Err`:`@@iterator` 或 `next` getter 抛错,透传原异常值。
fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue, Option<JsValue>)>, JsValue> {
if value.is_string()
|| is_array_value(value)
|| is_typed_array_value(value)
|| is_map_value(value)
|| is_set_value(value)
{
return Ok(Some(value));
return Ok(Some((value, None)));
}
// 非字符串 primitive(boolean/number/symbol/bigint):GetIterator 先 ToObject,
@@ -1432,14 +1469,23 @@ fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<JsValue>
"Result of the Symbol.iterator method is not an object",
));
}
return Ok(Some(iterator));
return Ok(Some((iterator, None)));
}
// 鸭子回退:对象自身有可调用 next(Map/Set 迭代器包装等既有用法)。
// getter 抛错透传原值(不落入"不可迭代"),读到的闭包随 inner 回传供缓存。
let next_si = vm.kernel_core().perm_interner().intern("next").0;
if let Ok(next) = vm.ordinary_get(obj, next_si, obj_value) {
if is_callable(next) {
return Ok(Some(obj_value));
let next = match vm.ordinary_get(obj, next_si, obj_value) {
Ok(n) => n,
Err(err) => {
// GetMethod 取 next 时 getter 抛出:透传原值,不降级为 "not iterable"。
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &err));
return Err(exc);
}
};
if is_callable(next) {
return Ok(Some((obj_value, Some(next))));
}
Ok(None)
@@ -53,6 +53,71 @@ fn iterator_from_iterator_forwards_next() {
assert_eq!(result.as_int(), 7);
}
#[test]
fn iterator_from_duck_fallback_next_getter_read_only_once() {
// 鸭子回退的 from() 恰读一次 next getter(规范单读语义):
// 包装器缓存 from() 时读到的闭包,消费期直用缓存闭包——getter 不被重触发,
// 底层新产的值经同一闭包透出。
let mut vm = Vm::new();
let bool_cases = [
(
"var nextGets = 0; \
var nextCalls = 0; \
var obj = { \
get next() { \
++nextGets; \
var i = 0; \
return function () { \
++nextCalls; \
return i < 4 ? { value: i++, done: false } : { value: undefined, done: true }; \
}; \
}, \
}; \
var it = Iterator.from(obj); \
nextGets === 1 && nextCalls === 0",
true,
),
(
"var nextGets = 0; \
var nextCalls = 0; \
var obj = { \
get next() { \
++nextGets; \
var i = 0; \
return function () { \
++nextCalls; \
return i < 4 ? { value: i++, done: false } : { value: undefined, done: true }; \
}; \
}, \
}; \
var it = Iterator.from(obj); \
var vals = []; \
for (var k = 0; k < 5; ++k) { var r = it.next(); if (!r.done) { vals.push(r.value); } } \
nextGets === 1 && nextCalls === 5 && vals.join(',') === '0,1,2,3'",
true,
),
];
for (src, expected) in bool_cases {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
}
#[test]
fn iterator_from_throwing_next_getter_propagates() {
// next getter 抛错经 from() 透传原异常,不降级为 "not iterable" TypeError。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var ok = false; \
try { Iterator.from({ get next() { throw new RangeError('read'); } }); } \
catch (e) { ok = e instanceof RangeError; } \
ok",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn new_iterator_throws_type_error() {
let mut vm = Vm::new();