From addaab394d235aab52bfacb114be4d93b83db1ea Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 17 Oct 2026 06:36:53 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=90=BD=E5=9C=B0=20Proxy=20trap=20?= =?UTF-8?q?=E6=B4=BE=E5=8F=91=E6=A1=86=E6=9E=B6=E4=B8=8E=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E5=AE=88=E5=8D=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 +++--- Cargo.toml | 2 +- crates/oxide_vm/src/proxy.rs | 230 +++++++++++++++++++++++++++++++- crates/oxide_vm/src/vm/props.rs | 22 +++ crates/oxide_vm/src/vm_props.rs | 59 ++++++++ 5 files changed, 331 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5f39bbb..3a310062 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.20.5" +version = "0.20.6" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.20.5" +version = "0.20.6" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.20.5" +version = "0.20.6" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.20.5" +version = "0.20.6" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.20.5" +version = "0.20.6" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.20.5" +version = "0.20.6" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.20.5" +version = "0.20.6" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.20.5" +version = "0.20.6" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.20.5" +version = "0.20.6" [[package]] name = "oxide_parser" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.20.5" +version = "0.20.6" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.20.5" +version = "0.20.6" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.20.5" +version = "0.20.6" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.20.5" +version = "0.20.6" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 86238123..8a7374e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.20.5" +version = "0.20.6" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/proxy.rs b/crates/oxide_vm/src/proxy.rs index 4142d5e3..f58ae2ec 100644 --- a/crates/oxide_vm/src/proxy.rs +++ b/crates/oxide_vm/src/proxy.rs @@ -3,11 +3,18 @@ //! 状态盒本体(`ProxyState`)定义在 `oxide_builtins::proxy`(构造器侧分配), //! 本模块只读其布局做 GC 接线;撤销函数(独立 tag)的边函数在 //! `oxide_builtins::proxy::proxy_revoker_native_edges`。 +//! +//! trap 派发框架:`call_trap` 从 handler 读 trap 值并同步调用,各入口 +//! (get / set / has / defineProperty)的 is_proxy 守卫路由到本助手;逐 trap +//! 完整语义(不变量检查、receiver 语义、target 自身属性描述符)由后续卡补全。 use oxide_builtins::proxy::ProxyState; -use oxide_types::object::JsObject; +use oxide_runtime_api::VmHost; +use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; +use crate::vm::Vm; + /// Proxy 状态盒内全部值边(GC mark 边):target 与 handler。 pub(crate) fn proxy_native_edges(obj: &JsObject) -> Vec { let Some(state) = oxide_builtins::proxy::proxy_state_ref(obj) else { @@ -42,10 +49,90 @@ pub(crate) fn drop_proxy_native(obj: &JsObject) -> u64 { bytes } +/// trap 调用助手:从 handler 读 trap 值并同步调用。 +/// +/// # 步骤 +/// 1. 取状态盒并检 revoked 位:位真抛 TypeError("Proxy is revoked")。 +/// 2. 经全 get 路径从 handler 读 trap 值(receiver 为 handler——handler 自身 +/// 是代理时其 get trap 依规范触发)。 +/// 3. trap 值 undefined 返 None(落穿普通行为)。 +/// 4. 经 `call_function_sync` 同步调用 trap 值(this 为 handler)。 +/// +/// # 边界与前提 +/// - `proxy` 须为带状态盒的 Proxy 对象(守卫以 `is_proxy_obj` 保证入口)。 +/// - 代理对象须为 GC 根(活栈上),handler 与 target 经家族表边标活, +/// trap 调用期间状态盒不被回收(重入 GC 安全点)。 +/// +/// # 副作用 +/// - trap 调用执行用户代码(含分配与 GC);trap 返回值原样透传。 +/// +/// # 注意事项 +/// - 逐 trap 返回值校验(布尔投影、不变量)由各卡守卫补全,本助手只给通用形态。 +pub(crate) fn call_trap( + vm: &mut Vm, proxy: &JsObject, trap_name_si: u32, args: &[JsValue], +) -> Result, String> { + let state = oxide_builtins::proxy::proxy_state_ref(proxy).expect("proxy state box must exist"); + if state.revoked { + return Err(vm.error_message_text("TypeError", "Proxy is revoked")); + } + let handler = state.handler; + // SAFETY: handler 由构造器校验为非空对象指针。 + let handler_obj = unsafe { &*handler.as_js_object_ptr() }; + let trap_val = vm.ordinary_get(handler_obj, trap_name_si, handler)?; + if trap_val.is_undefined() { + return Ok(None); + } + let result = vm.call_function_sync(trap_val, handler, args)?; + Ok(Some(result)) +} + +/// 布尔 trap 返回值校验:trap 返回值投影为布尔,非布尔返回抛 TypeError。 +/// +/// 错误文本为 `TypeError: ...` 前缀形态,调用边界经 `create_from_text` +/// 恢复为 TypeError 异常对象。 +pub(crate) fn trap_bool_result(result: JsValue, trap_name: &str) -> Result { + if result.is_bool() { + return Ok(result.as_bool()); + } + Err(oxide_runtime_api::format_error_message( + "TypeError", + &format!("Proxy {trap_name} trap must return a boolean"), + )) +} + +/// 构造 defineProperty trap 的属性描述符对象(value 加三标志位)。 +/// +/// 描述符属性均为不可写、不可枚举、不可配置(与内建对象属性口径一致); +/// 对象原型链落 Object.prototype。 +pub(crate) fn define_property_descriptor(vm: &mut Vm, val: JsValue, attributes: PropAttributes) -> JsValue { + let proto = vm.builtin_proto(oxide_runtime_api::ProtoKind::ObjectProto); + let obj = vm.alloc_object(JsObject::new_empty( + oxide_kernel::shape_forge::EMPTY_SHAPE_ID, + JsValue::from_js_object(proto), + )); + let attrs = PropAttributes::new(false, false, true); + let obj_ref = unsafe { &mut *obj }; + let _ = vm.define_data_property(obj_ref, vm.perm_intern("value"), val, attrs); + let _ = vm.define_data_property(obj_ref, vm.perm_intern("writable"), JsValue::bool(attributes.writable()), attrs); + let _ = + vm.define_data_property(obj_ref, vm.perm_intern("enumerable"), JsValue::bool(attributes.enumerable()), attrs); + let _ = vm.define_data_property( + obj_ref, + vm.perm_intern("configurable"), + JsValue::bool(attributes.configurable()), + attrs, + ); + JsValue::from_js_object(obj) +} + #[cfg(test)] mod tests { use super::*; - use oxide_types::shape::EMPTY_SHAPE_ID; + use crate::native::NativeFn; + use crate::vm::Vm; + use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; + use oxide_runtime_api::{NativeResult, ProtoKind}; + use oxide_types::object::NativeFnPtr; /// 建一个带状态盒的 Proxy 对象(测试构造路径,不经 Vm 分配)。 fn proxy_obj(target: JsValue, handler: JsValue) -> JsObject { @@ -66,6 +153,145 @@ mod tests { JsValue::from_js_object(Box::into_raw(obj)) } + /// get trap 原生函数体:逼一次完整收集并返回固定值。 + fn gc_forcing_get_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult { + vm.collect_session_gc(); + NativeResult::Ok(JsValue::int(42)) + } + + /// 计数 trap 原生函数体:receiver(handler)的计数属性加一,返回 true。 + fn counting_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult { + 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)); + NativeResult::Ok(JsValue::bool(true)) + } + + /// 建带原生函数项的函数对象(session 堆分配并登记)。 + fn native_fn_obj(vm: &mut Vm, func: NativeFn) -> *mut JsObject { + let proto = vm.builtin_proto(ProtoKind::FunctionProto); + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)); + obj.set_function(true); + // SAFETY: func 是合法 NativeFn 函数项。 + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(func as *const ()) })); + obj.set_native_arg_count(0); + vm.alloc_object(obj) + } + + /// 建带状态盒的 Proxy 对象(target、handler、代理均登记 session 堆)。 + fn proxy_with_state(vm: &mut Vm, target: JsValue, handler: JsValue) -> *mut JsObject { + let proto = vm.builtin_proto(ProtoKind::ObjectProto); + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_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); + vm.alloc_object(obj) + } + + /// 重入 GC 安全点:trap 调用期间逼完整收集,handler 与 target 指针身份 + /// 不变(经状态盒边标活),且收集计数推进(收集确在 trap 调用期间发生)。 + #[test] + fn trap_call_gc_safety_point_keeps_handler_and_target_alive() { + let mut vm = Vm::new(); + let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let trap_fn = native_fn_obj(&mut vm, gc_forcing_get_trap); + let get_si = vm.perm_intern("get"); + vm.set_or_create_prop_value(unsafe { &mut *handler }, get_si, JsValue::from_js_object(trap_fn)); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + // 代理置于活栈寄存器(Regs 根组,避开实参打包区),与生产 trap 调用路径同形。 + vm.regs[4] = JsValue::from_js_object(proxy); + let before = vm.session_gc_stats().total_collections; + let key_si = vm.perm_intern("k"); + let result = vm + .ordinary_get(unsafe { &*proxy }, key_si, JsValue::from_js_object(proxy)) + .expect("get 应成功"); + assert_eq!(result, JsValue::int(42), "get trap 返回值须原样返回"); + assert!(vm.session_gc_stats().total_collections > before, "trap 调用期间应发生收集"); + let state = oxide_builtins::proxy::proxy_state_ref(unsafe { &*proxy }).expect("状态盒"); + assert!( + std::ptr::eq(state.target.as_js_object_ptr(), target), + "target 不得被 trap 调用期间的收集回收" + ); + assert!( + std::ptr::eq(state.handler.as_js_object_ptr(), handler), + "handler 不得被 trap 调用期间的收集回收" + ); + } + + /// 已撤销代理:全部 trap 派发抛 TypeError。 + #[test] + fn call_trap_revoked_throws_type_error() { + let mut vm = Vm::new(); + let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + { + let state = oxide_builtins::proxy::proxy_state_mut(unsafe { &mut *proxy }).expect("状态盒"); + state.revoked = true; + } + let get_si = vm.perm_intern("get"); + let err = call_trap(&mut vm, unsafe { &*proxy }, get_si, &[]).expect_err("已撤销代理须抛错"); + assert!(err.contains("TypeError"), "已撤销代理须抛 TypeError:{err}"); + assert!(err.contains("Proxy is revoked"), "错误文本须带撤销标记:{err}"); + } + + /// 守卫路由:get / set / has trap 对代理命中,resolve_property 对代理保守返 None。 + #[test] + fn guard_routing_for_proxy_get_set_has_and_resolve() { + let mut vm = Vm::new(); + let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let trap_fn = native_fn_obj(&mut vm, counting_trap); + let handler_val = JsValue::from_js_object(handler); + for name in ["get", "set", "has"] { + 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 key_si = vm.perm_intern("k"); + let hits_si = vm.perm_intern("hits"); + + let result = vm.ordinary_get(unsafe { &*proxy }, key_si, handler_val).expect("get 应成功"); + assert_eq!(result, JsValue::bool(true), "get trap 返回值须原样返回"); + assert_eq!( + vm.resolve_property(unsafe { &*handler }, hits_si).unwrap(), + JsValue::int(1), + "get trap 须命中" + ); + + vm.ordinary_set(unsafe { &mut *proxy }, key_si, JsValue::int(7), handler_val, true) + .expect("set 应成功"); + assert_eq!( + vm.resolve_property(unsafe { &*handler }, hits_si).unwrap(), + JsValue::int(2), + "set trap 须命中" + ); + + let exists = vm.has_property(unsafe { &*proxy }, key_si).expect("has 应成功"); + assert!(exists, "has trap 须返回 true"); + assert_eq!( + vm.resolve_property(unsafe { &*handler }, hits_si).unwrap(), + JsValue::int(3), + "has trap 须命中" + ); + + assert!( + vm.resolve_property(unsafe { &*proxy }, key_si).is_none(), + "resolve_property 对代理须保守返 None" + ); + } + #[test] fn proxy_native_edges_return_target_and_handler() { let target = plain_obj_value(); diff --git a/crates/oxide_vm/src/vm/props.rs b/crates/oxide_vm/src/vm/props.rs index df81f8e1..55a3bf94 100644 --- a/crates/oxide_vm/src/vm/props.rs +++ b/crates/oxide_vm/src/vm/props.rs @@ -163,6 +163,11 @@ impl Vm { /// `MAX_PROTO_CHAIN_DEPTH` 深度上限。 pub(crate) fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option { vm_trace!("resolve_property: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si); + // Proxy:本方法是不可变签名,无法同步调 get trap;代理无自身属性, + // 保守返 None(get trap 是权威,须由可变签名入口承载)。 + if obj.is_proxy_obj() { + return None; + } let length_si = self.length_si; if obj.is_array() && prop_name_si == length_si { return Some(obj.logical_len_value()); @@ -218,6 +223,23 @@ impl Vm { /// (`in` 运算符逐级走 HasProperty;非 deferred 对象触发为 no-op),触发失败 /// (cyclic 守卫抛 TypeError / 失败缓存原值重抛)走可捕获异常展开。 pub(crate) fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result { + // Proxy [[HasProperty]]:has trap 以(target、P)调用;trap 报 true 即 + // 存在;报 false 落穿 target 的普通存在性判定(不变量检查由后续卡补全)。 + if obj.is_proxy_obj() { + let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target; + let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si); + if let Some(r) = crate::proxy::call_trap(self, obj, self.perm_intern("has"), &[target, key])? { + if crate::proxy::trap_bool_result(r, "has")? { + return Ok(true); + } + // target 即本代理(自环)时保守判不存在,避免无限递归。 + if std::ptr::eq(target.as_js_object_ptr(), obj as *const JsObject) { + return Ok(false); + } + // SAFETY: target 由构造器校验为非空对象指针。 + return self.has_property(unsafe { &*target.as_js_object_ptr() }, prop_name_si); + } + } let length_si = self.length_si; let mut current = Some(obj); let mut depth = 0usize; diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 19bb3fd5..876b66e1 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -108,6 +108,15 @@ impl Vm { &mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, target_reg: Option, ) -> Result { vm_trace!("ordinary_get_inner: shape={} prop_si={}", obj.shape_id(), prop_name_si); + // Proxy [[Get]]:get trap 权威(target、P、Receiver),trap 定义时其返回值 + // 即读结果;trap 未定义落穿普通属性路径。 + if obj.is_proxy_obj() { + let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target; + let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si); + if let Some(v) = crate::proxy::call_trap(self, obj, self.perm_intern("get"), &[target, key, receiver])? { + return Ok(v); + } + } let length_si = self.length_si; let mut current = Some(obj); let mut depth = 0usize; @@ -425,6 +434,30 @@ impl Vm { prop_name_si, use_frame_push ); + // Proxy [[Set]]:set trap 以(target、P、Value、Receiver)调用;trap 报 + // true 写成功;报 false 落穿 target 的普通写路径(不变量检查由后续卡补全)。 + if obj.is_proxy_obj() { + let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target; + let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si); + if let Some(r) = crate::proxy::call_trap(self, obj, self.perm_intern("set"), &[target, key, val, receiver])? + { + if crate::proxy::trap_bool_result(r, "set")? { + return Ok(()); + } + // target 即本代理(自环)时按写失败处理,避免无限递归。 + if std::ptr::eq(target.as_js_object_ptr(), obj as *const JsObject) { + return self.write_protection_failure(builtin, "Proxy set trap reported false"); + } + // SAFETY: target 由构造器校验为非空对象指针。 + return self.ordinary_set_inner( + unsafe { &mut *target.as_js_object_ptr() }, + prop_name_si, + val, + receiver, + contract, + ); + } + } // 模块命名空间 exotic [[Set]] 恒 false(规范 10.4.6.8,deferred 形态同口径 // 不触发求值):严格抛 TypeError,sloppy 静默 no-op;Reflect.set 以 // strict=true 调用并投影为 false。 @@ -1094,6 +1127,32 @@ impl Vm { &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes, ) -> Result<(), String> { vm_trace!("define_data_property: shape={} prop_si={}", obj.shape_id(), prop_name_si); + // Proxy [[DefineOwnProperty]]:defineProperty trap 以(target、P、Desc) + // 调用;trap 报 true 定义成功;报 false 落穿 target 的普通定义路径 + // (不变量检查由后续卡补全)。 + if obj.is_proxy_obj() { + let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target; + let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si); + let desc = crate::proxy::define_property_descriptor(self, val, attributes); + if let Some(r) = + crate::proxy::call_trap(self, obj, self.perm_intern("defineProperty"), &[target, key, desc])? + { + if crate::proxy::trap_bool_result(r, "defineProperty")? { + return Ok(()); + } + // target 即本代理(自环)时按定义失败处理,避免无限递归。 + if std::ptr::eq(target.as_js_object_ptr(), obj as *const JsObject) { + return Err("Proxy defineProperty trap reported false".to_string()); + } + // SAFETY: target 由构造器校验为非空对象指针。 + return self.define_data_property( + unsafe { &mut *target.as_js_object_ptr() }, + prop_name_si, + val, + attributes, + ); + } + } // TypedArray 统一数值键门:界内规范键走元素定义(界内写 buffer); // 规范越界键(含 detach:live 长 0)拒绝定义;非规范数值串 // ("+1"/"1.0" 等,round-trip 不成)与 symbol 键落命名属性路径。