feat: 新增结构化克隆缓冲区族分派与转移语义及全局绑定

This commit is contained in:
2026-10-08 03:17:06 +00:00
parent 3266a8b516
commit 3fa2f94fd1
9 changed files with 396 additions and 60 deletions
Generated
+18 -18
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.8.58" version = "0.8.59"
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.8.58" version = "0.8.59"
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.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1234,7 +1234,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1253,7 +1253,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1267,7 +1267,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1282,7 +1282,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1291,7 +1291,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1306,7 +1306,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1318,7 +1318,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1327,7 +1327,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1342,7 +1342,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1358,7 +1358,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1370,7 +1370,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1387,7 +1387,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"hashbrown 0.14.5", "hashbrown 0.14.5",
@@ -1399,7 +1399,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.8.58" version = "0.8.59"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.8.58" version = "0.8.59"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+14 -11
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr}; use oxide_types::object::{JsObject, NativeFnPtr};
use oxide_types::private_key::{encode_symbol_key, WELL_KNOWN_SYMBOL_SPECIES}; 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; /// ArrayBuffer 载荷:字节缓冲与状态位。`data` 为 `None` 即缓冲区已 detach;
/// `max_byte_length` 为存储态上限:0 即定长缓冲(resizable 判据),非 0 存 /// `max_byte_length` 为存储态上限:0 即定长缓冲(resizable 判据),非 0 存
/// 请求上限 + 1(上限 0 与定长必须可分);`immutable` 为字节缓冲写守卫标志。 /// 请求上限 + 1(上限 0 与定长必须可分);`immutable` 为字节缓冲写守卫标志。
/// 载荷经 `Box::into_raw` 存于对象 `native_fn` 槽,GC 两自由函数 /// 载荷经 `Arc::into_raw` 存于对象 `native_fn` 槽(引用计数盒:SharedArrayBuffer
/// (size/drop)对整结构体操作。 /// 克隆共享同一载荷,计数归零统一释放),GC 两自由函数(size/drop)对整结构体
/// 操作。
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct ArrayBufferPayload { pub(crate) struct ArrayBufferPayload {
pub(crate) data: Option<Vec<u8>>, pub(crate) data: Option<Vec<u8>>,
@@ -101,7 +104,7 @@ pub(crate) fn new_array_buffer<H: VmHost>(
max_byte_length, max_byte_length,
immutable: false, immutable: false,
}; };
let payload_ptr = Box::into_raw(Box::new(payload)); let payload_ptr = Arc::into_raw(Arc::new(payload));
// SAFETY: ArrayBuffer 对象不可调用,故 native_fn 槽复用作不透明载荷盒 // SAFETY: ArrayBuffer 对象不可调用,故 native_fn 槽复用作不透明载荷盒
// 指针,与既有 RegExp 存储模式一致。 // 指针,与既有 RegExp 存储模式一致。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); 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 { let Some(payload_ptr) = array_buffer_payload_ptr(obj) else {
return 0; return 0;
}; };
// SAFETY: payload_ptr 非空(array_buffer_native_size 已验证),Box::from_raw 恰好释放一次。 // SAFETY: payload_ptr 非空(array_buffer_native_size 已验证),Arc::from_raw 恰好减引用一次。
let payload = unsafe { Box::from_raw(payload_ptr) }; let payload = unsafe { Arc::from_raw(payload_ptr) };
drop(payload); drop(payload);
obj.set_native_fn(None); obj.set_native_fn(None);
bytes bytes
@@ -163,7 +166,7 @@ pub(crate) fn new_shared_array_buffer<H: VmHost>(
max_byte_length, max_byte_length,
immutable: false, immutable: false,
}; };
let payload_ptr = Box::into_raw(Box::new(payload)); let payload_ptr = Arc::into_raw(Arc::new(payload));
// SAFETY: SharedArrayBuffer 对象不可调用,native_fn 槽复用为不透明载荷盒 // SAFETY: SharedArrayBuffer 对象不可调用,native_fn 槽复用为不透明载荷盒
// 指针,与 ArrayBuffer 存储模式一致。 // 指针,与 ArrayBuffer 存储模式一致。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); 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 { let Some(payload_ptr) = shared_array_buffer_payload_ptr(obj) else {
return 0; return 0;
}; };
// SAFETY: payload_ptr 非空(size 已验证),Box::from_raw 恰好释放一次。 // SAFETY: payload_ptr 非空(size 已验证),Arc::from_raw 恰好减引用一次。
let payload = unsafe { Box::from_raw(payload_ptr) }; let payload = unsafe { Arc::from_raw(payload_ptr) };
drop(payload); drop(payload);
obj.set_native_fn(None); obj.set_native_fn(None);
bytes bytes
@@ -1163,7 +1166,7 @@ mod tests {
fn ab_object_with_payload(payload: ArrayBufferPayload) -> JsObject { fn ab_object_with_payload(payload: ArrayBufferPayload) -> JsObject {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; 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 槽存储一致,测试结束前恰好释放一次。 // SAFETY: 载荷盒形态与 new_array_buffer 的 native_fn 槽存储一致,测试结束前恰好释放一次。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) }));
obj obj
@@ -1200,7 +1203,7 @@ mod tests {
fn payload_ptr_brand_guard() { fn payload_ptr_brand_guard() {
// 非 ArrayBuffer 类型标签(PLAIN)对象取载荷指针恒 None。 // 非 ArrayBuffer 类型标签(PLAIN)对象取载荷指针恒 None。
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); 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]), data: Some(vec![0u8; 4]),
max_byte_length: 0, max_byte_length: 0,
immutable: false, immutable: false,
@@ -1209,7 +1212,7 @@ mod tests {
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) }));
assert!(array_buffer_payload_ptr(&obj).is_none()); assert!(array_buffer_payload_ptr(&obj).is_none());
// SAFETY: 测试构造的盒,恰好释放一次。 // SAFETY: 测试构造的盒,恰好释放一次。
unsafe { drop(Box::from_raw(payload_ptr)) }; unsafe { drop(Arc::from_raw(payload_ptr)) };
} }
/// JS 求值辅助:单脚本编译执行,返回完成值。 /// JS 求值辅助:单脚本编译执行,返回完成值。
+4 -4
View File
@@ -14,10 +14,10 @@ use oxide_runtime_api::{NativeResult, VmHost};
/// 存构造期 live − offset 初值,活读一律现算 live − offset。 /// 存构造期 live − offset 初值,活读一律现算 live − offset。
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(crate) struct DataViewData { pub(crate) struct DataViewData {
buffer: JsValue, pub(crate) buffer: JsValue,
byte_offset: usize, pub(crate) byte_offset: usize,
byte_length: usize, pub(crate) byte_length: usize,
length_is_auto: bool, pub(crate) length_is_auto: bool,
} }
macro_rules! native_try { macro_rules! native_try {
+354 -23
View File
@@ -3,11 +3,14 @@
//! //!
//! 本核以 `VmHost` 为接口、目标 realm 为分配上下文,做同 realm 克隆(源与目标 //! 本核以 `VmHost` 为接口、目标 realm 为分配上下文,做同 realm 克隆(源与目标
//! 同一 realm,符号映射为恒等)。可克隆类型白名单:原始值、数组、plain 对象、 //! 同一 realm,符号映射为恒等)。可克隆类型白名单:原始值、数组、plain 对象、
//! Map、Set、Date、RegExp、Error 家族;其余对象(function / DOM / 模块命名空间 / //! Map、Set、Date、RegExp、Error 家族、缓冲区族(ArrayBuffer / SharedArrayBuffer /
//! Promise / 迭代器 / 缓冲区 / 特型对象)一律 DataCloneError。缓冲区克隆(含 //! TypedArray / DataView);其余对象(function / DOM / 模块命名空间 / Promise /
//! transfer 转移)在缓冲区克隆子任务落地,当前统一报 DataCloneError。 //! 迭代器 / 特型对象)一律 DataCloneError。缓冲区克隆支持 transfer 转移:
//! 命中转移集合的 ArrayBuffer 移动载荷(源 detach),未命中克隆字节;
//! SharedArrayBuffer 克隆共享同一载荷盒。
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; 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}; use crate::set::{SetInner, SetKey};
/// 克隆状态:`seen` 映射保共享引用(同源对象二次出现复用同一克隆,循环引用不 /// 克隆状态:`seen` 映射保共享引用(同源对象二次出现复用同一克隆,循环引用不
/// 死循环);`transfer` 集合枚举待转移的 ArrayBuffer(缓冲区转移语义在缓冲区 /// 死循环);`transfer` 集合枚举待转移的 ArrayBuffer(命中移动载荷,未命中
/// 克隆落地时启用,当前恒空)。 /// 克隆字节)。
struct CloneState { struct CloneState {
seen: HashMap<*const JsObject, *mut JsObject>, seen: HashMap<*const JsObject, *mut JsObject>,
transfer: HashSet<*const JsObject>, transfer: HashSet<*const JsObject>,
@@ -37,25 +40,79 @@ impl CloneState {
} }
} }
/// `structuredClone(value, options)` 入口:解析 value,建克隆状态,调核心。 /// `structuredClone(value, options)` 入口:解析 value 与 options.transfer,
/// 建克隆状态,调核心。
/// ///
/// # 步骤 /// # 步骤
/// 1. 取第一实参 value(缺省 undefined) /// 1. 取第一实参 value(缺省 undefined)
/// 2. 建 `CloneState` /// 2. 取第二实参 options(缺省 undefined);在场时解析 transfer 列表
/// 3. 调 `clone_value` 递归克隆 /// 3. 建 `CloneState`
/// 4. 调 `clone_value` 递归克隆
/// ///
/// # 边界与前提 /// # 边界与前提
/// - options(含 transfer)在缓冲区克隆落地时处理,当前忽略; /// - options 非对象 → TypeError;`transfer` 非数组 → TypeError;元素非
/// - 返回 `Ok(克隆值)` 或 `Err(DataCloneError)`。 /// ArrayBuffer 或重复 → DataCloneError;
/// - 返回 `Ok(克隆值)` 或 `Err(TypeError / DataCloneError)`。
pub fn structured_clone_entry<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn structured_clone_entry<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; 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(); 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) { match clone_value(vm, &mut state, value) {
Ok(v) => NativeResult::Ok(v), Ok(v) => NativeResult::Ok(v),
Err(e) => NativeResult::Err(e), Err(e) => NativeResult::Err(e),
} }
} }
/// 解析 options.transfer 入 transfer 集合:options 须为对象,`transfer` 须为
/// 数组,元素须为不重复的 ArrayBuffer(缺省 `transfer` 为空集)。
///
/// # 边界与前提
/// - `transfer` 属性读取是用户代码窗口(getter 副作用与异常原值传播);
/// - 转移集合只收 ArrayBuffer 对象指针(SAB 不可转移,非 AB 元素报
/// DataCloneError)。
fn parse_transfer_list<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
/// ///
/// # 边界与前提 /// # 边界与前提
/// - 源对象由调用方寄存器保活;克隆经 `alloc_object` 入目标 realm 对象表即成根; /// - 源对象由调用方寄存器保活;克隆经 `alloc_object` 入目标 realm 对象表即成根;
/// - 可克隆白名单:数组 / plain / Map / Set / Date / RegExp / Error,其余对象 /// - 可克隆白名单:数组 / plain / Map / Set / Date / RegExp / Error / 缓冲区族,
/// (含缓冲区)报 DataCloneError。 /// 其余对象(function / DOM / 模块命名空间 / Promise / 迭代器 / 特型对象)
/// 报 DataCloneError。
fn clone_value<H: VmHost>(vm: &mut H, state: &mut CloneState, value: JsValue) -> Result<JsValue, JsValue> { fn clone_value<H: VmHost>(vm: &mut H, state: &mut CloneState, value: JsValue) -> Result<JsValue, JsValue> {
if !value.is_object() { if !value.is_object() {
return Ok(value); return Ok(value);
@@ -91,12 +149,14 @@ fn clone_value<H: VmHost>(vm: &mut H, state: &mut CloneState, value: JsValue) ->
alloc_regexp_clone(vm, src)? alloc_regexp_clone(vm, src)?
} else if src.is_error_obj() { } else if src.is_error_obj() {
alloc_error_clone(vm, src) alloc_error_clone(vm, src)
} else if is_buffer(src) { } else if src.is_array_buffer_obj() {
// transfer 集合命中时走转移语义(缓冲区克隆落地后启用);当前缓冲区一律不可克隆。 alloc_array_buffer_clone(vm, state, src, src_ptr as *const JsObject)?
if state.transfer.contains(&(src_ptr as *const JsObject)) { } else if src.is_shared_array_buffer_obj() {
return Err(data_clone_error(vm, "transferred buffer is not cloneable")); alloc_shared_array_buffer_clone(vm, src)?
} } else if src.is_typed_array_obj() {
return Err(data_clone_error(vm, "buffer value is not cloneable")); 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) { } else if is_plain_object(src) {
alloc_plain_clone(vm) alloc_plain_clone(vm)
} else { } else {
@@ -108,7 +168,8 @@ fn clone_value<H: VmHost>(vm: &mut H, state: &mut CloneState, value: JsValue) ->
} }
/// 填充克隆内容:Map / Set 走各自条目复制,数组 / plain / Error 统一走自有属性复制。 /// 填充克隆内容:Map / Set 走各自条目复制,数组 / plain / Error 统一走自有属性复制。
/// Date / RegExp 在分配时已填充(时间戳 / 源与标志),此处无操作。 /// Date / RegExp / 缓冲区族在分配时已填充(时间戳 / 源与标志 / 载荷与视图状态盒),
/// 此处无操作。
fn fill_clone<H: VmHost>( fn fill_clone<H: VmHost>(
vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject, vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject,
) -> Result<(), JsValue> { ) -> Result<(), JsValue> {
@@ -122,6 +183,11 @@ fn fill_clone<H: VmHost>(
if src.is_date_obj() || src.is_regexp_obj() { if src.is_date_obj() || src.is_regexp_obj() {
return Ok(()); return Ok(());
} }
// 缓冲区族(AB / SAB / TypedArray / DataView)在分配时已填充载荷与视图
// 状态盒,无自有属性可复制。
if is_buffer(src) {
return Ok(());
}
fill_object_props(vm, state, src, clone_ptr) fill_object_props(vm, state, src, clone_ptr)
} }
@@ -324,6 +390,110 @@ fn alloc_error_clone<H: VmHost>(vm: &mut H, src: &JsObject) -> *mut JsObject {
vm.alloc_object(obj) vm.alloc_object(obj)
} }
/// 分配 ArrayBuffer 克隆:transfer 命中移动载荷(源 `data → None` 即 detach),
/// 未命中克隆字节;存储态上限与 immutable 标志原样拷贝,detached 源产 detached
/// 克隆。
fn alloc_array_buffer_clone<H: VmHost>(
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<H: VmHost>(
vm: &mut H, data: Option<Vec<u8>>, 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<H: VmHost>(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<H: VmHost>(
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<H: VmHost>(
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<DataViewData>`
// 指针,与构造器存储形态一致。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(new_data as *const ()) }));
Ok(vm.alloc_object(obj))
}
/// 是否缓冲区对象(ArrayBuffer / SharedArrayBuffer / DataView / TypedArray)。 /// 是否缓冲区对象(ArrayBuffer / SharedArrayBuffer / DataView / TypedArray)。
fn is_buffer(src: &JsObject) -> bool { 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() 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()); 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<JsValue, JsValue> {
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<Vec<u8>> {
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] #[test]
fn clone_arraybuffer_data_clone_error() { fn clone_arraybuffer() {
let (mut vm, v) = eval("var ab = new ArrayBuffer(8); ab").unwrap(); let (mut vm, v) = eval("var ab = new Uint8Array([1, 2, 3, 4]).buffer; ab").unwrap();
let e = clone(&mut vm, v).unwrap_err(); let c = clone(&mut vm, v).unwrap();
assert!(e.is_object()); 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() }; let err = unsafe { &*e.as_js_object_ptr() };
assert!(err.is_error_obj()); assert!(err.is_error_obj());
} }
+2 -2
View File
@@ -57,7 +57,7 @@ fn to_index<H: VmHost>(vm: &mut H, value: JsValue, msg: &str) -> Result<usize, J
Ok(integer as usize) Ok(integer as usize)
} }
fn typed_array_proto_ptr<H: VmHost>(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject { pub(crate) fn typed_array_proto_ptr<H: VmHost>(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject {
{ {
let session = vm.session(); let session = vm.session();
let world = session.builtin_world(); 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 ()) })); obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(data as *const ()) }));
} }
fn create_typed_array<H: VmHost>( pub(crate) fn create_typed_array<H: VmHost>(
vm: &mut H, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize, auto_length: bool, vm: &mut H, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize, auto_length: bool,
proto: JsValue, proto: JsValue,
) -> *mut JsObject { ) -> *mut JsObject {
+1 -1
View File
@@ -151,7 +151,7 @@ fn parse_undefined_ident(e: &str) -> Option<&str> {
/// 已知缺失的宿主/标准全局白名单:这些标识符未绑定是能力缺失(skip), /// 已知缺失的宿主/标准全局白名单:这些标识符未绑定是能力缺失(skip),
/// 其它 `is not defined` 是引擎回归或语义缺口(fail)。 /// 其它 `is not defined` 是引擎回归或语义缺口(fail)。
const KNOWN_MISSING_GLOBALS: &[&str] = &["$262", "structuredClone", "queueMicrotask"]; const KNOWN_MISSING_GLOBALS: &[&str] = &["$262", "queueMicrotask"];
/// 判定编译期错误结果:compile 错误无条件放行 negative;能力未实现形态 /// 判定编译期错误结果:compile 错误无条件放行 negative;能力未实现形态
/// (含白名单内的未绑定标识符)→ Skip(`--no-skip` 下为 Fail);其余一律 Fail。 /// (含白名单内的未绑定标识符)→ Skip(`--no-skip` 下为 Fail);其余一律 Fail。
@@ -56,6 +56,7 @@ pub fn bind_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
("__moduleEval", oxide_builtins::module::module_eval::<crate::vm::Vm> as *const (), 1), ("__moduleEval", oxide_builtins::module::module_eval::<crate::vm::Vm> as *const (), 1),
("__moduleData", oxide_builtins::module::module_data::<crate::vm::Vm> as *const (), 2), ("__moduleData", oxide_builtins::module::module_data::<crate::vm::Vm> as *const (), 2),
("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1), ("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1),
("structuredClone", oxide_builtins::structured_clone::structured_clone_entry::<crate::vm::Vm> as *const (), 1),
], ],
); );
} }
+1
View File
@@ -857,6 +857,7 @@ fn bind_global_functions(core: &Arc<KernelCore>, session: &KernelSession, global
1, 1,
), ),
("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1), ("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1),
("structuredClone", oxide_builtins::structured_clone::structured_clone_entry::<crate::vm::Vm> as *const (), 1),
], ],
); );
} }