mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: define/create 非对象目标抛错与描述符两阶段收集
This commit is contained in:
@@ -89,8 +89,13 @@ const KIND_PREFIXES: [(&str, &str); 7] = [
|
||||
/// # 注意事项
|
||||
/// - 仅在错误文本通道(kind 前缀约定)内使用;kind 名与创建入口的映射保持
|
||||
/// 单向,调用方不得据此改写原始异常值。
|
||||
/// - 空消息错误的文本即裸 kind 名(如 `"uncaught RangeError"`,与
|
||||
/// `String(new RangeError())` 序列化一致),按原文恢复 kind。
|
||||
pub fn split_kinded(text: &str) -> Option<(&'static str, &str)> {
|
||||
let text = text.strip_prefix("uncaught ").unwrap_or(text);
|
||||
if let Some((kind, _)) = KIND_PREFIXES.iter().find(|(kind, _)| *kind == text) {
|
||||
return Some((*kind, ""));
|
||||
}
|
||||
KIND_PREFIXES
|
||||
.iter()
|
||||
.find_map(|(kind, prefix)| text.strip_prefix(prefix).map(|rest| (*kind, rest)))
|
||||
|
||||
@@ -566,7 +566,11 @@ pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
if args.len() >= 3 && !vm.reg(args[2]).is_undefined() {
|
||||
if let Err(msg) = define_all_from_properties(vm, obj, vm.reg(args[2])) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, &msg));
|
||||
// 强转期用户异常原值重抛;define 失败文本按 kind 前缀恢复原类型。
|
||||
if let Some(exc) = vm.take_pending_length_exception() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_define_failure(vm, &msg));
|
||||
}
|
||||
}
|
||||
return NativeResult::Ok(JsValue::from_js_object(obj));
|
||||
@@ -580,7 +584,11 @@ pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
|
||||
if args.len() >= 3 && !vm.reg(args[2]).is_undefined() {
|
||||
if let Err(msg) = define_all_from_properties(vm, obj, vm.reg(args[2])) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, &msg));
|
||||
// 强转期用户异常原值重抛;define 失败文本按 kind 前缀恢复原类型。
|
||||
if let Some(exc) = vm.take_pending_length_exception() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_define_failure(vm, &msg));
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||||
@@ -819,36 +827,54 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 遍历对象自身的可枚举属性,把每个值当作描述符依次定义到目标对象上。
|
||||
/// `Object.defineProperties` 与 `Object.create(proto, properties)` 共用。
|
||||
/// 把 properties 实参按 ToObject 装箱后遍历其可枚举自身属性,把每个值当作
|
||||
/// 描述符定义到目标对象上。`Object.defineProperties` 与
|
||||
/// `Object.create(proto, properties)` 共用。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. ToObject 装箱 properties(null/undefined 抛 TypeError,包装盒无键则零定义)
|
||||
/// 2. 先对全部可枚举键完成描述符值收集(触发全部 getter),再逐键定义——
|
||||
/// 先取后写,避免前键定义影响后键读取
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `props_val` 必须是对象,否则返回错误(null 触 ToObject 抛 TypeError)
|
||||
/// - 仅处理可枚举自身属性;描述符值经 ordinary_get 读取(触发访问器 getter)
|
||||
/// - 描述符值的非对象校验由 define_from_descriptor 承担(ToPropertyDescriptor)
|
||||
/// - String 盒的自身键是字符下标 0..len-1(String exotic,各键值为对应
|
||||
/// 字符),盒存储未落属性枚举面,此处按规范物化
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 修改 target 的 shape 链、属性表与 generation
|
||||
fn define_all_from_properties<H: VmHost>(
|
||||
vm: &mut H, target_ptr: *mut JsObject, props_val: JsValue,
|
||||
) -> Result<(), String> {
|
||||
if !props_val.is_object() {
|
||||
return Err("Property description must be an object".to_string());
|
||||
}
|
||||
let props_ptr = props_val.as_js_object_ptr();
|
||||
if props_ptr.is_null() {
|
||||
return Err("Property description must be an object".to_string());
|
||||
}
|
||||
let prop_keys: Vec<(u32, u32)> = {
|
||||
// ToObject:原始值装箱;null/undefined 的 TypeError 文本经调用方 kind
|
||||
// 前缀恢复后原类型抛出。
|
||||
let props_obj = oxide_runtime_api::to_object(props_val, vm)?;
|
||||
let props_ptr = props_obj.as_js_object_ptr();
|
||||
let descriptors: Vec<(u32, JsValue)> = if unsafe { &*props_ptr }.is_string_obj() {
|
||||
// String 盒:键为字符下标、值为字符本身;空串零键。
|
||||
let props = unsafe { &*props_ptr };
|
||||
let raw = props.boxed_value();
|
||||
let raw = if raw.is_undefined() { props.get_prop_at(0) } else { raw };
|
||||
let units = if raw.is_string() { vm.string_units(raw).to_vec() } else { Vec::new() };
|
||||
units
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, unit)| (make_int_key(i as u32), vm.new_string_units(std::slice::from_ref(unit))))
|
||||
.collect()
|
||||
} else {
|
||||
let props = unsafe { &*props_ptr };
|
||||
walk_own_keys(vm, props)
|
||||
.into_iter()
|
||||
// 非可枚举自身属性跳过;无显式 meta 视为可枚举(普通字面量默认)。
|
||||
.filter(|(_si, offset)| !props.prop_meta_at(*offset).map(|m| !m.attributes.enumerable()).unwrap_or(false))
|
||||
.map(|(key_si, _offset)| {
|
||||
let props = unsafe { &*props_ptr };
|
||||
vm.ordinary_get(props, key_si, props_obj).map(|desc_val| (key_si, desc_val))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
};
|
||||
for (key_si, offset) in prop_keys {
|
||||
let props = unsafe { &*props_ptr };
|
||||
// 非可枚举自身属性跳过;无显式 meta 视为可枚举(普通字面量默认)。
|
||||
if props.prop_meta_at(offset).map(|m| !m.attributes.enumerable()).unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
let desc_val = vm.ordinary_get(props, key_si, props_val)?;
|
||||
for (key_si, desc_val) in descriptors {
|
||||
define_from_descriptor(vm, target_ptr, key_si, desc_val)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -863,10 +889,11 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
|
||||
"Object.defineProperty: expected at least 3 arguments",
|
||||
));
|
||||
}
|
||||
let obj_val = match oxide_runtime_api::to_object(vm.reg(args[1]), vm) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
|
||||
};
|
||||
// target 非对象直接抛 TypeError(不做 ToObject 装箱)。
|
||||
let obj_val = vm.reg(args[1]);
|
||||
if !obj_val.is_object() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object"));
|
||||
}
|
||||
let obj_ptr = obj_val.as_js_object_ptr();
|
||||
// well-known symbol 等特殊键统一走 property_key_si(映射到各自的 Symbol 键),
|
||||
// 保证与计算属性访问、Reflect.defineProperty 等读键路径一致。
|
||||
@@ -1306,11 +1333,12 @@ pub fn object_define_properties<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
|
||||
"Object.defineProperties: expected at least 2 arguments",
|
||||
));
|
||||
}
|
||||
// target 非对象直接抛 TypeError(不做 ToObject 装箱)。
|
||||
let target_val = vm.reg(args[1]);
|
||||
let target_ptr = match require_obj_arg(vm, args, "defineProperties") {
|
||||
Ok(ptr) => ptr,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
};
|
||||
if !target_val.is_object() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperties called on non-object"));
|
||||
}
|
||||
let target_ptr = target_val.as_js_object_ptr();
|
||||
if let Err(msg) = define_all_from_properties(vm, target_ptr, vm.reg(args[2])) {
|
||||
// 与单属性入口一致:强转期用户异常原值重抛,非法 length 以 RangeError
|
||||
// kind 穿透,其余 define 失败投影为 TypeError。
|
||||
|
||||
@@ -432,3 +432,118 @@ fn object_get_own_property_descriptors_null_receiver_throws() {
|
||||
};
|
||||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||
}
|
||||
|
||||
// -- defineProperties / create properties 实参语义 --
|
||||
|
||||
#[test]
|
||||
fn object_define_properties_primitive_props_boxed() {
|
||||
// properties 实参经 ToObject 装箱(布尔/数字/空串/Symbol/数字盒),
|
||||
// 包装盒均无可枚举自身键:零定义,目标原样返回且自身键集不变。
|
||||
for arg in ["false", "-12", "''", "Symbol('s')", "42"] {
|
||||
let (_vm, result) = eval(&format!(
|
||||
"(function(){{ var t = {{m: 1}}; return Object.defineProperties(t, {arg}) === t }})()"
|
||||
))
|
||||
.unwrap();
|
||||
assert!(result.is_bool() && result.as_bool(), "arg {arg}");
|
||||
let (_vm, result) = eval(&format!(
|
||||
"(function(){{ var t = {{m: 1}}; Object.defineProperties(t, {arg}); return Object.keys(t).length }})()"
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(result.as_int(), 1, "arg {arg}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_create_primitive_props_boxed() {
|
||||
// Object.create 的 properties 实参同走 ToObject:数字盒零键,仅原型生效。
|
||||
let (_vm, result) = eval(
|
||||
"(function(){ var p = {}; var o = Object.create(p, 42); \
|
||||
return Object.getPrototypeOf(o) === p && Object.keys(o).length === 0 })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.is_bool() && result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_create_bigint_props_boxed() {
|
||||
// BigInt 实参装箱,BigInt 盒零可枚举自身键:仅原型生效。
|
||||
let (_vm, result) = eval(
|
||||
"(function(){ var p = {}; var o = Object.create(p, 0n); \
|
||||
return Object.getPrototypeOf(o) === p && Object.keys(o).length === 0 })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.is_bool() && result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_define_properties_null_props_throws() {
|
||||
// null 是 ToObject 拒绝的值:两入口均抛 TypeError。
|
||||
let err = match eval("Object.defineProperties({}, null)") {
|
||||
Ok(_) => panic!("null properties should throw TypeError"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||
let err = match eval("Object.create(null, null)") {
|
||||
Ok(_) => panic!("null properties should throw TypeError"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_define_property_primitive_target_throws() {
|
||||
// target 不做 ToObject 装箱:原始值直接抛 TypeError。
|
||||
for src in [
|
||||
"Object.defineProperty(0, 'a', {value: 1})",
|
||||
"Object.defineProperty(true, 'a', {value: 1})",
|
||||
"Object.defineProperty('abc', 'a', {value: 1})",
|
||||
] {
|
||||
let err = match eval(src) {
|
||||
Ok(_) => panic!("{src} should throw TypeError"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_define_properties_primitive_target_throws() {
|
||||
// target 不做 ToObject 装箱:原始值直接抛 TypeError。
|
||||
for src in [
|
||||
"Object.defineProperties(0, {})",
|
||||
"Object.defineProperties(true, {})",
|
||||
"Object.defineProperties('abc', {})",
|
||||
] {
|
||||
let err = match eval(src) {
|
||||
Ok(_) => panic!("{src} should throw TypeError"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_create_getter_range_error_propagates() {
|
||||
// 描述符收集期访问器 getter 抛出的用户异常原类型穿透,不投影为 TypeError。
|
||||
let err = match eval("Object.create(null, {get p(){ throw new RangeError('boom'); }})") {
|
||||
Ok(_) => panic!("getter RangeError should propagate"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert!(err.contains("RangeError"), "unexpected error: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_define_properties_two_phase_get_before_define() {
|
||||
// 两阶段语义:全部描述符 getter 先触发后逐键定义;后键 getter 抛出时
|
||||
// 前键尚未定义。
|
||||
let (_vm, result) = eval(
|
||||
"(function(){ var t = {}; var n = 0; \
|
||||
Object.defineProperty(t, 'p1', { get: function(){ n++; return {value: 1}; }, enumerable: true }); \
|
||||
Object.defineProperty(t, 'p2', { get: function(){ if (n === 1) { n++; throw 42; } return {value: 2}; }, enumerable: true }); \
|
||||
var target = {}; \
|
||||
try { Object.defineProperties(target, t); } catch (e) {} \
|
||||
return n === 2 && target.hasOwnProperty('p1') === false && target.hasOwnProperty('p2') === false })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.is_bool() && result.as_bool());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user