diff --git a/crates/oxide_builtins/src/global.rs b/crates/oxide_builtins/src/global.rs index 4af1c332..e210f55b 100644 --- a/crates/oxide_builtins/src/global.rs +++ b/crates/oxide_builtins/src/global.rs @@ -10,21 +10,43 @@ pub use annex_b::{js_escape, js_unescape}; pub use uri::{decode_uri, decode_uri_component, encode_uri, encode_uri_component}; /// `isNaN(x)`:ToNumber 后检查是否为 NaN(隐式类型转换)。 +/// +/// ToNumber 可抛:Symbol 抛 TypeError,对象 ToPrimitive 触发 valueOf/toString +/// 抛出的原生异常原样传播。 pub fn global_is_nan(vm: &mut H, args: &[u8]) -> NativeResult { let n = if args.len() < 2 { f64::NAN } else { - oxide_runtime_api::to_number(vm.reg(args[1])) + match oxide_runtime_api::to_number_full(vm.reg(args[1]), vm) { + Ok(n) => n, + 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 number")); + } + } }; NativeResult::Ok(JsValue::bool(n.is_nan())) } /// `isFinite(x)`:ToNumber 后检查是否为有限数(隐式类型转换)。 +/// +/// ToNumber 可抛:Symbol 抛 TypeError,对象 ToPrimitive 触发 valueOf/toString +/// 抛出的原生异常原样传播。 pub fn global_is_finite(vm: &mut H, args: &[u8]) -> NativeResult { let n = if args.len() < 2 { f64::NAN } else { - oxide_runtime_api::to_number(vm.reg(args[1])) + match oxide_runtime_api::to_number_full(vm.reg(args[1]), vm) { + Ok(n) => n, + 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 number")); + } + } }; NativeResult::Ok(JsValue::bool(n.is_finite())) } diff --git a/crates/oxide_builtins/src/string/basic.rs b/crates/oxide_builtins/src/string/basic.rs index f0a7a2c1..28b27218 100644 --- a/crates/oxide_builtins/src/string/basic.rs +++ b/crates/oxide_builtins/src/string/basic.rs @@ -14,14 +14,39 @@ use super::{ // ── 静态方法 / 构造 ───────────────────────────────────────────────────── -/// `String.fromCharCode(...codes)`:把各参数按 ToUint32 低 16 位转为单元拼接 +/// `String.fromCharCode(...codes)`:各参数经 ToUint16(ToNumber) 转单元拼接 /// 为字符串(surrogate 区间合法产出孤立 surrogate 单元)。 +/// +/// ToNumber 可抛:Symbol 抛 TypeError,BigInt 抛 TypeError,对象 ToPrimitive +/// 触发 valueOf/toString 抛出的原生异常原样传播。 pub fn string_from_char_code(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.fromCharCode called with {} args", args.len()); let mut units: Vec = Vec::new(); - for &arg_reg in args.iter().skip(1) { - let code = oxide_runtime_api::to_uint32(vm.reg(arg_reg)) & 0xFFFF; - units.push(code as u16); + // 经 native_arg_count/native_arg_at 读取:大实参集在寄存器窗口外走 spill + // 溢出区,小实参集与既有寄存器路径一致。 + for i in 0..vm.native_arg_count(args) { + let arg_val = vm.native_arg_at(args, i); + // ToNumber(BigInt) 按规范抛 TypeError;to_number_full 的 BigInt 快路径 + // 静默转换,须在此显式拦截。 + if arg_val.is_bigint() { + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a BigInt value to a number")); + } + let n = match oxide_runtime_api::to_number_full(arg_val, vm) { + Ok(n) => n, + 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 number")); + } + }; + // ToUint16:floorMod(n, 2^16);NaN/±0/±Infinity 归零。 + let code = if n == 0.0 || !n.is_finite() { + 0 + } else { + n.trunc().rem_euclid(65536.0) as u16 + }; + units.push(code); } NativeResult::Ok(vm.new_string_units_owned(units)) } diff --git a/crates/oxide_builtins/src/string/common.rs b/crates/oxide_builtins/src/string/common.rs index f9c1dd12..bed07771 100644 --- a/crates/oxide_builtins/src/string/common.rs +++ b/crates/oxide_builtins/src/string/common.rs @@ -219,9 +219,13 @@ pub(crate) fn take_code_points(units: &[u16], count: usize) -> &[u16] { &units[..i] } -/// ES 白空格判定的单元口径:Rust 空白表与 White_Space+LineTerminator 等价; +/// ES 白空格判定的单元口径:Rust 空白表与 White_Space+LineTerminator 等价, +/// 另补 U+FEFF(ES White_Space 含 BOM,Unicode White_Space 不含); /// 孤立 surrogate 非白空格。 pub(crate) fn is_trim_unit(u: u16) -> bool { + if u == 0xFEFF { + return true; + } if u < 0x80 { return matches!(u, 0x09..=0x0D | 0x20); } diff --git a/crates/oxide_kernel/src/builtin/construct.rs b/crates/oxide_kernel/src/builtin/construct.rs index 52e57463..35ea56d1 100644 --- a/crates/oxide_kernel/src/builtin/construct.rs +++ b/crates/oxide_kernel/src/builtin/construct.rs @@ -87,6 +87,17 @@ pub(crate) fn tag_number_proto(proto: &P) { obj.set_boxed_value(JsValue::int(0)); } +/// 把 Boolean.prototype 标成 Boolean 对象本体:规范 20.7.3 规定其是 +/// [[BooleanData]] = false 的 Boolean object。type_tag 是品牌表判据 +/// (置位后报 "[object Boolean]"),boxed 值供 valueOf/toString 的通用分支读取。 +pub(crate) fn tag_boolean_proto(proto: &P) { + let ptr = proto.as_ptr() as *mut JsObject; + // SAFETY: proto 是 make_named_pair 刚建的本进程对象,P 引用与裸指针同址。 + let obj = unsafe { &mut *ptr }; + obj.type_tag = JsObject::OBJ_TYPE_BOOLEAN_OBJ; + obj.set_boxed_value(JsValue::bool(false)); +} + /// 把 String.prototype 标成 String 对象本体:规范 22.7.3 规定其是 /// [[StringData]] = 空串的 String object。type_tag 是品牌表判据,boxed 值 /// 供 thisStringValue/valueOf 的通用分支读取;length 自身属性按构造期 @@ -476,6 +487,7 @@ impl BuiltinWorld { let (number_proto, number_constructor) = make_named_pair(string_forge, shape_forge, labels, "Number"); tag_number_proto(&number_proto); let (boolean_proto, boolean_constructor) = make_named_pair(string_forge, shape_forge, labels, "Boolean"); + tag_boolean_proto(&boolean_proto); let (error_proto, error_constructor) = make_named_pair(string_forge, shape_forge, labels, "Error"); let (symbol_proto, symbol_constructor) = make_named_pair(string_forge, shape_forge, labels, "Symbol"); diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index fc09ec06..bd9377bb 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -191,21 +191,29 @@ impl Vm { /// 入队一个请求;生成器处于可恢复状态(New/YieldSuspended/Completed)时立即 /// 启动处理,正在执行 / await 挂起时留待当前请求结算后再处理。 + /// + /// 坏 receiver(非对象或非异步生成器对象)按规范不同步抛错,而是返回 + /// 携带 TypeError 的 rejected Promise。 pub(crate) fn async_generator_enqueue( &mut self, gen_val: JsValue, mode: GeneratorResumeMode, ) -> Result { - if !gen_val.is_object() { - return Err(oxide_builtins::error::create_type_error( + let is_gen = if gen_val.is_object() { + unsafe { &*gen_val.as_js_object_ptr() }.is_async_generator_obj() + } else { + false + }; + if !is_gen { + let (promise, _, _) = self.new_promise_capability(); + let exc = oxide_builtins::error::create_type_error( self, - "AsyncGenerator methods called on non-object", - )); - } - let obj = unsafe { &*gen_val.as_js_object_ptr() }; - if !obj.is_async_generator_obj() { - return Err(oxide_builtins::error::create_type_error( - self, - "AsyncGenerator methods called on incompatible receiver", - )); + if gen_val.is_object() { + "AsyncGenerator methods called on incompatible receiver" + } else { + "AsyncGenerator methods called on non-object" + }, + ); + let _ = self.reject_promise(promise, exc); + return Ok(promise); } let (promise, resolve, reject) = self.new_promise_capability(); let state_ptr = self.async_gen_state_ptr(gen_val); diff --git a/crates/oxide_vm/src/bindings/bind_function.rs b/crates/oxide_vm/src/bindings/bind_function.rs index 04a62764..56541196 100644 --- a/crates/oxide_vm/src/bindings/bind_function.rs +++ b/crates/oxide_vm/src/bindings/bind_function.rs @@ -38,5 +38,24 @@ pub fn bind_function(core: &Arc, session: &KernelSession, global: &m ctor.ensure_hash_props().push(JsValue::int(1)); let length_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + + // Function.prototype 自身属性:length = 0、name = ""(不可写不可枚举可配置), + // 自身属性名序 length 在 name 之前。 + let proto_ptr = session.builtin_world().function_proto.as_ptr() as *mut JsObject; + let proto = unsafe { &mut *proto_ptr }; + let proto_length_shape = core.shape_forge().make_shape(proto.shape_id(), length_si); + proto.set_shape_id(proto_length_shape); + proto.ensure_hash_props().push(JsValue::int(0)); + let proto_length_pos = proto.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + proto.set_data_meta(proto_length_pos, PropAttributes::new(false, false, true)); + let name_si = core.perm_interner().intern("name").0; + let name_shape = core.shape_forge().make_shape(proto.shape_id(), name_si); + proto.set_shape_id(name_shape); + proto + .ensure_hash_props() + .push(JsValue::perm_string(core.perm_interner().string_ptr(core.perm_interner().intern("").0))); + let name_pos = proto.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + proto.set_data_meta(name_pos, PropAttributes::new(false, false, true)); + bind_global_value(core, global, "Function", JsValue::from_js_object(function_ctor)); } diff --git a/crates/oxide_vm/src/bindings/bind_number.rs b/crates/oxide_vm/src/bindings/bind_number.rs index 2d9781be..6820a5d2 100644 --- a/crates/oxide_vm/src/bindings/bind_number.rs +++ b/crates/oxide_vm/src/bindings/bind_number.rs @@ -28,7 +28,8 @@ pub fn bind_number(core: &Arc, session: &KernelSession, global: &mut oxide_builtins::number::number_is_safe_integer:: as *const (), 1, ), - ("parseInt", oxide_builtins::number::number_parse_int:: as *const (), 1), + // parseInt 形式参数为 (string, radix),length 按规范取 2。 + ("parseInt", oxide_builtins::number::number_parse_int:: as *const (), 2), ("parseFloat", oxide_builtins::number::number_parse_float:: as *const (), 1), ], ); @@ -86,7 +87,8 @@ pub fn bind_number(core: &Arc, session: &KernelSession, global: &mut global, core, &[ - ("parseInt", oxide_builtins::number::number_parse_int:: as *const (), 1), + // parseInt 形式参数为 (string, radix),length 按规范取 2。 + ("parseInt", oxide_builtins::number::number_parse_int:: as *const (), 2), ("parseFloat", oxide_builtins::number::number_parse_float:: as *const (), 1), ("isNaN", oxide_builtins::global::global_is_nan:: as *const (), 1), ("isFinite", oxide_builtins::global::global_is_finite:: as *const (), 1), diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 8cfd1479..7e9a0f63 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -775,7 +775,8 @@ fn bind_global_functions(core: &Arc, session: &KernelSession, global global, core, &[ - ("parseInt", oxide_builtins::number::number_parse_int:: as *const (), 1), + // parseInt 形式参数为 (string, radix),length 按规范取 2。 + ("parseInt", oxide_builtins::number::number_parse_int:: as *const (), 2), ("parseFloat", oxide_builtins::number::number_parse_float:: as *const (), 1), ("isNaN", oxide_builtins::global::global_is_nan:: as *const (), 1), ("isFinite", oxide_builtins::global::global_is_finite:: as *const (), 1),