From 2dc119874dc5ebb95857d5faadf5315e102f62b8 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 24 Sep 2026 21:19:27 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A1=A5=20Function=20=E6=9E=84?= =?UTF-8?q?=E9=80=A0=E5=99=A8=E6=AE=8B=E9=9D=A2=E4=B8=8E=20TypedArray/Atom?= =?UTF-8?q?ics=20=E8=BE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/atomics.rs | 21 +++++- crates/oxide_builtins/src/function.rs | 18 +++++ crates/oxide_builtins/src/typed_array.rs | 10 +++ crates/oxide_runtime_api/src/host.rs | 3 + crates/oxide_vm/src/bindings/bind_function.rs | 68 ++++++++++++++++++- crates/oxide_vm/src/vm/dynamic.rs | 6 +- crates/oxide_vm/src/vm/vmhost.rs | 5 ++ .../tests/builtins_atomics_wait_tests.rs | 17 +++-- 8 files changed, 137 insertions(+), 11 deletions(-) diff --git a/crates/oxide_builtins/src/atomics.rs b/crates/oxide_builtins/src/atomics.rs index a3cb342f..0c9c692f 100644 --- a/crates/oxide_builtins/src/atomics.rs +++ b/crates/oxide_builtins/src/atomics.rs @@ -425,7 +425,24 @@ pub fn atomics_wait_async(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(wait_async_result_object(vm, true, promise)) } -/// `Atomics.pause(hint)`:零参语义(不校验、不延迟),任意实参形直返 undefined。 -pub fn atomics_pause(_vm: &mut H, _args: &[u8]) -> NativeResult { +/// `Atomics.pause(hint)`:hint 为 undefined 或整数 Number 时直返 undefined; +/// 其余值(布尔/串/BigInt/对象/非整数 Number 等)抛 TypeError。 +pub fn atomics_pause(vm: &mut H, args: &[u8]) -> NativeResult { + let hint = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if hint.is_undefined() { + return NativeResult::Ok(JsValue::undefined()); + } + // hint 须为整数 Number:int 表示恒整数;double 表示须有限且无小数部分。 + let is_integral_number = if hint.is_int() { + true + } else if hint.is_double() { + let v = hint.as_double(); + v.is_finite() && v.fract() == 0.0 + } else { + false + }; + if !is_integral_number { + return NativeResult::Err(crate::error::create_type_error(vm, "iterationNumber must be an integer")); + } NativeResult::Ok(JsValue::undefined()) } diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index 10b797ec..a1ff1e93 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -342,6 +342,24 @@ fn bound_restricted_thrower(vm: &mut H, _args: &[u8]) -> NativeResult )) } +/// %ThrowTypeError%:Function.prototype 的 caller/arguments 受限访问器共用。 +/// +/// 按接收者严格性分流:严格函数对象与 native 函数(含 Function.prototype 本身) +/// 的 get/set 一律抛 TypeError;非严格函数对象读不抛、返回 undefined(写不抛、 +/// 丢弃写入),与规范"非严格函数 caller 可读"的残面一致。 +pub fn function_restricted_thrower(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let obj = match vm.checked_object_ptr(this_val, "restricted property access") { + Ok(Some(obj)) => obj, + Ok(None) => return NativeResult::Err(crate::error::create_type_error(vm, "restricted property access")), + Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)), + }; + if !vm.function_is_strict(unsafe { &*obj }) { + return NativeResult::Ok(JsValue::undefined()); + } + NativeResult::Err(crate::error::create_type_error(vm, "restricted property access")) +} + /// `Function.prototype.toString`:返回 `function name() { [native code] }` /// 或 `[bytecode]` 形式;函数名取自 `name` 属性或字节码子模块名。 pub fn function_to_string(vm: &mut H, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 70323aaa..a102c662 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -2699,6 +2699,11 @@ pub fn uint8array_set_from_base64(vm: &mut H, args: &[u8]) -> NativeR let string = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let options = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); + // 目标缓冲不可变(immutable):写前抛 TypeError(规范 ValidateUint8Array + // 后的不可变守卫步)。 + if unsafe { (*payload_ptr).immutable } { + return NativeResult::Err(type_error(vm, "ArrayBuffer is immutable")); + } // SAFETY: payload_ptr 来自活动缓冲区对象(AB/SAB 双认),视图范围由构造保证界内。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, @@ -2726,6 +2731,11 @@ pub fn uint8array_set_from_hex(vm: &mut H, args: &[u8]) -> NativeResu return NativeResult::Err(type_error(vm, "string argument required")); } let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); + // 目标缓冲不可变(immutable):写前抛 TypeError(规范 ValidateUint8Array + // 后的不可变守卫步)。 + if unsafe { (*payload_ptr).immutable } { + return NativeResult::Err(type_error(vm, "ArrayBuffer is immutable")); + } // SAFETY: payload_ptr 来自活动缓冲区对象(AB/SAB 双认),视图范围由构造保证界内。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, diff --git a/crates/oxide_runtime_api/src/host.rs b/crates/oxide_runtime_api/src/host.rs index 88cdf689..a6d76e36 100644 --- a/crates/oxide_runtime_api/src/host.rs +++ b/crates/oxide_runtime_api/src/host.rs @@ -221,6 +221,9 @@ pub trait VmHost { fn raise_captured(&mut self, exc: JsValue) -> Result<(), String>; fn error_message_text(&self, kind: &str, msg: &str) -> String; fn call_stack_function_names(&self) -> Vec; + /// 函数对象是否为严格模式函数(经子模块 `is_strict` 判定);native 函数 + /// (无子模块)视为严格。供 caller/arguments 受限访问器按严格性分流抛错。 + fn function_is_strict(&self, obj: &JsObject) -> bool; fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue; fn step_rng(&mut self); fn math_rng_value(&self) -> f64; diff --git a/crates/oxide_vm/src/bindings/bind_function.rs b/crates/oxide_vm/src/bindings/bind_function.rs index 56541196..a97cee7a 100644 --- a/crates/oxide_vm/src/bindings/bind_function.rs +++ b/crates/oxide_vm/src/bindings/bind_function.rs @@ -1,8 +1,9 @@ use std::sync::Arc; -use oxide_kernel::builtin::FunctionMethods; +use oxide_kernel::builtin::{FnWrapperKey, FunctionMethods}; use oxide_kernel::kernel::{KernelCore, KernelSession}; -use oxide_types::object::{JsObject, PropAttributes}; +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; use super::bind_global_value; @@ -57,5 +58,68 @@ pub fn bind_function(core: &Arc, session: &KernelSession, global: &m 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)); + // caller/arguments 受限访问器:两属性 get/set 共用同一 %ThrowTypeError% + // 函数对象,任何访问抛 TypeError(AddRestrictedFunctionProperties 语义)。 + bind_function_proto_restricted(core, session, proto); + bind_global_value(core, global, "Function", JsValue::from_js_object(function_ctor)); } + +/// 在 Function.prototype 上绑定 caller/arguments 受限访问器:两属性的 +/// get/set 共用同一 %ThrowTypeError% 函数对象(name="get caller"、length=0), +/// 描述符 { enumerable:false, configurable:true }。 +/// +/// # 副作用 +/// - 为 caller/arguments 各开 shape 槽位并写入访问器 meta;thrower 函数对象 +/// 登记进 world 释放表(与 `bind_accessor_getter` 的 getter 同一生命周期)。 +fn bind_function_proto_restricted(core: &Arc, session: &KernelSession, proto: &mut JsObject) { + let shape_forge = core.shape_forge().as_ref(); + let string_forge = core.perm_interner().as_ref(); + let world = session.builtin_world(); + let family = world.wrapper_family_of(proto as *const JsObject); + let si_name = string_forge.intern("name").0; + let si_length = string_forge.intern("length").0; + let si_caller = string_forge.intern("caller").0; + let si_arguments = string_forge.intern("arguments").0; + let si_label = string_forge.intern("get caller").0; + + // 构造单一 thrower 函数对象,供两属性 get/set 共用(选择性重建复用: + // 同家族同槽旧 thrower 迁移到新 proto 的访问器槽,不再新建对象)。 + // SAFETY: thrower 函数项指针转为 *const ()。 + let thrower_fn_ptr = unsafe { + NativeFnPtr::from_raw(oxide_builtins::function::function_restricted_thrower:: as *const ()) + }; + let reuse_key = FnWrapperKey::new(family, si_label, si_caller, si_name); + let thrower_ptr = match world.find_fn_wrapper(reuse_key, thrower_fn_ptr, 0) { + Some(ptr) => ptr, + None => { + let fn_proto_val = JsValue::from_js_object(world.function_proto.as_ptr() as *mut JsObject); + let mut thrower = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + thrower.set_function(true); + thrower.set_native_fn(Some(thrower_fn_ptr)); + thrower.set_native_arg_count(0); + let name_shape = shape_forge.make_shape(thrower.shape_id(), si_name); + thrower.set_shape_id(name_shape); + thrower.ensure_hash_props().push(JsValue::perm_string(string_forge.string_ptr(si_label))); + let name_pos = thrower.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + thrower.set_data_meta(name_pos, PropAttributes::new(false, false, true)); + let length_shape = shape_forge.make_shape(thrower.shape_id(), si_length); + thrower.set_shape_id(length_shape); + thrower.ensure_hash_props().push(JsValue::int(0)); + let length_pos = thrower.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + thrower.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + let ptr = Box::into_raw(thrower); + world.track_fn_wrapper(ptr, reuse_key); + ptr + } + }; + let thrower_val = JsValue::from_js_object(thrower_ptr); + let attrs = PropAttributes::new(false, false, true); + for key in [si_caller, si_arguments] { + let new_shape = shape_forge.make_shape(proto.shape_id(), key); + proto.set_shape_id(new_shape); + let pos = proto.push_prop(JsValue::undefined()); + proto.set_accessor_meta(pos, thrower_val, thrower_val, attrs); + proto.bump_generation(); + } +} diff --git a/crates/oxide_vm/src/vm/dynamic.rs b/crates/oxide_vm/src/vm/dynamic.rs index 267af3d4..ff88fe26 100644 --- a/crates/oxide_vm/src/vm/dynamic.rs +++ b/crates/oxide_vm/src/vm/dynamic.rs @@ -34,9 +34,11 @@ impl Vm { /// # 副作用 /// - 扩展当前代际平表(`tables[current_gen]`)与常量缓存。 pub fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result { - // wrap 源码:末尾换行防止 body 以行注释结尾吞掉右花括号。 + // wrap 源码:body 两端换行防止以行注释结尾吞掉右花括号;形参串后补 + // 换行,使形参以 HTML 注释(``)结尾时注释在行末终止、 + // 右括号不被吞(规范按形参串与体串各自独立解析,此处以换行等价)。 let params_str = params.join(", "); - let source = format!("function anonymous({params_str}) {{\n{body}\n}}"); + let source = format!("function anonymous({params_str}\n) {{\n{body}\n}}"); let allocator = oxide_parser::Allocator::default(); let program = oxide_parser::parse(&allocator, &source) diff --git a/crates/oxide_vm/src/vm/vmhost.rs b/crates/oxide_vm/src/vm/vmhost.rs index 9651fec4..05e47e29 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -174,6 +174,11 @@ impl oxide_runtime_api::VmHost for Vm { }) .collect() } + fn function_is_strict(&self, obj: &JsObject) -> bool { + // native 函数(sub_module_index == 0)无子模块,callee_module 返回 None, + // 按严格处理(受限访问器对其抛错)。 + self.callee_module(obj).map(|m| m.is_strict).unwrap_or(true) + } fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue { self.promote_if_needed_for_write_ptr(target_ptr, value) } diff --git a/crates/oxide_vm/tests/builtins_atomics_wait_tests.rs b/crates/oxide_vm/tests/builtins_atomics_wait_tests.rs index a2c80bfa..643fdd00 100644 --- a/crates/oxide_vm/tests/builtins_atomics_wait_tests.rs +++ b/crates/oxide_vm/tests/builtins_atomics_wait_tests.rs @@ -363,15 +363,22 @@ fn eval_pause_returns_undefined() { ); } -/// pause:零参语义不校验(true/小数/NaN/串 均不抛)。 +/// pause:非整数 iterationNumber(true/小数/NaN/串/对象)→ TypeError(零参与 +/// 整数不受影响,见 eval_pause_returns_undefined)。 #[test] -fn eval_pause_no_validation() { +fn eval_pause_non_integral_throws() { let mut vm = Vm::new(); truthy( &mut vm, - "Atomics.pause(true) === undefined && Atomics.pause(42.42) === undefined \ - && Atomics.pause(NaN) === undefined && Atomics.pause('42') === undefined \ - && Atomics.pause({}) === undefined", + &format!( + "(function () {{ \ + return {} && {} && {} && {} && {}; }})()", + throws("Atomics.pause(true)", "TypeError"), + throws("Atomics.pause(42.42)", "TypeError"), + throws("Atomics.pause(NaN)", "TypeError"), + throws("Atomics.pause('42')", "TypeError"), + throws("Atomics.pause({})", "TypeError"), + ), ); }