From 3fa2f94fd1cd3b3d5aae7ca36cadec526dad14d2 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 8 Oct 2026 03:17:06 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=8C=96=E5=85=8B=E9=9A=86=E7=BC=93=E5=86=B2=E5=8C=BA=E6=97=8F?= =?UTF-8?q?=E5=88=86=E6=B4=BE=E4=B8=8E=E8=BD=AC=E7=A7=BB=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E5=8F=8A=E5=85=A8=E5=B1=80=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 36 +- Cargo.toml | 2 +- crates/oxide_builtins/src/array_buffer.rs | 25 +- crates/oxide_builtins/src/data_view.rs | 8 +- crates/oxide_builtins/src/structured_clone.rs | 377 ++++++++++++++++-- crates/oxide_builtins/src/typed_array.rs | 4 +- crates/oxide_test262/src/judge.rs | 2 +- crates/oxide_vm/src/bindings/bind_global.rs | 1 + crates/oxide_vm/src/bindings/mod.rs | 1 + 9 files changed, 396 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8a81936..7b6669d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.58" +version = "0.8.59" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.58" +version = "0.8.59" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.58" +version = "0.8.59" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.58" +version = "0.8.59" dependencies = [ "ansi_term", "bumpalo", @@ -1225,7 +1225,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.58" +version = "0.8.59" dependencies = [ "lru", "oxide_bytecode", @@ -1234,7 +1234,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.58" +version = "0.8.59" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1253,7 +1253,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.58" +version = "0.8.59" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1267,7 +1267,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.58" +version = "0.8.59" dependencies = [ "num-bigint", "oxc_allocator", @@ -1282,7 +1282,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.58" +version = "0.8.59" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1291,7 +1291,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.58" +version = "0.8.59" dependencies = [ "bumpalo", "dashmap", @@ -1306,7 +1306,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.58" +version = "0.8.59" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1318,7 +1318,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.58" +version = "0.8.59" dependencies = [ "tracing", "tracing-appender", @@ -1327,7 +1327,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.58" +version = "0.8.59" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1342,7 +1342,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.58" +version = "0.8.59" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1358,7 +1358,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.58" +version = "0.8.59" dependencies = [ "num-bigint", "num-traits", @@ -1370,7 +1370,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.58" +version = "0.8.59" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1387,7 +1387,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.58" +version = "0.8.59" dependencies = [ "bumpalo", "hashbrown 0.14.5", @@ -1399,7 +1399,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.58" +version = "0.8.59" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 77e82f00..cd68b172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.8.58" +version = "0.8.59" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index 20ebb791..dd99c432 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr}; use oxide_types::private_key::{encode_symbol_key, WELL_KNOWN_SYMBOL_SPECIES}; @@ -12,8 +14,9 @@ pub(crate) const MAX_ARRAY_BUFFER_LENGTH: usize = 1 << 30; /// ArrayBuffer 载荷:字节缓冲与状态位。`data` 为 `None` 即缓冲区已 detach; /// `max_byte_length` 为存储态上限:0 即定长缓冲(resizable 判据),非 0 存 /// 请求上限 + 1(上限 0 与定长必须可分);`immutable` 为字节缓冲写守卫标志。 -/// 载荷经 `Box::into_raw` 存于对象 `native_fn` 槽,GC 两自由函数 -/// (size/drop)对整结构体操作。 +/// 载荷经 `Arc::into_raw` 存于对象 `native_fn` 槽(引用计数盒:SharedArrayBuffer +/// 克隆共享同一载荷,计数归零统一释放),GC 两自由函数(size/drop)对整结构体 +/// 操作。 #[derive(Clone)] pub(crate) struct ArrayBufferPayload { pub(crate) data: Option>, @@ -101,7 +104,7 @@ pub(crate) fn new_array_buffer( max_byte_length, immutable: false, }; - let payload_ptr = Box::into_raw(Box::new(payload)); + let payload_ptr = Arc::into_raw(Arc::new(payload)); // SAFETY: ArrayBuffer 对象不可调用,故 native_fn 槽复用作不透明载荷盒 // 指针,与既有 RegExp 存储模式一致。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); @@ -138,8 +141,8 @@ pub fn drop_array_buffer_native(obj: &mut JsObject) -> u64 { let Some(payload_ptr) = array_buffer_payload_ptr(obj) else { return 0; }; - // SAFETY: payload_ptr 非空(array_buffer_native_size 已验证),Box::from_raw 恰好释放一次。 - let payload = unsafe { Box::from_raw(payload_ptr) }; + // SAFETY: payload_ptr 非空(array_buffer_native_size 已验证),Arc::from_raw 恰好减引用一次。 + let payload = unsafe { Arc::from_raw(payload_ptr) }; drop(payload); obj.set_native_fn(None); bytes @@ -163,7 +166,7 @@ pub(crate) fn new_shared_array_buffer( max_byte_length, immutable: false, }; - let payload_ptr = Box::into_raw(Box::new(payload)); + let payload_ptr = Arc::into_raw(Arc::new(payload)); // SAFETY: SharedArrayBuffer 对象不可调用,native_fn 槽复用为不透明载荷盒 // 指针,与 ArrayBuffer 存储模式一致。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); @@ -238,8 +241,8 @@ pub fn drop_shared_array_buffer_native(obj: &mut JsObject) -> u64 { let Some(payload_ptr) = shared_array_buffer_payload_ptr(obj) else { return 0; }; - // SAFETY: payload_ptr 非空(size 已验证),Box::from_raw 恰好释放一次。 - let payload = unsafe { Box::from_raw(payload_ptr) }; + // SAFETY: payload_ptr 非空(size 已验证),Arc::from_raw 恰好减引用一次。 + let payload = unsafe { Arc::from_raw(payload_ptr) }; drop(payload); obj.set_native_fn(None); bytes @@ -1163,7 +1166,7 @@ mod tests { fn ab_object_with_payload(payload: ArrayBufferPayload) -> JsObject { let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; - let payload_ptr = Box::into_raw(Box::new(payload)); + let payload_ptr = Arc::into_raw(Arc::new(payload)); // SAFETY: 载荷盒形态与 new_array_buffer 的 native_fn 槽存储一致,测试结束前恰好释放一次。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); obj @@ -1200,7 +1203,7 @@ mod tests { fn payload_ptr_brand_guard() { // 非 ArrayBuffer 类型标签(PLAIN)对象取载荷指针恒 None。 let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); - let payload_ptr = Box::into_raw(Box::new(ArrayBufferPayload { + let payload_ptr = Arc::into_raw(Arc::new(ArrayBufferPayload { data: Some(vec![0u8; 4]), max_byte_length: 0, immutable: false, @@ -1209,7 +1212,7 @@ mod tests { obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); assert!(array_buffer_payload_ptr(&obj).is_none()); // SAFETY: 测试构造的盒,恰好释放一次。 - unsafe { drop(Box::from_raw(payload_ptr)) }; + unsafe { drop(Arc::from_raw(payload_ptr)) }; } /// JS 求值辅助:单脚本编译执行,返回完成值。 diff --git a/crates/oxide_builtins/src/data_view.rs b/crates/oxide_builtins/src/data_view.rs index 833c2ffd..4f531c51 100644 --- a/crates/oxide_builtins/src/data_view.rs +++ b/crates/oxide_builtins/src/data_view.rs @@ -14,10 +14,10 @@ use oxide_runtime_api::{NativeResult, VmHost}; /// 存构造期 live − offset 初值,活读一律现算 live − offset。 #[derive(Clone, Copy)] pub(crate) struct DataViewData { - buffer: JsValue, - byte_offset: usize, - byte_length: usize, - length_is_auto: bool, + pub(crate) buffer: JsValue, + pub(crate) byte_offset: usize, + pub(crate) byte_length: usize, + pub(crate) length_is_auto: bool, } macro_rules! native_try { diff --git a/crates/oxide_builtins/src/structured_clone.rs b/crates/oxide_builtins/src/structured_clone.rs index 5e7c75a2..5f5444e8 100644 --- a/crates/oxide_builtins/src/structured_clone.rs +++ b/crates/oxide_builtins/src/structured_clone.rs @@ -3,11 +3,14 @@ //! //! 本核以 `VmHost` 为接口、目标 realm 为分配上下文,做同 realm 克隆(源与目标 //! 同一 realm,符号映射为恒等)。可克隆类型白名单:原始值、数组、plain 对象、 -//! Map、Set、Date、RegExp、Error 家族;其余对象(function / DOM / 模块命名空间 / -//! Promise / 迭代器 / 缓冲区 / 特型对象)一律 DataCloneError。缓冲区克隆(含 -//! transfer 转移)在缓冲区克隆子任务落地,当前统一报 DataCloneError。 +//! Map、Set、Date、RegExp、Error 家族、缓冲区族(ArrayBuffer / SharedArrayBuffer / +//! TypedArray / DataView);其余对象(function / DOM / 模块命名空间 / Promise / +//! 迭代器 / 特型对象)一律 DataCloneError。缓冲区克隆支持 transfer 转移: +//! 命中转移集合的 ArrayBuffer 移动载荷(源 detach),未命中克隆字节; +//! SharedArrayBuffer 克隆共享同一载荷盒。 use std::collections::{HashMap, HashSet}; +use std::sync::Arc; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; @@ -21,8 +24,8 @@ use crate::object::{walk_own_keys, walk_own_symbol_keys}; use crate::set::{SetInner, SetKey}; /// 克隆状态:`seen` 映射保共享引用(同源对象二次出现复用同一克隆,循环引用不 -/// 死循环);`transfer` 集合枚举待转移的 ArrayBuffer(缓冲区转移语义在缓冲区 -/// 克隆落地时启用,当前恒空)。 +/// 死循环);`transfer` 集合枚举待转移的 ArrayBuffer(命中移动载荷,未命中 +/// 克隆字节)。 struct CloneState { seen: HashMap<*const JsObject, *mut JsObject>, transfer: HashSet<*const JsObject>, @@ -37,25 +40,79 @@ impl CloneState { } } -/// `structuredClone(value, options)` 入口:解析 value,建克隆状态,调核心。 +/// `structuredClone(value, options)` 入口:解析 value 与 options.transfer, +/// 建克隆状态,调核心。 /// /// # 步骤 /// 1. 取第一实参 value(缺省 undefined) -/// 2. 建 `CloneState` -/// 3. 调 `clone_value` 递归克隆 +/// 2. 取第二实参 options(缺省 undefined);在场时解析 transfer 列表 +/// 3. 建 `CloneState` +/// 4. 调 `clone_value` 递归克隆 /// /// # 边界与前提 -/// - options(含 transfer)在缓冲区克隆落地时处理,当前忽略; -/// - 返回 `Ok(克隆值)` 或 `Err(DataCloneError)`。 +/// - options 非对象 → TypeError;`transfer` 非数组 → TypeError;元素非 +/// ArrayBuffer 或重复 → DataCloneError; +/// - 返回 `Ok(克隆值)` 或 `Err(TypeError / DataCloneError)`。 pub fn structured_clone_entry(vm: &mut H, args: &[u8]) -> NativeResult { let value = 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 mut state = CloneState::new(); + if !options.is_undefined() { + if let Err(e) = parse_transfer_list(vm, options, &mut state) { + return NativeResult::Err(e); + } + } match clone_value(vm, &mut state, value) { Ok(v) => NativeResult::Ok(v), Err(e) => NativeResult::Err(e), } } +/// 解析 options.transfer 入 transfer 集合:options 须为对象,`transfer` 须为 +/// 数组,元素须为不重复的 ArrayBuffer(缺省 `transfer` 为空集)。 +/// +/// # 边界与前提 +/// - `transfer` 属性读取是用户代码窗口(getter 副作用与异常原值传播); +/// - 转移集合只收 ArrayBuffer 对象指针(SAB 不可转移,非 AB 元素报 +/// DataCloneError)。 +fn parse_transfer_list(vm: &mut H, options: JsValue, state: &mut CloneState) -> Result<(), JsValue> { + if !options.is_object() { + return Err(crate::error::create_type_error(vm, "structuredClone options must be an object")); + } + let opt_ptr = options.as_js_object_ptr(); + // SAFETY: is_object 保证非空指针;对象在 native 执行期间被根保持、不被回收。 + let opt = unsafe { &*opt_ptr }; + let si_transfer = vm.kernel_core().perm_interner().intern("transfer").0; + let transfer_val = match vm.ordinary_get(opt, si_transfer, options) { + Ok(v) => v, + Err(e) => { + if let Some(exc) = vm.take_uncaught_value() { + return Err(exc); + } + return Err(crate::error::create_type_error(vm, &e)); + } + }; + if transfer_val.is_undefined() { + return Ok(()); + } + if !transfer_val.is_object() || !unsafe { &*transfer_val.as_js_object_ptr() }.is_array() { + return Err(crate::error::create_type_error(vm, "transfer list must be an array")); + } + let list = unsafe { &*transfer_val.as_js_object_ptr() }; + let count = list.logical_len() as usize; + for i in 0..count { + let e = list.get_prop_at(i); + if !e.is_object() || !unsafe { &*e.as_js_object_ptr() }.is_array_buffer_obj() { + return Err(data_clone_error(vm, "transfer list element is not an ArrayBuffer")); + } + let ptr = e.as_js_object_ptr() as *const JsObject; + if !state.transfer.insert(ptr) { + return Err(data_clone_error(vm, "duplicate buffer in transfer list")); + } + } + Ok(()) +} + /// 递归克隆一个值:原始值原样拷贝,对象按类型分派。 /// /// # 步骤 @@ -67,8 +124,9 @@ pub fn structured_clone_entry(vm: &mut H, args: &[u8]) -> NativeResul /// /// # 边界与前提 /// - 源对象由调用方寄存器保活;克隆经 `alloc_object` 入目标 realm 对象表即成根; -/// - 可克隆白名单:数组 / plain / Map / Set / Date / RegExp / Error,其余对象 -/// (含缓冲区)报 DataCloneError。 +/// - 可克隆白名单:数组 / plain / Map / Set / Date / RegExp / Error / 缓冲区族, +/// 其余对象(function / DOM / 模块命名空间 / Promise / 迭代器 / 特型对象) +/// 报 DataCloneError。 fn clone_value(vm: &mut H, state: &mut CloneState, value: JsValue) -> Result { if !value.is_object() { return Ok(value); @@ -91,12 +149,14 @@ fn clone_value(vm: &mut H, state: &mut CloneState, value: JsValue) -> alloc_regexp_clone(vm, src)? } else if src.is_error_obj() { alloc_error_clone(vm, src) - } else if is_buffer(src) { - // transfer 集合命中时走转移语义(缓冲区克隆落地后启用);当前缓冲区一律不可克隆。 - if state.transfer.contains(&(src_ptr as *const JsObject)) { - return Err(data_clone_error(vm, "transferred buffer is not cloneable")); - } - return Err(data_clone_error(vm, "buffer value is not cloneable")); + } else if src.is_array_buffer_obj() { + alloc_array_buffer_clone(vm, state, src, src_ptr as *const JsObject)? + } else if src.is_shared_array_buffer_obj() { + alloc_shared_array_buffer_clone(vm, src)? + } else if src.is_typed_array_obj() { + alloc_typed_array_clone(vm, state, src)? + } else if src.is_data_view_obj() { + alloc_data_view_clone(vm, state, src)? } else if is_plain_object(src) { alloc_plain_clone(vm) } else { @@ -108,7 +168,8 @@ fn clone_value(vm: &mut H, state: &mut CloneState, value: JsValue) -> } /// 填充克隆内容:Map / Set 走各自条目复制,数组 / plain / Error 统一走自有属性复制。 -/// Date / RegExp 在分配时已填充(时间戳 / 源与标志),此处无操作。 +/// Date / RegExp / 缓冲区族在分配时已填充(时间戳 / 源与标志 / 载荷与视图状态盒), +/// 此处无操作。 fn fill_clone( vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject, ) -> Result<(), JsValue> { @@ -122,6 +183,11 @@ fn fill_clone( if src.is_date_obj() || src.is_regexp_obj() { return Ok(()); } + // 缓冲区族(AB / SAB / TypedArray / DataView)在分配时已填充载荷与视图 + // 状态盒,无自有属性可复制。 + if is_buffer(src) { + return Ok(()); + } fill_object_props(vm, state, src, clone_ptr) } @@ -324,6 +390,110 @@ fn alloc_error_clone(vm: &mut H, src: &JsObject) -> *mut JsObject { vm.alloc_object(obj) } +/// 分配 ArrayBuffer 克隆:transfer 命中移动载荷(源 `data → None` 即 detach), +/// 未命中克隆字节;存储态上限与 immutable 标志原样拷贝,detached 源产 detached +/// 克隆。 +fn alloc_array_buffer_clone( + vm: &mut H, state: &mut CloneState, src: &JsObject, src_ptr: *const JsObject, +) -> Result<*mut JsObject, JsValue> { + let Some(payload_ptr) = crate::array_buffer::array_buffer_payload_ptr(src) else { + return Err(data_clone_error(vm, "ArrayBuffer internal state invalid")); + }; + let transferred = state.transfer.contains(&src_ptr); + // SAFETY: payload_ptr 经 array_buffer_payload_ptr 校验为存活载荷盒;标量 + // 拷出后借用即结束,不跨 JS 调用。 + let (data, max_byte_length, immutable) = unsafe { + let p = &mut *payload_ptr; + // 转移命中移动载荷(源 data → None);未命中克隆字节。 + let data = if transferred { p.data.take() } else { p.data.clone() }; + (data, p.max_byte_length, p.immutable) + }; + let proto = crate::array_buffer::default_array_buffer_proto(vm); + let clone_ptr = alloc_buffer_object(vm, data, max_byte_length, immutable, proto); + Ok(clone_ptr) +} + +/// 分配携给定载荷形态(`data` 为 `None` 即 detached)的 ArrayBuffer 对象。 +fn alloc_buffer_object( + vm: &mut H, data: Option>, max_byte_length: usize, immutable: bool, proto: JsValue, +) -> *mut JsObject { + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); + obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; + let payload = crate::array_buffer::ArrayBufferPayload { data, max_byte_length, immutable }; + let payload_ptr = Arc::into_raw(Arc::new(payload)); + // SAFETY: ArrayBuffer 对象不可调用,native_fn 槽复用为不透明载荷盒指针, + // 与 new_array_buffer 的存储形态一致。 + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); + vm.alloc_object(obj) +} + +/// 分配 SharedArrayBuffer 克隆:共享同一载荷盒(引用计数加一),两对象共享 +/// 字节缓冲;SAB 无 detach 路径。 +fn alloc_shared_array_buffer_clone(vm: &mut H, src: &JsObject) -> Result<*mut JsObject, JsValue> { + let Some(payload_ptr) = crate::array_buffer::shared_array_buffer_payload_ptr(src) else { + return Err(data_clone_error(vm, "SharedArrayBuffer internal state invalid")); + }; + // SAFETY: payload_ptr 经 shared_array_buffer_payload_ptr 校验为存活载荷盒; + // 克隆共享同一载荷(引用计数加一),释放经计数归零统一收口。 + unsafe { Arc::increment_strong_count(payload_ptr) }; + let proto = crate::array_buffer::default_shared_array_buffer_proto(vm); + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); + obj.type_tag = JsObject::OBJ_TYPE_SHARED_ARRAY_BUFFER; + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); + Ok(vm.alloc_object(obj)) +} + +/// 分配 TypedArray 克隆:递归克隆(或转移)底层缓冲,再按同类型 / 同偏移 / +/// 同长度构造新视图。 +fn alloc_typed_array_clone( + vm: &mut H, state: &mut CloneState, src: &JsObject, +) -> Result<*mut JsObject, JsValue> { + let Some(data_ptr) = src + .native_fn() + .map(|p| p.as_ptr() as *const crate::typed_array::TypedArrayData) + .filter(|p| !p.is_null()) + else { + return Err(data_clone_error(vm, "TypedArray internal state invalid")); + }; + // SAFETY: data_ptr 经对象类型标签校验为存活状态盒;Copy 语义,不跨 JS 调用持借用。 + let data = unsafe { *data_ptr }; + let cloned_buffer = clone_value(vm, state, data.buffer)?; + let proto = JsValue::from_js_object(crate::typed_array::typed_array_proto_ptr(vm, data.kind)); + let clone_ptr = + crate::typed_array::create_typed_array(vm, data.kind, cloned_buffer, data.byte_offset, data.length, data.auto_length, proto); + Ok(clone_ptr) +} + +/// 分配 DataView 克隆:递归克隆(或转移)底层缓冲,再按同偏移 / 同长度构造 +/// 新视图。 +fn alloc_data_view_clone( + vm: &mut H, state: &mut CloneState, src: &JsObject, +) -> Result<*mut JsObject, JsValue> { + let Some(data_ptr) = src + .native_fn() + .map(|p| p.as_ptr() as *const crate::data_view::DataViewData) + .filter(|p| !p.is_null()) + else { + return Err(data_clone_error(vm, "DataView internal state invalid")); + }; + // SAFETY: data_ptr 经对象类型标签校验为存活状态盒;Copy 语义,不跨 JS 调用持借用。 + let data = unsafe { *data_ptr }; + let cloned_buffer = clone_value(vm, state, data.buffer)?; + let proto = JsValue::from_js_object(vm.session().builtin_world().data_view_proto.as_ptr() as *mut JsObject); + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); + obj.type_tag = JsObject::OBJ_TYPE_DATA_VIEW; + let new_data = Box::into_raw(Box::new(crate::data_view::DataViewData { + buffer: cloned_buffer, + byte_offset: data.byte_offset, + byte_length: data.byte_length, + length_is_auto: data.length_is_auto, + })); + // SAFETY: DataView 对象不可调用,native_fn 槽存不透明 `Box` + // 指针,与构造器存储形态一致。 + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(new_data as *const ()) })); + Ok(vm.alloc_object(obj)) +} + /// 是否缓冲区对象(ArrayBuffer / SharedArrayBuffer / DataView / TypedArray)。 fn is_buffer(src: &JsObject) -> bool { src.is_array_buffer_obj() || src.is_shared_array_buffer_obj() || src.is_data_view_obj() || src.is_typed_array_obj() @@ -515,11 +685,172 @@ mod tests { assert!(vm.lookup_str(obj_prop(&vm, err, "message")).is_some()); } + /// 把 value 与 options 置入寄存器并调克隆入口,返回克隆值或错误。 + fn clone_with_options(vm: &mut Vm, value: JsValue, options: JsValue) -> Result { + vm.set_reg(1, value); + vm.set_reg(2, options); + let args = [0u8, 1, 2]; + match structured_clone_entry(vm, &args) { + NativeResult::Ok(v) => Ok(v), + NativeResult::Err(e) => Err(e), + NativeResult::TailCall { .. } => unreachable!("structuredClone 不返回 TailCall"), + } + } + + /// 读 ArrayBuffer 载荷字节序列(detached 返回 None)。 + fn ab_bytes(ab: &JsObject) -> Option> { + let ptr = crate::array_buffer::array_buffer_payload_ptr(ab)?; + // SAFETY: ptr 经 array_buffer_payload_ptr 校验为存活载荷盒。 + unsafe { &*ptr }.data.as_ref().cloned() + } + + /// 读 TypedArray 视图状态盒(Copy)。 + fn ta_data(ta: &JsObject) -> crate::typed_array::TypedArrayData { + let ptr = ta.native_fn().map(|p| p.as_ptr() as *const crate::typed_array::TypedArrayData).unwrap(); + // SAFETY: ptr 经对象类型标签校验为存活状态盒。 + unsafe { *ptr } + } + #[test] - fn clone_arraybuffer_data_clone_error() { - let (mut vm, v) = eval("var ab = new ArrayBuffer(8); ab").unwrap(); - let e = clone(&mut vm, v).unwrap_err(); - assert!(e.is_object()); + fn clone_arraybuffer() { + let (mut vm, v) = eval("var ab = new Uint8Array([1, 2, 3, 4]).buffer; ab").unwrap(); + let c = clone(&mut vm, v).unwrap(); + let cl = unsafe { &*c.as_js_object_ptr() }; + assert!(cl.is_array_buffer_obj()); + assert!(!std::ptr::eq(v.as_js_object_ptr(), c.as_js_object_ptr()), "克隆应为新对象"); + assert_eq!(ab_bytes(cl), Some(vec![1, 2, 3, 4])); + // 源缓冲未 detach。 + assert_eq!(ab_bytes(unsafe { &*v.as_js_object_ptr() }), Some(vec![1, 2, 3, 4])); + } + + #[test] + fn clone_arraybuffer_transfer() { + let (mut vm, v) = + eval("var ab = new Uint8Array([9, 8, 7]).buffer; var opts = {transfer: [ab]}; ({ab: ab, opts: opts})").unwrap(); + let holder = unsafe { &*v.as_js_object_ptr() }; + let si_ab = vm.kernel_core().perm_interner().intern("ab").0; + let si_opts = vm.kernel_core().perm_interner().intern("opts").0; + let ab = holder.get_prop_at(vm.get_own_property_slot(holder, si_ab).unwrap()); + let opts = holder.get_prop_at(vm.get_own_property_slot(holder, si_opts).unwrap()); + let c = clone_with_options(&mut vm, ab, opts).unwrap(); + let cl = unsafe { &*c.as_js_object_ptr() }; + assert!(cl.is_array_buffer_obj()); + assert_eq!(ab_bytes(cl), Some(vec![9, 8, 7])); + // 源缓冲已 detach。 + assert!(ab_bytes(unsafe { &*ab.as_js_object_ptr() }).is_none()); + } + + #[test] + fn clone_typed_array() { + let (mut vm, v) = eval("var ta = new Uint8Array([5, 6, 7]); ta").unwrap(); + let c = clone(&mut vm, v).unwrap(); + let cl = unsafe { &*c.as_js_object_ptr() }; + assert!(cl.is_typed_array_obj()); + let data = ta_data(cl); + let src_data = ta_data(unsafe { &*v.as_js_object_ptr() }); + assert!(!std::ptr::eq(data.buffer.as_js_object_ptr(), src_data.buffer.as_js_object_ptr()), "克隆缓冲应为新对象"); + assert_eq!(data.length, 3); + assert_eq!(data.byte_offset, 0); + let buf = unsafe { &*data.buffer.as_js_object_ptr() }; + assert_eq!(ab_bytes(buf), Some(vec![5, 6, 7])); + } + + #[test] + fn clone_typed_array_with_transfer() { + let (mut vm, v) = + eval("var ta = new Uint8Array([1, 2, 3]); var opts = {transfer: [ta.buffer]}; ({ta: ta, opts: opts})").unwrap(); + let holder = unsafe { &*v.as_js_object_ptr() }; + let si_ta = vm.kernel_core().perm_interner().intern("ta").0; + let si_opts = vm.kernel_core().perm_interner().intern("opts").0; + let ta = holder.get_prop_at(vm.get_own_property_slot(holder, si_ta).unwrap()); + let opts = holder.get_prop_at(vm.get_own_property_slot(holder, si_opts).unwrap()); + let c = clone_with_options(&mut vm, ta, opts).unwrap(); + let cl = unsafe { &*c.as_js_object_ptr() }; + assert!(cl.is_typed_array_obj()); + let data = ta_data(cl); + let buf = unsafe { &*data.buffer.as_js_object_ptr() }; + assert_eq!(ab_bytes(buf), Some(vec![1, 2, 3])); + // 源缓冲已 detach。 + let src_data = ta_data(unsafe { &*ta.as_js_object_ptr() }); + let src_buf = unsafe { &*src_data.buffer.as_js_object_ptr() }; + assert!(ab_bytes(src_buf).is_none()); + } + + #[test] + fn clone_data_view() { + let (mut vm, v) = eval("var dv = new DataView(new Uint8Array([10, 20, 30, 40]).buffer); dv").unwrap(); + let c = clone(&mut vm, v).unwrap(); + let cl = unsafe { &*c.as_js_object_ptr() }; + assert!(cl.is_data_view_obj()); + let data_ptr = cl.native_fn().map(|p| p.as_ptr() as *const crate::data_view::DataViewData).unwrap(); + // SAFETY: data_ptr 经对象类型标签校验为存活状态盒。 + let data = unsafe { *data_ptr }; + assert_eq!(data.byte_length, 4); + assert_eq!(data.byte_offset, 0); + let buf = unsafe { &*data.buffer.as_js_object_ptr() }; + assert_eq!(ab_bytes(buf), Some(vec![10, 20, 30, 40])); + } + + #[test] + fn clone_shared_array_buffer_shares_payload() { + let (mut vm, v) = eval("var sab = new SharedArrayBuffer(4); sab").unwrap(); + let c = clone(&mut vm, v).unwrap(); + let cl = unsafe { &*c.as_js_object_ptr() }; + assert!(cl.is_shared_array_buffer_obj()); + // 两对象共享同一载荷盒(克隆侧写入对源侧可见)。 + let src_payload = crate::array_buffer::shared_array_buffer_payload_ptr(unsafe { &*v.as_js_object_ptr() }).unwrap(); + let cl_payload = crate::array_buffer::shared_array_buffer_payload_ptr(cl).unwrap(); + assert!(std::ptr::eq(src_payload, cl_payload), "克隆应共享同一载荷"); + // SAFETY: 载荷盒存活。 + unsafe { (*cl_payload).data.as_mut().unwrap()[0] = 0xAB }; + assert_eq!(unsafe { &*src_payload }.data.as_ref().unwrap()[0], 0xAB); + } + + #[test] + fn transfer_non_arraybuffer_data_clone_error() { + let (mut vm, v) = + eval("var sab = new SharedArrayBuffer(4); var opts = {transfer: [sab]}; ({v: sab, opts: opts})").unwrap(); + let holder = unsafe { &*v.as_js_object_ptr() }; + let si_v = vm.kernel_core().perm_interner().intern("v").0; + let si_opts = vm.kernel_core().perm_interner().intern("opts").0; + let val = holder.get_prop_at(vm.get_own_property_slot(holder, si_v).unwrap()); + let opts = holder.get_prop_at(vm.get_own_property_slot(holder, si_opts).unwrap()); + let e = clone_with_options(&mut vm, val, opts).unwrap_err(); + let err = unsafe { &*e.as_js_object_ptr() }; + assert!(err.is_error_obj()); + } + + #[test] + fn transfer_duplicate_buffer_data_clone_error() { + let (mut vm, v) = + eval("var ab = new ArrayBuffer(4); var opts = {transfer: [ab, ab]}; ({v: ab, opts: opts})").unwrap(); + let holder = unsafe { &*v.as_js_object_ptr() }; + let si_v = vm.kernel_core().perm_interner().intern("v").0; + let si_opts = vm.kernel_core().perm_interner().intern("opts").0; + let val = holder.get_prop_at(vm.get_own_property_slot(holder, si_v).unwrap()); + let opts = holder.get_prop_at(vm.get_own_property_slot(holder, si_opts).unwrap()); + let e = clone_with_options(&mut vm, val, opts).unwrap_err(); + let err = unsafe { &*e.as_js_object_ptr() }; + assert!(err.is_error_obj()); + } + + #[test] + fn transfer_options_not_object_type_error() { + let (mut vm, v) = eval("1").unwrap(); + let e = clone_with_options(&mut vm, v, JsValue::int(42)).unwrap_err(); + let err = unsafe { &*e.as_js_object_ptr() }; + assert!(err.is_error_obj()); + } + + #[test] + fn transfer_list_not_array_type_error() { + let (mut vm, v) = eval("var opts = {transfer: 'x'}; ({v: 1, opts: opts})").unwrap(); + let holder = unsafe { &*v.as_js_object_ptr() }; + let si_v = vm.kernel_core().perm_interner().intern("v").0; + let si_opts = vm.kernel_core().perm_interner().intern("opts").0; + let val = holder.get_prop_at(vm.get_own_property_slot(holder, si_v).unwrap()); + let opts = holder.get_prop_at(vm.get_own_property_slot(holder, si_opts).unwrap()); + let e = clone_with_options(&mut vm, val, opts).unwrap_err(); let err = unsafe { &*e.as_js_object_ptr() }; assert!(err.is_error_obj()); } diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 58eeae07..46c52c42 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -57,7 +57,7 @@ fn to_index(vm: &mut H, value: JsValue, msg: &str) -> Result(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject { +pub(crate) fn typed_array_proto_ptr(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject { { let session = vm.session(); let world = session.builtin_world(); @@ -144,7 +144,7 @@ fn materialize_typed_array( obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(data as *const ()) })); } -fn create_typed_array( +pub(crate) fn create_typed_array( vm: &mut H, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize, auto_length: bool, proto: JsValue, ) -> *mut JsObject { diff --git a/crates/oxide_test262/src/judge.rs b/crates/oxide_test262/src/judge.rs index d2958971..0180d2b3 100644 --- a/crates/oxide_test262/src/judge.rs +++ b/crates/oxide_test262/src/judge.rs @@ -151,7 +151,7 @@ fn parse_undefined_ident(e: &str) -> Option<&str> { /// 已知缺失的宿主/标准全局白名单:这些标识符未绑定是能力缺失(skip), /// 其它 `is not defined` 是引擎回归或语义缺口(fail)。 -const KNOWN_MISSING_GLOBALS: &[&str] = &["$262", "structuredClone", "queueMicrotask"]; +const KNOWN_MISSING_GLOBALS: &[&str] = &["$262", "queueMicrotask"]; /// 判定编译期错误结果:compile 错误无条件放行 negative;能力未实现形态 /// (含白名单内的未绑定标识符)→ Skip(`--no-skip` 下为 Fail);其余一律 Fail。 diff --git a/crates/oxide_vm/src/bindings/bind_global.rs b/crates/oxide_vm/src/bindings/bind_global.rs index fc2a85d0..c201249c 100644 --- a/crates/oxide_vm/src/bindings/bind_global.rs +++ b/crates/oxide_vm/src/bindings/bind_global.rs @@ -56,6 +56,7 @@ pub fn bind_global(core: &Arc, session: &KernelSession, global: &mut ("__moduleEval", oxide_builtins::module::module_eval:: as *const (), 1), ("__moduleData", oxide_builtins::module::module_data:: as *const (), 2), ("eval", oxide_builtins::eval::eval:: as *const (), 1), + ("structuredClone", oxide_builtins::structured_clone::structured_clone_entry:: as *const (), 1), ], ); } diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index d97f994b..266c650d 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -857,6 +857,7 @@ fn bind_global_functions(core: &Arc, session: &KernelSession, global 1, ), ("eval", oxide_builtins::eval::eval:: as *const (), 1), + ("structuredClone", oxide_builtins::structured_clone::structured_clone_entry:: as *const (), 1), ], ); }