diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index f2a3dd39..22b94758 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -11,12 +11,13 @@ use oxide_runtime_api::{NativeResult, VmHost}; use crate::builtins_debug; -/// 判断字符串是否为规范整数索引:非空、无前导零、全数字且值 < 2^32。 +/// 判断字符串是否为规范整数索引:非空、无前导零、全数字且值 <= 2^32-2 +/// (array index 上限,2^32-1 落字符串键组)。 fn is_integer_index(key: &str) -> bool { if key.is_empty() || key.len() > 1 && key.as_bytes()[0] == b'0' { return false; } - key.bytes().all(|b| b.is_ascii_digit()) && key.parse::().unwrap_or(u64::MAX) < (1u64 << 32) + key.bytes().all(|b| b.is_ascii_digit()) && key.parse::().unwrap_or(u64::MAX) <= (1u64 << 32) - 2 } /// 收集对象全部自身属性(数组元素区 + shape 链),按规范顺序排列:整数索引在前 @@ -635,12 +636,17 @@ pub fn object_create(vm: &mut H, args: &[u8]) -> NativeResult { if proto_val.is_null() { 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])) { - // 强转期用户异常原值重抛;define 失败文本按 kind 前缀恢复原类型。 - if let Some(exc) = vm.take_pending_length_exception() { - return NativeResult::Err(exc); + match define_all_from_properties(vm, obj, vm.reg(args[2])) { + Ok(()) => {} + // 描述符字段读取期用户异常原值重抛。 + Err(DefineDescFailure::User(exc)) => return NativeResult::Err(exc), + Err(DefineDescFailure::Engine(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::Err(crate::error::create_define_failure(vm, &msg)); } } return NativeResult::Ok(JsValue::from_js_object(obj)); @@ -653,12 +659,17 @@ pub fn object_create(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])) { - // 强转期用户异常原值重抛;define 失败文本按 kind 前缀恢复原类型。 - if let Some(exc) = vm.take_pending_length_exception() { - return NativeResult::Err(exc); + match define_all_from_properties(vm, obj, vm.reg(args[2])) { + Ok(()) => {} + // 描述符字段读取期用户异常原值重抛。 + Err(DefineDescFailure::User(exc)) => return NativeResult::Err(exc), + Err(DefineDescFailure::Engine(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::Err(crate::error::create_define_failure(vm, &msg)); } } NativeResult::Ok(JsValue::from_js_object(obj)) @@ -738,6 +749,20 @@ pub fn object_is(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(oxide_runtime_api::same_value(lhs, rhs))) } +/// 描述符定义通道的失败原因:`Engine` 为 DefineOwnProperty 失败文本 +/// (kind 前缀约定不变,调用方按既有口径恢复);`User` 为描述符字段读取期 +/// 用户代码抛出的原始异常值,原值重抛。 +pub(crate) enum DefineDescFailure { + Engine(String), + User(JsValue), +} + +impl From for DefineDescFailure { + fn from(exc: JsValue) -> Self { + DefineDescFailure::User(exc) + } +} + /// 按 ToPropertyDescriptor 语义把描述符定义/修改到对象的自身属性上。 /// /// 处理数据(value/writable)与访问器(get/set)两类描述符;描述符字段沿原型 @@ -755,11 +780,15 @@ pub fn object_is(vm: &mut H, args: &[u8]) -> NativeResult { /// /// # 副作用 /// - 修改 obj 的 shape 链、属性表与 generation +/// +/// # 注意事项 +/// - 描述符字段读取期用户代码(accessor getter)抛出的异常经 +/// `DefineDescFailure::User` 原值传播,调用方原值重抛。 pub(crate) fn define_from_descriptor( vm: &mut H, obj_ptr: *mut JsObject, key_si: u32, desc_val: JsValue, -) -> Result<(), String> { +) -> Result<(), DefineDescFailure> { if !desc_val.is_object() { - return Err("Property description must be an object".to_string()); + return Err(DefineDescFailure::Engine("Property description must be an object".to_string())); } let value_si = vm.kernel_core().perm_interner().intern("value").0; @@ -769,12 +798,13 @@ pub(crate) fn define_from_descriptor( 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_field = own_field(vm, desc_val, enumerable_si); - let configurable_field = own_field(vm, desc_val, configurable_si); + // 六字段按规范序逐个 GetV,读取期异常原值传播(ReturnIfAbrupt)。 + 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_field = own_field(vm, desc_val, enumerable_si)?; + let configurable_field = own_field(vm, desc_val, configurable_si)?; // 数组 length 是无 shape 槽的虚拟数据属性:需与普通已有属性一样参与描述符 // 缺省回填(writable 保持当前值、value 保持当前长度),其当前描述符由元素 @@ -809,15 +839,17 @@ pub(crate) fn define_from_descriptor( // 与 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()); + return Err(DefineDescFailure::Engine( + "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()); + return Err(DefineDescFailure::Engine("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()); + return Err(DefineDescFailure::Engine("Cannot redefine module namespace export".to_string())); } } } @@ -833,7 +865,9 @@ pub(crate) fn define_from_descriptor( 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 Err("Invalid property descriptor: cannot mix data and accessor fields".to_string()); + return Err(DefineDescFailure::Engine( + "Invalid property descriptor: cannot mix data and accessor fields".to_string(), + )); } let existing_value = if is_array_length { @@ -861,7 +895,9 @@ pub(crate) fn define_from_descriptor( .unwrap_or(JsValue::undefined()) }); if (!get.is_undefined() && !is_callable(get)) || (!set.is_undefined() && !is_callable(set)) { - return Err("accessor descriptor get/set must be callable or undefined".to_string()); + return Err(DefineDescFailure::Engine( + "accessor descriptor get/set must be callable or undefined".to_string(), + )); } (get, set) } else { @@ -880,19 +916,22 @@ pub(crate) fn define_from_descriptor( || enumerable_field.is_some_and(|e| !oxide_runtime_api::to_boolean(e)) || configurable_field.is_some_and(|c| !oxide_runtime_api::to_boolean(c)) { - return Err( + return Err(DefineDescFailure::Engine( "cannot define property: TypedArray index only accepts a writable, enumerable, configurable data descriptor" .to_string(), - ); + )); } let value = value_field.unwrap_or_else(|| { crate::typed_array::typed_array_element_get(vm, unsafe { &*obj_ptr }, index) .unwrap_or(JsValue::undefined()) }); - return crate::typed_array::typed_array_element_define(vm, unsafe { &mut *obj_ptr }, index, value); + return crate::typed_array::typed_array_element_define(vm, unsafe { &mut *obj_ptr }, index, value) + .map_err(DefineDescFailure::Engine); } crate::typed_array::TaIndexGate::NumericInvalid => { - return Err("cannot define property: TypedArray index out of range".to_string()); + return Err(DefineDescFailure::Engine( + "cannot define property: TypedArray index out of range".to_string(), + )); } crate::typed_array::TaIndexGate::Ordinary => {} } @@ -900,7 +939,8 @@ pub(crate) fn define_from_descriptor( let obj = unsafe { &mut *obj_ptr }; if has_accessor { - vm.define_accessor_property(obj, key_si, get, set, PropAttributes::new(false, enumerable, configurable))?; + vm.define_accessor_property(obj, key_si, get, set, PropAttributes::new(false, enumerable, configurable)) + .map_err(DefineDescFailure::Engine)?; } else if has_data { let value = if has_existing { value_field.unwrap_or(existing_value) @@ -914,7 +954,8 @@ pub(crate) fn define_from_descriptor( } else { writable_field.map(oxide_runtime_api::to_boolean).unwrap_or(false) }; - vm.define_data_property(obj, key_si, value, PropAttributes::new(writable, enumerable, configurable))?; + vm.define_data_property(obj, key_si, value, PropAttributes::new(writable, enumerable, configurable)) + .map_err(DefineDescFailure::Engine)?; } else { if !has_existing { vm.define_data_property( @@ -922,7 +963,8 @@ pub(crate) fn define_from_descriptor( key_si, JsValue::undefined(), PropAttributes::new(false, enumerable, configurable), - )?; + ) + .map_err(DefineDescFailure::Engine)?; return Ok(()); } let is_accessor = existing_meta.map(|m| m.is_accessor).unwrap_or(false); @@ -931,9 +973,11 @@ pub(crate) fn define_from_descriptor( if is_accessor { let get = existing_meta.map(|m| m.get).unwrap_or(JsValue::undefined()); let set = existing_meta.map(|m| m.set).unwrap_or(JsValue::undefined()); - vm.define_accessor_property(obj, key_si, get, set, attrs)?; + vm.define_accessor_property(obj, key_si, get, set, attrs) + .map_err(DefineDescFailure::Engine)?; } else { - vm.define_data_property(obj, key_si, existing_value, attrs)?; + vm.define_data_property(obj, key_si, existing_value, attrs) + .map_err(DefineDescFailure::Engine)?; } } Ok(()) @@ -956,12 +1000,16 @@ pub(crate) fn define_from_descriptor( /// /// # 副作用 /// - 修改 target 的 shape 链、属性表与 generation +/// +/// # 注意事项 +/// - 描述符值读取期(getter)与字段读取期用户代码抛出的异常经 +/// `DefineDescFailure::User` 原值传播,调用方原值重抛。 fn define_all_from_properties( vm: &mut H, target_ptr: *mut JsObject, props_val: JsValue, -) -> Result<(), String> { +) -> Result<(), DefineDescFailure> { // ToObject:原始值装箱;null/undefined 的 TypeError 文本经调用方 kind // 前缀恢复后原类型抛出。 - let props_obj = oxide_runtime_api::to_object(props_val, vm)?; + let props_obj = oxide_runtime_api::to_object(props_val, vm).map_err(DefineDescFailure::Engine)?; let props_ptr = props_obj.as_js_object_ptr(); let descriptors: Vec<(u32, JsValue)> = { let props = unsafe { &*props_ptr }; @@ -973,7 +1021,9 @@ fn define_all_from_properties( let props = unsafe { &*props_ptr }; vm.ordinary_get(props, key_si, props_obj).map(|desc_val| (key_si, desc_val)) }) - .collect::, _>>()? + // 描述符值 getter 抛出的异常原值传播。 + .collect::, _>>() + .map_err(|msg| DefineDescFailure::User(crate::array::from_engine_error(vm, &msg)))? }; for (key_si, desc_val) in descriptors { define_from_descriptor(vm, target_ptr, key_si, desc_val)?; @@ -1000,13 +1050,18 @@ pub fn object_define_property(vm: &mut H, args: &[u8]) -> NativeResul // 保证与计算属性访问、Reflect.defineProperty 等读键路径一致。 let si = vm.property_key_si(vm.reg(args[2])); - if let Err(msg) = define_from_descriptor(vm, obj_ptr, si, vm.reg(args[3])) { - // 强转期用户代码(valueOf / Symbol.toPrimitive)抛出的异常值转存专用槽, - // 须原值重抛而非改写成引擎 TypeError。 - if let Some(exc) = vm.take_pending_length_exception() { - return NativeResult::Err(exc); + match define_from_descriptor(vm, obj_ptr, si, vm.reg(args[3])) { + Ok(()) => {} + // 描述符字段读取期用户代码(accessor getter)抛出的异常原值重抛。 + Err(DefineDescFailure::User(exc)) => return NativeResult::Err(exc), + Err(DefineDescFailure::Engine(msg)) => { + // 强转期用户代码(valueOf / Symbol.toPrimitive)抛出的异常值转存专用槽, + // 须原值重抛而非改写成引擎 TypeError。 + 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::Err(crate::error::create_define_failure(vm, &msg)); } NativeResult::Ok(obj_val) } @@ -1184,12 +1239,18 @@ pub fn object_get_own_property_descriptors(vm: &mut H, args: &[u8]) - /// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经 /// ordinary_get 取值(触发 accessor getter,receiver 为描述符对象本身)。 -fn own_field(vm: &mut H, desc: JsValue, prop_si: u32) -> Option { +/// 用户代码(accessor getter)抛出的异常原值传播。 +fn own_field(vm: &mut H, desc: JsValue, prop_si: u32) -> Result, 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() + let Some(_) = vm.resolve_property(obj, prop_si) else { + return Ok(None); + }; + match vm.ordinary_get(obj, prop_si, desc) { + Ok(v) => Ok(Some(v)), + Err(msg) => Err(crate::array::from_engine_error(vm, &msg)), + } } fn is_callable(value: JsValue) -> bool { @@ -1492,13 +1553,18 @@ pub fn object_define_properties(vm: &mut H, args: &[u8]) -> NativeRes 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。 - if let Some(exc) = vm.take_pending_length_exception() { - return NativeResult::Err(exc); + match define_all_from_properties(vm, target_ptr, vm.reg(args[2])) { + Ok(()) => {} + // 与单属性入口一致:描述符字段读取期用户异常原值重抛。 + Err(DefineDescFailure::User(exc)) => return NativeResult::Err(exc), + Err(DefineDescFailure::Engine(msg)) => { + // 强转期用户异常原值重抛,非法 length 以 RangeError kind 穿透, + // 其余 define 失败投影为 TypeError。 + 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::Err(crate::error::create_define_failure(vm, &msg)); } NativeResult::Ok(target_val) } diff --git a/crates/oxide_builtins/src/reflect.rs b/crates/oxide_builtins/src/reflect.rs index d1319caa..fa25ae8b 100644 --- a/crates/oxide_builtins/src/reflect.rs +++ b/crates/oxide_builtins/src/reflect.rs @@ -1,5 +1,6 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, PropMetaEntry}; +use oxide_types::private_key::is_int_key; use oxide_types::value::JsValue; use crate::array::{arraylike_get, from_engine_error, is_constructor_value}; @@ -11,6 +12,12 @@ use oxide_runtime_api::{to_length, NativeResult, VmHost}; const MAX_PROTO_CHAIN_DEPTH: usize = 1024; /// `Reflect.apply(target, thisArgument, argumentsList)`:以指定 this 与参数数组调用函数。 +/// +/// # 步骤 +/// 1. IsCallable(target) 为 false → TypeError;argumentsList 非对象 → TypeError。 +/// 2. CreateListFromArrayLike:规范读 length(访问器异常原值传播)、ToLength、 +/// 逐索引规范 Get。 +/// 3. Call(target, thisArgument, args);调用异常原值传播。 pub fn reflect_apply(vm: &mut H, args: &[u8]) -> NativeResult { let target = arg(vm, args, 1); let this_arg = arg(vm, args, 2); @@ -18,12 +25,30 @@ pub fn reflect_apply(vm: &mut H, args: &[u8]) -> NativeResult { if !is_callable(target) { return type_error(vm, "Reflect.apply target is not callable"); } - let Some(call_args) = array_like_elements(arg_list) else { + let Some(arg_list_ptr) = object_ptr(arg_list) else { return type_error(vm, "Reflect.apply argumentsList must be an array-like object"); }; + // SAFETY: object_ptr 保证指针非空且指向存活对象。 + let arg_list_obj = unsafe { &*arg_list_ptr }; + + // CreateListFromArrayLike:规范读 length(访问器异常原值传播),ToLength 纯函数不抛。 + let length_si = vm.kernel_core().perm_interner().intern("length").0; + let len_val = match vm.ordinary_get(arg_list_obj, length_si, arg_list) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)), + }; + let len = to_length(len_val) as usize; + let mut call_args = Vec::with_capacity(len); + for i in 0..len { + match arraylike_get(vm, arg_list_ptr, i) { + Ok(v) => call_args.push(v), + Err(exc) => return NativeResult::Err(exc), + } + } + match vm.call_function_sync(target, this_arg, &call_args) { Ok(value) => NativeResult::Ok(value), - Err(err) => type_error(vm, &err), + Err(err) => NativeResult::Err(from_engine_error(vm, &err)), } } @@ -103,7 +128,9 @@ pub fn reflect_define_property(vm: &mut H, args: &[u8]) -> NativeResu match crate::object::define_from_descriptor(vm, target_ptr, key_si, desc_val) { Ok(()) => NativeResult::Ok(JsValue::bool(true)), - Err(msg) => { + // 描述符字段读取期用户代码(accessor getter)抛出的异常原值传播。 + Err(crate::object::DefineDescFailure::User(exc)) => NativeResult::Err(exc), + Err(crate::object::DefineDescFailure::Engine(msg)) => { // 强转期用户代码(valueOf / Symbol.toPrimitive)抛出的异常值转存专用槽, // 须原值重抛,不得投影为 false 或改写成引擎错误。 if let Some(exc) = vm.take_pending_length_exception() { @@ -147,7 +174,8 @@ pub fn reflect_get(vm: &mut H, args: &[u8]) -> NativeResult { let receiver = if args.len() > 3 { vm.reg(args[3]) } else { target_val }; match vm.ordinary_get(unsafe { &*target_ptr }, key_si, receiver) { Ok(value) => NativeResult::Ok(value), - Err(err) => type_error(vm, &err), + // 访问器 getter 抛出的异常原值传播(ReturnIfAbrupt)。 + Err(err) => NativeResult::Err(from_engine_error(vm, &err)), } } @@ -200,7 +228,8 @@ pub fn reflect_is_extensible(vm: &mut H, args: &[u8]) -> NativeResult /// `Reflect.ownKeys(target)`:返回对象全部自身属性键,字符串键在前、Symbol 键在后。 /// /// # 步骤 -/// 1. 收集字符串键与整数键(整数升序,其余插入序)。 +/// 1. 收集字符串键与整数键(整数升序,其余插入序);数组目标注入虚拟 +/// `length`(字符串组首)。 /// 2. 追加自身 Symbol 键(保持插入序),符合 OrdinaryOwnPropertyKeys 的排序。 /// /// # 副作用 @@ -214,7 +243,15 @@ pub fn reflect_own_keys(vm: &mut H, args: &[u8]) -> NativeResult { // Symbol 键分流在消费端追加:底层字符串枚举源(Object.keys / JSON / for-in) // 语义不变,不泄漏 Symbol 键。 - let keys = walk_own_keys(vm, target); + let mut keys = walk_own_keys(vm, target); + // 数组 length 是虚拟自身属性(无 shape 槽、不在形状链):注入字符串组首 + // (创建序第一枚字符串键),存储下标哨兵 u32::MAX = 无槽位(消费端只读 + // si,不读存储下标)。 + if target.is_array() { + let length_si = vm.kernel_core().perm_interner().intern("length").0; + let insert_at = keys.iter().position(|(si, _)| !is_int_key(*si)).unwrap_or(keys.len()); + keys.insert(insert_at, (length_si, u32::MAX)); + } let symbols = own_symbol_key_values(vm, target); let n = keys.len() + symbols.len(); @@ -453,9 +490,3 @@ fn is_callable(value: JsValue) -> bool { fn type_error(vm: &mut H, message: &str) -> NativeResult { NativeResult::Err(crate::error::create_type_error(vm, message)) } - -fn array_like_elements(value: JsValue) -> Option> { - let ptr = object_ptr(value)?; - let obj = unsafe { &*ptr }; - Some((0..obj.prop_count() as usize).map(|idx| obj.get_prop_at(idx)).collect()) -} diff --git a/crates/oxide_vm/tests/builtins_reflect_tests.rs b/crates/oxide_vm/tests/builtins_reflect_tests.rs index 64ad9711..a81cc2cd 100644 --- a/crates/oxide_vm/tests/builtins_reflect_tests.rs +++ b/crates/oxide_vm/tests/builtins_reflect_tests.rs @@ -367,3 +367,106 @@ fn construct_direct_new_derived_array_guard() { .unwrap(); assert!(result.as_bool()); } + +// 数组 length 是虚拟自身属性:ownKeys 注入字符串组首(创建序第一枚字符串键)。 +#[test] +fn reflect_own_keys_array_includes_virtual_length() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var k1 = Reflect.ownKeys([]); \ + var k2 = Reflect.ownKeys([,,2]); \ + var a = []; a.x = 1; var k3 = Reflect.ownKeys(a); \ + k1.join(',') === 'length' && k2.join(',') === '2,length' && k3.join(',') === 'length,x'", + ) + .unwrap(); + assert!(result.as_bool()); +} + +// array index 上限 2^32-2:4294967294 落索引组(整数升序在前), +// 4294967295 落字符串组(创建序)。 +#[test] +fn reflect_own_keys_large_index_boundary() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var o = { b: 1, a: 2, 4294967294: 3, 4294967295: 4 }; \ + Reflect.ownKeys(o).join(',')", + ) + .unwrap(); + assert_eq!(to_str(&vm, result), "4294967294,b,a,4294967295"); +} + +// Reflect.get 的 own 与 inherited 访问器异常两臂均原值传播。 +#[test] +fn reflect_get_propagates_accessor_exception_original_value() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var boom = { value: 1 }; \ + var own = { get p() { throw boom; } }; \ + var caughtOwn; \ + try { Reflect.get(own, 'p'); caughtOwn = 'none'; } catch (e) { caughtOwn = e; } \ + var inherited = { q: 1 }; \ + Object.defineProperty(inherited, 'q', { get() { throw boom; }, configurable: true }); \ + var sub = Object.create(inherited); \ + var caughtInh; \ + try { Reflect.get(sub, 'q'); caughtInh = 'none'; } catch (e) { caughtInh = e; } \ + caughtOwn === boom && caughtInh === boom", + ) + .unwrap(); + assert!(result.as_bool()); +} + +// apply 的 argumentsList 按 CreateListFromArrayLike 读取:length 经 getter, +// 逐索引规范 Get;函数对象命名槽(prototype/length/name)不被误读。 +#[test] +fn reflect_apply_arguments_list_array_like() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "function fn() { return [arguments.length, arguments[0]]; } \ + var list = Object.create(null); \ + Object.defineProperty(list, 'length', { get: function() { return 1; } }); \ + var r = Reflect.apply(fn, null, list); \ + r[0] === 1 && r[1] === undefined", + ) + .unwrap(); + assert!(result.as_bool()); +} + +// apply 的 length getter 异常原值传播;非对象 argumentsList 抛 TypeError。 +#[test] +fn reflect_apply_propagates_length_getter_original_value() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var boom = 'X'; \ + var list = { get length() { throw boom; } }; \ + var caught; \ + try { Reflect.apply(function(){}, null, list); caught = 'none'; } catch (e) { caught = e; } \ + var thrown; \ + try { Reflect.apply(function(){}, null, 42); thrown = 'none'; } catch (e) { thrown = (e instanceof TypeError); } \ + caught === boom && thrown === true", + ) + .unwrap(); + assert!(result.as_bool()); +} + +// defineProperty 描述符字段 getter 抛出的异常原值传播(Reflect 与 Object 两侧同通道)。 +#[test] +fn define_property_propagates_attribute_getter_original_value() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var boom = 'X'; \ + var desc = { get enumerable() { throw boom; } }; \ + var caught; \ + try { Reflect.defineProperty({}, 'a', desc); caught = 'none'; } catch (e) { caught = e; } \ + var caughtObj; \ + try { Object.defineProperty({}, 'a', desc); caughtObj = 'none'; } catch (e) { caughtObj = e; } \ + caught === boom && caughtObj === boom", + ) + .unwrap(); + assert!(result.as_bool()); +} diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index 5d6d3979..db743bae 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -2073,12 +2073,13 @@ fn ta_reflect_has_subarray_view() { #[test] fn ta_keys_int_index_2pow32_boundary() { - // 2^32 边界:2^32-1 为规范数组索引(升序前置),2^32 起为串键(插入序)。 + // 2^32 边界:2^32-2 为规范数组索引上限(升序前置),2^32-1 起为串键 + // (创建序)。 let mut vm = Vm::new(); let result = eval( &mut vm, - "(function () { var ks1 = Object.keys({ a: 1, '4294967295': 2 }); \ - if (ks1.join(',') !== '4294967295,a') return false; \ + "(function () { var ks1 = Object.keys({ a: 1, '4294967294': 2, '4294967295': 3 }); \ + if (ks1.join(',') !== '4294967294,a,4294967295') return false; \ var ks2 = Object.keys({ b: 1, '4294967296': 2 }); \ return ks2.join(',') === 'b,4294967296'; })()", )