feat: freeze/seal/preventExtensions 写路径拦截(逐属性 meta + extensible 检查)
freeze/seal 逐属性改写 meta(数据属性 writable/configurable=false, 访问器 configurable=false,含数组元素区),使 has_prop_meta() 恒 true, IC 直写路径自动失效;ordinary_set/create_named_prop_fast/ set_or_create_prop_value/define_* 增补 extensible/frozen 检查, SET_ELEM 直写入口回落 ordinary_set 或拒绝新元素。 引擎未实现 strict 标志,赋值失败统一 raise_type_error(sloppy 静默留待后续)。
This commit is contained in:
@@ -530,9 +530,8 @@ fn define_from_descriptor<H: VmHost>(
|
|||||||
// 已有属性无显式 meta(普通数据属性/数组元素)时按默认属性回填:
|
// 已有属性无显式 meta(普通数据属性/数组元素)时按默认属性回填:
|
||||||
// 描述符缺省字段保持现有值(writable/enumerable/configurable 均 true),
|
// 描述符缺省字段保持现有值(writable/enumerable/configurable 均 true),
|
||||||
// 而非按新属性处理为 false(Object.defineProperty 省略字段不改已有属性)。
|
// 而非按新属性处理为 false(Object.defineProperty 省略字段不改已有属性)。
|
||||||
obj.prop_meta_at(pos).unwrap_or_else(|| {
|
obj.prop_meta_at(pos)
|
||||||
oxide_types::object::PropMetaEntry::data(PropAttributes::DEFAULT_DATA)
|
.unwrap_or_else(|| oxide_types::object::PropMetaEntry::data(PropAttributes::DEFAULT_DATA))
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 修改已有属性时缺省字段回填现有值,仅定义新属性时缺省才为 false。
|
// 修改已有属性时缺省字段回填现有值,仅定义新属性时缺省才为 false。
|
||||||
@@ -759,7 +758,40 @@ macro_rules! native_try {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 把指定存储下标的 own 属性 meta 改写为冻结形态:数据属性 writable=false +
|
||||||
|
/// configurable=false,访问器属性 configurable=false(writable 不适用),
|
||||||
|
/// enumerable 保留原值。
|
||||||
|
fn freeze_own_prop_meta(obj: &mut JsObject, store: u32) {
|
||||||
|
let meta = obj
|
||||||
|
.prop_meta_at(store)
|
||||||
|
.unwrap_or_else(|| PropMetaEntry::data(PropAttributes::DEFAULT_DATA));
|
||||||
|
let attrs = PropAttributes::new(false, meta.attributes.enumerable(), false);
|
||||||
|
if meta.is_accessor {
|
||||||
|
obj.set_accessor_meta(store, meta.get, meta.set, attrs);
|
||||||
|
} else {
|
||||||
|
obj.set_data_meta(store, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 把指定存储下标的 own 属性 meta 改写为密封形态:configurable=false,
|
||||||
|
/// writable/enumerable 与访问器形态保留原值。
|
||||||
|
fn seal_own_prop_meta(obj: &mut JsObject, store: u32) {
|
||||||
|
let meta = obj
|
||||||
|
.prop_meta_at(store)
|
||||||
|
.unwrap_or_else(|| PropMetaEntry::data(PropAttributes::DEFAULT_DATA));
|
||||||
|
let attrs = PropAttributes::new(meta.attributes.writable(), meta.attributes.enumerable(), false);
|
||||||
|
if meta.is_accessor {
|
||||||
|
obj.set_accessor_meta(store, meta.get, meta.set, attrs);
|
||||||
|
} else {
|
||||||
|
obj.set_data_meta(store, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `Object.freeze(obj)`:冻结对象(不可扩展 + 全部属性不可配置/不可写),返回原对象。
|
/// `Object.freeze(obj)`:冻结对象(不可扩展 + 全部属性不可配置/不可写),返回原对象。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续
|
||||||
|
/// 写/define 一律回落 ordinary_set / define 检查(writable/configurable 判定)
|
||||||
pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.freeze called on non-object"));
|
return NativeResult::Err(crate::error::create_type_error(vm, "Object.freeze called on non-object"));
|
||||||
@@ -769,14 +801,34 @@ pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
return NativeResult::Ok(val);
|
return NativeResult::Ok(val);
|
||||||
}
|
}
|
||||||
let obj_ptr = val.as_js_object_ptr();
|
let obj_ptr = val.as_js_object_ptr();
|
||||||
unsafe {
|
{
|
||||||
(*obj_ptr).set_frozen(true);
|
let obj = unsafe { &mut *obj_ptr };
|
||||||
(*obj_ptr).set_extensible(false);
|
// 命名属性:shape 链(数组对象存储索引 = 元素数 + 槽位)。
|
||||||
|
let keys = walk_own_keys(vm, obj);
|
||||||
|
for (_si, pos) in keys {
|
||||||
|
let store = if obj.is_array() { obj.array_prop_count + pos } else { pos };
|
||||||
|
freeze_own_prop_meta(obj, store);
|
||||||
|
}
|
||||||
|
// 数组元素区独立于 shape 链(hole 非 own 属性,跳过)。
|
||||||
|
if obj.is_array() {
|
||||||
|
for i in 0..obj.array_prop_count {
|
||||||
|
if obj.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
freeze_own_prop_meta(obj, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.set_frozen(true);
|
||||||
|
obj.set_extensible(false);
|
||||||
}
|
}
|
||||||
NativeResult::Ok(val)
|
NativeResult::Ok(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Object.seal(obj)`:密封对象(不可扩展 + 全部属性不可配置),返回原对象。
|
/// `Object.seal(obj)`:密封对象(不可扩展 + 全部属性不可配置),返回原对象。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续
|
||||||
|
/// define 回落 non-configurable 检查(已有属性写仍可正常进行)
|
||||||
pub fn object_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn object_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.seal called on non-object"));
|
return NativeResult::Err(crate::error::create_type_error(vm, "Object.seal called on non-object"));
|
||||||
@@ -786,9 +838,23 @@ pub fn object_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
return NativeResult::Ok(val);
|
return NativeResult::Ok(val);
|
||||||
}
|
}
|
||||||
let obj_ptr = val.as_js_object_ptr();
|
let obj_ptr = val.as_js_object_ptr();
|
||||||
unsafe {
|
{
|
||||||
(*obj_ptr).set_sealed(true);
|
let obj = unsafe { &mut *obj_ptr };
|
||||||
(*obj_ptr).set_extensible(false);
|
let keys = walk_own_keys(vm, obj);
|
||||||
|
for (_si, pos) in keys {
|
||||||
|
let store = if obj.is_array() { obj.array_prop_count + pos } else { pos };
|
||||||
|
seal_own_prop_meta(obj, store);
|
||||||
|
}
|
||||||
|
if obj.is_array() {
|
||||||
|
for i in 0..obj.array_prop_count {
|
||||||
|
if obj.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seal_own_prop_meta(obj, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.set_sealed(true);
|
||||||
|
obj.set_extensible(false);
|
||||||
}
|
}
|
||||||
NativeResult::Ok(val)
|
NativeResult::Ok(val)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{ic_debug, ic_trace, vm_trace};
|
|||||||
use oxide_bytecode::opcode::{OpCode, IC_EXT_WORDS};
|
use oxide_bytecode::opcode::{OpCode, IC_EXT_WORDS};
|
||||||
use oxide_runtime_api as coercion;
|
use oxide_runtime_api as coercion;
|
||||||
use oxide_types::object::JsObject;
|
use oxide_types::object::JsObject;
|
||||||
use oxide_types::private_key::{int_key_value, is_int_key, make_private_name_id};
|
use oxide_types::private_key::{int_key_value, is_int_key, make_int_key, make_private_name_id};
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
use crate::ic_helper::{self, ic_get_hit, ic_set_hit};
|
use crate::ic_helper::{self, ic_get_hit, ic_set_hit};
|
||||||
@@ -606,6 +606,19 @@ impl Vm {
|
|||||||
};
|
};
|
||||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
|
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
let obj = unsafe { &mut *obj_ptr };
|
||||||
|
// 有属性 meta(freeze/seal 逐属性写 meta 后恒 true,含 accessor 元素/描述符
|
||||||
|
// 元素):回落 ordinary_set 走完整 writable/accessor/extensible 检查,防止
|
||||||
|
// 直写绕过冻结语义。
|
||||||
|
if obj.has_prop_meta() {
|
||||||
|
let si = make_int_key(idx);
|
||||||
|
return self.ordinary_set_dispatch(obj, si, value, self.regs[rd]);
|
||||||
|
}
|
||||||
|
// 无 meta 快路径:不可扩展对象禁止新增元素(越界下标);已有元素直写不受限
|
||||||
|
// (无 meta 即默认可写)。
|
||||||
|
if !obj.is_extensible() && idx >= obj.array_prop_count {
|
||||||
|
self.raise_type_error("object is not extensible")?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
obj.set_prop_at(idx, value);
|
obj.set_prop_at(idx, value);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,6 +190,11 @@ impl Vm {
|
|||||||
// prop_count/迭代/内置方法看到的长度不一致。
|
// prop_count/迭代/内置方法看到的长度不一致。
|
||||||
let length_si = self.length_si;
|
let length_si = self.length_si;
|
||||||
if obj.is_array() && prop_name_si == length_si {
|
if obj.is_array() && prop_name_si == length_si {
|
||||||
|
// 冻结数组的 length 属性不可写(writable=false),赋值直接失败。
|
||||||
|
// sloppy/strict 差异未实现,与其它只读属性写一致统一抛 TypeError。
|
||||||
|
if obj.is_frozen() {
|
||||||
|
return self.raise_type_error("Cannot assign to read only property 'length'");
|
||||||
|
}
|
||||||
let pc_before = self.pc;
|
let pc_before = self.pc;
|
||||||
let number_len = self.coerce_number_bounded(val)?;
|
let number_len = self.coerce_number_bounded(val)?;
|
||||||
// ToPrimitive 抛错(valueOf/toString throw)已被 unwind 定向到外围 catch 时
|
// ToPrimitive 抛错(valueOf/toString throw)已被 unwind 定向到外围 catch 时
|
||||||
@@ -209,6 +214,11 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
let old_logical = obj.logical_len() as usize;
|
let old_logical = obj.logical_len() as usize;
|
||||||
let old_count = obj.array_prop_count as usize;
|
let old_count = obj.array_prop_count as usize;
|
||||||
|
// ArraySetLength:增长(newLen > oldLen)要求对象可扩展,不可扩展时
|
||||||
|
// 整个赋值失败且不修改(sloppy 静默失败;VM 未实现 strict 标志)。
|
||||||
|
if raw_new_len > old_logical && !obj.is_extensible() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
// ArraySetLength:收缩时若 [newLen, oldLen) 内存在不可配置元素,整个收缩
|
// ArraySetLength:收缩时若 [newLen, oldLen) 内存在不可配置元素,整个收缩
|
||||||
// 失败且不做任何修改(sloppy 赋值静默失败;VM 未实现 strict 标志,统一按 no-op)。
|
// 失败且不做任何修改(sloppy 赋值静默失败;VM 未实现 strict 标志,统一按 no-op)。
|
||||||
if raw_new_len < old_logical {
|
if raw_new_len < old_logical {
|
||||||
@@ -271,6 +281,11 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新属性(自身与原型链均无同名):须对象可扩展(OrdinarySet 的 extensible
|
||||||
|
// 检查),不可扩展时赋值失败。
|
||||||
|
if !obj.is_extensible() {
|
||||||
|
return self.raise_type_error("object is not extensible");
|
||||||
|
}
|
||||||
self.set_or_create_prop_value(obj, prop_name_si, val);
|
self.set_or_create_prop_value(obj, prop_name_si, val);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -461,6 +476,10 @@ impl Vm {
|
|||||||
return self.raise_type_error("cannot assign to read-only property");
|
return self.raise_type_error("cannot assign to read-only property");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 新属性创建要求对象可扩展(规范 extensible 检查在原型链 setter/只读判定之后)。
|
||||||
|
if !obj.is_extensible() {
|
||||||
|
return self.raise_type_error("object is not extensible");
|
||||||
|
}
|
||||||
// 新 shape 槽位 = 追加前命名属性数(与 push_prop 的追加位置一致)。
|
// 新 shape 槽位 = 追加前命名属性数(与 push_prop 的追加位置一致)。
|
||||||
let slot = obj.prop_vec_len() as u32;
|
let slot = obj.prop_vec_len() as u32;
|
||||||
let new_shape_id = self.kernel_core.shape_forge().make_shape(obj.shape_id(), prop_name_si);
|
let new_shape_id = self.kernel_core.shape_forge().make_shape(obj.shape_id(), prop_name_si);
|
||||||
@@ -501,6 +520,13 @@ impl Vm {
|
|||||||
// 数组下标键写入元素区(维护 array_prop_count),不进入 shape 链。
|
// 数组下标键写入元素区(维护 array_prop_count),不进入 shape 链。
|
||||||
if obj.is_array() {
|
if obj.is_array() {
|
||||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||||
|
// 新元素(越界或 hole 空洞)要求对象可扩展;已有元素覆盖不受限制。
|
||||||
|
// 常规入口(ordinary_set)已预先拦截,此处为 REST/SPREAD/builtin
|
||||||
|
// 内部等直调方的兜底。
|
||||||
|
let is_new = index >= obj.array_prop_count || obj.prop_meta_at(index).is_some_and(|m| m.is_hole());
|
||||||
|
if is_new && !obj.is_extensible() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
obj.set_prop_at(index, val);
|
obj.set_prop_at(index, val);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -508,6 +534,10 @@ impl Vm {
|
|||||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||||
obj.set_prop_shape(pos, val);
|
obj.set_prop_shape(pos, val);
|
||||||
} else {
|
} else {
|
||||||
|
// 不可扩展对象禁止新增命名属性(兜底路径,常规入口已拦截)。
|
||||||
|
if !obj.is_extensible() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let new_shape_id = self.kernel_core.shape_forge().make_shape(obj.shape_id(), prop_name_si);
|
let new_shape_id = self.kernel_core.shape_forge().make_shape(obj.shape_id(), prop_name_si);
|
||||||
obj.set_shape_id(new_shape_id);
|
obj.set_shape_id(new_shape_id);
|
||||||
// 数组对象:属性追加到 hash_props 属性区(元素之后),array_prop_count 不变。
|
// 数组对象:属性追加到 hash_props 属性区(元素之后),array_prop_count 不变。
|
||||||
@@ -541,6 +571,17 @@ impl Vm {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 新命名属性(shape 链 lookup miss)且对象不可扩展 → 拒绝定义
|
||||||
|
// (Object.defineProperty 抛 TypeError;Reflect.defineProperty 自动转 false)。
|
||||||
|
if self
|
||||||
|
.kernel_core
|
||||||
|
.shape_forge()
|
||||||
|
.lookup_position(obj.shape_id(), prop_name_si)
|
||||||
|
.is_none()
|
||||||
|
&& !obj.is_extensible()
|
||||||
|
{
|
||||||
|
return Err("object is not extensible".to_string());
|
||||||
|
}
|
||||||
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||||
// shape 槽位 → 存储索引(数组属性在元素区之后)。
|
// shape 槽位 → 存储索引(数组属性在元素区之后)。
|
||||||
if obj.is_array() {
|
if obj.is_array() {
|
||||||
@@ -589,6 +630,16 @@ impl Vm {
|
|||||||
return self.define_array_index_element(obj, index, JsValue::undefined(), attributes, true, get, set);
|
return self.define_array_index_element(obj, index, JsValue::undefined(), attributes, true, get, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 新命名属性(shape 链 lookup miss)且对象不可扩展 → 拒绝定义。
|
||||||
|
if self
|
||||||
|
.kernel_core
|
||||||
|
.shape_forge()
|
||||||
|
.lookup_position(obj.shape_id(), prop_name_si)
|
||||||
|
.is_none()
|
||||||
|
&& !obj.is_extensible()
|
||||||
|
{
|
||||||
|
return Err("object is not extensible".to_string());
|
||||||
|
}
|
||||||
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||||
// shape 槽位 → 存储索引(数组属性在元素区之后)。
|
// shape 槽位 → 存储索引(数组属性在元素区之后)。
|
||||||
if obj.is_array() {
|
if obj.is_array() {
|
||||||
@@ -629,6 +680,12 @@ impl Vm {
|
|||||||
if pos > oxide_types::object::MAX_DENSE_PROPS {
|
if pos > oxide_types::object::MAX_DENSE_PROPS {
|
||||||
return Err("array index out of dense range".to_string());
|
return Err("array index out of dense range".to_string());
|
||||||
}
|
}
|
||||||
|
// 新元素(越界或 hole 空洞)要求对象可扩展;已有元素重定义不受限(走下方
|
||||||
|
// non-configurable 校验)。
|
||||||
|
let is_new = pos >= obj.array_prop_count as usize || obj.prop_meta_at(pos).is_some_and(|m| m.is_hole());
|
||||||
|
if is_new && !obj.is_extensible() {
|
||||||
|
return Err("object is not extensible".to_string());
|
||||||
|
}
|
||||||
let pos = pos as u32;
|
let pos = pos as u32;
|
||||||
if let Some(current) = obj.prop_meta_at(pos) {
|
if let Some(current) = obj.prop_meta_at(pos) {
|
||||||
if !current.attributes.configurable() {
|
if !current.attributes.configurable() {
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ fn eval(source: &str) -> Result<JsValue, String> {
|
|||||||
vm.run(&module)
|
vm.run(&module)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回保活的 Vm:结果可能含对象指针(数组/描述符),读取前必须保持 Vm 存活,
|
||||||
|
// 否则 epoch arena 随 drop 释放后指针悬垂(use-after-free)。
|
||||||
|
fn eval_keep_vm(source: &str) -> Result<(Vm, JsValue), String> {
|
||||||
|
let allocator = oxide_parser::Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||||
|
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = vm.run(&module)?;
|
||||||
|
Ok((vm, result))
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_many(lines: &[&str]) -> Result<JsValue, String> {
|
fn eval_many(lines: &[&str]) -> Result<JsValue, String> {
|
||||||
let source = lines.join("; ");
|
let source = lines.join("; ");
|
||||||
eval(&source)
|
eval(&source)
|
||||||
@@ -71,3 +82,153 @@ fn assign_non_writable_proto_configurable_prop_throws() {
|
|||||||
]);
|
]);
|
||||||
assert_err_contains(result, "read-only");
|
assert_err_contains(result, "read-only");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Object.freeze 后写已有属性抛 TypeError ──
|
||||||
|
#[test]
|
||||||
|
fn frozen_write_existing_prop_throws() {
|
||||||
|
let result = eval_many(&["var o = Object.freeze({a: 1})", "o.a = 2"]);
|
||||||
|
assert_err_contains(result, "read-only");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Object.freeze 后写新属性抛 TypeError ──
|
||||||
|
#[test]
|
||||||
|
fn frozen_write_new_prop_throws() {
|
||||||
|
let result = eval_many(&["var o = Object.freeze({a: 1})", "o.b = 2"]);
|
||||||
|
assert_err_contains(result, "not extensible");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Object.freeze 后 defineProperty 抛 TypeError ──
|
||||||
|
#[test]
|
||||||
|
fn frozen_define_property_throws() {
|
||||||
|
let result = eval_many(&["var o = Object.freeze({})", "Object.defineProperty(o, 'x', {value: 1})"]);
|
||||||
|
assert_err_contains(result, "not extensible");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Object.seal 后写新属性抛错、删属性返回 false、改值生效 ──
|
||||||
|
#[test]
|
||||||
|
fn sealed_semantics() {
|
||||||
|
let (_vm, r) = eval_keep_vm(
|
||||||
|
&[
|
||||||
|
"var o = Object.seal({a: 1})",
|
||||||
|
"var new_throws = false; try { o.b = 2 } catch (e) { new_throws = true }",
|
||||||
|
"var del = delete o.a",
|
||||||
|
"o.a = 5",
|
||||||
|
"[new_throws, del, o.a, o.hasOwnProperty('a')]",
|
||||||
|
]
|
||||||
|
.join("; "),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||||
|
assert!(
|
||||||
|
arr.get_prop_at(0).is_bool() && arr.get_prop_at(0).as_bool(),
|
||||||
|
"new prop write should throw"
|
||||||
|
);
|
||||||
|
assert!(arr.get_prop_at(1).is_bool() && !arr.get_prop_at(1).as_bool(), "delete should be false");
|
||||||
|
assert!(arr.get_prop_at(2).is_int() && arr.get_prop_at(2).as_int() == 5, "value write should work");
|
||||||
|
assert!(arr.get_prop_at(3).is_bool() && arr.get_prop_at(3).as_bool(), "prop should remain own");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Object.preventExtensions 后新属性不生效(sloppy 抛错),已有属性可写 ──
|
||||||
|
#[test]
|
||||||
|
fn prevent_extensions_semantics() {
|
||||||
|
let (_vm, r) = eval_keep_vm(
|
||||||
|
&[
|
||||||
|
"var o = Object.preventExtensions({a: 1})",
|
||||||
|
"var new_throws = false; try { o.b = 3 } catch (e) { new_throws = true }",
|
||||||
|
"o.a = 2",
|
||||||
|
"[new_throws, o.a, o.b === undefined]",
|
||||||
|
]
|
||||||
|
.join("; "),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||||
|
assert!(
|
||||||
|
arr.get_prop_at(0).is_bool() && arr.get_prop_at(0).as_bool(),
|
||||||
|
"new prop write should throw"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
arr.get_prop_at(1).is_int() && arr.get_prop_at(1).as_int() == 2,
|
||||||
|
"existing prop write should work"
|
||||||
|
);
|
||||||
|
assert!(arr.get_prop_at(2).is_bool() && arr.get_prop_at(2).as_bool(), "new prop should be absent");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Object.freeze 数组:元素写与 length 收缩均被拦截 ──
|
||||||
|
#[test]
|
||||||
|
fn frozen_array_write_and_length_throws() {
|
||||||
|
let (_vm, r) = eval_keep_vm(
|
||||||
|
&[
|
||||||
|
"var a = Object.freeze([1, 2])",
|
||||||
|
"var e1 = false, e2 = false",
|
||||||
|
"try { a[0] = 9 } catch (e) { e1 = true }",
|
||||||
|
"try { a.length = 0 } catch (e) { e2 = true }",
|
||||||
|
"[e1, e2, a[0], a.length, Object.getOwnPropertyDescriptor(a, '0').writable]",
|
||||||
|
]
|
||||||
|
.join("; "),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||||
|
assert!(arr.get_prop_at(0).is_bool() && arr.get_prop_at(0).as_bool(), "element write should throw");
|
||||||
|
assert!(arr.get_prop_at(1).is_bool() && arr.get_prop_at(1).as_bool(), "length shrink should throw");
|
||||||
|
assert!(arr.get_prop_at(2).is_int() && arr.get_prop_at(2).as_int() == 1, "element value unchanged");
|
||||||
|
assert!(arr.get_prop_at(3).is_int() && arr.get_prop_at(3).as_int() == 2, "length unchanged");
|
||||||
|
assert!(
|
||||||
|
arr.get_prop_at(4).is_bool() && !arr.get_prop_at(4).as_bool(),
|
||||||
|
"descriptor writable should be false"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── isFrozen/isSealed:手动 defineProperty 全部冻结(未调 freeze)也应判 true ──
|
||||||
|
#[test]
|
||||||
|
fn is_frozen_manual_define_props() {
|
||||||
|
let r = eval_many(&[
|
||||||
|
"var o = {a: 1}",
|
||||||
|
"Object.defineProperty(o, 'a', {writable: false, configurable: false})",
|
||||||
|
"Object.preventExtensions(o)",
|
||||||
|
"Object.isFrozen(o)",
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
assert!(r.is_bool() && r.as_bool(), "manually frozen object should be isFrozen");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reflect.set / Reflect.defineProperty 对 frozen 对象返回 false ──
|
||||||
|
#[test]
|
||||||
|
fn reflect_ops_on_frozen_return_false() {
|
||||||
|
let (_vm, r) = eval_keep_vm(
|
||||||
|
&[
|
||||||
|
"var f = Object.freeze({a: 1})",
|
||||||
|
"[Reflect.set(f, 'a', 2), Reflect.set(f, 'b', 2), Reflect.defineProperty(f, 'x', {value: 1})]",
|
||||||
|
]
|
||||||
|
.join("; "),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||||
|
for i in 0..3 {
|
||||||
|
assert!(
|
||||||
|
arr.get_prop_at(i).is_bool() && !arr.get_prop_at(i).as_bool(),
|
||||||
|
"reflect op {} should be false",
|
||||||
|
i
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── freeze/seal 后 IC 直写路径失效:循环写仍被拦截 ──
|
||||||
|
#[test]
|
||||||
|
fn frozen_ic_path_still_blocks_write() {
|
||||||
|
let r = eval_many(&[
|
||||||
|
"var o = Object.freeze({a: 1})",
|
||||||
|
"var threw = false",
|
||||||
|
"for (var i = 0; i < 3; i++) { try { o.a = i } catch (e) { threw = true } }",
|
||||||
|
"threw",
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
assert!(r.is_bool() && r.as_bool(), "IC path should still throw on frozen write");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── sealed 对象 IC 路径:已有属性写正常 ──
|
||||||
|
#[test]
|
||||||
|
fn sealed_ic_path_allows_existing_write() {
|
||||||
|
let r = eval_many(&["var o = Object.seal({a: 1})", "for (var i = 0; i < 3; i++) { o.a = i }", "o.a"]).unwrap();
|
||||||
|
let ok = (r.is_int() && r.as_int() == 2) || (r.is_double() && r.as_double() == 2.0);
|
||||||
|
assert!(ok, "sealed existing prop write should work via IC, got {:?}", r);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user