mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 数组 push/pop/shift/unshift 按规范接收 arraylike 与字符串接收者
This commit is contained in:
@@ -48,19 +48,39 @@ fn delete_prop_or_throw<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32)
|
||||
/// `Array.prototype.push(...items)`:追加元素到尾部,返回新长度。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读 `LengthOfArrayLike(this)` 作起始索引。
|
||||
/// 2. 每个实参按当前索引生成属性键,以严格模式 `[[Set]]` 写入(继承 setter /
|
||||
/// 1. `ToObject` 装箱基元 this(null/undefined 抛 TypeError),装箱体钉入返回
|
||||
/// 寄存器跨用户窗口保 GC 根;长度按规范 `ToLength(Get(O, "length"))`
|
||||
/// 读取(上限 2^53-1)。
|
||||
/// 2. `len + 参数数 > 2^53-1` 抛 TypeError("Invalid array length")。
|
||||
/// 3. 每个实参按当前索引生成属性键,以严格模式 `[[Set]]` 写入(继承 setter /
|
||||
/// 不可写元素按规范抛 TypeError),写入成功后才递增索引。
|
||||
/// 3. 以 `Set(this, "length", len, true)` 收尾,length 不可写时抛 TypeError。
|
||||
/// 4. 以 `Set(this, "length", len, true)` 收尾,length 不可写时抛 TypeError。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 修改元素区与 length;元素写入可经原型链触发用户 setter。
|
||||
pub fn array_push<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("Array.prototype.push called with {} args", args.len());
|
||||
let arr_ptr = array_ptr!(vm, args);
|
||||
let recv = JsValue::from_js_object(arr_ptr);
|
||||
let mut len = unsafe { &*arr_ptr }.logical_len() as usize;
|
||||
|
||||
// ToObject:基元 this 装箱(null/undefined 抛 TypeError)。
|
||||
let this_val = match oxide_runtime_api::to_object(vm.reg(args[0]), vm) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||||
};
|
||||
// 装箱体钉入返回寄存器:它只存于 Rust 局部,this 寄存器持原始基元(不在根集),
|
||||
// 跨 length getter / 元素 Set 用户窗口须保持 GC 根。call 转发形态的实参寄存器集
|
||||
// 可占该槽,占位时跳过钉位(装箱体按接收者值传递保活)。
|
||||
if !args.contains(&0) {
|
||||
vm.set_reg(0, this_val);
|
||||
}
|
||||
let (arr_ptr, mut len, _is_array) = match get_this_arraylike(vm, this_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
let recv = this_val;
|
||||
let n_items = args.len().saturating_sub(1);
|
||||
// len + 参数数超 2^53-1 抛 TypeError(node 消息 "Invalid array length")。
|
||||
if len + n_items > 9_007_199_254_740_991 {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Invalid array length"));
|
||||
}
|
||||
for &arg_reg in args.iter().skip(1) {
|
||||
let key = vm.string_key_si(&len.to_string());
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, key, vm.reg(arg_reg), recv, true) {
|
||||
@@ -80,16 +100,34 @@ pub fn array_push<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// `Array.prototype.pop()`:移除并返回末位元素;空数组返回 undefined。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读 `LengthOfArrayLike(this)`;为 0 时以 `Set(this, "length", +0, true)` 收尾。
|
||||
/// 2. 否则按 Get / DeletePropertyOrThrow / Set length 的顺序处理末位元素。
|
||||
/// 1. `ToObject` 装箱基元 this(null/undefined 抛 TypeError),装箱体钉入返回
|
||||
/// 寄存器跨用户窗口保 GC 根;长度按规范 `ToLength(Get(O, "length"))`
|
||||
/// 读取(上限 2^53-1)。
|
||||
/// 2. 长度为 0 时以 `Set(this, "length", +0, true)` 收尾返回 undefined。
|
||||
/// 3. 否则 Get 末位元素(钉入返回寄存器跨后续窗口保 GC 根),
|
||||
/// DeletePropertyOrThrow 末位(不可配置自有索引抛 TypeError),
|
||||
/// 以 `Set(this, "length", len-1, true)` 收尾,返回末位元素。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 删除末位元素并收缩 length;Get 可触发原型链 getter,length 不可写时抛 TypeError。
|
||||
pub fn array_pop<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("Array.prototype.pop called with {} args", args.len());
|
||||
let arr_ptr = array_ptr!(vm, args);
|
||||
let recv = JsValue::from_js_object(arr_ptr);
|
||||
let len = unsafe { &*arr_ptr }.logical_len() as usize;
|
||||
// ToObject:基元 this 装箱(null/undefined 抛 TypeError)。
|
||||
let this_val = match oxide_runtime_api::to_object(vm.reg(args[0]), vm) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||||
};
|
||||
// 装箱体钉入返回寄存器:它只存于 Rust 局部,this 寄存器持原始基元(不在根集),
|
||||
// 跨 length getter 用户窗口须保持 GC 根;末位元素钉入后此槽让位,其后装箱体
|
||||
// 按接收者值传递保活。call 转发形态的实参寄存器集可占该槽,占位时跳过钉位。
|
||||
if !args.contains(&0) {
|
||||
vm.set_reg(0, this_val);
|
||||
}
|
||||
let (arr_ptr, len, _is_array) = match get_this_arraylike(vm, this_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
let recv = this_val;
|
||||
let length_si = vm.string_key_si("length");
|
||||
if len == 0 {
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, length_si, JsValue::int(0), recv, true) {
|
||||
@@ -104,18 +142,17 @@ pub fn array_pop<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||||
};
|
||||
|
||||
// DeletePropertyOrThrow:不可配置元素删除失败抛 TypeError,否则标记为 hole。
|
||||
match unsafe { &*arr_ptr }.prop_meta_at(index) {
|
||||
Some(meta) if !meta.attributes.configurable() => {
|
||||
return NativeResult::Err(array_type_error(vm, "Cannot delete property"));
|
||||
}
|
||||
_ => unsafe { (*arr_ptr).mark_hole_at(index) },
|
||||
// 末位元素可能来自原型链 getter 的全新临时对象:钉入返回寄存器
|
||||
// 跨 delete / Set length 用户窗口保 GC 根,收尾自钉位读回。
|
||||
vm.set_reg(0, last);
|
||||
// DeletePropertyOrThrow:不可配置自有索引删除失败抛 TypeError,hole / 缺失位空操作。
|
||||
if let Err(err) = delete_prop_or_throw(vm, unsafe { &mut *arr_ptr }, key) {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, length_si, js_array_index(index), recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
NativeResult::Ok(last)
|
||||
NativeResult::Ok(vm.reg(0))
|
||||
}
|
||||
|
||||
/// `Array.prototype.slice(start, end)`:复制区间元素返回新数组(支持负索引)。
|
||||
@@ -900,18 +937,35 @@ pub fn array_flat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// `Array.prototype.shift()`:移除并返回首元素,其余元素前移;空数组返回 undefined。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读 `LengthOfArrayLike(this)`;为 0 时以 `Set(this, "length", +0, true)` 收尾。
|
||||
/// 2. Get 首元素后,把 `1..len` 逐位前移:源存在则 Get + 严格 Set 到前一格,
|
||||
/// 源缺失(hole)则 DeletePropertyOrThrow 目标格。
|
||||
/// 3. 以 `Set(this, "length", len-1, true)` 收尾。
|
||||
/// 1. `ToObject` 装箱基元 this(null/undefined 抛 TypeError),装箱体钉入返回
|
||||
/// 寄存器跨用户窗口保 GC 根;长度按规范 `ToLength(Get(O, "length"))`
|
||||
/// 读取(上限 2^53-1)。
|
||||
/// 2. 长度为 0 时以 `Set(this, "length", +0, true)` 收尾返回 undefined。
|
||||
/// 3. Get 首元素并钉入返回寄存器;`1..len` 逐位前移:HasProperty 命中
|
||||
/// (自身与原型链,hole 视同缺失)则 Get + 严格 Set 到前一格,缺失则
|
||||
/// DeletePropertyOrThrow 目标格。
|
||||
/// 4. 末位 DeletePropertyOrThrow 后以 `Set(this, "length", len-1, true)` 收尾。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 元素整体前移并收缩 length;Get/Set 可经原型链触发用户代码。
|
||||
pub fn array_shift<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("Array.prototype.shift called with {} args", args.len());
|
||||
let arr_ptr = array_ptr!(vm, args);
|
||||
let recv = JsValue::from_js_object(arr_ptr);
|
||||
let len = unsafe { &*arr_ptr }.logical_len() as usize;
|
||||
// ToObject:基元 this 装箱(null/undefined 抛 TypeError)。
|
||||
let this_val = match oxide_runtime_api::to_object(vm.reg(args[0]), vm) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||||
};
|
||||
// 装箱体钉入返回寄存器:它只存于 Rust 局部,this 寄存器持原始基元(不在根集),
|
||||
// 跨 length getter 用户窗口须保持 GC 根;首位元素钉入后此槽让位,其后装箱体
|
||||
// 按接收者值传递保活。call 转发形态的实参寄存器集可占该槽,占位时跳过钉位。
|
||||
if !args.contains(&0) {
|
||||
vm.set_reg(0, this_val);
|
||||
}
|
||||
let (arr_ptr, len, _is_array) = match get_this_arraylike(vm, this_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
let recv = this_val;
|
||||
let length_si = vm.string_key_si("length");
|
||||
if len == 0 {
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, length_si, JsValue::int(0), recv, true) {
|
||||
@@ -930,28 +984,30 @@ pub fn array_shift<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
vm.set_reg(0, first);
|
||||
|
||||
for k in 1..len {
|
||||
let from = vm.string_key_si(&k.to_string());
|
||||
let to = vm.string_key_si(&(k - 1).to_string());
|
||||
// HasProperty 走原型链存在性判定(hole 视同缺失),命中才 Get + Set。
|
||||
if vm.resolve_property(unsafe { &*arr_ptr }, from).is_some() {
|
||||
let val = match vm.ordinary_get(unsafe { &*arr_ptr }, from, recv) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||||
};
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, to, val, recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
} else {
|
||||
// DeletePropertyOrThrow(O, to):不可配置元素删除失败抛 TypeError。
|
||||
match unsafe { &*arr_ptr }.prop_meta_at(k - 1) {
|
||||
Some(meta) if !meta.attributes.configurable() => {
|
||||
return NativeResult::Err(array_type_error(vm, "Cannot delete property"));
|
||||
match gated_get(vm, arr_ptr, k, recv) {
|
||||
Ok(Some(val)) => {
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, to, val, recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
_ => unsafe { (*arr_ptr).mark_hole_at(k - 1) },
|
||||
}
|
||||
Ok(None) => {
|
||||
// DeletePropertyOrThrow(O, to):不可配置元素删除失败抛 TypeError。
|
||||
if let Err(err) = delete_prop_or_throw(vm, unsafe { &mut *arr_ptr }, to) {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
}
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
// 末位 DeletePropertyOrThrow:真数组上该位已由 length 写入截断覆盖,
|
||||
// arraylike 上该位不在搬移路径内,须显式删除。
|
||||
let last_key = vm.string_key_si(&(len - 1).to_string());
|
||||
if let Err(err) = delete_prop_or_throw(vm, unsafe { &mut *arr_ptr }, last_key) {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, length_si, js_array_index(len - 1), recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
@@ -962,50 +1018,69 @@ pub fn array_shift<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// `Array.prototype.unshift(...items)`:插入元素到头部,返回新长度。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读 `LengthOfArrayLike(this)`;`len + 参数数` 超出 2^53-1 时抛 RangeError。
|
||||
/// 2. 参数数大于 0 时自高到低搬移已有元素(源存在则 Get + 严格 Set,缺失则
|
||||
/// DeletePropertyOrThrow),再把实参逐个严格 Set 到头部。
|
||||
/// 3. 以 `Set(this, "length", len+参数数, true)` 收尾。
|
||||
/// 1. `ToObject` 装箱基元 this(null/undefined 抛 TypeError),装箱体钉入返回
|
||||
/// 寄存器跨用户窗口保 GC 根;长度按规范 `ToLength(Get(O, "length"))`
|
||||
/// 读取(上限 2^53-1)。
|
||||
/// 2. 参数数大于 0 且 `len + 参数数 > 2^53-1` 抛 TypeError(现行规范文本,
|
||||
/// 非 RangeError)。
|
||||
/// 3. 参数数大于 0 时自高到低搬移已有元素:HasProperty 命中(自身与原型链,
|
||||
/// hole 视同缺失)则 Get + 严格 Set(搬移值钉入返回寄存器跨 Set 窗口保
|
||||
/// GC 根),缺失则 DeletePropertyOrThrow,再把实参逐个严格 Set 到头部。
|
||||
/// 4. 以 `Set(this, "length", len+参数数, true)` 收尾。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 元素整体后移并扩容 length;Get/Set 可经原型链触发用户代码。
|
||||
pub fn array_unshift<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("Array.prototype.unshift called with {} args", args.len());
|
||||
let arr_ptr = array_ptr!(vm, args);
|
||||
let recv = JsValue::from_js_object(arr_ptr);
|
||||
let len = unsafe { &*arr_ptr }.logical_len() as usize;
|
||||
// ToObject:基元 this 装箱(null/undefined 抛 TypeError)。
|
||||
let this_val = match oxide_runtime_api::to_object(vm.reg(args[0]), vm) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||||
};
|
||||
// 装箱体钉入返回寄存器:它只存于 Rust 局部,this 寄存器持原始基元(不在根集),
|
||||
// 跨 length getter / 搬移窗口用户窗口须保持 GC 根;搬移值钉入后此槽让位,
|
||||
// 其后装箱体按接收者值传递保活。call 转发形态的实参寄存器集可占该槽,
|
||||
// 占位时跳过钉位。
|
||||
if !args.contains(&0) {
|
||||
vm.set_reg(0, this_val);
|
||||
}
|
||||
let (arr_ptr, len, _is_array) = match get_this_arraylike(vm, this_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
let recv = this_val;
|
||||
let n_items = args.len().saturating_sub(1);
|
||||
let length_si = vm.string_key_si("length");
|
||||
// 实参值先读入局部:搬移循环把搬移值钉入返回寄存器,可能覆写实参寄存器
|
||||
// 槽,读取须先于钉位完成。
|
||||
let arg_values: Vec<JsValue> = args.iter().skip(1).map(|&r| vm.reg(r)).collect();
|
||||
if n_items > 0 {
|
||||
if len as f64 + n_items as f64 > 9_007_199_254_740_991.0 {
|
||||
// 长度上限越界按规范为 RangeError。
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid array length"));
|
||||
// len + 参数数超 2^53-1 抛 TypeError(规范现行文本,node 实测同形)。
|
||||
if len + n_items > 9_007_199_254_740_991 {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Invalid array length"));
|
||||
}
|
||||
for k in (1..=len).rev() {
|
||||
let from = vm.string_key_si(&(k - 1).to_string());
|
||||
let to_idx = k - 1 + n_items;
|
||||
let to = vm.string_key_si(&to_idx.to_string());
|
||||
if vm.resolve_property(unsafe { &*arr_ptr }, from).is_some() {
|
||||
let val = match vm.ordinary_get(unsafe { &*arr_ptr }, from, recv) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||||
};
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, to, val, recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
} else {
|
||||
// DeletePropertyOrThrow(O, to):不可配置元素删除失败抛 TypeError。
|
||||
match unsafe { &*arr_ptr }.prop_meta_at(to_idx) {
|
||||
Some(meta) if !meta.attributes.configurable() => {
|
||||
return NativeResult::Err(array_type_error(vm, "Cannot delete property"));
|
||||
match gated_get(vm, arr_ptr, k - 1, recv) {
|
||||
Ok(Some(val)) => {
|
||||
// 搬移值跨 Set 用户调用窗口:先钉返回寄存器再写,写后从钉位读回。
|
||||
vm.set_reg(0, val);
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, to, vm.reg(0), recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
_ => unsafe { (*arr_ptr).mark_hole_at(to_idx) },
|
||||
}
|
||||
Ok(None) => {
|
||||
if let Err(err) = delete_prop_or_throw(vm, unsafe { &mut *arr_ptr }, to) {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
}
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
}
|
||||
for (j, &arg_reg) in args.iter().skip(1).enumerate() {
|
||||
for (j, val) in arg_values.iter().enumerate() {
|
||||
let key = vm.string_key_si(&j.to_string());
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, key, vm.reg(arg_reg), recv, true) {
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, key, *val, recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! splice/reverse/copyWithin/at 四方法洞语义与原型链读语义引擎钉:
|
||||
//! 源洞保洞/换洞/删洞、重叠反向复制、元素读走 Get。
|
||||
//! 源洞保洞/换洞/删洞、重叠反向复制、元素读走 Get;
|
||||
//! push/pop/shift/unshift arraylike 接收者族:ToObject 通用入口、
|
||||
//! ToLength 长度源、2^53-1 上限、DeletePropertyOrThrow 删位、末位显式删。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1440,3 +1442,167 @@ fn test_slice_plain_array_guard() {
|
||||
.unwrap();
|
||||
assert_eq!(out, "true|1,2,3");
|
||||
}
|
||||
|
||||
// ── push/pop/shift/unshift arraylike 接收者族收口钉(node v20.19.2 实测值钉死) ──
|
||||
|
||||
// 引擎钉:push arraylike 接收者——length 经 ToLength 折算(1.5→1),尾部写值,
|
||||
// length 置新长度。
|
||||
#[test]
|
||||
fn test_push_arraylike_receiver_length_coerce() {
|
||||
let out = eval_str(
|
||||
"(() => { const o = { length: 1.5, 0: 'a' }; \
|
||||
const r = Array.prototype.push.call(o, -13); \
|
||||
return r + '|' + o[1] + '|' + o.length; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "2|-13|2");
|
||||
}
|
||||
|
||||
// 引擎钉:push arraylike valueOf length——ToObject 后 ToLength 取整定界。
|
||||
#[test]
|
||||
fn test_push_arraylike_value_of_length() {
|
||||
let out = eval_str(
|
||||
"(() => { const r = Array.prototype.push.call({ length: { valueOf() { return 3; } } }); \
|
||||
return String(r); })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "3");
|
||||
}
|
||||
|
||||
// 引擎钉:push ToObject 装箱基元 this——Boolean 包装体 length 0,返回 0。
|
||||
#[test]
|
||||
fn test_push_primitive_this_boxed() {
|
||||
let out = eval_str("(() => { return String(Array.prototype.push.call(true)); })()").unwrap();
|
||||
assert_eq!(out, "0");
|
||||
}
|
||||
|
||||
// 引擎钉:push 2^53-1 预检——len + 参数数越限抛 TypeError。
|
||||
#[test]
|
||||
fn test_push_integer_limit_throws_type_error() {
|
||||
let out = eval_str(
|
||||
"(() => { const o = {}; o.length = 9007199254740991; \
|
||||
let t = '?'; try { Array.prototype.push.call(o, 1); } catch (e) { t = e.name; } \
|
||||
return t; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "TypeError");
|
||||
}
|
||||
|
||||
// 引擎钉:pop arraylike 2^53 长度——ToLength 钳 2^53-1,pop 末位(undefined),
|
||||
// length 收 2^53-2。
|
||||
#[test]
|
||||
fn test_pop_arraylike_integer_limit() {
|
||||
let out = eval_str(
|
||||
"(() => { const o = { length: 9007199254740992 }; \
|
||||
const r = Array.prototype.pop.call(o); \
|
||||
return String(r === undefined) + '|' + o.length; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "true|9007199254740990");
|
||||
}
|
||||
|
||||
// 引擎钉:pop 末位非可配置自有索引——DeletePropertyOrThrow 抛 TypeError。
|
||||
#[test]
|
||||
fn test_pop_last_slot_non_configurable_throws() {
|
||||
let out = eval_str(
|
||||
"(() => { const o = { length: 1, 0: 5 }; \
|
||||
Object.defineProperty(o, '0', { value: 5, writable: true, enumerable: true, configurable: false }); \
|
||||
let t = '?'; try { Array.prototype.pop.call(o); } catch (e) { t = e.name; } \
|
||||
return t; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "TypeError");
|
||||
}
|
||||
|
||||
// 引擎钉:pop 原型链继承末位元素——Get 读继承值,末位非自有删位空操作,
|
||||
// 继承读持续可读。
|
||||
#[test]
|
||||
fn test_pop_inherited_last_element() {
|
||||
let out = eval_str(
|
||||
"(() => { Array.prototype[1] = 1; \
|
||||
try { const x = [0]; x.length = 2; const r = x.pop(); \
|
||||
return r + '|' + x.length + '|' + x[1]; } \
|
||||
finally { delete Array.prototype[1]; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "1|1|1");
|
||||
}
|
||||
|
||||
// 引擎钉:shift arraylike 接收者——缺失中位删目标格、present 位前移、
|
||||
// 末位显式删除。
|
||||
#[test]
|
||||
fn test_shift_arraylike_receiver() {
|
||||
let out = eval_str(
|
||||
"(() => { const o = { 0: 0, 3: 3, length: 4 }; \
|
||||
const r = Array.prototype.shift.call(o); \
|
||||
return r + '|' + o.length + '|' + (3 in o) + '|' + o[2]; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "0|3|false|3");
|
||||
}
|
||||
|
||||
// 引擎钉:shift ToObject 装箱基元 this——Boolean 包装体 length 0,返回 undefined。
|
||||
#[test]
|
||||
fn test_shift_primitive_this_boxed() {
|
||||
let out = eval_str("(() => { return String(Array.prototype.shift.call(true)); })()").unwrap();
|
||||
assert_eq!(out, "undefined");
|
||||
}
|
||||
|
||||
// 引擎钉:unshift 无参 2^53-1 极限——argCount 0 不查限,返回 2^53-1。
|
||||
#[test]
|
||||
fn test_unshift_no_args_integer_limit_no_check() {
|
||||
let out = eval_str(
|
||||
"(() => { const o = {}; o.length = 9007199254740991; \
|
||||
const r = Array.prototype.unshift.call(o); \
|
||||
return String(r === 9007199254740991) + '|' + o.length; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "true|9007199254740991");
|
||||
}
|
||||
|
||||
// 引擎钉:unshift 2^53-1 预检——len + 参数数越限抛 TypeError(现行规范文本,
|
||||
// 非 RangeError)。
|
||||
#[test]
|
||||
fn test_unshift_integer_limit_throws_type_error() {
|
||||
let out = eval_str(
|
||||
"(() => { const o = {}; o.length = 9007199254740991; \
|
||||
let t = '?'; try { Array.prototype.unshift.call(o, 1); } catch (e) { t = e.name; } \
|
||||
return t; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "TypeError");
|
||||
}
|
||||
|
||||
// 引擎钉:unshift arraylike 接收者——已有元素右移、实参填头、length 扩容。
|
||||
#[test]
|
||||
fn test_unshift_arraylike_receiver() {
|
||||
let out = eval_str(
|
||||
"(() => { const o = { length: 1, 0: 'a' }; \
|
||||
const r = Array.prototype.unshift.call(o, 'x'); \
|
||||
return r + '|' + o[0] + '|' + o[1]; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "2|x|a");
|
||||
}
|
||||
|
||||
// 引擎钉:unshift ToObject 装箱基元 this——Boolean 包装体 length 0,两实参返回 2。
|
||||
#[test]
|
||||
fn test_unshift_primitive_this_boxed() {
|
||||
let out = eval_str("(() => { return String(Array.prototype.unshift.call(false, 7, 8)); })()").unwrap();
|
||||
assert_eq!(out, "2");
|
||||
}
|
||||
|
||||
// 引擎钉:pop 末位 getter 返回冻结哨兵对象跨 delete/Set-length 用户窗口——
|
||||
// 身份保持(钉位语义的可观察契约)。
|
||||
#[test]
|
||||
fn test_pop_getter_sentinel_identity_across_window() {
|
||||
let out = eval_str(
|
||||
"(() => { const a = [10, 20]; const sentinel = Object.freeze({ tag: 'S' }); \
|
||||
Object.defineProperty(a, 1, { get() { const junk = []; for (let i = 0; i < 100000; i++) junk.push({ i }); return sentinel; }, \
|
||||
configurable: true }); \
|
||||
const r = a.pop(); \
|
||||
return String(r === sentinel) + '|' + a.length; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "true|1");
|
||||
}
|
||||
|
||||
@@ -383,6 +383,15 @@ impl Vm {
|
||||
if obj.is_array() && prop_name_si == length_si {
|
||||
return self.set_array_length_value(obj, val, strict, builtin);
|
||||
}
|
||||
// 字符串 exotic 的 length 是 [[Writable]]: false 的固有数据属性(规范
|
||||
// String exotic [[Set]]):写不成立,严格抛 TypeError、sloppy 静默
|
||||
// no-op——不得落命名属性区。
|
||||
if obj.is_string_obj() && prop_name_si == length_si {
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "cannot assign to read-only property");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(pos) = self.get_own_property_slot(obj, prop_name_si) {
|
||||
if let Some(meta) = obj.prop_meta_at(pos) {
|
||||
if meta.is_accessor {
|
||||
|
||||
Reference in New Issue
Block a user