mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: @@iterator 解析不到可调用时迭代可选入口回退 array-like
This commit is contained in:
@@ -1367,16 +1367,17 @@ pub fn iterator_wrapper_next<H: VmHost>(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<H: VmHost>(vm: &mut H, value: JsValue) -> Result<bool, JsValue> {
|
||||
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() {
|
||||
|
||||
@@ -104,11 +104,12 @@ pub fn key_si_to_js_value<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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
|
||||
}
|
||||
|
||||
@@ -406,7 +406,16 @@ fn write_element<H: VmHost>(vm: &mut H, kind: TypedArrayKind, bytes: &mut [u8],
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_array_like<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Vec<JsValue>, JsValue> {
|
||||
/// 收集源对象的元素值。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. Array/TypedArray 源走元素区直读(不经迭代协议)。
|
||||
/// 2. `consult_iterator` 为 true 且 `@@iterator` 解析到可调用时走迭代路径
|
||||
/// (含用户自定义迭代器);否则按 array-like 读 `length` 逐索引取值。
|
||||
///
|
||||
/// # 边界
|
||||
/// 源须为对象,否则抛 TypeError。
|
||||
fn collect_array_like<H: VmHost>(vm: &mut H, value: JsValue, consult_iterator: bool) -> Result<Vec<JsValue>, 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<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Vec<JsVal
|
||||
.collect());
|
||||
}
|
||||
|
||||
// 有 @@iterator 走迭代路径(含用户自定义迭代器),否则按 array-like 读 length 逐索引取值。
|
||||
if crate::iterator::peek_iterator_method(vm, value)? {
|
||||
// 咨询迭代器的入口(构造器)仅在 @@iterator 解析到可调用时走迭代路径,
|
||||
// 否则落 array-like 读 length 逐索引取值(方法解析为 null/undefined 时同此)。
|
||||
if consult_iterator && crate::iterator::peek_iterator_method(vm, value)? {
|
||||
let mut values = Vec::new();
|
||||
crate::iterator::iterate_elements(vm, value, |_vm, elem| {
|
||||
values.push(elem);
|
||||
@@ -497,7 +507,7 @@ fn typed_array_new<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
|
||||
/// `TypedArray.prototype.set(source, offset)`:从 array-like/另一个 TypedArray 拷贝元素;
|
||||
/// 越界抛 RangeError。
|
||||
///
|
||||
/// # 边界
|
||||
/// 源为 TypedArray 时直读元素区;否则恒按 array-like 索引读
|
||||
/// (不咨询源的 `@@iterator`,即便其可调用)。
|
||||
pub fn typed_array_set<H: VmHost>(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<H: VmHost>(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"));
|
||||
}
|
||||
|
||||
@@ -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<KernelCore>, 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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user