feat: 落地原型面七方法 trait 路由与内建入口改接

This commit is contained in:
2026-10-18 06:43:14 +00:00
parent 8f14b18ec8
commit 7f49aa96ec
7 changed files with 503 additions and 40 deletions
Generated
+21 -21
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1258,7 +1258,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1272,7 +1272,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1288,7 +1288,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_engine" name = "oxide_engine"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1300,7 +1300,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_fold" name = "oxide_fold"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_bytecode", "oxide_bytecode",
@@ -1312,7 +1312,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1335,7 +1335,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1347,7 +1347,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1356,11 +1356,11 @@ dependencies = [
[[package]] [[package]]
name = "oxide_net" name = "oxide_net"
version = "0.20.10" version = "0.20.11"
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1375,7 +1375,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1391,7 +1391,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1404,7 +1404,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1424,7 +1424,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.20.10" version = "0.20.11"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -25,7 +25,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.20.10" version = "0.20.11"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+14 -12
View File
@@ -1911,7 +1911,7 @@ fn seal_own_prop_meta(obj: &mut JsObject, store: u32) {
/// trap 以(target)调用,结果经 ToBoolean 投影后须与 target 实际可扩展状态 /// trap 以(target)调用,结果经 ToBoolean 投影后须与 target 实际可扩展状态
/// 一致(不一致抛 TypeError);trap 缺失落穿 target 的普通 preventExtensions /// 一致(不一致抛 TypeError);trap 缺失落穿 target 的普通 preventExtensions
/// (置不可扩展标志,返 true)。 /// (置不可扩展标志,返 true)。
fn proxy_prevent_extensions<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<bool, JsValue> { pub fn proxy_prevent_extensions<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<bool, JsValue> {
let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target; let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target;
// SAFETY: target 由构造器校验为非空对象指针。 // SAFETY: target 由构造器校验为非空对象指针。
let target_obj = unsafe { &*target.as_js_object_ptr() }; let target_obj = unsafe { &*target.as_js_object_ptr() };
@@ -2326,7 +2326,11 @@ pub fn object_from_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `Object.getPrototypeOf(obj)`:返回对象的 prototype。 /// `Object.getPrototypeOf(obj)`:返回对象的 prototype。
pub fn object_get_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn object_get_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "getPrototypeOf")); let obj_ptr = native_try!(require_obj_arg(vm, args, "getPrototypeOf"));
NativeResult::Ok(unsafe { (*obj_ptr).proto() }) match vm.get_prototype_of(unsafe { &*obj_ptr }) {
Ok(proto) => NativeResult::Ok(proto),
// 代理 trap 异常原值传播(ReturnIfAbrupt)。
Err(err) => NativeResult::Err(crate::array::from_engine_error(vm, &err)),
}
} }
/// `Object.setPrototypeOf(obj, proto)`:设置对象的 prototype,返回原值。 /// `Object.setPrototypeOf(obj, proto)`:设置对象的 prototype,返回原值。
@@ -2360,19 +2364,17 @@ pub fn object_set_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
return NativeResult::Ok(target_val); return NativeResult::Ok(target_val);
} }
let target_ptr = target_val.as_js_object_ptr(); let target_ptr = target_val.as_js_object_ptr();
let target = unsafe { &mut *target_ptr }; // OrdinarySetPrototypeOf 步 2-4 语义在 trait 方法内承载:新旧 SameValue
// OrdinarySetPrototypeOf 步 2-4:新旧原型 SameValue 时直接成功,可扩展标志 // 直接成功,不可扩展且新旧不同或成环返回 false(此处按规范抛 TypeError)。
// 不参与判断;不可扩展且新旧不同才失败。set_proto 是裸写槽操作,不含该判定。 match vm.set_prototype_of(unsafe { &mut *target_ptr }, proto) {
if !target.is_extensible() && target.proto() != proto { Ok(true) => NativeResult::Ok(target_val),
return NativeResult::Err(crate::error::create_type_error( Ok(false) => NativeResult::Err(crate::error::create_type_error(
vm, vm,
"Object.setPrototypeOf: cannot set prototype of non-extensible object", "Object.setPrototypeOf: cannot set prototype of non-extensible object",
)); )),
// 代理 trap 异常原值传播(ReturnIfAbrupt)。
Err(err) => NativeResult::Err(crate::array::from_engine_error(vm, &err)),
} }
if target.set_proto(proto).is_err() {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.setPrototypeOf: cannot set prototype"));
}
NativeResult::Ok(target_val)
} }
/// `Object.hasOwn(obj, key)`:对象是否有指定自身属性。 /// `Object.hasOwn(obj, key)`:对象是否有指定自身属性。
+11 -5
View File
@@ -202,7 +202,11 @@ pub fn reflect_get_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
let Some(target_ptr) = object_ptr(target_val) else { let Some(target_ptr) = object_ptr(target_val) else {
return type_error(vm, "Reflect.getPrototypeOf target is not an object"); return type_error(vm, "Reflect.getPrototypeOf target is not an object");
}; };
NativeResult::Ok(unsafe { &*target_ptr }.proto()) match vm.get_prototype_of(unsafe { &*target_ptr }) {
Ok(proto) => NativeResult::Ok(proto),
// 代理 trap 异常原值传播(ReturnIfAbrupt)。
Err(err) => NativeResult::Err(from_engine_error(vm, &err)),
}
} }
/// `Reflect.has(target, key)`:属性存在性判定(规范 HasProperty,含原型链)。 /// `Reflect.has(target, key)`:属性存在性判定(规范 HasProperty,含原型链)。
@@ -475,11 +479,13 @@ pub fn reflect_set_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
return type_error(vm, "Reflect.setPrototypeOf prototype must be an object or null"); return type_error(vm, "Reflect.setPrototypeOf prototype must be an object or null");
} }
let target = unsafe { &mut *target_ptr }; let target = unsafe { &mut *target_ptr };
// OrdinarySetPrototypeOf 步 2-4:新旧相同先于可扩展判定,直接成功。 // OrdinarySetPrototypeOf 步 2-4 语义在 trait 方法内承载:新旧 SameValue
if !target.is_extensible() && target.proto() != proto { // 直接成功,不可扩展且新旧不同或成环返回 false。
return NativeResult::Ok(JsValue::bool(false)); match vm.set_prototype_of(target, proto) {
Ok(set) => NativeResult::Ok(JsValue::bool(set)),
// 代理 trap 异常原值传播(ReturnIfAbrupt)。
Err(err) => NativeResult::Err(from_engine_error(vm, &err)),
} }
NativeResult::Ok(JsValue::bool(target.set_proto(proto).is_ok()))
} }
fn arg<H: VmHost>(vm: &H, args: &[u8], idx: usize) -> JsValue { fn arg<H: VmHost>(vm: &H, args: &[u8], idx: usize) -> JsValue {
+59
View File
@@ -327,6 +327,65 @@ pub trait VmHost {
/// Proxy trap 返回值布尔投影:非布尔返回抛 TypeError。 /// Proxy trap 返回值布尔投影:非布尔返回抛 TypeError。
fn proxy_trap_bool_result(&self, result: JsValue, trap_name: &str) -> Result<bool, String>; fn proxy_trap_bool_result(&self, result: JsValue, trap_name: &str) -> Result<bool, String>;
// 原型与扩展性面(代理感知路由:代理经 trap 路由、非代理委托固有语义)
/// `[[GetPrototypeOf]]`:返回对象的 prototype(对象或 null);代理经
/// getPrototypeOf trap 路由——缺失落穿 target 的 `[[GetPrototypeOf]]`,
/// trap 返值须为对象或 null,否则抛 TypeError。
///
/// # 边界与前提
/// - `obj` 须为存活对象;代理的 target 由构造器校验为非空对象指针。
///
/// # 副作用
/// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。
fn get_prototype_of(&mut self, obj: &JsObject) -> Result<JsValue, String>;
/// `[[SetPrototypeOf]]`:设置 prototype(对象或 null)。
///
/// # 返回值
/// - `Ok(true)` 设置成功;`Ok(false)` 未设置(不可扩展且新旧原型不同,
/// 或新原型已在原型链上构成环);`Err` 为 TypeError(trap 报告与
/// target 实际状态不一致等)。
///
/// # 边界与前提
/// - `proto` 须为对象或 null(调用方入口校验)。
///
/// # 副作用
/// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。
fn set_prototype_of(&mut self, obj: &mut JsObject, proto: JsValue) -> Result<bool, String>;
/// `[[IsExtensible]]`:返回可扩展标志;代理经 isExtensible trap 路由
/// (结果经布尔投影),trap 缺失落穿 target。
///
/// # 副作用
/// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。
fn is_extensible(&mut self, obj: &JsObject) -> Result<bool, String>;
/// `[[PreventExtensions]]`:置不可扩展标志;代理经 preventExtensions
/// trap 路由(结果经布尔投影,报告须与 target 实际可扩展状态一致),
/// trap 缺失落穿 target 的普通 preventExtensions。
///
/// # 副作用
/// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。
fn prevent_extensions(&mut self, obj: &mut JsObject) -> Result<(), String>;
/// `[[OwnPropertyKeys]]`:返回自身属性键列表(JS 可见键值,整数键升序
/// 在前、字符串键插入序、Symbol 键插入序);代理经 ownKeys trap 路由
/// (缺失落穿 target 自身键,重复键与不可配置键做不变量检查)。
///
/// # 副作用
/// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。
fn own_keys(&mut self, obj: &JsObject) -> Result<Vec<JsValue>, String>;
/// `[[GetOwnProperty]]`:返回自身属性描述符对象(JS 可见形态);缺失
/// 返回 `Ok(None)`;代理经 getOwnPropertyDescriptor trap 路由(缺失
/// 落穿 target 自身描述符,undefined 与对象返值做不变量交叉检查)。
///
/// # 副作用
/// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。
fn get_own_property_desc(&mut self, obj: &JsObject, key_si: u32) -> Result<Option<JsValue>, String>;
/// `[[Delete]]`:删除自身属性并投影为布尔(成功 true、不可配置 false);
/// 代理经 deleteProperty trap 路由(缺失落穿 target 普通删除,报 true
/// 时 target 自身属性不可配置或 target 不可扩展抛 TypeError)。
///
/// # 副作用
/// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。
fn delete_own(&mut self, obj: &mut JsObject, key_si: u32) -> Result<bool, String>;
// 属性定义 // 属性定义
fn define_data_property( fn define_data_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes, &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
+273 -1
View File
@@ -228,7 +228,7 @@ mod tests {
use crate::native::NativeFn; use crate::native::NativeFn;
use crate::vm::Vm; use crate::vm::Vm;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::{NativeResult, ProtoKind}; use oxide_runtime_api::{NativeResult, ProtoKind, VmHost};
use oxide_types::object::NativeFnPtr; use oxide_types::object::NativeFnPtr;
/// 建一个带状态盒的 Proxy 对象(测试构造路径,不经 Vm 分配)。 /// 建一个带状态盒的 Proxy 对象(测试构造路径,不经 Vm 分配)。
@@ -267,6 +267,48 @@ mod tests {
NativeResult::Ok(JsValue::bool(true)) NativeResult::Ok(JsValue::bool(true))
} }
/// handler 计数属性加一(receiver 由 trap 调用约定保证为 handler)。
fn bump_hits(vm: &mut Vm) {
let receiver = vm.reg(253);
// SAFETY: receiver 由 trap 调用约定保证为 handler 对象值。
let handler = unsafe { &mut *receiver.as_js_object_ptr() };
let hits_si = vm.perm_intern("hits");
let cur = vm.resolve_property(handler, hits_si).unwrap_or(JsValue::int(0));
vm.set_or_create_prop_value(handler, hits_si, JsValue::int(cur.as_int() + 1));
}
/// getPrototypeOf trap 原生函数体:计数加一,返回一个新对象。
fn counting_get_prototype_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult {
bump_hits(vm);
let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()));
NativeResult::Ok(JsValue::from_js_object(result))
}
/// ownKeys trap 原生函数体:计数加一,返回单元素数组 ["k"]。
fn counting_own_keys_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult {
bump_hits(vm);
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), 1));
unsafe { (*arr).set_prop_at(0, vm.new_string("k")) };
unsafe { (*arr).set_prop_count(1) };
NativeResult::Ok(JsValue::from_js_object(arr))
}
/// getOwnPropertyDescriptor trap 原生函数体:计数加一,返回数据描述符
/// 对象 {value: 7, writable/enumerable/configurable 全真}。
fn counting_gopd_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult {
bump_hits(vm);
let proto = vm.builtin_proto(ProtoKind::ObjectProto);
let desc = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)));
let attrs = PropAttributes::DEFAULT_DATA;
let d = unsafe { &mut *desc };
let _ = vm.define_data_property(d, vm.perm_intern("value"), JsValue::int(7), attrs);
let _ = vm.define_data_property(d, vm.perm_intern("writable"), JsValue::bool(true), attrs);
let _ = vm.define_data_property(d, vm.perm_intern("enumerable"), JsValue::bool(true), attrs);
let _ = vm.define_data_property(d, vm.perm_intern("configurable"), JsValue::bool(true), attrs);
NativeResult::Ok(JsValue::from_js_object(desc))
}
/// 建带原生函数项的函数对象(session 堆分配并登记)。 /// 建带原生函数项的函数对象(session 堆分配并登记)。
fn native_fn_obj(vm: &mut Vm, func: NativeFn) -> *mut JsObject { fn native_fn_obj(vm: &mut Vm, func: NativeFn) -> *mut JsObject {
let proto = vm.builtin_proto(ProtoKind::FunctionProto); let proto = vm.builtin_proto(ProtoKind::FunctionProto);
@@ -540,6 +582,236 @@ mod tests {
); );
} }
/// 7 个 trait 方法:非代理路径零行为变化(返回值与固有方法直读一致)。
#[test]
fn proto_trait_methods_plain_object_paths() {
let mut vm = Vm::new();
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()));
let key_si = vm.perm_intern("k");
vm.set_or_create_prop_value(unsafe { &mut *obj }, key_si, JsValue::int(1));
let obj_ref = unsafe { &*obj };
// get_prototype_of:与直读一致。
assert_eq!(
vm.get_prototype_of(obj_ref).expect("get_prototype_of 应成功"),
obj_ref.proto(),
"非代理路径须与直读一致"
);
// set_prototype_of:正常设置成功;新旧 SameValue 直接成功。
let new_proto = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()));
assert!(
vm.set_prototype_of(unsafe { &mut *obj }, JsValue::from_js_object(new_proto))
.expect("set_prototype_of 应成功"),
"正常设置须成功"
);
assert_eq!(unsafe { &*obj }.proto(), JsValue::from_js_object(new_proto));
assert!(
vm.set_prototype_of(unsafe { &mut *obj }, JsValue::from_js_object(new_proto))
.expect("set_prototype_of 应成功"),
"新旧 SameValue 须直接成功"
);
// is_extensible:与直读一致。
assert_eq!(
vm.is_extensible(obj_ref).expect("is_extensible 应成功"),
obj_ref.is_extensible(),
"非代理路径须与直读一致"
);
// prevent_extensions:置不可扩展。
vm.prevent_extensions(unsafe { &mut *obj }).expect("prevent_extensions 应成功");
assert!(!unsafe { &*obj }.is_extensible(), "prevent_extensions 后须不可扩展");
// own_keys:与 walk_own_keys 一致(仅字符串键 "k")。
let keys = vm.own_keys(obj_ref).expect("own_keys 应成功");
assert_eq!(keys.len(), 1, "自身键须恰有一个");
assert_eq!(vm.lookup_str(keys[0]).as_deref(), Some("k"), "键值须为字符串 k");
// get_own_property_desc:存在属性返描述符,缺失返 None。
assert!(
vm.get_own_property_desc(obj_ref, key_si)
.expect("get_own_property_desc 应成功")
.is_some(),
"存在属性须有描述符"
);
assert!(
vm.get_own_property_desc(obj_ref, vm.perm_intern("missing"))
.expect("get_own_property_desc 应成功")
.is_none(),
"缺失属性须返 None"
);
// delete_own:删除成功返 true;缺失键删除亦返 true。
assert!(vm.delete_own(unsafe { &mut *obj }, key_si).expect("delete_own 应成功"), "删除须成功");
assert!(
vm.delete_own(unsafe { &mut *obj }, key_si).expect("delete_own 应成功"),
"缺失键删除须成功"
);
}
/// 7 个 trait 方法:代理路径经 trap 路由(trap 命中、返值原样透传)。
#[test]
fn proto_trait_methods_proxy_paths_route_traps() {
let mut vm = Vm::new();
let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()));
// target 原型须为对象或 null(setPrototypeOf trap 报 true 的不变量检查)。
unsafe { (*target).set_proto(JsValue::null()).expect("set_proto 应成功") };
let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()));
let handler_val = JsValue::from_js_object(handler);
let get_proto_fn = native_fn_obj(&mut vm, counting_get_prototype_trap);
let counting_fn = native_fn_obj(&mut vm, counting_trap);
let own_keys_fn = native_fn_obj(&mut vm, counting_own_keys_trap);
let gopd_fn = native_fn_obj(&mut vm, counting_gopd_trap);
for (name, trap_fn) in [
("getPrototypeOf", get_proto_fn),
("setPrototypeOf", counting_fn),
("isExtensible", counting_fn),
("preventExtensions", counting_fn),
("ownKeys", own_keys_fn),
("getOwnPropertyDescriptor", gopd_fn),
("deleteProperty", counting_fn),
] {
vm.set_or_create_prop_value(
unsafe { &mut *handler },
vm.perm_intern(name),
JsValue::from_js_object(trap_fn),
);
}
let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), handler_val);
let proxy_ref = unsafe { &*proxy };
let handler_ref = unsafe { &*handler };
let hits_si = vm.perm_intern("hits");
let key_si = vm.perm_intern("k");
// getPrototypeOf:trap 返对象,原样透传。
let proto = vm.get_prototype_of(proxy_ref).expect("get_prototype_of 应成功");
assert!(proto.is_object(), "getPrototypeOf trap 返值须为对象");
assert_eq!(
vm.resolve_property(handler_ref, hits_si).unwrap(),
JsValue::int(1),
"getPrototypeOf trap 须命中"
);
// isExtensible:trap 报 true,布尔投影为 true。
assert!(
vm.is_extensible(proxy_ref).expect("is_extensible 应成功"),
"isExtensible trap 报 true 须投影为 true"
);
assert_eq!(
vm.resolve_property(handler_ref, hits_si).unwrap(),
JsValue::int(2),
"isExtensible trap 须命中"
);
// preventExtensions:trap 报 true(与 target 可扩展状态一致)。
vm.prevent_extensions(unsafe { &mut *proxy })
.expect("prevent_extensions 应成功");
assert_eq!(
vm.resolve_property(handler_ref, hits_si).unwrap(),
JsValue::int(3),
"preventExtensions trap 须命中"
);
// setPrototypeOf:trap 报 true,不变量检查通过(target 原型为 null)。
assert!(
vm.set_prototype_of(unsafe { &mut *proxy }, JsValue::null())
.expect("set_prototype_of 应成功"),
"setPrototypeOf trap 报 true 须投影为 true"
);
assert_eq!(
vm.resolve_property(handler_ref, hits_si).unwrap(),
JsValue::int(4),
"setPrototypeOf trap 须命中"
);
// ownKeys:trap 返数组,原样透传。
let keys = vm.own_keys(proxy_ref).expect("own_keys 应成功");
assert_eq!(keys.len(), 1, "ownKeys trap 返值须为单元素数组");
assert_eq!(vm.lookup_str(keys[0]).as_deref(), Some("k"), "ownKeys trap 返值须原样透传");
assert_eq!(vm.resolve_property(handler_ref, hits_si).unwrap(), JsValue::int(5), "ownKeys trap 须命中");
// getOwnPropertyDescriptor:trap 返描述符对象,原样透传。
let desc = vm
.get_own_property_desc(proxy_ref, key_si)
.expect("get_own_property_desc 应成功");
assert!(desc.is_some(), "getOwnPropertyDescriptor trap 须返描述符");
assert_eq!(
vm.resolve_property(handler_ref, hits_si).unwrap(),
JsValue::int(6),
"getOwnPropertyDescriptor trap 须命中"
);
// deleteProperty:trap 报 true,布尔投影为 true。
assert!(
vm.delete_own(unsafe { &mut *proxy }, key_si).expect("delete_own 应成功"),
"deleteProperty trap 报 true 须投影为 true"
);
assert_eq!(
vm.resolve_property(handler_ref, hits_si).unwrap(),
JsValue::int(7),
"deleteProperty trap 须命中"
);
}
/// 7 个 trait 方法:代理路径不变量检查(getPrototypeOf 返非对象非 null
/// 抛 TypeError、setPrototypeOf 报 false 返 false、ownKeys 返非数组抛
/// TypeError)。
#[test]
fn proto_trait_methods_proxy_invariants() {
let mut vm = Vm::new();
let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()));
unsafe { (*target).set_proto(JsValue::null()).expect("set_proto 应成功") };
let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()));
// getPrototypeOf trap 返非对象非 null 值。
fn bad_get_prototype_trap(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::int(42))
}
// setPrototypeOf trap 报 false。
fn false_trap(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::bool(false))
}
// ownKeys trap 返非数组值。
fn bad_own_keys_trap(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::int(42))
}
let get_proto_fn = native_fn_obj(&mut vm, bad_get_prototype_trap);
let set_proto_fn = native_fn_obj(&mut vm, false_trap);
let own_keys_fn = native_fn_obj(&mut vm, bad_own_keys_trap);
vm.set_or_create_prop_value(
unsafe { &mut *handler },
vm.perm_intern("getPrototypeOf"),
JsValue::from_js_object(get_proto_fn),
);
vm.set_or_create_prop_value(
unsafe { &mut *handler },
vm.perm_intern("setPrototypeOf"),
JsValue::from_js_object(set_proto_fn),
);
vm.set_or_create_prop_value(
unsafe { &mut *handler },
vm.perm_intern("ownKeys"),
JsValue::from_js_object(own_keys_fn),
);
let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler));
let proxy_ref = unsafe { &*proxy };
// getPrototypeOf trap 返非对象非 null 抛 TypeError。
let err = vm.get_prototype_of(proxy_ref).expect_err("须抛 TypeError");
assert!(err.contains("TypeError"), "须抛 TypeError:{err}");
// setPrototypeOf trap 报 false 投影为 false(非错误)。
assert!(
!vm.set_prototype_of(unsafe { &mut *proxy }, JsValue::null())
.expect("set_prototype_of 应成功"),
"trap 报 false 须投影为 false"
);
// ownKeys trap 返非数组抛 TypeError。
let err = vm.own_keys(proxy_ref).expect_err("须抛 TypeError");
assert!(err.contains("TypeError"), "须抛 TypeError:{err}");
}
#[test] #[test]
fn proxy_native_edges_return_target_and_handler() { fn proxy_native_edges_return_target_and_handler() {
let target = plain_obj_value(); let target = plain_obj_value();
+124
View File
@@ -262,6 +262,130 @@ impl oxide_runtime_api::VmHost for Vm {
fn proxy_trap_bool_result(&self, result: JsValue, trap_name: &str) -> Result<bool, String> { fn proxy_trap_bool_result(&self, result: JsValue, trap_name: &str) -> Result<bool, String> {
crate::proxy::trap_bool_result(result, trap_name) crate::proxy::trap_bool_result(result, trap_name)
} }
fn get_prototype_of(&mut self, obj: &JsObject) -> Result<JsValue, String> {
if !obj.is_proxy_obj() {
return Ok(obj.proto());
}
// 代理路径:getPrototypeOf trap(实参 target),缺失落穿 target 的
// [[GetPrototypeOf]](target 自身为代理时经递归再路由)。
let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target;
match crate::proxy::call_trap(self, obj, self.perm_intern("getPrototypeOf"), &[target])? {
Some(result) => {
// 返值须为对象或 null(规范 10.5.3.1),否则 TypeError。
if !result.is_object() && !result.is_null() {
return Err(oxide_runtime_api::format_error_message(
"TypeError",
"a [[GetPrototypeOf]] trap returned a non-object and non-null value",
));
}
Ok(result)
}
// SAFETY: target 由构造器校验为非空对象指针。
None => self.get_prototype_of(unsafe { &*target.as_js_object_ptr() }),
}
}
fn set_prototype_of(&mut self, obj: &mut JsObject, proto: JsValue) -> Result<bool, String> {
if !obj.is_proxy_obj() {
// OrdinarySetPrototypeOf 步 2-4:新旧 SameValue 直接成功;不可扩展
// 且新旧不同返回 false。
if !obj.is_extensible() && obj.proto() != proto {
return Ok(false);
}
return match obj.set_proto(proto) {
Ok(()) => Ok(true),
// 成环(新原型已在原型链上):规范返回 false,非错误。
Err("cyclic __proto__ value") => Ok(false),
Err(msg) => Err(oxide_runtime_api::format_error_message("TypeError", msg)),
};
}
// 代理路径:setPrototypeOf trap(实参 target、newProto),缺失落穿 target
// 的 [[SetPrototypeOf]](target 自身为代理时经递归再路由)。
let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target;
let trap_result =
match crate::proxy::call_trap(self, obj, self.perm_intern("setPrototypeOf"), &[target, proto])? {
Some(r) => r,
// SAFETY: target 由构造器校验为非空对象指针。
None => return self.set_prototype_of(unsafe { &mut *target.as_js_object_ptr() }, proto),
};
if !crate::proxy::trap_bool_result(trap_result, "setPrototypeOf")? {
return Ok(false);
}
// 不变量检查:trap 报 true 时 target 的 prototype 须为对象或 null。
// SAFETY: target 由构造器校验为非空对象指针。
let target_proto = self.get_prototype_of(unsafe { &*target.as_js_object_ptr() })?;
if !target_proto.is_object() && !target_proto.is_null() {
return Err(oxide_runtime_api::format_error_message(
"TypeError",
"a [[SetPrototypeOf]] trap returned true for a non-object and non-null target prototype",
));
}
Ok(true)
}
fn is_extensible(&mut self, obj: &JsObject) -> Result<bool, String> {
if !obj.is_proxy_obj() {
return Ok(obj.is_extensible());
}
// 代理路径:isExtensible trap(实参 target),结果经布尔投影,缺失
// 落穿 target(target 自身为代理时经递归再路由)。
let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target;
match crate::proxy::call_trap(self, obj, self.perm_intern("isExtensible"), &[target])? {
Some(result) => Ok(oxide_runtime_api::to_boolean(result)),
// SAFETY: target 由构造器校验为非空对象指针。
None => self.is_extensible(unsafe { &*target.as_js_object_ptr() }),
}
}
fn prevent_extensions(&mut self, obj: &mut JsObject) -> Result<(), String> {
if !obj.is_proxy_obj() {
obj.set_extensible(false);
return Ok(());
}
// 代理路径:preventExtensions trap 路由(含报告与 target 可扩展状态
// 一致性检查),缺失落穿 target 的普通 preventExtensions。
match oxide_builtins::object::proxy_prevent_extensions(self, obj) {
Ok(_) => Ok(()),
// 还原原始异常值入 uncaught 侧通道(保身份)。
Err(exc) => {
self.last_uncaught_value = Some(exc);
Err(self.error_text(exc))
}
}
}
fn own_keys(&mut self, obj: &JsObject) -> Result<Vec<JsValue>, String> {
match oxide_builtins::object::own_keys_proxy_aware(self, obj) {
Ok((keys, _storage)) => {
let mut out = Vec::with_capacity(keys.len());
for (si, _pos) in keys {
out.push(oxide_builtins::object::key_si_to_js_key(self, si));
}
Ok(out)
}
// 还原原始异常值入 uncaught 侧通道(保身份)。
Err(exc) => {
self.last_uncaught_value = Some(exc);
Err(self.error_text(exc))
}
}
}
fn get_own_property_desc(&mut self, obj: &JsObject, key_si: u32) -> Result<Option<JsValue>, String> {
match oxide_builtins::object::own_descriptor_value(self, obj, key_si) {
Ok(desc) => Ok(desc),
// 还原原始异常值入 uncaught 侧通道(保身份)。
Err(exc) => {
self.last_uncaught_value = Some(exc);
Err(self.error_text(exc))
}
}
}
fn delete_own(&mut self, obj: &mut JsObject, key_si: u32) -> Result<bool, String> {
match oxide_builtins::object::delete_own_property(self, obj, key_si) {
Ok(deleted) => Ok(deleted),
// 还原原始异常值入 uncaught 侧通道(保身份)。
Err(exc) => {
self.last_uncaught_value = Some(exc);
Err(self.error_text(exc))
}
}
}
fn define_data_property( fn define_data_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes, &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
) -> Result<(), String> { ) -> Result<(), String> {