mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
feat: 落地 Proxy 构造器与 revocable 及撤销函数
This commit is contained in:
@@ -5,8 +5,8 @@
|
||||
//! ArrayBuffer、DataView、Reflect、Function、Error 与全局函数(URI/escape 等)。
|
||||
//! 每个内置方法以 `fn xxx<H: VmHost>(vm, args: &[u8]) -> NativeResult` 形式暴露,
|
||||
//! 由上层(oxide_vm / oxide_api)注册为 JS 全局对象上的 native function。
|
||||
//! 架构性延后的特性(Proxy、WeakMap、WeakSet、WeakRef、FinalizationRegistry)
|
||||
//! 在 `stubs` 中仅提供占位实现,调用时抛 TypeError。
|
||||
//! 架构性延后的特性(WeakSet、WeakRef、FinalizationRegistry)在 `stubs` 中
|
||||
//! 仅提供占位实现,调用时抛 TypeError;Proxy 与 WeakMap 已落地真构造器。
|
||||
|
||||
/// Array 内置对象实现(目录模块:common 共享助手、from 构造器与静态方法、element 元素变更、iterate 高阶迭代、sort_iterator 排序与迭代协议、immutable ES2023 不可变方法族)。
|
||||
pub mod array;
|
||||
@@ -64,6 +64,8 @@ pub mod module;
|
||||
pub mod number;
|
||||
/// Object 内置对象实现(keys/create/assign/defineProperty 等静态方法)。
|
||||
pub mod object;
|
||||
/// Proxy 内置对象实现(%Proxy% 构造器、参数与不变量校验、Proxy.revocable 与撤销函数)。
|
||||
pub mod proxy;
|
||||
/// Reflect 内置对象实现(属性操作镜像)。
|
||||
pub mod reflect;
|
||||
/// RegExp 内置对象实现(constructor/test/exec/toString)。
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
//! %Proxy% 构造器与 revocable:Proxy 状态盒(target、handler、revoked 位)、
|
||||
//! 参数与不变量校验、函数代理 is_function 位、Proxy.revocable 的撤销函数。
|
||||
//!
|
||||
//! 状态盒经 `Box::into_raw` 挂 `JsObject.native_data`,GC 边函数族(mark 边)
|
||||
//! 由 `oxide_vm` 的家族表消费;撤销函数以独立 tag 存被撤销代理指针(GC 边),
|
||||
//! 保证仅持撤销函数时代理不被回收。
|
||||
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_runtime_api::{NativeResult, ProtoKind, VmHost};
|
||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// Proxy 状态盒:存于 Proxy 对象 `native_data`,target 与 handler 是 GC 边,
|
||||
/// revoked 位标记已撤销状态(撤销后全部 trap 抛 TypeError)。
|
||||
///
|
||||
/// 生命周期与 Proxy 对象一致:构造期经 `Box::into_raw` 分配,GC drop 链释放,
|
||||
/// 此后不替换。
|
||||
pub struct ProxyState {
|
||||
/// 目标对象(GC 边)。
|
||||
pub target: JsValue,
|
||||
/// 处理器对象(GC 边)。
|
||||
pub handler: JsValue,
|
||||
/// 撤销位:置位后所有 trap 派发抛 TypeError。
|
||||
pub revoked: bool,
|
||||
}
|
||||
|
||||
/// 读 Proxy 状态盒(盒未建立时 None)。
|
||||
pub fn proxy_state_ref(obj: &JsObject) -> Option<&ProxyState> {
|
||||
let ptr = obj.native_data() as *const ProxyState;
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: native_data 由 Box::into_raw 分配,生命周期与 Proxy 对象一致。
|
||||
Some(unsafe { &*ptr })
|
||||
}
|
||||
}
|
||||
|
||||
/// 可变访问 Proxy 状态盒(写 revoked 位)。
|
||||
pub fn proxy_state_mut(obj: &mut JsObject) -> Option<&mut ProxyState> {
|
||||
let ptr = obj.native_data() as *mut ProxyState;
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: native_data 由 Box::into_raw 分配,生命周期与 Proxy 对象一致。
|
||||
Some(unsafe { &mut *ptr })
|
||||
}
|
||||
}
|
||||
|
||||
/// 撤销函数的对象边(GC mark 强边):`native_data` 存的被撤销代理指针。
|
||||
pub fn proxy_revoker_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||||
if !obj.is_proxy_revoker_obj() {
|
||||
return Vec::new();
|
||||
}
|
||||
let ptr = obj.native_data() as *mut JsObject;
|
||||
if ptr.is_null() {
|
||||
return Vec::new();
|
||||
}
|
||||
// SAFETY: native_data 存存活代理指针(GC 边),mark 期对象存活。
|
||||
vec![JsValue::from_js_object(ptr)]
|
||||
}
|
||||
|
||||
/// 参数校验:target 与 handler 均须对象,否则抛 TypeError(规范 10.2.4 步骤 1-2)。
|
||||
fn check_target_and_handler(target: JsValue, handler: JsValue) -> Result<(), &'static str> {
|
||||
if !target.is_object() || target.as_js_object_ptr().is_null() {
|
||||
return Err("Proxy target must be an object");
|
||||
}
|
||||
if !handler.is_object() || handler.as_js_object_ptr().is_null() {
|
||||
return Err("Proxy handler must be an object");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 不变量检查(规范 10.2.4 步骤 4-5):handler 的 isExtensible 与 setPrototypeOf
|
||||
/// trap 若存在,其布尔值须与 target 实际状态一致,否则抛 TypeError。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 仅当 handler 自有对应 trap 属性时检查(继承属性不触发)。
|
||||
/// - trap 读为传播读,getter 抛错原值上抛。
|
||||
fn proxy_create_invariants<H: VmHost>(
|
||||
vm: &mut H, target_obj: &JsObject, handler_obj: &JsObject, handler: JsValue,
|
||||
) -> Result<(), JsValue> {
|
||||
// isExtensible trap:handler 报 true 时 target 须可扩展,报 false 时须不可扩展。
|
||||
let si_is_extensible = vm.perm_intern("isExtensible");
|
||||
if vm.get_own_property_slot(handler_obj, si_is_extensible).is_some() {
|
||||
let trap_val = vm
|
||||
.ordinary_get(handler_obj, si_is_extensible, handler)
|
||||
.map_err(|e| crate::iterator::engine_error(vm, &e))?;
|
||||
let target_extensible = target_obj.is_extensible();
|
||||
let trap_is_true = trap_val.is_bool() && trap_val.as_bool();
|
||||
let trap_is_false = trap_val.is_bool() && !trap_val.as_bool();
|
||||
if (trap_is_true && !target_extensible) || (trap_is_false && target_extensible) {
|
||||
return Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Proxy invariant: isExtensible trap inconsistent with target",
|
||||
));
|
||||
}
|
||||
}
|
||||
// setPrototypeOf trap:handler 报 true 时 target 原型须为对象或 null,
|
||||
// 报 false 时须为其它类型。
|
||||
let si_set_proto = vm.perm_intern("setPrototypeOf");
|
||||
if vm.get_own_property_slot(handler_obj, si_set_proto).is_some() {
|
||||
let trap_val = vm
|
||||
.ordinary_get(handler_obj, si_set_proto, handler)
|
||||
.map_err(|e| crate::iterator::engine_error(vm, &e))?;
|
||||
let target_proto = target_obj.proto();
|
||||
let target_proto_is_obj_or_null = target_proto.is_object() || target_proto.is_null();
|
||||
let trap_is_true = trap_val.is_bool() && trap_val.as_bool();
|
||||
let trap_is_false = trap_val.is_bool() && !trap_val.as_bool();
|
||||
if (trap_is_true && !target_proto_is_obj_or_null) || (trap_is_false && target_proto_is_obj_or_null) {
|
||||
return Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Proxy invariant: setPrototypeOf trap inconsistent with target",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 创建代理对象本体:tag 39 + ProxyState 载荷盒 + Object.prototype 原型链;
|
||||
/// target 可调用时置 is_function 位(typeof 经该位返回 function)。
|
||||
fn create_proxy_object<H: VmHost>(vm: &mut H, target: JsValue, handler: JsValue) -> JsValue {
|
||||
let object_proto = vm.builtin_proto(ProtoKind::ObjectProto);
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||||
obj.type_tag = JsObject::OBJ_TYPE_PROXY;
|
||||
let state = Box::new(ProxyState { target, handler, revoked: false });
|
||||
obj.set_native_data(Box::into_raw(state) as *mut u8);
|
||||
if crate::iterator::is_callable(target) {
|
||||
obj.set_function(true);
|
||||
}
|
||||
let proxy_obj = vm.alloc_object(obj);
|
||||
JsValue::from_js_object(proxy_obj)
|
||||
}
|
||||
|
||||
/// `Proxy ( target, handler )` 构造器:NewTarget 校验(须经 new 调用)后按
|
||||
/// 规范 10.2.4 建代理对象并返回。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. NewTarget 校验:非构造形态调用抛 TypeError。
|
||||
/// 2. 参数校验:target 与 handler 均须对象。
|
||||
/// 3. 不变量检查:handler 的 isExtensible / setPrototypeOf trap 与 target 一致性。
|
||||
/// 4. 创建代理对象(tag 39 + 状态盒 + Object.prototype 原型链)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - %Proxy% 无 prototype 属性(规范 25.1),返回的代理对象原型链落
|
||||
/// Object.prototype。
|
||||
/// - target 可调用时代理置 is_function 位(typeof 返 function)。
|
||||
pub fn proxy_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if !vm.constructing_native() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Proxy constructor requires 'new'"));
|
||||
}
|
||||
let target = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
let handler = vm.reg(if args.len() > 2 { args[2] } else { 0 });
|
||||
if let Err(msg) = check_target_and_handler(target, handler) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, msg));
|
||||
}
|
||||
// SAFETY: check_target_and_handler 已校验非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
let handler_obj = unsafe { &*handler.as_js_object_ptr() };
|
||||
if let Err(exc) = proxy_create_invariants(vm, target_obj, handler_obj, handler) {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
NativeResult::Ok(create_proxy_object(vm, target, handler))
|
||||
}
|
||||
|
||||
/// `Proxy.revocable ( target, handler )`:建代理对象后返回 `{proxy, revoke}`
|
||||
/// 结果对象;revoke 为撤销函数(调用后代理全部 trap 抛 TypeError)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 参数校验与不变量检查(同构造器)。
|
||||
/// 2. 创建代理对象。
|
||||
/// 3. 创建撤销函数(length 0、name 空串、非构造器,native_data 存代理指针)。
|
||||
/// 4. 建结果对象并装 `proxy` / `revoke` 两数据属性。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 撤销函数经独立 tag 存代理指针,GC 边保活代理(仅持撤销函数时代理不回收)。
|
||||
/// - 撤销函数可重复调用,幂等置 revoked 位。
|
||||
pub fn proxy_revocable<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let target = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
let handler = vm.reg(if args.len() > 2 { args[2] } else { 0 });
|
||||
if let Err(msg) = check_target_and_handler(target, handler) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, msg));
|
||||
}
|
||||
// SAFETY: check_target_and_handler 已校验非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
let handler_obj = unsafe { &*handler.as_js_object_ptr() };
|
||||
if let Err(exc) = proxy_create_invariants(vm, target_obj, handler_obj, handler) {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
let proxy_val = create_proxy_object(vm, target, handler);
|
||||
|
||||
// 撤销函数:Function.prototype 原型链 + native 函数项 + length 0 / name 空串。
|
||||
let function_proto = vm.builtin_proto(ProtoKind::FunctionProto);
|
||||
let mut revoker = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto));
|
||||
revoker.set_function(true);
|
||||
revoker.type_tag = JsObject::OBJ_TYPE_PROXY_REVOKER;
|
||||
// SAFETY: proxy_revoker_dispatcher 是合法 NativeFn 函数项。
|
||||
revoker.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(proxy_revoker_dispatcher::<H> as *const ()) }));
|
||||
revoker.set_native_arg_count(0);
|
||||
let revoker_obj = vm.alloc_object(revoker);
|
||||
let revoker_ref = unsafe { &mut *revoker_obj };
|
||||
let length_si = vm.perm_intern("length");
|
||||
if let Err(e) = vm.define_data_property(
|
||||
revoker_ref,
|
||||
length_si,
|
||||
JsValue::int(0),
|
||||
PropAttributes::new(false, false, true),
|
||||
) {
|
||||
return NativeResult::Err(crate::iterator::engine_error(vm, &e));
|
||||
}
|
||||
let name_si = vm.perm_intern("name");
|
||||
let empty_name = vm.new_string("");
|
||||
if let Err(e) = vm.define_data_property(
|
||||
revoker_ref,
|
||||
name_si,
|
||||
empty_name,
|
||||
PropAttributes::new(false, false, true),
|
||||
) {
|
||||
return NativeResult::Err(crate::iterator::engine_error(vm, &e));
|
||||
}
|
||||
// 代理指针存进撤销函数 native_data(GC 边,mark 期经家族表传播)。
|
||||
unsafe {
|
||||
(*revoker_obj).set_native_data(proxy_val.as_js_object_ptr() as *mut u8);
|
||||
}
|
||||
let revoker_val = JsValue::from_js_object(revoker_obj);
|
||||
|
||||
// 结果对象 {proxy, revoke}:两数据属性(可写、可枚举、可配置)。
|
||||
let object_proto = vm.builtin_proto(ProtoKind::ObjectProto);
|
||||
let result_obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
|
||||
let result_ref = unsafe { &mut *result_obj };
|
||||
let proxy_si = vm.perm_intern("proxy");
|
||||
let revoke_si = vm.perm_intern("revoke");
|
||||
vm.set_or_create_prop_value(result_ref, proxy_si, proxy_val);
|
||||
vm.set_or_create_prop_value(result_ref, revoke_si, revoker_val);
|
||||
NativeResult::Ok(JsValue::from_js_object(result_obj))
|
||||
}
|
||||
|
||||
/// 撤销函数调用体:置被撤销代理的 revoked 位,幂等返回 undefined。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 经 callee 槽(reg 254)读撤销函数本体。
|
||||
/// 2. 读其 native_data 的被撤销代理指针。
|
||||
/// 3. 置代理状态盒 revoked 位。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 撤销函数本体缺失或代理指针为空时静默返 undefined(防御性)。
|
||||
fn proxy_revoker_dispatcher<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
let revoker_val = vm.reg(254);
|
||||
let revoker_ptr = revoker_val.as_js_object_ptr();
|
||||
if revoker_ptr.is_null() {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
// SAFETY: revoker 为存活函数对象。
|
||||
let revoker = unsafe { &*revoker_ptr };
|
||||
let proxy_ptr = revoker.native_data() as *mut JsObject;
|
||||
if proxy_ptr.is_null() {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
// SAFETY: proxy_ptr 为存活代理对象(经撤销函数 GC 边保活)。
|
||||
let proxy = unsafe { &mut *proxy_ptr };
|
||||
let Some(state) = proxy_state_mut(proxy) else {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
};
|
||||
state.revoked = true;
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use oxide_types::shape::EMPTY_SHAPE_ID;
|
||||
|
||||
/// 建一个带状态盒的 Proxy 对象(测试构造路径,不经 Vm 分配)。
|
||||
fn proxy_obj(target: JsValue, handler: JsValue) -> JsObject {
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
|
||||
obj.type_tag = JsObject::OBJ_TYPE_PROXY;
|
||||
let state = Box::new(ProxyState { target, handler, revoked: false });
|
||||
obj.set_native_data(Box::into_raw(state) as *mut u8);
|
||||
obj
|
||||
}
|
||||
|
||||
/// 堆分配一个普通对象值,作 GC 边占位。
|
||||
fn plain_obj_value() -> JsValue {
|
||||
let obj = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()));
|
||||
JsValue::from_js_object(Box::into_raw(obj))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proxy_state_ref_reads_target_and_handler() {
|
||||
let target = plain_obj_value();
|
||||
let handler = plain_obj_value();
|
||||
let obj = proxy_obj(target, handler);
|
||||
let state = proxy_state_ref(&obj).expect("状态盒");
|
||||
assert_eq!(state.target, target);
|
||||
assert_eq!(state.handler, handler);
|
||||
assert!(!state.revoked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proxy_state_mut_toggles_revoked() {
|
||||
let mut obj = proxy_obj(plain_obj_value(), plain_obj_value());
|
||||
let state = proxy_state_mut(&mut obj).expect("状态盒");
|
||||
state.revoked = true;
|
||||
assert!(proxy_state_ref(&obj).expect("状态盒").revoked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proxy_revoker_edges_return_proxy_pointer() {
|
||||
let proxy = plain_obj_value();
|
||||
let mut revoker = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
|
||||
revoker.type_tag = JsObject::OBJ_TYPE_PROXY_REVOKER;
|
||||
revoker.set_native_data(proxy.as_js_object_ptr() as *mut u8);
|
||||
assert_eq!(proxy_revoker_native_edges(&revoker), vec![proxy]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proxy_revoker_edges_empty_for_non_revoker() {
|
||||
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
|
||||
assert!(proxy_revoker_native_edges(&obj).is_empty());
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,6 @@ fn stub_error<H: VmHost>(vm: &mut H, name: &str) -> NativeResult {
|
||||
}
|
||||
|
||||
// 架构性延后:这些特性按设计排除在受支持语言子集之外。
|
||||
/// `Proxy` 全局占位:始终抛 TypeError。Proxy 按设计延后实现。
|
||||
pub fn proxy_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
stub_error(vm, "Proxy")
|
||||
}
|
||||
/// `WeakSet` 全局占位:始终抛 TypeError。WeakSet 按设计延后实现。
|
||||
pub fn weakset_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
stub_error(vm, "WeakSet")
|
||||
|
||||
Reference in New Issue
Block a user