mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: 补 Function 构造器残面与 TypedArray/Atomics 边
This commit is contained in:
@@ -425,7 +425,24 @@ pub fn atomics_wait_async<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(wait_async_result_object(vm, true, promise))
|
||||
}
|
||||
|
||||
/// `Atomics.pause(hint)`:零参语义(不校验、不延迟),任意实参形直返 undefined。
|
||||
pub fn atomics_pause<H: VmHost>(_vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
/// `Atomics.pause(hint)`:hint 为 undefined 或整数 Number 时直返 undefined;
|
||||
/// 其余值(布尔/串/BigInt/对象/非整数 Number 等)抛 TypeError。
|
||||
pub fn atomics_pause<H: VmHost>(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())
|
||||
}
|
||||
|
||||
@@ -342,6 +342,24 @@ fn bound_restricted_thrower<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult
|
||||
))
|
||||
}
|
||||
|
||||
/// %ThrowTypeError%:Function.prototype 的 caller/arguments 受限访问器共用。
|
||||
///
|
||||
/// 按接收者严格性分流:严格函数对象与 native 函数(含 Function.prototype 本身)
|
||||
/// 的 get/set 一律抛 TypeError;非严格函数对象读不抛、返回 undefined(写不抛、
|
||||
/// 丢弃写入),与规范"非严格函数 caller 可读"的残面一致。
|
||||
pub fn function_restricted_thrower<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
@@ -2699,6 +2699,11 @@ pub fn uint8array_set_from_base64<H: VmHost>(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<H: VmHost>(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;入口校验先行,
|
||||
|
||||
@@ -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<String>;
|
||||
/// 函数对象是否为严格模式函数(经子模块 `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;
|
||||
|
||||
@@ -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<KernelCore>, 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<KernelCore>, 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::<crate::vm::Vm> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,11 @@ impl Vm {
|
||||
/// # 副作用
|
||||
/// - 扩展当前代际平表(`tables[current_gen]`)与常量缓存。
|
||||
pub fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result<JsValue, String> {
|
||||
// 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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user