From 53505dc38c14a6782a2277220267b79b300dc602 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 24 Sep 2026 17:33:49 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A1=A5=20Error=20=E6=97=8F=E5=8E=9F?= =?UTF-8?q?=E5=9E=8B=E4=B8=8E=20Math=20=E5=85=AD=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E8=BE=B9=E5=8F=8A=20Symbol=20=E9=9D=A2=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/error.rs | 54 +++++- crates/oxide_builtins/src/math.rs | 199 ++++++++++++++++++-- crates/oxide_builtins/src/object.rs | 26 +-- crates/oxide_builtins/src/symbol.rs | 38 ++++ crates/oxide_kernel/src/builtin/bind.rs | 8 + crates/oxide_kernel/src/builtin/rebuild.rs | 9 +- crates/oxide_vm/src/bindings/bind_symbol.rs | 24 ++- crates/oxide_vm/src/dispatch/property.rs | 17 +- crates/oxide_vm/src/promise/aggregate.rs | 12 +- 9 files changed, 334 insertions(+), 53 deletions(-) diff --git a/crates/oxide_builtins/src/error.rs b/crates/oxide_builtins/src/error.rs index 92ca4381..f546fdbf 100644 --- a/crates/oxide_builtins/src/error.rs +++ b/crates/oxide_builtins/src/error.rs @@ -1,20 +1,30 @@ use std::sync::Arc; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; -use oxide_runtime_api::{to_string, to_string_full, NativeResult, VmHost}; +use oxide_runtime_api::{to_string_full, NativeResult, VmHost}; use oxide_types::mem::P; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; -fn set_own_message(host: &mut H, this: *mut JsObject, args: &[u8]) { +fn set_own_message(host: &mut H, this: *mut JsObject, args: &[u8]) -> NativeResult { if args.len() <= 1 { - return; + return NativeResult::Ok(JsValue::undefined()); } let msg_val = host.reg(args[1]); if msg_val.is_undefined() { - return; + return NativeResult::Ok(JsValue::undefined()); } - let msg_str = to_string(msg_val); + // 完整 ToString:对象经 ToPrimitive(string hint),Symbol 抛 TypeError, + // 用户转换方法抛出的异常原样传播。 + let msg_str = match to_string_full(msg_val, host) { + Ok(s) => s, + Err(_) => { + if let Some(exc) = host.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(create_type_error(host, "Cannot convert value to a string")); + } + }; let sf = Arc::clone(host.kernel_core().perm_interner()); let sh = Arc::clone(host.kernel_core().shape_forge()); let si = sf.intern("message").0; @@ -26,6 +36,7 @@ fn set_own_message(host: &mut H, this: *mut JsObject, args: &[u8]) { // message 按规范为非枚举数据属性(CreateNonEnumerableDataPropertyOrThrow)。 (*this).set_data_meta(pos, PropAttributes::new(true, false, true)); } + NativeResult::Ok(JsValue::undefined()) } /// 按错误类型名创建对应 Error 对象(message 非空时写为自身属性)。 @@ -170,7 +181,14 @@ macro_rules! error_ctor { unsafe { (*this).type_tag = JsObject::OBJ_TYPE_ERROR; } - set_own_message(host, this, args); + if let NativeResult::Err(e) = set_own_message(host, this, args) { + return NativeResult::Err(e); + } + // InstallErrorCause:message 转换之后、构造收尾之前。 + let options = if args.len() > 2 { host.reg(args[2]) } else { JsValue::undefined() }; + if let NativeResult::Err(e) = install_error_cause(host, this, options) { + return NativeResult::Err(e); + } NativeResult::Ok(JsValue::from_js_object(this)) } }; @@ -198,6 +216,30 @@ fn set_own_data_prop(host: &mut H, obj: *mut JsObject, key: &str, val } } +/// InstallErrorCause:options 为 Object 且 HasProperty(options, "cause") 时, +/// Get(options, "cause") 并写入 O 的非枚举数据属性。 +/// +/// # 边界与前提 +/// - options 非对象或无 "cause" 属性(含链上)→ 不写属性; +/// - "cause" 访问器 getter 抛出的异常原样传播。 +pub fn install_error_cause(host: &mut H, obj: *mut JsObject, options: JsValue) -> NativeResult { + if !options.is_object() { + return NativeResult::Ok(JsValue::undefined()); + } + // SAFETY: is_object 保证指针非空且对象本 session 存活。 + let opts = unsafe { &*options.as_js_object_ptr() }; + let si = host.kernel_core().perm_interner().intern("cause").0; + if host.resolve_property(opts, si).is_none() { + return NativeResult::Ok(JsValue::undefined()); + } + let cause = match host.ordinary_get(opts, si, options) { + Ok(v) => v, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(host, &e)), + }; + set_own_data_prop(host, obj, "cause", cause); + NativeResult::Ok(JsValue::undefined()) +} + /// `SuppressedError(error, suppressed, message)` 构造器:三参,length=3。 /// /// 以规范 CreateSuppressedError 语义建对象,三字段属性按 message → error diff --git a/crates/oxide_builtins/src/math.rs b/crates/oxide_builtins/src/math.rs index 223d8833..a1f76a38 100644 --- a/crates/oxide_builtins/src/math.rs +++ b/crates/oxide_builtins/src/math.rs @@ -2,7 +2,7 @@ use num_bigint::BigInt; use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero}; use oxide_types::value::JsValue; -use oxide_runtime_api::{NativeResult, VmHost}; +use oxide_runtime_api::{to_number_full, NativeResult, VmHost}; fn num(vm: &mut H, reg: u8) -> f64 { vm.coerce_number_bounded(vm.reg(reg)).unwrap_or(f64::NAN) @@ -76,11 +76,37 @@ pub fn math_atan2(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(a.atan2(b))) } -/// `Math.round`:四舍五入到最接近的整数,.5 时向正无穷取整(JS 语义)。 +/// `Math.round`:舍入到最接近的整数,.5 时向正无穷取整。 +/// +/// # 步骤 +/// 1. NaN → NaN;±∞ → 原值。 +/// 2. |x| ≥ 2^52 时 x 已是整数,恒等返回(免浮点加 0.5 在指数区丢精度)。 +/// 3. 其余 |x| < 2^52:`floor(x + 0.5)`(该区间 x+0.5 精确可表示), +/// 结果为 0 且 x 带负号时返回 -0(规范 -0.5 与 -0 臂)。 pub fn math_round(vm: &mut H, args: &[u8]) -> NativeResult { let x = arg1(vm, args); - let r = if x < 0.0 { (x - 0.5).ceil() } else { (x + 0.5).floor() }; - NativeResult::Ok(JsValue::float(r)) + NativeResult::Ok(JsValue::float(round_f64(x))) +} + +/// 规范 `Math.round` 数值核心(21.1.3.25):|x| ≥ 2^52 恒等,其余 `floor(x+0.5)`, +/// 零结果按 x 符号定 ±0。 +fn round_f64(x: f64) -> f64 { + if x.is_nan() { + return f64::NAN; + } + if x.is_infinite() { + return x; + } + // 2^52:该界上 x 必为整数,round 即恒等。 + const TWO_POW_52: f64 = 4_503_599_627_370_496.0; + if x >= TWO_POW_52 || x <= -TWO_POW_52 { + return x; + } + let r = (x + 0.5).floor(); + if r == 0.0 && x.is_sign_negative() { + return -0.0; + } + r } /// `Math.sign`:返回 1 / -1 / 0 / -0 / NaN。 @@ -97,9 +123,21 @@ pub fn math_sign(vm: &mut H, args: &[u8]) -> NativeResult { } } -/// `Math.clz32`:返回 32 位无符号整数表示的前导零个数。 +/// 规范 ToUint32(§7.1.10):NaN/±∞ → 0,其余按 2^32 取模归入 [0, 2^32)。 +fn to_uint32(x: f64) -> u32 { + if x.is_nan() || x.is_infinite() { + return 0; + } + let mut n = x % 4_294_967_296.0; + if n < 0.0 { + n += 4_294_967_296.0; + } + n as u32 +} + +/// `Math.clz32`:返回 ToUint32(x) 的 32 位无符号表示的前导零个数。 pub fn math_clz32(vm: &mut H, args: &[u8]) -> NativeResult { - let n = arg1(vm, args) as u32; + let n = to_uint32(arg1(vm, args)); NativeResult::Ok(JsValue::int(n.leading_zeros() as i32)) } @@ -212,57 +250,180 @@ pub fn math_f16round(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(f16_bits_to_f64(f64_to_f16_bits(arg1(vm, args))))) } -/// `Math.hypot`:返回 sqrt(a²+b²)(当前仅支持两个参数)。 +/// `Math.hypot`:返回实参平方和的平方根(sqrt(Σx²)),Kahan 求和免溢出/下溢。 +/// +/// # 步骤 +/// 1. 逐实参 ToNumber(传播异常;首个错误即停,后续实参不再求值)。 +/// 2. 无实参 → +0;任一 ±∞ → +∞(先于 NaN 判定);任一 NaN → NaN。 +/// 3. 按 max(|x|) 缩放后 Kahan 累加平方,全零 → +0。 pub fn math_hypot(vm: &mut H, args: &[u8]) -> NativeResult { - let (a, b) = arg2(vm, args); - NativeResult::Ok(JsValue::float(a.hypot(b))) + let n = vm.native_arg_count(args); + let mut vals: Vec = Vec::with_capacity(n); + for i in 0..n { + let v = vm.native_arg_at(args, i); + match to_number_full(v, vm) { + Ok(x) => vals.push(x), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), + } + } + if vals.is_empty() { + return NativeResult::Ok(JsValue::float(0.0)); + } + let mut max = 0.0f64; + let mut has_nan = false; + for &x in &vals { + if x.is_infinite() { + return NativeResult::Ok(JsValue::float(f64::INFINITY)); + } + if x.is_nan() { + has_nan = true; + } + let a = x.abs(); + if a > max { + max = a; + } + } + if has_nan { + return NativeResult::Ok(JsValue::float(f64::NAN)); + } + if max == 0.0 { + return NativeResult::Ok(JsValue::float(0.0)); + } + // Kahan 求和:补偿平方和的舍入误差。 + let mut sum = 0.0f64; + let mut comp = 0.0f64; + for &x in &vals { + let n = x / max; + let y = n * n - comp; + let t = sum + y; + comp = (t - sum) - y; + sum = t; + } + NativeResult::Ok(JsValue::float(sum.sqrt() * max)) } /// `Math.imul`:按 32 位整数做 wrap-around 乘法。 pub fn math_imul(vm: &mut H, args: &[u8]) -> NativeResult { let (a, b) = arg2(vm, args); - NativeResult::Ok(JsValue::int((a as i32).wrapping_mul(b as i32))) + let product = (to_uint32(a) as u64).wrapping_mul(to_uint32(b) as u64) & 0xFFFF_FFFF; + NativeResult::Ok(JsValue::int(product as i32)) } /// `Math.pow(base, exp)`:返回幂运算结果。 pub fn math_pow(vm: &mut H, args: &[u8]) -> NativeResult { let (a, b) = arg2(vm, args); - NativeResult::Ok(JsValue::float(a.powf(b))) + NativeResult::Ok(JsValue::float(pow_f64(a, b))) } -/// `Math.max`:返回参数中的最大值;任一无参时返回 -Infinity,任一 NaN 时返回 NaN。 +/// 规范 `Math.pow` 数值核心(Applying the `**` operator):特殊值 +/// (NaN/±∞/±0/±1)按规范臂判定,一般情形委托 IEEE 幂运算。 +fn pow_f64(a: f64, b: f64) -> f64 { + if b.is_nan() || a.is_nan() { + return f64::NAN; + } + if a.is_infinite() { + return if b == 0.0 { 1.0 } else if b > 0.0 { f64::INFINITY } else { 0.0 }; + } + if a == 0.0 { + // 保 a 的符号:+0^+b → +0,-0^+b → -0。 + return if b > 0.0 { a } else if b < 0.0 { f64::INFINITY } else { 1.0 }; + } + if a == 1.0 { + return if b.is_infinite() { f64::NAN } else { 1.0 }; + } + if a == -1.0 { + if b.is_infinite() { + return f64::NAN; + } + if b.fract() == 0.0 { + return if b % 2.0 == 0.0 { 1.0 } else { -1.0 }; + } + return f64::NAN; + } + if a < 0.0 { + return if b.fract() == 0.0 { a.powf(b) } else { f64::NAN }; + } + a.powf(b) +} + +/// 变长实参 Math 函数(max/min)的公共前段:逐实参 ToNumber 成列表 +/// (传播异常,全部元素先转换再比较)。 +fn to_number_all(vm: &mut H, args: &[u8]) -> Result, JsValue> { + let n = vm.native_arg_count(args); + let mut vals: Vec = Vec::with_capacity(n); + for i in 0..n { + let v = vm.native_arg_at(args, i); + match to_number_full(v, vm) { + Ok(x) => vals.push(x), + Err(e) => return Err(crate::iterator::engine_error(vm, &e)), + } + } + Ok(vals) +} + +/// `Math.max`:返回实参中的最大值;无实参返回 -Infinity,任一 NaN 返回 NaN。 +/// +/// # 步骤 +/// 1. 全部实参 ToNumber(传播异常)。 +/// 2. 任一 NaN → NaN。 +/// 3. +0 视为大于 -0:结果为 0 且出现过 +0 时返回 +0。 pub fn math_max(vm: &mut H, args: &[u8]) -> NativeResult { - if args.len() < 2 { + let vals = match to_number_all(vm, args) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if vals.is_empty() { return NativeResult::Ok(JsValue::float(f64::NEG_INFINITY)); } let mut m = f64::NEG_INFINITY; - for &r in args.iter().skip(1) { - let x = num(vm, r); + let mut has_plus_zero = false; + for &x in &vals { if x.is_nan() { return NativeResult::Ok(JsValue::float(f64::NAN)); } + if x == 0.0 && !x.is_sign_negative() { + has_plus_zero = true; + } if x > m { m = x; } } + if m == 0.0 && has_plus_zero { + m = 0.0; + } NativeResult::Ok(JsValue::float(m)) } -/// `Math.min`:返回参数中的最小值;无参时返回 Infinity,任一 NaN 时返回 NaN。 +/// `Math.min`:返回实参中的最小值;无实参返回 Infinity,任一 NaN 返回 NaN。 +/// +/// # 步骤 +/// 1. 全部实参 ToNumber(传播异常)。 +/// 2. 任一 NaN → NaN。 +/// 3. +0 视为大于 -0:结果为 0 且出现过 -0 时返回 -0。 pub fn math_min(vm: &mut H, args: &[u8]) -> NativeResult { - if args.len() < 2 { + let vals = match to_number_all(vm, args) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if vals.is_empty() { return NativeResult::Ok(JsValue::float(f64::INFINITY)); } let mut m = f64::INFINITY; - for &r in args.iter().skip(1) { - let x = num(vm, r); + let mut has_minus_zero = false; + for &x in &vals { if x.is_nan() { return NativeResult::Ok(JsValue::float(f64::NAN)); } + if x == 0.0 && x.is_sign_negative() { + has_minus_zero = true; + } if x < m { m = x; } } + if m == 0.0 && has_minus_zero { + m = -0.0; + } NativeResult::Ok(JsValue::float(m)) } diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 4a309dd2..f2a3dd39 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1845,23 +1845,13 @@ pub fn object_proto_lookup_setter(vm: &mut H, args: &[u8]) -> NativeR lookup_accessor_field(vm, args, false) } -/// 沿原型链判断是否为 Error 家族对象:对象自身带 `OBJ_TYPE_ERROR` 标签,或原型链上 -/// 含 Error.prototype(等价 [[ErrorData]] 内部槽语义,覆盖 Error 用户子类)。 -fn is_error_family(vm: &H, ptr: *mut JsObject) -> bool { - let error_proto_ptr = vm.session().builtin_world().error_proto.as_ptr() as *mut JsObject; - let mut cur = ptr; - while !cur.is_null() { - if std::ptr::eq(cur, error_proto_ptr) { - return true; - } - // SAFETY: cur 沿原型链遍历,链上每个节点都是合法 JsObject(proto 非对象时为空指针终止)。 - let o = unsafe { &*cur }; - if o.is_error_obj() { - return true; - } - cur = o.proto().as_js_object_ptr(); - } - false +/// 判断是否为 Error 家族对象:只看对象自身的 `OBJ_TYPE_ERROR` 标签(等价 +/// [[ErrorData]] 内部槽语义)。原型本体与 `Object.create(Error.prototype)` +/// 伪错误无标签,均按普通对象品牌。 +fn is_error_family(ptr: *mut JsObject) -> bool { + // SAFETY: 调用方保证 ptr 为本 session 存活对象。 + let o = unsafe { &*ptr }; + o.is_error_obj() } /// `Object.prototype.toString`:返回 `[object Tag]`。 @@ -1911,7 +1901,7 @@ pub fn object_proto_to_string(vm: &mut H, args: &[u8]) -> NativeResul "TypedArray" } else if obj.is_arguments_obj() { "Arguments" - } else if is_error_family(vm, obj_val.as_js_object_ptr()) { + } else if is_error_family(obj_val.as_js_object_ptr()) { "Error" } else { "Object" diff --git a/crates/oxide_builtins/src/symbol.rs b/crates/oxide_builtins/src/symbol.rs index 60a694b3..f86b1be5 100644 --- a/crates/oxide_builtins/src/symbol.rs +++ b/crates/oxide_builtins/src/symbol.rs @@ -95,6 +95,44 @@ pub fn symbol_to_string(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(vm.new_string_owned(result)) } +/// `Symbol.prototype[Symbol.toPrimitive](hint)`:thisSymbolValue 校验后按 +/// hint 返回 Symbol 或其描述串。 +/// +/// # 步骤 +/// 1. this 非 Symbol 或 Symbol 包装对象 → TypeError。 +/// 2. hint 缺省 → 返回 Symbol。 +/// 3. hint 做完整 ToString(异常传播);结果串为 "string" → 返回 +/// `Symbol(description)` 描述串,其余值(含 "number")→ 返回 Symbol。 +pub fn symbol_to_primitive(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(args[0]); + let sym = match this_symbol_value(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if args.len() <= 1 { + return NativeResult::Ok(sym); + } + let hint = vm.reg(args[1]); + if hint.is_undefined() { + return NativeResult::Ok(sym); + } + let hint_str = match oxide_runtime_api::to_string_full(hint, vm) { + Ok(s) => s, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); + } + }; + if hint_str == "string" { + let idx = sym.as_symbol_index(); + let desc = vm.symbol_description(idx).unwrap_or("").to_string(); + return NativeResult::Ok(vm.new_string_owned(format!("Symbol({})", desc))); + } + NativeResult::Ok(sym) +} + /// `Symbol.prototype.valueOf`:Symbol 原样返回;包装对象解盒返回其原始 Symbol。 pub fn symbol_value_of(vm: &mut H, args: &[u8]) -> NativeResult { match this_symbol_value(vm, vm.reg(args[0])) { diff --git a/crates/oxide_kernel/src/builtin/bind.rs b/crates/oxide_kernel/src/builtin/bind.rs index 71d70228..230305c9 100644 --- a/crates/oxide_kernel/src/builtin/bind.rs +++ b/crates/oxide_kernel/src/builtin/bind.rs @@ -216,6 +216,14 @@ impl BuiltinWorld { let name_pos = proto.push_prop(name_val); // 子类型原型上的 name 同 Error.prototype.name:非枚举数据属性。 proto.set_data_meta(name_pos, oxide_types::object::PropAttributes::new(true, false, true)); + // 子类型原型上的 message 初始值为空串,描述符同 name。 + let si_message = string_forge.intern("message").0; + let empty_si = string_forge.intern("").0; + let empty_val = JsValue::perm_string(string_forge.string_ptr(empty_si)); + let msg_shape = shape_forge.make_shape(proto.shape_id(), si_message); + proto.set_shape_id(msg_shape); + let msg_pos = proto.push_prop(empty_val); + proto.set_data_meta(msg_pos, oxide_types::object::PropAttributes::new(true, false, true)); } /// 把 String 家族方法安装到 String 构造器与原型上。 diff --git a/crates/oxide_kernel/src/builtin/rebuild.rs b/crates/oxide_kernel/src/builtin/rebuild.rs index ff814ad7..c5ab4cac 100644 --- a/crates/oxide_kernel/src/builtin/rebuild.rs +++ b/crates/oxide_kernel/src/builtin/rebuild.rs @@ -7,7 +7,8 @@ use oxide_types::object::JsObject; use oxide_types::value::JsValue; use super::construct::{ - builtin_labels, make_error_subtypes, make_named_pair, make_typed_array_family, tag_number_proto, tag_string_proto, + builtin_labels, make_error_subtypes, make_named_pair, make_typed_array_family, tag_boolean_proto, tag_number_proto, + tag_string_proto, wire_builtin_world_links, ErrorSubtypeProtos, TypedArrayFamily, }; use super::BuiltinWorld; @@ -196,7 +197,11 @@ impl BuiltinWorld { (current.number_proto.clone(), current.number_constructor.clone()) }; let (boolean_proto, boolean_constructor) = if dirty.boolean { - make_named_pair(string_forge, shape_forge, labels, "Boolean") + // 脏重建与全量构造同形:原型本体须带 Boolean 对象 tag 与 false 包值, + // 漏此分支 full_reset 后 tag 翻回 "Object"。 + let (proto, ctor) = make_named_pair(string_forge, shape_forge, labels, "Boolean"); + tag_boolean_proto(&proto); + (proto, ctor) } else { (current.boolean_proto.clone(), current.boolean_constructor.clone()) }; diff --git a/crates/oxide_vm/src/bindings/bind_symbol.rs b/crates/oxide_vm/src/bindings/bind_symbol.rs index ecc36910..ae87bc95 100644 --- a/crates/oxide_vm/src/bindings/bind_symbol.rs +++ b/crates/oxide_vm/src/bindings/bind_symbol.rs @@ -1,8 +1,9 @@ use std::sync::Arc; -use crate::bindings::{apply_binding_table, bind_accessor_getter, configure_native_constructor}; +use crate::bindings::{apply_binding_table, bind_accessor_getter, bind_well_known_method, configure_native_constructor}; use oxide_kernel::kernel::{KernelCore, KernelSession}; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::private_key::{make_well_known_symbol_key, WELL_KNOWN_SYMBOL_TO_PRIMITIVE}; use oxide_types::value::JsValue; use crate::bind_constructor; @@ -45,6 +46,22 @@ pub fn bind_symbol(core: &Arc, session: &KernelSession, global: &mut oxide_builtins::symbol::symbol_description_getter:: as *const (), ); + // [Symbol.toPrimitive] 方法:bind 默认描述符可写,规范为不可写,绑定后改 meta。 + bind_well_known_method( + session.builtin_world(), + core, + proto, + WELL_KNOWN_SYMBOL_TO_PRIMITIVE, + "[Symbol.toPrimitive]", + oxide_builtins::symbol::symbol_to_primitive:: as *const (), + 1, + ); + let to_prim_key = make_well_known_symbol_key(WELL_KNOWN_SYMBOL_TO_PRIMITIVE); + if let Some(pos) = core.shape_forge().lookup_position(proto.shape_id(), to_prim_key) { + proto.set_data_meta(pos, PropAttributes::new(false, false, true)); + proto.bump_generation(); + } + // well-known symbol 以符号原语绑定,下标与内建符号表(0..WELL_KNOWN_SYMBOL_COUNT) // 一一对应;名称表是 id/名映射的唯一来源,属性名去掉 `Symbol.` 前缀。 for (id, full_name) in oxide_types::private_key::WELL_KNOWN_SYMBOL_NAMES.iter().enumerate() { @@ -60,5 +77,8 @@ fn bind_well_known_symbol(core: &Arc, ctor: &mut JsObject, name: &st let shape_id = core.shape_forge().make_shape(ctor.shape_id(), si); ctor.set_shape_id(shape_id); ctor.ensure_hash_props().push(val); + // well-known symbol 属性按规范 { writable:false, enumerable:false, configurable:false }。 + let pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(pos, PropAttributes::new(false, false, false)); ctor.bump_generation(); } diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 58a9ef03..f5330dd0 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -31,6 +31,17 @@ impl Vm { } } + /// SET 路径的接收者解析:对象直取;非对象经 ToObject 自动装箱 + /// (null/undefined 抛 TypeError,其余原始值写盒后丢弃盒,与规范 + /// OrdinarySetWithOwnDescriptor 的 ToObject 基语义一致)。 + fn set_prop_base(&mut self, receiver: JsValue) -> Result, String> { + if receiver.is_object() { + return self.checked_object_ptr(receiver, "Cannot create property on non-object"); + } + let boxed = coercion::to_object(receiver, self)?; + Ok(Some(boxed.as_js_object_ptr())) + } + fn private_key_from_reg(&self, reg: usize) -> u32 { let value = self.regs[reg]; let local_id = if value.is_int() { @@ -401,7 +412,7 @@ impl Vm { } fn dispatch_ic_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { - let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else { + let Some(obj_ptr) = self.set_prop_base(self.regs[rd])? else { return Ok(()); }; @@ -484,7 +495,7 @@ impl Vm { fn dispatch_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("SET_PROP rd={} a={} b={}", rd, a, b); - let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else { + let Some(obj_ptr) = self.set_prop_base(self.regs[rd])? else { return Ok(()); }; let prop_name_si = self.property_key_si(self.regs[b])?; @@ -560,7 +571,7 @@ impl Vm { fn dispatch_set_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("SET_PROP_DYNAMIC rd={} a={} b={}", rd, a, b); - let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else { + let Some(obj_ptr) = self.set_prop_base(self.regs[rd])? else { return Ok(()); }; let prop_name_si = self.property_key_si(self.regs[a])?; diff --git a/crates/oxide_vm/src/promise/aggregate.rs b/crates/oxide_vm/src/promise/aggregate.rs index bcd73c48..b869bf21 100644 --- a/crates/oxide_vm/src/promise/aggregate.rs +++ b/crates/oxide_vm/src/promise/aggregate.rs @@ -191,7 +191,6 @@ impl Vm { pub(crate) fn init_aggregate_error_intrinsics(&mut self) { let sf = self.kernel_core.perm_interner().as_ref(); let sh = self.kernel_core.shape_forge().as_ref(); - let fn_proto_val = self.session.builtin_world().fn_proto_val(); let error_proto_val = JsValue::from_js_object(self.session.builtin_world().error_proto.as_ptr() as *mut JsObject); @@ -213,8 +212,10 @@ impl Vm { let msg_pos = proto.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("").0))); proto.set_data_meta(msg_pos, PropAttributes::new(true, false, true)); - // %AggregateError% 构造器:proto = %Function.prototype%,length 2。 - let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + // %AggregateError% 构造器:proto = %Error%(NativeError 构造器继承 Error 构造器),length 2。 + let error_ctor_val = + JsValue::from_js_object(self.session.builtin_world().error_constructor.as_ptr() as *mut JsObject); + let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, error_ctor_val)); ctor.set_function(true); ctor.type_tag = JsObject::OBJ_TYPE_CONSTRUCTOR; // SAFETY: aggregate_error_constructor 是 NativeFn 函数项。 @@ -1071,6 +1072,11 @@ fn aggregate_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { // SAFETY: this 是本次构造的存活对象。 let _ = vm.define_data_property(unsafe { &mut *this }, msg_si, msg_val, PropAttributes::new(true, false, true)); } + // InstallErrorCause:options 为第三实参(args[3]),message 之后、errors 收集之前。 + let options = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() }; + if let NativeResult::Err(e) = oxide_builtins::error::install_error_cause(vm, this, options) { + return NativeResult::Err(e); + } let errors_list = match vm.aggregate_errors_to_list(errors) { Ok(list) => list, Err(err) => return NativeResult::Err(err),