mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 新增结构化克隆缓冲区族分派与转移语义及全局绑定
This commit is contained in:
Generated
+18
-18
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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 求值辅助:单脚本编译执行,返回完成值。
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user