mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 11:11:11 +08:00
fix: 模块命名空间对象设为不可扩展并拦截写入
This commit is contained in:
@@ -14,9 +14,12 @@ use oxide_types::value::JsValue;
|
||||
/// `Symbol.toStringTag` 在 well-known symbol 表中的序号,命名空间标签键按其编码。
|
||||
const TO_STRING_TAG_SYMBOL_ID: u32 = 9;
|
||||
|
||||
/// 模块命名空间导出属性描述符:不可写、可枚举、不可配置(module namespace exotic)。
|
||||
/// 模块命名空间导出属性描述符:可写、可枚举、不可配置(module namespace exotic)。
|
||||
///
|
||||
/// 规范 10.4.6.5 返回 `{writable: true, enumerable: true, configurable: false}`;
|
||||
/// 写保护由命名空间的 `[[Set]]`/`[[DefineOwnProperty]]` 专属语义承担,不靠描述符位。
|
||||
fn ns_attrs() -> PropAttributes {
|
||||
PropAttributes::new(false, true, false)
|
||||
PropAttributes::new(true, true, false)
|
||||
}
|
||||
|
||||
fn type_error<H: VmHost>(vm: &mut H, msg: &str) -> NativeResult {
|
||||
@@ -24,6 +27,10 @@ fn type_error<H: VmHost>(vm: &mut H, msg: &str) -> NativeResult {
|
||||
}
|
||||
|
||||
/// `__moduleObject()`:创建模块命名空间对象(null 原型,带 @@toStringTag Symbol 键)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 对象创建起即标记 module namespace exotic 并置 non-extensible:导出定义由
|
||||
/// `module_set`/`module_star` 的瞬态可扩展窗口旁路,其余写/定义一律拒绝。
|
||||
pub fn module_object<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let tag_si = make_well_known_symbol_key(TO_STRING_TAG_SYMBOL_ID);
|
||||
@@ -32,6 +39,8 @@ pub fn module_object<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
if let Err(e) = vm.define_data_property(obj_ref, tag_si, tag_val, PropAttributes::new(false, false, false)) {
|
||||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||||
}
|
||||
obj_ref.set_module_namespace(true);
|
||||
obj_ref.set_extensible(false);
|
||||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||||
}
|
||||
|
||||
@@ -50,7 +59,12 @@ pub fn module_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let value = vm.reg(args[3]);
|
||||
let name_si = vm.property_key_si(name_val);
|
||||
let obj = unsafe { &mut *ns_ptr };
|
||||
if let Err(e) = vm.define_data_property(obj, name_si, value, ns_attrs()) {
|
||||
// 导出定义窗口:命名空间创建起不可扩展,定义新导出须瞬态放开扩展性。窗口内
|
||||
// 不运行 JS,无可观察副作用;错误路径先复位以维持 non-extensible 不变式。
|
||||
obj.set_extensible(true);
|
||||
let result = vm.define_data_property(obj, name_si, value, ns_attrs());
|
||||
obj.set_extensible(false);
|
||||
if let Err(e) = result {
|
||||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||||
}
|
||||
// 每次导出写入后重排:自导入在 body 内即可见有序的 [[OwnPropertyKeys]]。
|
||||
@@ -138,7 +152,11 @@ pub fn module_star<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
continue;
|
||||
}
|
||||
let value = src.get_prop_at(pos);
|
||||
if let Err(e) = vm.define_data_property(dst, name_si, value, ns_attrs()) {
|
||||
// star 导出同 module_set:定义新导出走瞬态可扩展窗口,错误路径先复位。
|
||||
dst.set_extensible(true);
|
||||
let result = vm.define_data_property(dst, name_si, value, ns_attrs());
|
||||
dst.set_extensible(false);
|
||||
if let Err(e) = result {
|
||||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||||
}
|
||||
}
|
||||
@@ -183,6 +201,9 @@ pub fn module_eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
|
||||
/// `__moduleData(kind, content)`:构造数据模块命名空间(json/text;bytes 未支持)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 同 `__moduleObject`:标记 module namespace exotic 并置 non-extensible。
|
||||
pub fn module_data<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 3 {
|
||||
return type_error(vm, "__moduleData: 2 arguments required");
|
||||
@@ -216,6 +237,7 @@ pub fn module_data<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if let Err(e) = vm.define_data_property(obj_ref, default_si, default_val, ns_attrs()) {
|
||||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||||
}
|
||||
obj_ref.set_module_namespace(true);
|
||||
obj_ref.set_extensible(false);
|
||||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||||
}
|
||||
|
||||
@@ -640,7 +640,7 @@ pub fn object_is<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 修改 obj 的 shape 链、属性表与 generation
|
||||
fn define_from_descriptor<H: VmHost>(
|
||||
pub(crate) fn define_from_descriptor<H: VmHost>(
|
||||
vm: &mut H, obj_ptr: *mut JsObject, key_si: u32, desc_val: JsValue,
|
||||
) -> Result<(), String> {
|
||||
if !desc_val.is_object() {
|
||||
@@ -672,6 +672,25 @@ fn define_from_descriptor<H: VmHost>(
|
||||
.unwrap_or_else(|| oxide_types::object::PropMetaEntry::data(PropAttributes::DEFAULT_DATA))
|
||||
});
|
||||
|
||||
// 模块命名空间 exotic [[DefineOwnProperty]](规范 10.4.6.5):Symbol 键委托
|
||||
// 普通语义;导出键仅接受无变更、值不变或 writable:true,值变与 writable:false
|
||||
// 拒绝;非导出键由对象不可扩展在后续 define 处拒绝。enumerable/configurable
|
||||
// 与 accessor 的收窄由 define_* 的不可配置守卫承担。
|
||||
if unsafe { &*obj_ptr }.is_module_namespace() && !is_symbol_key(key_si) {
|
||||
let Some(pos) = existing_pos else {
|
||||
return Err("Cannot define property on module namespace: not an export".to_string());
|
||||
};
|
||||
if writable_field.is_some_and(|w| !oxide_runtime_api::to_boolean(w)) {
|
||||
return Err("Cannot redefine module namespace export".to_string());
|
||||
}
|
||||
if let Some(v) = value_field {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
if !oxide_runtime_api::same_value(v, obj.get_prop_at(pos)) {
|
||||
return Err("Cannot redefine module namespace export".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改已有属性时缺省字段回填现有值,仅定义新属性时缺省才为 false。
|
||||
let enumerable = own_field(vm, desc_val, enumerable_si)
|
||||
.map(oxide_runtime_api::to_boolean)
|
||||
@@ -959,6 +978,14 @@ pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Ok(val);
|
||||
}
|
||||
let obj_ptr = val.as_js_object_ptr();
|
||||
{
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
// 模块命名空间导出保持 writable:true,冻结须失败(规范 SetIntegrityLevel)。
|
||||
// 无字符串导出的空命名空间没有可写属性,允许冻结成功。
|
||||
if obj.is_module_namespace() && !walk_own_keys(vm, obj).is_empty() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot freeze module namespace"));
|
||||
}
|
||||
}
|
||||
{
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
// 命名属性:walk_own_keys 返回绝对存储索引(数组含元素区偏移)。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::mem::P;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::object::{delete_own_property, key_si_to_js_value, own_symbol_key_values, walk_own_keys};
|
||||
@@ -70,6 +70,10 @@ pub fn reflect_construct<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
/// `Reflect.defineProperty(target, key, descriptor)`:按 descriptor 定义属性,
|
||||
/// 数据/访问器属性混用或非法 getter/setter 返回 false。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 与 `Object.defineProperty` 共用 `define_from_descriptor`:缺失字段回填现有
|
||||
/// 属性、模块命名空间 exotic 收窄语义一处生效;错误投影为布尔 false。
|
||||
pub fn reflect_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let target_val = arg(vm, args, 1);
|
||||
let Some(target_ptr) = object_ptr(target_val) else {
|
||||
@@ -81,43 +85,7 @@ pub fn reflect_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
};
|
||||
|
||||
let key_si = vm.property_key_si(arg(vm, args, 2));
|
||||
let value_si = vm.kernel_core().perm_interner().intern("value").0;
|
||||
let get_si = vm.kernel_core().perm_interner().intern("get").0;
|
||||
let set_si = vm.kernel_core().perm_interner().intern("set").0;
|
||||
let writable_si = vm.kernel_core().perm_interner().intern("writable").0;
|
||||
let enumerable_si = vm.kernel_core().perm_interner().intern("enumerable").0;
|
||||
let configurable_si = vm.kernel_core().perm_interner().intern("configurable").0;
|
||||
|
||||
let value_field = own_field(vm, desc_val, value_si);
|
||||
let get_field = own_field(vm, desc_val, get_si);
|
||||
let set_field = own_field(vm, desc_val, set_si);
|
||||
let writable_field = own_field(vm, desc_val, writable_si);
|
||||
let enumerable = own_field(vm, desc_val, enumerable_si)
|
||||
.map(oxide_runtime_api::to_boolean)
|
||||
.unwrap_or(false);
|
||||
let configurable = own_field(vm, desc_val, configurable_si)
|
||||
.map(oxide_runtime_api::to_boolean)
|
||||
.unwrap_or(false);
|
||||
|
||||
let has_data = value_field.is_some() || writable_field.is_some();
|
||||
let has_accessor = get_field.is_some() || set_field.is_some();
|
||||
if has_data && has_accessor {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
|
||||
let target = unsafe { &mut *target_ptr };
|
||||
let result = if has_accessor {
|
||||
let get = get_field.unwrap_or(JsValue::undefined());
|
||||
let set = set_field.unwrap_or(JsValue::undefined());
|
||||
if (!get.is_undefined() && !is_callable(get)) || (!set.is_undefined() && !is_callable(set)) {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
vm.define_accessor_property(target, key_si, get, set, PropAttributes::new(false, enumerable, configurable))
|
||||
} else {
|
||||
let value = value_field.unwrap_or(JsValue::undefined());
|
||||
let writable = writable_field.map(oxide_runtime_api::to_boolean).unwrap_or(false);
|
||||
vm.define_data_property(target, key_si, value, PropAttributes::new(writable, enumerable, configurable))
|
||||
};
|
||||
let result = crate::object::define_from_descriptor(vm, target_ptr, key_si, desc_val);
|
||||
NativeResult::Ok(JsValue::bool(result.is_ok()))
|
||||
}
|
||||
|
||||
@@ -295,13 +263,3 @@ fn array_like_elements(value: JsValue) -> Option<Vec<JsValue>> {
|
||||
let obj = unsafe { &*ptr };
|
||||
Some((0..obj.prop_count() as usize).map(|idx| obj.get_prop_at(idx)).collect())
|
||||
}
|
||||
|
||||
/// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经
|
||||
/// ordinary_get 取值(触发 accessor getter,receiver 为描述符对象本身)。
|
||||
fn own_field<H: VmHost>(vm: &mut H, desc: JsValue, prop_si: u32) -> Option<JsValue> {
|
||||
// ordinary_get 对缺失属性返回 undefined,无法区分"不存在"与"值为
|
||||
// undefined",故先用 resolve_property 沿原型链判 HasProperty。
|
||||
let obj = unsafe { &*desc.as_js_object_ptr() };
|
||||
vm.resolve_property(obj, prop_si)?;
|
||||
vm.ordinary_get(obj, prop_si, desc).ok()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user