From 53da6ef8178d8c193f43e76b202f827662ece849 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 14 Sep 2026 19:45:52 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20@@iterator=20=E8=A7=A3=E6=9E=90=E4=B8=8D?= =?UTF-8?q?=E5=88=B0=E5=8F=AF=E8=B0=83=E7=94=A8=E6=97=B6=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?=E5=8F=AF=E9=80=89=E5=85=A5=E5=8F=A3=E5=9B=9E=E9=80=80=20array-?= =?UTF-8?q?like?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/iterator.rs | 17 ++-- crates/oxide_builtins/src/object.rs | 14 ++-- crates/oxide_builtins/src/typed_array.rs | 24 ++++-- crates/oxide_vm/src/bindings/mod.rs | 13 ++++ .../oxide_vm/tests/builtins_iterator_tests.rs | 77 +++++++++++++++++++ 5 files changed, 126 insertions(+), 19 deletions(-) diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index ddc08c1d..2070bf8c 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1367,16 +1367,17 @@ pub fn iterator_wrapper_next(vm: &mut H, args: &[u8]) -> NativeResult /// 判断 value 是否可迭代,只读取 `@@iterator` 方法而不调用它(GetMethod 语义)。 /// -/// 与 [`get_iterator`] 的判定一致:内建集合(String/Array/TypedArray/Map/Set)恒可迭代; -/// 其它对象读取 `@@iterator`,可调用即视为可迭代,否则回退到自身可调用的 `next`。 +/// 与 [`get_iterator`] 的判定一致:统一经原型链解析 `@@iterator`(String 臂与 +/// `get_iterator` 同为无条件快速路径),解析到可调用才视为可迭代;解析为 +/// null/undefined(或非对象值)时返回 false,由调用方落 array-like 索引读臂。 +/// 不可调用时再回退到自身可调用的 `next`。 +/// +/// # 边界 +/// 非对象原始值(number/boolean 等)不装箱,直接判不可迭代。 /// `@@iterator` getter 抛错时透传 `Err`。 pub(crate) fn peek_iterator_method(vm: &mut H, value: JsValue) -> Result { - if value.is_string() - || is_array_value(value) - || is_typed_array_value(value) - || is_map_value(value) - || is_set_value(value) - { + // String 臂无条件可迭代(与 get_iterator 的 String 臂同一近似)。 + if value.is_string() { return Ok(true); } if value.is_object() { diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 7e6d3470..56dd5017 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -104,11 +104,12 @@ pub fn key_si_to_js_value(vm: &mut H, si: u32) -> JsValue { } /// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。 -/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键,供 `getOwnPropertySymbols` 使用。 +/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键。返回 `(属性键 si, 绝对存储索引)`: +/// 槽位计数须含全部非空节点(含字符串键)并对数组加元素区偏移,与物理存储及 +/// [`walk_own_keys`] 的口径一致,消费方(delete 重建)才能按槽位取回正确值。 fn walk_own_symbol_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { let mut keys: Vec<(u32, u32)> = Vec::new(); let shape_id = obj.shape_id(); - let mut pos: u32 = 0; let mut shape_ids = Vec::new(); let mut cursor = Some(shape_id); while let Some(id) = cursor { @@ -117,20 +118,21 @@ fn walk_own_symbol_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { } if let Some(shape) = vm.kernel_core().shape_forge().get_shape(id) { cursor = shape.parent; - if shape.property_name != u32::MAX && is_symbol_key(shape.property_name) { + if shape.property_name != u32::MAX { shape_ids.push(id); } } else { break; } } - for id in shape_ids.iter().rev() { + for (pos, id) in (0_u32..).zip(shape_ids.iter().rev()) { if let Some(shape) = vm.kernel_core().shape_forge().get_shape(*id) { + // Symbol 键节点按绝对槽位回传(数组命名属性位于元素区之后)。 + let store = if obj.is_array() { obj.array_prop_count + pos } else { pos }; if shape.property_name != 0 && is_symbol_key(shape.property_name) { - keys.push((shape.property_name, pos)); + keys.push((shape.property_name, store)); } } - pos += 1; } keys } diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 3c1b3097..0a4fa06c 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -406,7 +406,16 @@ fn write_element(vm: &mut H, kind: TypedArrayKind, bytes: &mut [u8], } } -fn collect_array_like(vm: &mut H, value: JsValue) -> Result, JsValue> { +/// 收集源对象的元素值。 +/// +/// # 步骤 +/// 1. Array/TypedArray 源走元素区直读(不经迭代协议)。 +/// 2. `consult_iterator` 为 true 且 `@@iterator` 解析到可调用时走迭代路径 +/// (含用户自定义迭代器);否则按 array-like 读 `length` 逐索引取值。 +/// +/// # 边界 +/// 源须为对象,否则抛 TypeError。 +fn collect_array_like(vm: &mut H, value: JsValue, consult_iterator: bool) -> Result, JsValue> { if !value.is_object() { return Err(type_error(vm, "TypedArray source must be array-like or iterable")); } @@ -428,8 +437,9 @@ fn collect_array_like(vm: &mut H, value: JsValue) -> Result(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> } (first, byte_offset, length) } else { - let values = native_try!(collect_array_like(vm, first)); + let values = native_try!(collect_array_like(vm, first, true)); let byte_len = values.len().saturating_mul(bpe); let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len])); let buffer_ptr = native_try!(array_buffer_data_ptr(vm, buffer)); @@ -646,6 +656,10 @@ pub fn typed_array_subarray(vm: &mut H, args: &[u8]) -> NativeResult /// `TypedArray.prototype.set(source, offset)`:从 array-like/另一个 TypedArray 拷贝元素; /// 越界抛 RangeError。 +/// +/// # 边界 +/// 源为 TypedArray 时直读元素区;否则恒按 array-like 索引读 +/// (不咨询源的 `@@iterator`,即便其可调用)。 pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); @@ -658,7 +672,7 @@ pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { } else { 0 }; - let values = native_try!(collect_array_like(vm, source)); + let values = native_try!(collect_array_like(vm, source, false)); if offset > view.length || values.len() > view.length - offset { return NativeResult::Err(range_error(vm, "TypedArray.set offset out of bounds")); } diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 68d755fd..56f2efac 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -332,6 +332,13 @@ pub(crate) fn bind_well_known_method( } /// 把原型上 `source` 属性已绑定的函数值复制到 well-known symbol 键名下(共享同一函数对象)。 +/// +/// # 边界 +/// `source` 键不在原型上时静默跳过。 +/// +/// # 注意 +/// well-known 键已在位时原位更新值,不追加重复 shape 槽:初始化与重绑可多轮 +/// 经过同一原型,重复槽会让 delete 只删其一、读仍命中残留槽。 pub(crate) fn bind_well_known_method_alias( core: &Arc, proto: &mut JsObject, source: &str, well_known_id: u32, ) { @@ -343,6 +350,12 @@ pub(crate) fn bind_well_known_method_alias( }; let value = proto.get_prop_at(pos); let key = oxide_types::private_key::make_well_known_symbol_key(well_known_id); + if let Some(existing) = shape_forge.lookup_position(proto.shape_id(), key) { + proto.set_prop_at(existing, value); + proto.set_data_meta(existing, PropAttributes::new(true, false, true)); + proto.bump_generation(); + return; + } let new_shape = shape_forge.make_shape(proto.shape_id(), key); proto.set_shape_id(new_shape); let alias_pos = proto.push_prop(value); diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index 0ec749bd..b9605cfe 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -2171,3 +2171,80 @@ fn for_of_throws_when_array_prototype_iterator_deleted() { .unwrap(); assert_eq!(to_str(&vm, r), "TypeError"); } + +#[test] +fn array_from_falls_back_to_array_like_when_iterator_unresolvable() { + let mut vm = Vm::new(); + // 红形钉:真集合的 @@iterator 解析为 null/undefined(原型删除、自身置 null) + // 时,Array.from 须经 GetMethod-可选语义落 array-like 索引读臂,红形为误抛 + // TypeError。 + let r = eval( + &mut vm, + "(() => { + const old = Array.prototype[Symbol.iterator]; + delete Array.prototype[Symbol.iterator]; + let deleted; + try { deleted = Array.from([1, 2]).join(','); } + catch (e) { deleted = e.constructor.name; } + finally { Array.prototype[Symbol.iterator] = old; } + const own = [1, 2]; + own[Symbol.iterator] = null; + let ownNull; + try { ownNull = Array.from(own).join(','); } + catch (e) { ownNull = e.constructor.name; } + return deleted + '|' + ownNull; + })()", + ) + .unwrap(); + assert_eq!(to_str(&vm, r), "1,2|1,2"); +} + +#[test] +fn typed_array_ctor_and_set_fall_back_when_iterator_unresolvable() { + let mut vm = Vm::new(); + // 红形钉:真集合的 @@iterator 被删除时,TA 构造器(GetMethod-可选语义) + // 须落 array-like 臂(无 length 得 0 元素),set 恒索引读;红形为误抛 + // TypeError。真数组经构造器恒走元素区直读(不受 @@iterator 解析影响), + // 一并作回归守卫。 + let r = eval( + &mut vm, + "(() => { + const old = Map.prototype[Symbol.iterator]; + delete Map.prototype[Symbol.iterator]; + const m = new Map(); m.set(7, 8); + let ctor; + try { ctor = new Uint8Array(m).length; } + catch (e) { ctor = e.constructor.name; } + let setResult; + const t = new Uint8Array([5, 6]); + try { t.set(m); setResult = t.join(','); } + catch (e) { setResult = e.constructor.name; } + let arrCtor; + try { arrCtor = new Uint8Array([1, 2, 3]).join(','); } + catch (e) { arrCtor = e.constructor.name; } + Map.prototype[Symbol.iterator] = old; + return ctor + '|' + setResult + '|' + arrCtor; + })()", + ) + .unwrap(); + assert_eq!(to_str(&vm, r), "0|5,6|1,2,3"); +} + +#[test] +fn typed_array_set_reads_array_like_source_by_index() { + let mut vm = Vm::new(); + // 红形钉:TA.set 对非 TypedArray 源恒按 array-like 索引读,不咨询源的 + // @@iterator(即便其可调用);红形为走用户迭代器得 99 填充。 + let r = eval( + &mut vm, + "(() => { + const src = { 0: 7, 1: 8, length: 2 }; + src[Symbol.iterator] = function* () { yield 99; }; + const t = new Uint8Array(4); + t.set(src); + return t.join(','); + })()", + ) + .unwrap(); + assert_eq!(to_str(&vm, r), "7,8,0,0"); +}