fix: 补 Error 族原型与 Math 六函数边及 Symbol 面描述符

This commit is contained in:
2026-09-24 17:33:49 +00:00
parent 44ead358ed
commit 53505dc38c
9 changed files with 334 additions and 53 deletions
+48 -6
View File
@@ -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<H: VmHost>(host: &mut H, this: *mut JsObject, args: &[u8]) {
fn set_own_message<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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
+180 -19
View File
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<f64> = 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<Vec<f64>, JsValue> {
let n = vm.native_arg_count(args);
let mut vals: Vec<f64> = 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<H: VmHost>(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<H: VmHost>(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))
}
+8 -18
View File
@@ -1845,23 +1845,13 @@ pub fn object_proto_lookup_setter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeR
lookup_accessor_field(vm, args, false)
}
/// 沿原型链判断是否为 Error 家族对象:对象自身带 `OBJ_TYPE_ERROR` 标签,或原型链上
/// 含 Error.prototype(等价 [[ErrorData]] 内部槽语义,覆盖 Error 用户子类)。
fn is_error_family<H: VmHost>(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<H: VmHost>(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"
+38
View File
@@ -95,6 +95,44 @@ pub fn symbol_to_string<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
match this_symbol_value(vm, vm.reg(args[0])) {