mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 数组长度赋值走统一检查并支持部分截断
This commit is contained in:
@@ -9,37 +9,81 @@ use crate::builtins_debug;
|
||||
use crate::builtins_error;
|
||||
|
||||
use super::common::{
|
||||
array_ptr, arraylike_get, arraylike_get_or_err, check_array_create_len, clamp_relative, create_new_array,
|
||||
get_this_array_ref, get_this_arraylike, js_array_index, to_integer_or_infinity_bounded,
|
||||
array_ptr, array_type_error, arraylike_get, arraylike_get_or_err, check_array_create_len, clamp_relative,
|
||||
create_new_array, get_this_array_ref, get_this_arraylike, js_array_index, to_integer_or_infinity_bounded,
|
||||
};
|
||||
use super::from::{array_species_create, create_data_property_or_throw, from_engine_error};
|
||||
|
||||
/// `Array.prototype.push(...items)`:追加元素到尾部,返回新长度。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读 `LengthOfArrayLike(this)` 作起始索引。
|
||||
/// 2. 每个实参按当前索引生成属性键,以严格模式 `[[Set]]` 写入(继承 setter /
|
||||
/// 不可写元素按规范抛 TypeError),写入成功后才递增索引。
|
||||
/// 3. 以 `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;
|
||||
|
||||
for &arg_reg in args.iter().skip(1) {
|
||||
let val = vm.promote_if_needed_for_write_ptr(arr_ptr, vm.reg(arg_reg));
|
||||
// 元素写入须维护 array_prop_count(set_prop_at 对数组自动更新)。
|
||||
let arr = unsafe { &mut *arr_ptr };
|
||||
let idx = arr.prop_count();
|
||||
arr.set_prop_at(idx, val);
|
||||
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) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
len += 1;
|
||||
}
|
||||
let len = unsafe { &*arr_ptr }.prop_count();
|
||||
NativeResult::Ok(JsValue::int(len as i32))
|
||||
|
||||
// length 收尾同样走 Set:不可写 length 在此抛 TypeError。
|
||||
let length_si = vm.string_key_si("length");
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, length_si, js_array_index(len), recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
NativeResult::Ok(js_array_index(len))
|
||||
}
|
||||
|
||||
/// `Array.prototype.pop()`:移除并返回末位元素;空数组返回 undefined。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读 `LengthOfArrayLike(this)`;为 0 时以 `Set(this, "length", +0, true)` 收尾。
|
||||
/// 2. 否则按 Get / DeletePropertyOrThrow / Set length 的顺序处理末位元素。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 删除末位元素并收缩 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 arr = unsafe { &mut *arr_ptr };
|
||||
let len = arr.prop_count();
|
||||
let recv = JsValue::from_js_object(arr_ptr);
|
||||
let len = unsafe { &*arr_ptr }.logical_len() as usize;
|
||||
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) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let last = arr.get_prop_at(len - 1);
|
||||
arr.set_prop_count_fast(len - 1);
|
||||
let index = len - 1;
|
||||
let key = vm.string_key_si(&index.to_string());
|
||||
let last = match vm.ordinary_get(unsafe { &*arr_ptr }, key, recv) {
|
||||
Ok(v) => v,
|
||||
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) },
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -410,40 +454,118 @@ 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)` 收尾。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 元素整体前移并收缩 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 arr = unsafe { &mut *arr_ptr };
|
||||
let len = arr.prop_count();
|
||||
let recv = JsValue::from_js_object(arr_ptr);
|
||||
let len = unsafe { &*arr_ptr }.logical_len() as usize;
|
||||
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) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let first = arr.get_prop_at(0);
|
||||
for i in 1..len {
|
||||
let v = arr.get_prop_at(i);
|
||||
arr.set_prop_at(i - 1, v);
|
||||
let first_key = vm.string_key_si("0");
|
||||
let first = match vm.ordinary_get(unsafe { &*arr_ptr }, first_key, recv) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||||
};
|
||||
|
||||
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"));
|
||||
}
|
||||
_ => unsafe { (*arr_ptr).mark_hole_at(k - 1) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
arr.set_prop_count_fast(len - 1);
|
||||
NativeResult::Ok(first)
|
||||
}
|
||||
|
||||
/// `Array.prototype.unshift(...items)`:插入元素到头部,返回新长度。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读 `LengthOfArrayLike(this)`;`len + 参数数` 超出 2^53-1 时抛 TypeError。
|
||||
/// 2. 参数数大于 0 时自高到低搬移已有元素(源存在则 Get + 严格 Set,缺失则
|
||||
/// DeletePropertyOrThrow),再把实参逐个严格 Set 到头部。
|
||||
/// 3. 以 `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 arr = unsafe { &mut *arr_ptr };
|
||||
let len = arr.prop_count();
|
||||
let recv = JsValue::from_js_object(arr_ptr);
|
||||
let len = unsafe { &*arr_ptr }.logical_len() as usize;
|
||||
let n_items = args.len().saturating_sub(1);
|
||||
for i in (0..len as usize).rev() {
|
||||
let v = arr.get_prop_at(i);
|
||||
arr.set_prop_at(i + n_items, v);
|
||||
let length_si = vm.string_key_si("length");
|
||||
if n_items > 0 {
|
||||
if len as f64 + n_items as f64 > 9_007_199_254_740_991.0 {
|
||||
return NativeResult::Err(array_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"));
|
||||
}
|
||||
_ => unsafe { (*arr_ptr).mark_hole_at(to_idx) },
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j, &arg_reg) in args.iter().skip(1).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) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j, &arg_reg) in args.iter().skip(1).enumerate() {
|
||||
arr.set_prop_at(j, vm.reg(arg_reg));
|
||||
|
||||
let new_len = len + n_items;
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, length_si, js_array_index(new_len), recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
let new_len = len as usize + n_items;
|
||||
arr.set_prop_count_fast(new_len);
|
||||
NativeResult::Ok(JsValue::int(new_len as i32))
|
||||
NativeResult::Ok(js_array_index(new_len))
|
||||
}
|
||||
|
||||
/// `Array.prototype.fill(value, start, end)`:用给定值填充区间,返回 this。
|
||||
|
||||
@@ -96,7 +96,8 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
fn ordinary_set(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, strict: bool,
|
||||
) -> Result<(), String> {
|
||||
self.ordinary_set(obj, prop_name_si, val, receiver, strict)
|
||||
// builtin 调用边界:写失败返回格式化 Err,不就地展开(见 ordinary_set_builtin)。
|
||||
self.ordinary_set_builtin(obj, prop_name_si, val, receiver, strict)
|
||||
}
|
||||
fn define_data_property(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
|
||||
|
||||
+140
-95
@@ -308,19 +308,33 @@ impl Vm {
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, strict: bool,
|
||||
) -> Result<(), String> {
|
||||
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, false, strict)
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, false, strict, false)
|
||||
}
|
||||
|
||||
/// builtin 内部写入口:失败一律返回格式化 `Err` 由调用边界恢复为异常对象,
|
||||
/// 不就地 `unwind`——builtin 执行在重入的字节码上下文中,就地展开会跳入
|
||||
/// 调用方 catch 并让 builtin 继续,随后覆盖异常寄存器。
|
||||
pub(crate) fn ordinary_set_builtin(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, strict: bool,
|
||||
) -> Result<(), String> {
|
||||
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, false, strict, true)
|
||||
}
|
||||
|
||||
/// 分发期入口:调用方(dispatch_set_prop 等)已对值做过 promote。
|
||||
pub(crate) fn ordinary_set_dispatch(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, strict: bool,
|
||||
) -> Result<(), String> {
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, true, strict)
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, true, strict, false)
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::too_many_arguments,
|
||||
reason = "写路径须透传 receiver、帧化与 builtin 三组调用契约标志"
|
||||
)]
|
||||
fn ordinary_set_inner(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, use_frame_push: bool,
|
||||
strict: bool,
|
||||
strict: bool, builtin: bool,
|
||||
) -> Result<(), String> {
|
||||
vm_trace!(
|
||||
"ordinary_set_inner: shape={} prop_si={} frame_push={}",
|
||||
@@ -332,7 +346,7 @@ impl Vm {
|
||||
// sloppy 静默 no-op;Reflect.set 以 strict=true 调用并投影为 false。
|
||||
if obj.is_module_namespace() {
|
||||
if strict {
|
||||
return self.raise_type_error("Cannot assign to a module namespace export");
|
||||
return self.write_protection_failure(builtin, "Cannot assign to a module namespace export");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -349,86 +363,18 @@ impl Vm {
|
||||
index as usize,
|
||||
use_frame_push,
|
||||
strict,
|
||||
builtin,
|
||||
);
|
||||
}
|
||||
return oxide_builtins::typed_array::typed_array_element_set(self, obj, index, val);
|
||||
}
|
||||
}
|
||||
// 数组 length 赋值:ArraySetLength 语义(ToUint32 + 调整元素区)。
|
||||
// length 不得落影子命名属性:否则 `arr.length = N` 后 prop_count /
|
||||
// 迭代 / 内置方法看到的长度与元素区不一致。
|
||||
// 数组 length 赋值:走 ArraySetLength 语义(两次数值强转、可写性判定与
|
||||
// 元素区调整),不得落影子命名属性——否则 prop_count / 迭代 / 内置方法
|
||||
// 看到的长度与元素区不一致。
|
||||
let length_si = self.length_si;
|
||||
if obj.is_array() && prop_name_si == length_si {
|
||||
// 冻结数组的 length 属性不可写(writable=false),赋值直接失败;
|
||||
// 两模式统一抛 TypeError(length 冻结检查的 strict/sloppy 差异不在本路径范围)。
|
||||
if obj.is_frozen() {
|
||||
return self.raise_type_error("Cannot assign to read only property 'length'");
|
||||
}
|
||||
// defineProperty 显式收窄的不可写 length:严格模式抛 TypeError,sloppy 静默。
|
||||
if !obj.is_length_writable() {
|
||||
if strict {
|
||||
return self.raise_type_error("Cannot assign to read only property 'length'");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let pc_before = self.pc;
|
||||
let number_len = self.coerce_number_bounded(val)?;
|
||||
// ToPrimitive 抛错(valueOf/toString throw)已被 unwind 定向到外围 catch 时
|
||||
// pc 指向 catch 入口:主 dispatch 约定异常后 opcode 不得继续 raise,直接
|
||||
// 返回由 dispatch 继续执行 catch,避免二次抛错覆盖原异常。
|
||||
if self.pc != pc_before {
|
||||
return Ok(());
|
||||
}
|
||||
let raw_new_len = if number_len == 0.0 || !number_len.is_finite() {
|
||||
0
|
||||
} else {
|
||||
number_len.trunc().rem_euclid(4_294_967_296.0) as u32 as usize
|
||||
};
|
||||
// ToUint32 截断结果与 ToNumber 不等价(1.5 / NaN / Infinity / 负数 /
|
||||
// 2**32 等输入)时抛 RangeError:赋值后 length 读不回原值。
|
||||
if raw_new_len as f64 != number_len {
|
||||
return self.raise_error_kind("RangeError", "Invalid array length");
|
||||
}
|
||||
let old_logical = obj.logical_len() as usize;
|
||||
let old_count = obj.array_prop_count as usize;
|
||||
// ArraySetLength:增长(newLen > oldLen)要求对象可扩展,不可扩展时
|
||||
// 整个赋值失败且不修改(length 失败两模式均静默 no-op,不抛)。
|
||||
if raw_new_len > old_logical && !obj.is_extensible() {
|
||||
return Ok(());
|
||||
}
|
||||
// ArraySetLength:收缩时若 [newLen, oldLen) 内存在不可配置元素,整个收缩
|
||||
// 失败且不做任何修改(length 失败两模式均静默 no-op,不抛)。
|
||||
if raw_new_len < old_logical {
|
||||
for idx in raw_new_len..old_count {
|
||||
if let Some(meta) = obj.prop_meta_at(idx) {
|
||||
if !meta.attributes.configurable() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 元素区物理槽数以 `MAX_DENSE_PROPS` 为上限,超限长度不扩槽。
|
||||
let new_len_u = raw_new_len.min(oxide_types::object::MAX_DENSE_PROPS);
|
||||
obj.set_prop_count(new_len_u);
|
||||
// 扩出的槽是稀疏 hole:存在性检查与原型链读取须视同不存在,
|
||||
// 故逐个打 hole 标记。
|
||||
for idx in old_count..new_len_u {
|
||||
obj.mark_hole_at(idx);
|
||||
}
|
||||
// 超过稠密上限的长度另记逻辑长度槽:`a.length` 读回真实赋值
|
||||
// 而非封顶后的物理槽数。
|
||||
if raw_new_len > oxide_types::object::MAX_DENSE_PROPS {
|
||||
obj.set_array_len_override(raw_new_len as u32);
|
||||
} else {
|
||||
obj.clear_array_len_override();
|
||||
}
|
||||
// 对象自身属性区可能存在影子 length 命名属性(shape 槽),同步写入
|
||||
// 新逻辑长度,保证 IC 快路径读到新值。
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), length_si) {
|
||||
let store_idx = obj.array_prop_count as usize + pos as usize;
|
||||
obj.set_prop_storage(store_idx, obj.logical_len_value());
|
||||
}
|
||||
return Ok(());
|
||||
return self.set_array_length_value(obj, val, strict, builtin);
|
||||
}
|
||||
if let Some(pos) = self.get_own_property_slot(obj, prop_name_si) {
|
||||
if let Some(meta) = obj.prop_meta_at(pos) {
|
||||
@@ -436,7 +382,7 @@ impl Vm {
|
||||
if meta.set.is_undefined() {
|
||||
// 无 setter:严格抛错,sloppy 静默 no-op。
|
||||
if strict {
|
||||
return self.raise_type_error("property has no setter");
|
||||
return self.write_protection_failure(builtin, "property has no setter");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -445,7 +391,7 @@ impl Vm {
|
||||
if !meta.attributes.writable() {
|
||||
// 只读数据属性:严格抛错,sloppy 静默 no-op。
|
||||
if strict {
|
||||
return self.raise_type_error("cannot assign to read-only property");
|
||||
return self.write_protection_failure(builtin, "cannot assign to read-only property");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -461,7 +407,7 @@ impl Vm {
|
||||
if meta.set.is_undefined() {
|
||||
// 继承无 setter:严格抛错,sloppy 静默 no-op。
|
||||
if strict {
|
||||
return self.raise_type_error("property has no setter");
|
||||
return self.write_protection_failure(builtin, "property has no setter");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -470,17 +416,30 @@ impl Vm {
|
||||
if !meta.attributes.writable() {
|
||||
// 继承只读数据属性:严格抛错,sloppy 静默 no-op(不遮蔽)。
|
||||
if strict {
|
||||
return self.raise_type_error("cannot assign to read-only property");
|
||||
return self.write_protection_failure(builtin, "cannot assign to read-only property");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// 数组索引增长(index >= 当前 length)先过 length 可写性检查:与 define 侧
|
||||
// `define_array_index_element` 同序,早于对象可扩展性检查;length 不可写时
|
||||
// 严格抛 TypeError、sloppy 静默。索引小于 length 的已有元素写入不受此限。
|
||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||
if obj.is_array() && index >= obj.logical_len() && !obj.is_length_writable() {
|
||||
return self.array_length_write_failure(
|
||||
strict,
|
||||
builtin,
|
||||
"Cannot add property, array length is not writable",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 新属性(自身与原型链均无同名):须对象可扩展(OrdinarySet 的 extensible
|
||||
// 检查),不可扩展时赋值失败(严格抛错,sloppy 静默 no-op)。
|
||||
if !obj.is_extensible() {
|
||||
if strict {
|
||||
return self.raise_type_error("object is not extensible");
|
||||
return self.write_protection_failure(builtin, "object is not extensible");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -488,13 +447,92 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 数组 length 赋值路径:`a.length = v` 的 `[[Set]]`,按 ArraySetLength 语义
|
||||
/// 调整元素区(与 define 侧 [`Self::define_array_length`] 共用截断/扩洞逻辑)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. `ToUint32` 与 `ToNumber` 两次强转各自执行一次(均可触发用户代码)。
|
||||
/// 2. 两次结果不等(非整数、负数、NaN/Infinity、2^32 等)抛 RangeError。
|
||||
/// 3. 强转完成后按当前可写位判定:不可写时严格抛 TypeError、sloppy 静默,且
|
||||
/// 不做任何截断(写不可写数据描述符先于 ArraySetLength 失败)。
|
||||
/// 4. 收缩求最高不可配置阻挡索引:有则以「阻挡索引 + 1」部分截断后按模式返回
|
||||
/// 失败;无则完整截断/扩 hole 到目标长度。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 仅由 `ordinary_set_inner` 对 `is_array()` 对象且键为 length 时调用。
|
||||
/// - 可写位判定必须放在两次强转之后:用户代码可能在强转期收窄 length 可写位。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 修改元素区与元素元数据、可能置/清 `array_len_override`、bump 世代。
|
||||
fn set_array_length_value(
|
||||
&mut self, obj: &mut JsObject, val: JsValue, strict: bool, builtin: bool,
|
||||
) -> Result<(), String> {
|
||||
// ToNumber(BigInt) 抛 TypeError:在通用强转近似接受 BigInt 之前拦截。
|
||||
if val.is_bigint() {
|
||||
return self.write_protection_failure(builtin, "Cannot convert a BigInt value to a number");
|
||||
}
|
||||
|
||||
let pc_before = self.pc;
|
||||
let new_len = self.coerce_uint32_bounded(val)?;
|
||||
// 强转抛错在主 dispatch 下已 unwind 到外围 catch,此时 opcode 不得继续,
|
||||
// 直接返回由 dispatch 执行 catch,避免二次抛错覆盖原异常。
|
||||
if self.pc != pc_before {
|
||||
return Ok(());
|
||||
}
|
||||
let number_len = self.coerce_number_bounded(val)?;
|
||||
if self.pc != pc_before {
|
||||
return Ok(());
|
||||
}
|
||||
if new_len as f64 != number_len {
|
||||
if builtin {
|
||||
return Err(self.error_message_text("RangeError", "Invalid array length"));
|
||||
}
|
||||
return self.raise_error_kind("RangeError", "Invalid array length");
|
||||
}
|
||||
|
||||
// 两次强转后重新判定:强转期间用户代码可能已把 length 收窄为不可写。
|
||||
if !obj.is_length_writable() {
|
||||
return self.array_length_write_failure(strict, builtin, "Cannot assign to read only property 'length'");
|
||||
}
|
||||
|
||||
let old_logical = obj.logical_len();
|
||||
let target_len = Self::array_length_shrink_target(obj, new_len, old_logical);
|
||||
Self::apply_array_length(obj, target_len);
|
||||
obj.bump_generation();
|
||||
|
||||
if target_len != new_len {
|
||||
return self.array_length_write_failure(strict, builtin, "Cannot assign to read only property 'length'");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 属性写保护失败的 strict/sloppy 分派:严格模式抛 TypeError(非 builtin 就地
|
||||
/// 展开到最近 catch),sloppy 静默 no-op。
|
||||
fn array_length_write_failure(&mut self, strict: bool, builtin: bool, msg: &str) -> Result<(), String> {
|
||||
if !strict {
|
||||
return Ok(());
|
||||
}
|
||||
self.write_protection_failure(builtin, msg)
|
||||
}
|
||||
|
||||
/// TypeError 的两态出口:非 builtin 就地抛可捕获异常并展开,builtin 内部返回
|
||||
/// 格式化 `Err` 由调用边界恢复为异常对象(builtin 在重入字节码上下文中,就地
|
||||
/// 展开会跳入调用方 catch 并让 builtin 继续,随后覆盖异常寄存器)。
|
||||
fn write_protection_failure(&mut self, builtin: bool, msg: &str) -> Result<(), String> {
|
||||
if builtin {
|
||||
Err(self.error_message_text("TypeError", msg))
|
||||
} else {
|
||||
self.raise_type_error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/// TypedArray 整数索引在 `receiver` ≠ TA 时的 [[Set]] 语义:越界或非对象
|
||||
/// receiver 直接返回 true(不写、不 ToNumber);界内对象 receiver 走普通 set
|
||||
/// 把属性落到 receiver 自身。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn set_to_receiver(
|
||||
&mut self, ta_obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, index: usize,
|
||||
use_frame_push: bool, strict: bool,
|
||||
use_frame_push: bool, strict: bool, builtin: bool,
|
||||
) -> Result<(), String> {
|
||||
let Some((_, length)) = oxide_builtins::typed_array::typed_array_integer_index(self, ta_obj, prop_name_si)
|
||||
else {
|
||||
@@ -509,7 +547,7 @@ impl Vm {
|
||||
}
|
||||
let promoted = self.promote_if_needed_for_write_ptr(receiver_ptr, val);
|
||||
let receiver_obj = unsafe { &mut *receiver_ptr };
|
||||
self.ordinary_set_inner(receiver_obj, prop_name_si, promoted, receiver, use_frame_push, strict)
|
||||
self.ordinary_set_inner(receiver_obj, prop_name_si, promoted, receiver, use_frame_push, strict, builtin)
|
||||
}
|
||||
|
||||
/// 调用或帧化访问器 setter:native(或禁止帧化的调用方)同步执行,
|
||||
@@ -1104,17 +1142,8 @@ impl Vm {
|
||||
return Err("cannot redefine non-configurable property".to_string());
|
||||
}
|
||||
|
||||
// 收缩按 ArraySetLength 删除循环语义:自最高索引向下删除可配置元素,遇最高
|
||||
// 不可配置索引 P 时停止并把 length 收敛到 P+1(其上元素已在循环中删除)。
|
||||
// 仅当 [newLen, oldLen) 全可配置时才完整截断到 newLen。
|
||||
let target_len = if new_len < old_logical {
|
||||
match Self::highest_non_configurable_index(obj, new_len, old_logical) {
|
||||
Some(blocker) => blocker + 1,
|
||||
None => new_len,
|
||||
}
|
||||
} else {
|
||||
new_len
|
||||
};
|
||||
// 收缩按 ArraySetLength 删除循环语义求目标长度(部分截断 / 完整截断)。
|
||||
let target_len = Self::array_length_shrink_target(obj, new_len, old_logical);
|
||||
|
||||
Self::apply_array_length(obj, target_len);
|
||||
obj.set_length_non_writable(!attributes.writable());
|
||||
@@ -1126,6 +1155,22 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 数组 length 收缩的目标长度:`[new_len, old_len)` 内存在不可配置元素时取
|
||||
/// 最高阻挡索引 + 1(部分截断,其上可配置元素已删除),否则完整截断到 `new_len`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `new_len >= old_logical` 时直接返回 `new_len`(非收缩路径)。
|
||||
fn array_length_shrink_target(obj: &JsObject, new_len: u32, old_logical: u32) -> u32 {
|
||||
if new_len < old_logical {
|
||||
match Self::highest_non_configurable_index(obj, new_len, old_logical) {
|
||||
Some(blocker) => blocker + 1,
|
||||
None => new_len,
|
||||
}
|
||||
} else {
|
||||
new_len
|
||||
}
|
||||
}
|
||||
|
||||
/// 返回 `[from, old_len)` 内最高的不可配置自身元素索引;无则 `None`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
//! 数组 length 虚拟属性的赋值侧回归:`a.length = v` 的两次数值强转、strict/sloppy
|
||||
//! 可写位语义、部分截断、增长、异常原值传播,以及 push/pop/shift/unshift 与
|
||||
//! `a[i] = v` 字节码路径对不可写 length 的行为。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
/// 求值并取字符串结果内容(字符串值走 lookup_str,非字符串走 Display)。
|
||||
fn eval_str(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse ok");
|
||||
let module = Compiler::new().compile(&program).expect("compile ok");
|
||||
let mut vm = Vm::new();
|
||||
let val = vm.run(&Arc::new(module)).expect("run ok");
|
||||
if val.is_string() {
|
||||
vm.lookup_str(val).unwrap_or_default()
|
||||
} else {
|
||||
format!("{val}")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_truncates_and_grows() {
|
||||
assert_eq!(eval_str("var a=[1,2,3]; a.length=1; ''+a.length+':'+(2 in a)"), "1:false");
|
||||
assert_eq!(eval_str("var a=[1]; a.length=3; ''+a.length+':'+(1 in a)"), "3:false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_non_writable_strict_throws_sloppy_silent() {
|
||||
// sloppy:静默 no-op,length 不变。
|
||||
assert_eq!(
|
||||
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); a.length=1; ''+a.length"),
|
||||
"3"
|
||||
);
|
||||
// strict:TypeError,length 不变。
|
||||
assert_eq!(
|
||||
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); (function(){'use strict';try{a.length=1;return 'no-throw';}catch(e){return e.name;}})()+':'+a.length"),
|
||||
"TypeError:3"
|
||||
);
|
||||
// 同值赋值同样失败:不可写数据描述符先于 ArraySetLength 返回 false。
|
||||
assert_eq!(
|
||||
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); (function(){'use strict';try{a.length=3;return 'no-throw';}catch(e){return e.name;}})()+':'+a.length"),
|
||||
"TypeError:3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_frozen_strict_throws_sloppy_silent() {
|
||||
// 冻结数组的 length 同样不可写:sloppy 静默而非恒抛。
|
||||
assert_eq!(eval_str("var a=Object.freeze([1,2,3]); a.length=1; ''+a.length"), "3");
|
||||
assert_eq!(
|
||||
eval_str("var a=Object.freeze([1,2,3]); (function(){'use strict';try{a.length=1;return 'no-throw';}catch(e){return e.name;}})()+':'+a.length"),
|
||||
"TypeError:3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_shrink_partial_truncation_at_blocker() {
|
||||
// strict:阻挡索引 1,length 收敛到 2,index 2 起删除,并抛 TypeError。
|
||||
assert_eq!(
|
||||
eval_str("var a=[0,1,2,3,4]; Object.defineProperty(a,'1',{configurable:false}); var r; (function(){'use strict';try{a.length=1;r='no-throw';}catch(e){r=e.name;}})(); r+':'+a.length+':'+a.hasOwnProperty('1')+':'+a.hasOwnProperty('2')"),
|
||||
"TypeError:2:true:false"
|
||||
);
|
||||
// sloppy:同样部分截断,静默失败。
|
||||
assert_eq!(
|
||||
eval_str("var a=[0,1,2,3,4]; Object.defineProperty(a,'1',{configurable:false}); a.length=0; ''+a.length+':'+a.hasOwnProperty('1')+':'+a.hasOwnProperty('2')"),
|
||||
"2:true:false"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_growth_ignores_extensibility() {
|
||||
// ArraySetLength 的增长不要求对象可扩展:preventExtensions 后仍成功。
|
||||
assert_eq!(
|
||||
eval_str("var a=[1]; Object.preventExtensions(a); a.length=3; ''+a.length+':'+(1 in a)+':'+(2 in a)"),
|
||||
"3:false:false"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_invalid_values_throw_range_error() {
|
||||
assert_eq!(
|
||||
eval_str("var a=[1,2,3]; var r=[]; [1.5,-1,4294967296,NaN].forEach(function(v){try{a.length=v;r.push('no-throw');}catch(e){r.push(e.name);}}); r.join(',')"),
|
||||
"RangeError,RangeError,RangeError,RangeError"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_coerces_twice_before_writable_check() {
|
||||
// ToUint32 + ToNumber 各触发一次 ToPrimitive(number);第二次强转收窄可写位后
|
||||
// 由可写位判定拒绝,且不做任何截断。
|
||||
assert_eq!(
|
||||
eval_str("var a=[1,2,3], n=0; var v={}; v[Symbol.toPrimitive]=function(h){n++;Object.defineProperty(a,'length',{writable:false});return 0;}; var r; (function(){'use strict';try{a.length=v;r='no-throw';}catch(e){r=e.name;}})(); r+':'+n+':'+a.length"),
|
||||
"TypeError:2:3"
|
||||
);
|
||||
// Reflect.set 以严格模式调用并投影为 false,两次强转同样执行。
|
||||
assert_eq!(
|
||||
eval_str("var a=[1,2,3], n=0; var v={}; v[Symbol.toPrimitive]=function(h){n++;Object.defineProperty(a,'length',{writable:false});return 0;}; Reflect.set(a,'length',v)+':'+n+':'+a.length"),
|
||||
"false:2:3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_coercion_exception_propagates_original_value() {
|
||||
// 强转期 valueOf / Symbol.toPrimitive 抛出的自定义错误须原值重抛(identity 保留)。
|
||||
assert_eq!(
|
||||
eval_str("var err=new Error('boom'); var v={valueOf:function(){throw err;}}; var a=[]; var c=null; try{a.length=v;}catch(e){c=e;} (c===err)+':'+c.name"),
|
||||
"true:Error"
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("var err=new RangeError('sym'); var v={}; v[Symbol.toPrimitive]=function(h){throw err;}; var a=[]; var c=null; try{a.length=v;}catch(e){c=e;} (c===err)+':'+c.name"),
|
||||
"true:RangeError"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_length_bigint_and_symbol_throw_type_error() {
|
||||
assert_eq!(
|
||||
eval_str("var a=[]; var r=[]; try{a.length=1n;r.push('no-throw');}catch(e){r.push(e.name);} try{a.length=Symbol();r.push('no-throw');}catch(e){r.push(e.name);} r.join(',')"),
|
||||
"TypeError,TypeError"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_elem_beyond_non_writable_length_respects_strict() {
|
||||
// `a[i] = v` 字节码路径:越界写须过 length 可写性检查,sloppy 静默、strict 抛。
|
||||
assert_eq!(
|
||||
eval_str(
|
||||
"var a=[]; Object.defineProperty(a,'length',{writable:false}); var i=3; a[i]=9; ''+a.length+':'+(3 in a)"
|
||||
),
|
||||
"0:false"
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("var a=[]; Object.defineProperty(a,'length',{writable:false}); var i=3; var r; (function(){'use strict';try{a[i]=9;r='no-throw';}catch(e){r=e.name;}})(); r+':'+a.length+':'+(3 in a)"),
|
||||
"TypeError:0:false"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mutators_on_non_writable_length_throw() {
|
||||
// 空数组 + 不可写 length:四个变更方法收尾的 Set length 均抛 TypeError。
|
||||
assert_eq!(
|
||||
eval_str("var a=[]; Object.defineProperty(a,'length',{writable:false}); var r=[]; try{a.push();r.push('no-throw');}catch(e){r.push(e.name);} r.push(a.length); r.join(',')"),
|
||||
"TypeError,0"
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str(
|
||||
"var a=[]; Object.defineProperty(a,'length',{writable:false}); try{a.pop();'no-throw';}catch(e){e.name}"
|
||||
),
|
||||
"TypeError"
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str(
|
||||
"var a=[]; Object.defineProperty(a,'length',{writable:false}); try{a.shift();'no-throw';}catch(e){e.name}"
|
||||
),
|
||||
"TypeError"
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("var a=[]; Object.defineProperty(a,'length',{writable:false}); try{a.unshift();'no-throw';}catch(e){e.name}"),
|
||||
"TypeError"
|
||||
);
|
||||
// frozen 空数组同样四方法皆抛。
|
||||
assert_eq!(
|
||||
eval_str("var a=Object.freeze([]); var r=[]; try{a.push();r.push('no-throw');}catch(e){r.push(e.name);} try{a.pop();r.push('no-throw');}catch(e){r.push(e.name);} try{a.shift();r.push('no-throw');}catch(e){r.push(e.name);} try{a.unshift();r.push('no-throw');}catch(e){r.push(e.name);} r.join(',')"),
|
||||
"TypeError,TypeError,TypeError,TypeError"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_element_write_uses_prototype_setter() {
|
||||
// 继承的索引 setter 在元素写入时执行并收窄 length,收尾 Set length 失败。
|
||||
assert_eq!(
|
||||
eval_str("var a=[]; var calls=0; Object.defineProperty(Array.prototype,'0',{set:function(v){Object.defineProperty(a,'length',{writable:false});calls++;}}); var r; try{a.push(1);r='no-throw';}catch(e){r=e.name;} r+':'+a.length+':'+a.hasOwnProperty(0)+':'+calls"),
|
||||
"TypeError:0:false:1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mutators_still_work_with_writable_length() {
|
||||
// 重写后正常路径回归:push/pop/shift/unshift 的元素搬移与返回值。
|
||||
assert_eq!(eval_str("var a=[]; a.push(1,2); ''+a.length+':'+a[1]"), "2:2");
|
||||
assert_eq!(
|
||||
eval_str("var a=[1,2,3]; var p=a.pop(); var s=a.shift(); var u=a.unshift(9,8); p+':'+s+':'+u+':'+a.join(',')"),
|
||||
"3:1:3:9,8,2"
|
||||
);
|
||||
}
|
||||
@@ -235,7 +235,7 @@ fn frozen_array_write_and_length_throws_in_strict() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── sloppy Object.freeze 数组:元素写静默失败(length 冻结检查仍抛错) ──
|
||||
// ── sloppy Object.freeze 数组:元素写与 length 写均静默失败(严格模式才抛) ──
|
||||
#[test]
|
||||
fn frozen_array_element_write_silently_fails_in_sloppy() {
|
||||
let (_vm, r) = eval_keep_vm(
|
||||
|
||||
Reference in New Issue
Block a user