mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: strict 删除非可配置属性抛错并修正数组长度
This commit is contained in:
@@ -211,19 +211,34 @@ fn array_index_of<H: VmHost>(vm: &H, key_si: u32) -> Option<u32> {
|
||||
key.parse::<u32>().ok()
|
||||
}
|
||||
|
||||
/// 删除对象自身属性(字节码 delete 与 Reflect.deleteProperty 共用)。
|
||||
/// 自身属性删除结果三态:区分「属性缺失 / 已删除 / 不可配置」,让共享实现同时
|
||||
/// 服务 delete 运算符(不可配置在严格模式须抛 TypeError)与
|
||||
/// `Reflect.deleteProperty`(恒投影为布尔、不抛错)两个消费者。
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum DeleteOutcome {
|
||||
/// 属性不在对象自身(含原型链属性):删除按规范成功。
|
||||
Missing,
|
||||
/// 属性存在且已删除。
|
||||
Deleted,
|
||||
/// 属性存在但不可配置:删除失败。
|
||||
NonConfigurable,
|
||||
}
|
||||
|
||||
/// 删除对象自身属性并返回三态结果(字节码 delete 与 Reflect.deleteProperty 共用)。
|
||||
///
|
||||
/// 数组下标键在元素区(shape 链外),标记为 hole(值 undefined + hole meta),
|
||||
/// length 不变;命名属性经 shape 链重建移除。属性不可配置时返回 false。
|
||||
/// length 不变;数组 length 虚拟属性与命名属性经 shape 链处理。仅对象自身属性
|
||||
/// 参与删除判定,原型链属性不影响结果。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 数组下标元素:检查 configurable,`mark_hole_at` 标记为 hole
|
||||
/// 2. 命名属性:walk_own_keys 定位槽位,不可配置返回 false
|
||||
/// 3. 数组先保存元素区(值 + meta),重建命名属性后恢复元素区
|
||||
/// 1. 数组 length 虚拟属性:不可配置,返回 `NonConfigurable`
|
||||
/// 2. 数组下标元素:检查 configurable,`mark_hole_at` 标记为 hole
|
||||
/// 3. 命名属性:walk_own_keys 定位槽位,不可配置返回 `NonConfigurable`
|
||||
/// 4. 数组先保存元素区(值 + meta),重建命名属性后恢复元素区
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 键不在对象自身(含原型链属性)返回 true
|
||||
/// - 非 configurable 属性返回 false
|
||||
/// - 键不在对象自身(含原型链属性)返回 `Missing`
|
||||
/// - 非 configurable 属性返回 `NonConfigurable`
|
||||
/// - `key_si` 须已 intern
|
||||
///
|
||||
/// # 副作用
|
||||
@@ -232,7 +247,7 @@ fn array_index_of<H: VmHost>(vm: &H, key_si: u32) -> Option<u32> {
|
||||
/// # 注意事项
|
||||
/// - 数组元素存在性以 `prop_meta_at` 的 hole 标记判定,删除后重新写入元素
|
||||
/// 会自动清除 hole 标记恢复存在
|
||||
pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> bool {
|
||||
pub fn delete_own_property_outcome<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> DeleteOutcome {
|
||||
// 数组下标元素在元素区,不参与 shape 链,单独删除(保持 length 不变)。
|
||||
if obj.is_array() {
|
||||
if let Some(index) = array_index_of(vm, key_si) {
|
||||
@@ -240,16 +255,21 @@ pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u3
|
||||
let meta = obj.prop_meta_at(index);
|
||||
// 已是 hole 视为不存在;非 configurable 不可删。
|
||||
if meta.is_some_and(|m| m.is_hole()) {
|
||||
return true;
|
||||
return DeleteOutcome::Missing;
|
||||
}
|
||||
if meta.is_some_and(|m| !m.attributes.configurable()) {
|
||||
return false;
|
||||
return DeleteOutcome::NonConfigurable;
|
||||
}
|
||||
obj.mark_hole_at(index);
|
||||
obj.bump_generation();
|
||||
return true;
|
||||
return DeleteOutcome::Deleted;
|
||||
}
|
||||
return true;
|
||||
return DeleteOutcome::Missing;
|
||||
}
|
||||
// 非下标键:length 是虚拟属性(无 shape 槽、不在元素区),但描述符声明
|
||||
// configurable:false,删除恒失败。
|
||||
if key_si == vm.kernel_core().perm_interner().intern("length").0 {
|
||||
return DeleteOutcome::NonConfigurable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +283,7 @@ pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u3
|
||||
// 属性缺失:delete 按规范返 true,镜像槽同步 undefined 保持
|
||||
// "槽 = A 侧原始存储"不变式(与入口预载缺位语义幂等)。
|
||||
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
|
||||
return true;
|
||||
return DeleteOutcome::Missing;
|
||||
};
|
||||
// walk_own_keys 已返回绝对存储索引(数组含元素区偏移),直接使用。
|
||||
if obj
|
||||
@@ -271,7 +291,7 @@ pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u3
|
||||
.map(|meta| !meta.attributes.configurable())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
return DeleteOutcome::NonConfigurable;
|
||||
}
|
||||
|
||||
// 数组重建前保存元素区(值 + meta),重建后恢复到命名属性之前。
|
||||
@@ -330,7 +350,13 @@ pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u3
|
||||
// 删除成功:镜像槽同步 undefined(成员形删除 / Reflect.deleteProperty /
|
||||
// 0x9C 清槽同源收口,后两者对此幂等)。
|
||||
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
|
||||
true
|
||||
DeleteOutcome::Deleted
|
||||
}
|
||||
|
||||
/// 删除对象自身属性并投影为布尔(`Reflect.deleteProperty` 语义):成功返回 true,
|
||||
/// 属性不可配置返回 false,恒不抛错。
|
||||
pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> bool {
|
||||
delete_own_property_outcome(vm, obj, key_si) != DeleteOutcome::NonConfigurable
|
||||
}
|
||||
|
||||
/// JS `Object()` 构造逻辑:创建空对象(prototype 为 null,由 VM 补装内置原型)。
|
||||
|
||||
@@ -649,10 +649,10 @@ impl Vm {
|
||||
return Ok(false);
|
||||
};
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
// 统一走共享删除逻辑(与 Reflect.deleteProperty 一致);不可配置返回 false。
|
||||
let deleted = oxide_builtins::object::delete_own_property(self, obj, prop_name_si);
|
||||
self.regs[rd] = JsValue::bool(deleted);
|
||||
Ok(false)
|
||||
// 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);严格模式分派见
|
||||
// finish_member_delete。
|
||||
let outcome = oxide_builtins::object::delete_own_property_outcome(self, obj, prop_name_si);
|
||||
self.finish_member_delete(rd, outcome)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_delete_prop_dynamic(&mut self, rd: usize, b: usize) -> Result<bool, String> {
|
||||
@@ -667,9 +667,33 @@ impl Vm {
|
||||
return Ok(false);
|
||||
};
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
// 统一走共享删除逻辑(与 Reflect.deleteProperty 一致);不可配置返回 false。
|
||||
let deleted = oxide_builtins::object::delete_own_property(self, obj, prop_name_si);
|
||||
self.regs[rd] = JsValue::bool(deleted);
|
||||
// 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);严格模式分派见
|
||||
// finish_member_delete。
|
||||
let outcome = oxide_builtins::object::delete_own_property_outcome(self, obj, prop_name_si);
|
||||
self.finish_member_delete(rd, outcome)
|
||||
}
|
||||
|
||||
/// 成员形 delete 的结果分派:不可配置属性在严格模式抛 TypeError(规范要求
|
||||
/// delete 运算符对 [[Delete]] 返回 false 的引用抛错),其余情形把「是否删除
|
||||
/// 成功」写入 `rd`。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 不可配置且当前上下文严格:抛 TypeError,返回 `Ok(true)` 令调用方重取 pc
|
||||
/// 2. 否则按三态投影布尔结果写 `rd`(`Missing`/`Deleted` 为 true)
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 严格性取 `current_strict()`(覆盖帧栈顶 / inline 基线 / 顶层脚本三形态)
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 抛错路径经 `unwind` 改写 pc / 异常通道;成功路径写 `regs[rd]`
|
||||
fn finish_member_delete(
|
||||
&mut self, rd: usize, outcome: oxide_builtins::object::DeleteOutcome,
|
||||
) -> Result<bool, String> {
|
||||
if outcome == oxide_builtins::object::DeleteOutcome::NonConfigurable && self.current_strict() {
|
||||
self.raise_type_error("Cannot delete property")?;
|
||||
return Ok(true);
|
||||
}
|
||||
self.regs[rd] = JsValue::bool(outcome != oxide_builtins::object::DeleteOutcome::NonConfigurable);
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -117,19 +117,24 @@ impl Vm {
|
||||
/// true。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. null/undefined 基抛 TypeError(调用方经 `?` 传播异常)
|
||||
/// 1. null/undefined 基抛 TypeError 并早返回(异常已展开,不再写结果槽)
|
||||
/// 2. 字符串基按下标键区间判自身属性;非字符串基恒真
|
||||
/// 3. 布尔结果写 `rd` 槽(与对象基臂同槽位)
|
||||
/// 3. 严格模式下删除失败(字符串下标/length 不可配置)抛 TypeError
|
||||
/// 4. 布尔结果写 `rd` 槽(与对象基臂同槽位)
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 调用点已证基非对象;对象基走既有对象臂,不进本函数
|
||||
/// - 字符串长度口径按 UTF-16 码元(与字符串自身属性面一致);非规范数字串
|
||||
/// 键(前导零等)与 symbol 键走"无自身属性"面
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 抛错路径经 `unwind` 改写 pc / 异常通道;成功路径写 `regs[rd]`
|
||||
pub(crate) fn delete_prop_non_object_base(
|
||||
&mut self, base: JsValue, rd: usize, key_si: u32,
|
||||
) -> Result<bool, String> {
|
||||
if base.is_null() || base.is_undefined() {
|
||||
self.raise_error_kind("TypeError", "delete on non-object")?;
|
||||
self.raise_type_error("delete on non-object")?;
|
||||
return Ok(true);
|
||||
}
|
||||
let deleted = if base.is_string() {
|
||||
let len = unsafe { (*base.as_string_ptr()).units().len() };
|
||||
@@ -141,6 +146,11 @@ impl Vm {
|
||||
} else {
|
||||
true
|
||||
};
|
||||
// 删除失败即命中不可配置自身属性,严格模式下 delete 运算符须抛 TypeError。
|
||||
if !deleted && self.current_strict() {
|
||||
self.raise_type_error("Cannot delete property")?;
|
||||
return Ok(true);
|
||||
}
|
||||
self.regs[rd] = JsValue::bool(deleted);
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -91,3 +91,23 @@ fn delete_undefined_base_throws_type_error() {
|
||||
fn delete_nan_base_keys_are_true() {
|
||||
truthy("delete NaN.x");
|
||||
}
|
||||
|
||||
// ── 严格模式:删除字符串不可配置下标属性抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_delete_string_index_throws_type_error() {
|
||||
truthy(
|
||||
"\"use strict\"; \
|
||||
var threw = false; try { delete 'ab'[0]; } catch (e) { threw = e instanceof TypeError; } \
|
||||
threw",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格模式:删除字符串 length 抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_delete_string_length_throws_type_error() {
|
||||
truthy(
|
||||
"\"use strict\"; \
|
||||
var threw = false; try { delete 'abc'.length; } catch (e) { threw = e instanceof TypeError; } \
|
||||
threw",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ fn eval_many(lines: &[&str]) -> Result<JsValue, String> {
|
||||
eval(&source)
|
||||
}
|
||||
|
||||
fn truthy(source: &str) {
|
||||
let result = eval(source).unwrap_or_else(|e| panic!("{source} -> {e}"));
|
||||
assert!(result.is_bool() && result.as_bool(), "{source} -> 期望 true,得 {:?}", result);
|
||||
}
|
||||
|
||||
// ── 删除可配置属性成功 ──
|
||||
#[test]
|
||||
fn delete_configurable_property_succeeds_and_returns_true() {
|
||||
@@ -93,3 +98,98 @@ fn delete_configurable_proto_property_on_child_returns_true() {
|
||||
r
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格模式:不可配置数据属性抛 TypeError 且属性保留 ──
|
||||
#[test]
|
||||
fn strict_delete_non_configurable_data_throws_type_error() {
|
||||
truthy(
|
||||
"\"use strict\"; var o = {}; \
|
||||
Object.defineProperty(o, 'x', {value: 1, configurable: false}); \
|
||||
var threw = false; try { delete o.x; } catch (e) { threw = e instanceof TypeError; } \
|
||||
threw && o.x === 1",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格模式:不可配置访问器属性同样抛 TypeError 且 getter 保留 ──
|
||||
#[test]
|
||||
fn strict_delete_non_configurable_accessor_throws_type_error() {
|
||||
truthy(
|
||||
"\"use strict\"; var o = {}; \
|
||||
Object.defineProperty(o, 'x', {get: function () { return 1; }, configurable: false}); \
|
||||
var threw = false; try { delete o.x; } catch (e) { threw = e instanceof TypeError; } \
|
||||
threw && o.x === 1",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格模式:删除全局不可配置内置属性(NaN)抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_delete_global_nan_throws_type_error() {
|
||||
truthy(
|
||||
"\"use strict\"; var g = this; \
|
||||
var threw = false; try { delete g.NaN; } catch (e) { threw = e instanceof TypeError; } \
|
||||
threw && g.NaN !== undefined",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格模式:删除 Math.LN2 抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_delete_math_ln2_throws_type_error() {
|
||||
truthy(
|
||||
"\"use strict\"; \
|
||||
var threw = false; try { delete Math.LN2; } catch (e) { threw = e instanceof TypeError; } \
|
||||
threw && Math.LN2 !== undefined",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格模式:可配置属性仍可删除 ──
|
||||
#[test]
|
||||
fn strict_delete_configurable_property_succeeds() {
|
||||
truthy(
|
||||
"\"use strict\"; var o = {}; \
|
||||
Object.defineProperty(o, 'x', {value: 1, configurable: true}); \
|
||||
delete o.x === true && o.x === undefined",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格模式:删除不存在的属性返回 true(不得过抛) ──
|
||||
#[test]
|
||||
fn strict_delete_missing_property_returns_true() {
|
||||
truthy("\"use strict\"; var o = {}; delete o.missing === true");
|
||||
}
|
||||
|
||||
// ── 数组 length 虚拟属性:sloppy 删除返回 false 且 length 不变 ──
|
||||
#[test]
|
||||
fn sloppy_delete_array_length_returns_false() {
|
||||
truthy("var a = [1, 2, 3]; a.x = 10; delete a.length === false && a.length === 3");
|
||||
}
|
||||
|
||||
// ── 数组 length 虚拟属性:严格模式删除抛 TypeError 且 length 不变 ──
|
||||
#[test]
|
||||
fn strict_delete_array_length_throws_type_error() {
|
||||
truthy(
|
||||
"\"use strict\"; var a = [1, 2, 3]; \
|
||||
var threw = false; try { delete a.length; } catch (e) { threw = e instanceof TypeError; } \
|
||||
threw && a.length === 3",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 冻结数组元素不可配置:严格模式删除抛 TypeError 且元素保留 ──
|
||||
#[test]
|
||||
fn strict_delete_frozen_array_element_throws_type_error() {
|
||||
truthy(
|
||||
"\"use strict\"; var a = [1, 2, 3]; Object.freeze(a); \
|
||||
var threw = false; try { delete a[0]; } catch (e) { threw = e instanceof TypeError; } \
|
||||
threw && a[0] === 1",
|
||||
);
|
||||
}
|
||||
|
||||
// ── Reflect.deleteProperty 恒不抛:不可配置与数组 length 均返回 false ──
|
||||
#[test]
|
||||
fn reflect_delete_non_configurable_and_array_length_return_false() {
|
||||
truthy(
|
||||
"var o = {}; Object.defineProperty(o, 'x', {value: 1, configurable: false}); \
|
||||
var a = [1, 2, 3]; \
|
||||
Reflect.deleteProperty(o, 'x') === false && o.x === 1 \
|
||||
&& Reflect.deleteProperty(a, 'length') === false && a.length === 3",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user