From f3533167b5f5269ab3746a5f9f814967caafb468 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 11 Sep 2026 16:29:35 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20Iterator.from=20=E9=B8=AD=E5=AD=90?= =?UTF-8?q?=E5=9B=9E=E9=80=80=E5=8C=85=E8=A3=85=E5=99=A8=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=97=B6=E7=BC=93=E5=AD=98=20next=20=E9=97=AD=E5=8C=85?= =?UTF-8?q?=E2=80=94=E2=80=94getter=20=E6=81=B0=E8=A7=A6=E5=8F=91=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=EF=BC=8C=E6=B6=88=E8=B4=B9=E6=9C=9F=E9=9B=B6=E9=87=8D?= =?UTF-8?q?=E8=AF=BB=EF=BC=8Cgetter=20=E6=8A=9B=E9=94=99=E9=80=8F=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/iterator.rs | 80 +++++++++++++++---- .../oxide_vm/tests/builtins_iterator_tests.rs | 65 +++++++++++++++ 2 files changed, 128 insertions(+), 17 deletions(-) diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 538cd30d..177cea26 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1200,7 +1200,8 @@ pub(crate) fn make_iterator_for_value_with_proto( /// /// # 步骤 /// 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( pub(crate) fn try_make_iterator_inner_proto( vm: &mut H, value: JsValue, bind_return: bool, wrapper_proto: Option<*mut JsObject>, ) -> Result, 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( 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:: 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:: 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(vm: &mut H, args: &[u8]) -> NativeResult { } /// 迭代器包装器的 `next` 方法:对 Array/String/Map/Set 直接按索引取值, -/// 其它对象委托其自身 `next`;底层抛出异常时透传原始值(不做二次包装)。 +/// 其它对象优先用 `__next__` 槽(鸭子回退包装器创建时只读一次缓存的闭包), +/// 槽缺失时委托内层自身 `next`;底层抛出异常时透传原始值(不做二次包装)。 pub fn iterator_wrapper_next(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(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(vm: &mut H, value: JsValue) -> Res Ok(false) } -fn get_iterator(vm: &mut H, value: JsValue) -> Result, JsValue> { +/// 经迭代协议取内层迭代器:内建集合直接作为内层(无缓存 next),`@@iterator` +/// 调用结果作为内层(无缓存 next),鸭子回退路径同时回传读到的 `next` 闭包 +/// 供调用方缓存进包装器 `__next__` 槽(消费期不重触发 getter)。 +/// +/// # 返回值 +/// - `Ok(Some((inner, next)))`:`inner` 为内层迭代器;鸭子回退时 `next` 为 +/// 读到的闭包,其余路径为 `None`; +/// - `Ok(None)`:不可迭代; +/// - `Err`:`@@iterator` 或 `next` getter 抛错,透传原异常值。 +fn get_iterator(vm: &mut H, value: JsValue) -> Result)>, 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(vm: &mut H, value: JsValue) -> Result "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) diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index eae4dcce..a9bebb3c 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -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();