mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
1325 lines
61 KiB
Rust
1325 lines
61 KiB
Rust
//! 结构化克隆核(structured clone algorithm):递归遍历加 seen 映射保共享引用、
|
||
//! 逐类型分派、DataCloneError 报错路径。
|
||
//!
|
||
//! 本核以 `VmHost` 为接口、目标 realm 为分配上下文,做同 realm 克隆(源与目标
|
||
//! 同一 realm,符号映射为恒等)。可克隆类型白名单:原始值(symbol 除外)、
|
||
//! 装箱对象(Boolean / Number / String 盒与 BigInt 包装)、数组、plain 对象、
|
||
//! Map、Set、Date、RegExp、Error 家族、缓冲区族(ArrayBuffer / SharedArrayBuffer /
|
||
//! TypedArray / DataView);symbol 原始值、装箱 Symbol 盒及其余对象
|
||
//! (function / DOM / 模块命名空间 / Promise / 迭代器 / 特型对象)一律
|
||
//! DataCloneError。plain 对象与数组只复制可枚举自有属性,克隆侧描述符一律
|
||
//! writable / enumerable / configurable 全真数据属性。缓冲区克隆支持 transfer
|
||
//! 转移:转移条目在序列化前注册为 seen 映射占位(空载荷),整个序列化成功后
|
||
//! 才移动源载荷入克隆(源 detach);值图外条目同样 detach;值图内 detached
|
||
//! 缓冲报 DataCloneError。未转移的 ArrayBuffer 克隆字节;SharedArrayBuffer
|
||
//! 克隆共享同一载荷盒。
|
||
|
||
use std::collections::HashMap;
|
||
use std::sync::Arc;
|
||
|
||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||
use oxide_types::value::JsValue;
|
||
|
||
use oxide_runtime_api::{NativeResult, VmHost};
|
||
|
||
use crate::array::create_new_array;
|
||
use crate::map::MapInner;
|
||
use crate::object::{walk_own_keys, walk_own_symbol_keys};
|
||
use crate::set::{SetInner, SetKey};
|
||
|
||
/// 克隆状态:`seen` 映射保共享引用(同源对象二次出现复用同一克隆,循环引用不
|
||
/// 死循环);`transfer_placeholders` 记录转移条目(源指针、克隆指针)——克隆体
|
||
/// 是空载荷占位,整个序列化成功后才移动源载荷入克隆(源 detach)。
|
||
struct CloneState {
|
||
seen: HashMap<*const JsObject, *mut JsObject>,
|
||
transfer_placeholders: Vec<(*const JsObject, *mut JsObject)>,
|
||
}
|
||
|
||
impl CloneState {
|
||
fn new() -> Self {
|
||
Self {
|
||
seen: HashMap::new(),
|
||
transfer_placeholders: Vec::new(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// `structuredClone(value, options)` 入口:解析 value 与 options.transfer,
|
||
/// 建克隆状态,调核心,成功后完成转移(源 detach 并填克隆载荷)。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 取第一实参 value(缺省 undefined)
|
||
/// 2. 取第二实参 options(缺省 undefined);在场时解析 transfer 列表(注册占位)
|
||
/// 3. 建 `CloneState`
|
||
/// 4. 调 `clone_value` 递归克隆
|
||
/// 5. 成功后对每个转移条目完成转移(源 detach 并填克隆载荷)
|
||
///
|
||
/// # 边界与前提
|
||
/// - options 非对象 → TypeError;`transfer` 非数组 → TypeError;元素非
|
||
/// ArrayBuffer 或重复 → DataCloneError;
|
||
/// - 序列化中值图内 detached 缓冲 → DataCloneError(源保持未 detach);
|
||
/// - 序列化成功后转移条目 detached → DataCloneError;
|
||
/// - 返回 `Ok(克隆值)` 或 `Err(TypeError / DataCloneError)`。
|
||
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 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);
|
||
}
|
||
}
|
||
let cloned = match clone_value(vm, &mut state, value) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
if let Err(e) = finish_transfer(vm, &mut state) {
|
||
return NativeResult::Err(e);
|
||
}
|
||
NativeResult::Ok(cloned)
|
||
}
|
||
|
||
/// 解析 options.transfer 并把转移条目注册进 seen 映射:options 须为对象,
|
||
/// `transfer` 须为数组,元素须为不重复的 ArrayBuffer(缺省 `transfer` 为空集)。
|
||
/// 每个元素分配空载荷占位克隆(存储态上限继承源、immutable 恒 false),登记
|
||
/// seen 映射并记入占位列表(源、克隆),序列化成功后才移动载荷。
|
||
///
|
||
/// # 边界与前提
|
||
/// - `transfer` 属性读取是用户代码窗口(getter 副作用与异常原值传播);
|
||
/// - 转移条目只收 ArrayBuffer 对象(SAB 不可转移,非 AB 元素报
|
||
/// DataCloneError);重复经 seen 映射判定(占位注册即重复检查)。
|
||
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 src_ptr = e.as_js_object_ptr() as *const JsObject;
|
||
if state.seen.contains_key(&src_ptr) {
|
||
return Err(data_clone_error(vm, "duplicate buffer in transfer list"));
|
||
}
|
||
// SAFETY: is_object 保证非空指针;对象在 native 执行期间被根保持、不被回收。
|
||
let src = unsafe { &*e.as_js_object_ptr() };
|
||
let Some(payload_ptr) = crate::array_buffer::array_buffer_payload_ptr(src) else {
|
||
return Err(data_clone_error(vm, "ArrayBuffer internal state invalid"));
|
||
};
|
||
// SAFETY: payload_ptr 经 array_buffer_payload_ptr 校验为存活载荷盒;标量
|
||
// 拷出后借用即结束,不跨 JS 调用。
|
||
let max_byte_length = unsafe { (*payload_ptr).max_byte_length };
|
||
let proto = crate::array_buffer::default_array_buffer_proto(vm);
|
||
let clone_ptr = alloc_buffer_object(vm, None, max_byte_length, false, proto);
|
||
state.seen.insert(src_ptr, clone_ptr);
|
||
state.transfer_placeholders.push((src_ptr, clone_ptr));
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 序列化成功后完成转移:对每个转移条目校验源未 detach,再移动源载荷入克隆
|
||
/// 占位(源 detach);值图外条目同样 detach。
|
||
///
|
||
/// # 副作用
|
||
/// - 源缓冲载荷移入克隆占位(源转 detached 态);
|
||
/// - 克隆占位的空载荷被填为源载荷。
|
||
fn finish_transfer<H: VmHost>(vm: &mut H, state: &mut CloneState) -> Result<(), JsValue> {
|
||
for (src_ptr, clone_ptr) in &state.transfer_placeholders {
|
||
// SAFETY: 源对象由调用方寄存器保活(值图根),载荷指针本段有效。
|
||
let src = unsafe { &**src_ptr };
|
||
let Some(src_payload) = crate::array_buffer::array_buffer_payload_ptr(src) else {
|
||
return Err(data_clone_error(vm, "ArrayBuffer internal state invalid"));
|
||
};
|
||
// SAFETY: clone_ptr 是本克隆核新分配的占位对象,载荷指针本段有效。
|
||
let clone_payload = unsafe {
|
||
(**clone_ptr)
|
||
.native_fn()
|
||
.map(|p| p.as_ptr() as *mut crate::array_buffer::ArrayBufferPayload)
|
||
.unwrap()
|
||
};
|
||
// SAFETY: 两载荷指针均经校验为存活载荷盒;移动为标量操作,不跨 JS 调用。
|
||
let data = unsafe { (*src_payload).data.take() };
|
||
// 转移条目 detached 不可转移(规范报 DataCloneError)。
|
||
let Some(data) = data else {
|
||
return Err(data_clone_error(vm, "detached buffer in transfer list"));
|
||
};
|
||
unsafe { (*clone_payload).data = Some(data) };
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 递归克隆一个值:symbol 原始值拒绝,其余原始值原样拷贝,对象按类型分派。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 非对象值:symbol 原始值报 DataCloneError,其余原样返回
|
||
/// 2. 对象查 `seen` 映射:命中返回既有克隆(保共享引用与循环)
|
||
/// 3. 按类型分配克隆空壳(不可克隆类型在此报 DataCloneError)
|
||
/// 4. 登记 `seen`(先于递归填充,循环引用不重入)
|
||
/// 5. 递归填充克隆内容
|
||
///
|
||
/// # 边界与前提
|
||
/// - 源对象由调用方寄存器保活;克隆经 `alloc_object` 入目标 realm 对象表即成根;
|
||
/// - 可克隆白名单:装箱对象 / 数组 / plain / Map / Set / Date / RegExp / Error /
|
||
/// 缓冲区族,其余对象(function / DOM / 模块命名空间 / Promise / 迭代器 /
|
||
/// 特型对象)报 DataCloneError。
|
||
fn clone_value<H: VmHost>(vm: &mut H, state: &mut CloneState, value: JsValue) -> Result<JsValue, JsValue> {
|
||
if !value.is_object() {
|
||
// symbol 原始值不可克隆(规范枚举的拒绝面)。
|
||
if value.is_symbol() {
|
||
return Err(data_clone_error(vm, "symbol values are not cloneable"));
|
||
}
|
||
return Ok(value);
|
||
}
|
||
let src_ptr = value.as_js_object_ptr();
|
||
if let Some(&existing) = state.seen.get(&(src_ptr as *const JsObject)) {
|
||
return Ok(JsValue::from_js_object(existing));
|
||
}
|
||
// SAFETY: is_object 保证非空指针;对象在 native 执行期间被根保持、不被回收。
|
||
let src = unsafe { &*src_ptr };
|
||
let clone_ptr = if is_boxed_cloneable(src) {
|
||
alloc_boxed_clone(vm, src)
|
||
} else if src.is_symbol_obj() {
|
||
return Err(data_clone_error(vm, "symbol objects are not cloneable"));
|
||
} else if src.is_date_obj() {
|
||
alloc_date_clone(vm, src)
|
||
} else if src.is_regexp_obj() {
|
||
alloc_regexp_clone(vm, src)?
|
||
} else if src.is_array_buffer_obj() {
|
||
alloc_array_buffer_clone(vm, src)?
|
||
} 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 src.is_map() {
|
||
alloc_map_clone(vm)
|
||
} else if src.is_set() {
|
||
alloc_set_clone(vm)
|
||
} else if src.is_error_obj() {
|
||
alloc_error_clone(vm, src)
|
||
} else if src.is_array() {
|
||
alloc_array_clone(vm, src)
|
||
} else if is_plain_object(src) {
|
||
alloc_plain_clone(vm)
|
||
} else {
|
||
return Err(data_clone_error(vm, "value is not cloneable"));
|
||
};
|
||
state.seen.insert(src_ptr as *const JsObject, clone_ptr);
|
||
fill_clone(vm, state, src, clone_ptr)?;
|
||
Ok(JsValue::from_js_object(clone_ptr))
|
||
}
|
||
|
||
/// 填充克隆内容:Map / Set 走各自条目复制,Error 走规范字段定义,
|
||
/// 数组 / plain 统一走可枚举自有属性复制。Date / RegExp / 装箱对象 /
|
||
/// 缓冲区族在分配时已填充(时间戳 / 源与标志 / 载荷与视图状态盒),此处无操作。
|
||
fn fill_clone<H: VmHost>(
|
||
vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject,
|
||
) -> Result<(), JsValue> {
|
||
if src.is_map() {
|
||
return fill_map(vm, state, src, clone_ptr);
|
||
}
|
||
if src.is_set() {
|
||
return fill_set(vm, state, src, clone_ptr);
|
||
}
|
||
// Date / RegExp 在分配时已填充(时间戳 / 源与标志与 lastIndex),不再复制自有属性。
|
||
if src.is_date_obj() || src.is_regexp_obj() {
|
||
return Ok(());
|
||
}
|
||
// 装箱对象在分配时已填充载荷(String 盒含物化的字符索引与 length 面),
|
||
// 不再复制自有属性。
|
||
if is_boxed_cloneable(src) {
|
||
return Ok(());
|
||
}
|
||
// 缓冲区族(AB / SAB / TypedArray / DataView)在分配时已填充载荷与视图
|
||
// 状态盒,无自有属性可复制。
|
||
if is_buffer(src) {
|
||
return Ok(());
|
||
}
|
||
// Error 臂按规范显式定义 message(非枚举)与 cause(在场时全真数据属性),
|
||
// 不走可枚举自有属性通用复制。
|
||
if src.is_error_obj() {
|
||
return fill_error(vm, state, src, clone_ptr);
|
||
}
|
||
fill_object_props(vm, state, src, clone_ptr)
|
||
}
|
||
|
||
/// 填充 Error 克隆:按规范 message 定义为非枚举数据属性,cause 在场时定义为
|
||
/// 可枚举数据属性(CreateDataProperty 口径),其余自有属性不复制。
|
||
///
|
||
/// # 边界与前提
|
||
/// - message / cause 缺失(无对应自有属性)时不定义;
|
||
/// - 值读取经 `read_own_value`(数据属性直读槽值,访问器经 getter)。
|
||
fn fill_error<H: VmHost>(
|
||
vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject,
|
||
) -> Result<(), JsValue> {
|
||
let src_val = JsValue::from_js_object(src as *const JsObject as *mut JsObject);
|
||
let si_msg = vm.kernel_core().perm_interner().intern("message").0;
|
||
if let Some(store) = vm.get_own_property_slot(src, si_msg) {
|
||
let val = read_own_value(vm, src, src_val, si_msg, store)?;
|
||
let cloned = clone_value(vm, state, val)?;
|
||
define_on_clone(vm, clone_ptr, si_msg, cloned, PropAttributes::new(true, false, true))?;
|
||
}
|
||
let si_cause = vm.kernel_core().perm_interner().intern("cause").0;
|
||
if let Some(store) = vm.get_own_property_slot(src, si_cause) {
|
||
let val = read_own_value(vm, src, src_val, si_cause, store)?;
|
||
let cloned = clone_value(vm, state, val)?;
|
||
define_on_clone(vm, clone_ptr, si_cause, cloned, PropAttributes::DEFAULT_DATA)?;
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 复制可枚举自有属性(数组元素 / 命名属性 / Symbol 键)到克隆:逐键读源值、
|
||
/// 克隆,克隆侧一律按 writable / enumerable / configurable 全真数据属性定义
|
||
/// (CreateDataProperty 口径,不保留源描述符标志)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 不可枚举自有属性不复制(不读值、不触发 getter);
|
||
/// - 访问器属性经 getter 取值(用户代码窗口),数据属性直读槽值;
|
||
/// - 数组元素键经 `define_data_property` 路由到元素区,命名 / Symbol 键路由到
|
||
/// 命名属性区;数组 hole 在克隆中落为在位 undefined(稀疏数组边界,后续补齐)。
|
||
fn fill_object_props<H: VmHost>(
|
||
vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject,
|
||
) -> Result<(), JsValue> {
|
||
let src_val = JsValue::from_js_object(src as *const JsObject as *mut JsObject);
|
||
let str_keys = walk_own_keys(vm, src);
|
||
let sym_keys = walk_own_symbol_keys(vm, src);
|
||
for (si, store) in str_keys {
|
||
if !is_enumerable_own(src, store) {
|
||
continue;
|
||
}
|
||
let val = read_own_value(vm, src, src_val, si, store)?;
|
||
let cloned = clone_value(vm, state, val)?;
|
||
define_on_clone(vm, clone_ptr, si, cloned, PropAttributes::DEFAULT_DATA)?;
|
||
}
|
||
for (si, store) in sym_keys {
|
||
if !is_enumerable_own(src, store) {
|
||
continue;
|
||
}
|
||
let val = read_own_value(vm, src, src_val, si, store)?;
|
||
let cloned = clone_value(vm, state, val)?;
|
||
define_on_clone(vm, clone_ptr, si, cloned, PropAttributes::DEFAULT_DATA)?;
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 读源对象自有属性的值:数据属性直读槽值,访问器属性经 getter 取值。
|
||
///
|
||
/// # 边界与前提
|
||
/// - getter 抛出的用户异常经 `take_uncaught_value` 原值恢复;
|
||
/// - 无元数据槽(默认数据属性)按 `get_prop_at` 取值。
|
||
fn read_own_value<H: VmHost>(
|
||
vm: &mut H, src: &JsObject, src_val: JsValue, si: u32, store: u32,
|
||
) -> Result<JsValue, JsValue> {
|
||
let is_accessor = src.prop_meta_at(store).is_some_and(|m| m.is_accessor);
|
||
if !is_accessor {
|
||
return Ok(src.get_prop_at(store));
|
||
}
|
||
match vm.ordinary_get(src, si, src_val) {
|
||
Ok(v) => Ok(v),
|
||
Err(e) => {
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return Err(exc);
|
||
}
|
||
Err(crate::error::create_type_error(vm, &e))
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 判定 store 槽的自有属性是否可枚举(无元数据槽时按默认数据属性,可枚举)。
|
||
fn is_enumerable_own(src: &JsObject, store: u32) -> bool {
|
||
src.prop_meta_at(store)
|
||
.map(|m| m.attributes.enumerable())
|
||
.unwrap_or(true)
|
||
}
|
||
|
||
/// 把克隆值按描述符定义到克隆对象。
|
||
///
|
||
/// # 副作用
|
||
/// - 更新克隆对象的 shape 链与属性表。
|
||
fn define_on_clone<H: VmHost>(
|
||
vm: &mut H, clone_ptr: *mut JsObject, si: u32, val: JsValue, attrs: PropAttributes,
|
||
) -> Result<(), JsValue> {
|
||
// SAFETY: clone_ptr 是本克隆核新分配的对象,存活且本段无别名。
|
||
let clone = unsafe { &mut *clone_ptr };
|
||
match vm.define_data_property(clone, si, val, attrs) {
|
||
Ok(()) => Ok(()),
|
||
Err(e) => Err(crate::error::create_type_error(vm, &e)),
|
||
}
|
||
}
|
||
|
||
/// 复制 Map 条目到克隆:逐条克隆键值后插入克隆的 `MapInner`。
|
||
fn fill_map<H: VmHost>(
|
||
vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject,
|
||
) -> Result<(), JsValue> {
|
||
let src_inner = src.native_data() as *const MapInner;
|
||
if src_inner.is_null() {
|
||
return Ok(());
|
||
}
|
||
// SAFETY: clone_ptr 是本克隆核新分配的 Map 对象,native_data 槽已写入非空盒。
|
||
let clone_inner = unsafe { (*clone_ptr).native_data() } as *mut MapInner;
|
||
if clone_inner.is_null() {
|
||
return Ok(());
|
||
}
|
||
// 先收集条目(迭代器持有 &MapInner 借用,克隆递归前须结束借用)。
|
||
let entries: Vec<(SetKey, JsValue)> = unsafe { (*src_inner).iter() }.collect();
|
||
for (key, value) in entries {
|
||
let cloned_key = clone_value(vm, state, key.0)?;
|
||
let cloned_value = clone_value(vm, state, value)?;
|
||
// SAFETY: clone_inner 为本克隆核新分配的 MapInner,存活且单线程独占。
|
||
unsafe { (*clone_inner).insert(SetKey(cloned_key), cloned_value) };
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 复制 Set 元素到克隆:逐个克隆后插入克隆的 `SetInner`。
|
||
fn fill_set<H: VmHost>(
|
||
vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject,
|
||
) -> Result<(), JsValue> {
|
||
let src_inner = src.native_data() as *const SetInner;
|
||
if src_inner.is_null() {
|
||
return Ok(());
|
||
}
|
||
// SAFETY: clone_ptr 是本克隆核新分配的 Set 对象,native_data 槽已写入非空盒。
|
||
let clone_inner = unsafe { (*clone_ptr).native_data() } as *mut SetInner;
|
||
if clone_inner.is_null() {
|
||
return Ok(());
|
||
}
|
||
// 先收集元素(迭代器持有 &SetInner 借用,克隆递归前须结束借用)。
|
||
let entries: Vec<SetKey> = unsafe { (*src_inner).iter() }.copied().collect();
|
||
for key in entries {
|
||
let cloned_key = clone_value(vm, state, key.0)?;
|
||
// SAFETY: clone_inner 为本克隆核新分配的 SetInner,存活且单线程独占。
|
||
unsafe { (*clone_inner).insert(SetKey(cloned_key)) };
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 分配 plain 对象克隆(proto = %Object.prototype%)。
|
||
fn alloc_plain_clone<H: VmHost>(vm: &mut H) -> *mut JsObject {
|
||
let proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||
vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)))
|
||
}
|
||
|
||
/// 分配装箱对象克隆:同类型标签、同原型、同被包载荷。String 盒复用构造期
|
||
/// 物化路径(字符索引与 length 面与构造器一致),其余装箱类型直写载荷。
|
||
fn alloc_boxed_clone<H: VmHost>(vm: &mut H, src: &JsObject) -> *mut JsObject {
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, src.proto());
|
||
obj.type_tag = src.type_tag;
|
||
let payload = src.boxed_value();
|
||
if src.is_string_obj() {
|
||
oxide_runtime_api::materialize_string_box(vm, &mut obj, payload);
|
||
} else {
|
||
obj.set_boxed_value(payload);
|
||
}
|
||
vm.alloc_object(obj)
|
||
}
|
||
|
||
/// 分配数组克隆(同长度,元素区初始为在位 undefined)。
|
||
fn alloc_array_clone<H: VmHost>(vm: &mut H, src: &JsObject) -> *mut JsObject {
|
||
create_new_array(vm, src.logical_len() as usize)
|
||
}
|
||
|
||
/// 分配 Map 克隆(空 `MapInner`,proto = %Map.prototype%)。
|
||
fn alloc_map_clone<H: VmHost>(vm: &mut H) -> *mut JsObject {
|
||
let map_proto = vm.session().builtin_world().map_proto.as_ptr() as *mut JsObject;
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(map_proto));
|
||
obj.set_map(true);
|
||
let inner = Box::into_raw(Box::new(MapInner::new()));
|
||
obj.set_native_data(inner as *mut u8);
|
||
vm.alloc_object(obj)
|
||
}
|
||
|
||
/// 分配 Set 克隆(空 `SetInner`,proto = %Set.prototype%)。
|
||
fn alloc_set_clone<H: VmHost>(vm: &mut H) -> *mut JsObject {
|
||
let set_proto = vm.session().builtin_world().set_proto.as_ptr() as *mut JsObject;
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(set_proto));
|
||
obj.set_set(true);
|
||
let inner = Box::into_raw(Box::new(SetInner::new()));
|
||
obj.set_native_data(inner as *mut u8);
|
||
vm.alloc_object(obj)
|
||
}
|
||
|
||
/// 分配 Date 克隆(同时间戳,proto = %Date.prototype%)。
|
||
fn alloc_date_clone<H: VmHost>(vm: &mut H, src: &JsObject) -> *mut JsObject {
|
||
let proto = vm.session().builtin_world().date_proto.as_ptr() as *mut JsObject;
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
|
||
obj.type_tag = JsObject::OBJ_TYPE_DATE;
|
||
obj.set_prop_at(0, src.get_prop_at(0));
|
||
vm.alloc_object(obj)
|
||
}
|
||
|
||
/// 分配 RegExp 克隆(同源与标志重编译,proto = %RegExp.prototype%)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 源模式非法(理论上不发生,源已是合法 RegExp)时抛 SyntaxError;
|
||
/// - lastIndex 初始化为 0(writable / 非枚举 / 非可配置)。
|
||
fn alloc_regexp_clone<H: VmHost>(vm: &mut H, src: &JsObject) -> Result<*mut JsObject, JsValue> {
|
||
let proto = vm.session().builtin_world().regexp_proto.as_ptr() as *mut JsObject;
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
|
||
let source_val = src.get_regexp_source();
|
||
let flags_val = src.get_regexp_flags();
|
||
let source_text = vm.lookup_str(source_val).unwrap_or_default();
|
||
let flags_text = vm.lookup_str(flags_val).unwrap_or_default();
|
||
match regress::Regex::with_flags(source_text.as_str(), flags_text.as_str()) {
|
||
Ok(re) => {
|
||
let re_ptr = Box::into_raw(Box::new(re));
|
||
// SAFETY: re_ptr 是 `Box<regress::Regex>` 指针,与 RegExp 构造器同形态,
|
||
// 对象存活期间有效,由 `drop_regexp_native` 释放。
|
||
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(re_ptr as *const ()) }));
|
||
}
|
||
Err(e) => {
|
||
return Err(crate::error::create_syntax_error(vm, &format!("Invalid regular expression: {e}")));
|
||
}
|
||
}
|
||
obj.type_tag = JsObject::OBJ_TYPE_REGEXP;
|
||
obj.set_regexp_source(source_val);
|
||
obj.set_regexp_flags(flags_val);
|
||
let si_lastindex = vm.kernel_core().perm_interner().intern("lastIndex").0;
|
||
let _ = vm.define_data_property(&mut obj, si_lastindex, JsValue::int(0), PropAttributes::new(true, false, false));
|
||
Ok(vm.alloc_object(obj))
|
||
}
|
||
|
||
/// 分配 Error 克隆(原型与源一致 → 同类型新 Error,标记 Error 家族标签)。
|
||
fn alloc_error_clone<H: VmHost>(vm: &mut H, src: &JsObject) -> *mut JsObject {
|
||
let proto = src.proto();
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto);
|
||
obj.type_tag = JsObject::OBJ_TYPE_ERROR;
|
||
vm.alloc_object(obj)
|
||
}
|
||
|
||
/// 分配 ArrayBuffer 克隆:克隆源字节(转移条目已被 seen 映射短路返回占位,
|
||
/// 不会到达此处);存储态上限与 immutable 标志原样拷贝,detached 源报
|
||
/// DataCloneError。
|
||
fn alloc_array_buffer_clone<H: VmHost>(vm: &mut H, src: &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"));
|
||
};
|
||
// SAFETY: payload_ptr 经 array_buffer_payload_ptr 校验为存活载荷盒;标量
|
||
// 拷出后借用即结束,不跨 JS 调用。
|
||
let (data, max_byte_length, immutable) = unsafe {
|
||
let p = &*payload_ptr;
|
||
(p.data.clone(), p.max_byte_length, p.immutable)
|
||
};
|
||
// 值图内 detached 缓冲不可克隆(规范报 DataCloneError)。
|
||
let Some(data) = data else {
|
||
return Err(data_clone_error(vm, "detached ArrayBuffer is not cloneable"));
|
||
};
|
||
let proto = crate::array_buffer::default_array_buffer_proto(vm);
|
||
let clone_ptr = alloc_buffer_object(vm, Some(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)。
|
||
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()
|
||
}
|
||
|
||
/// 是否 plain 对象:类型标签为 PLAIN 且非函数、非模块命名空间(Map / Set 等
|
||
/// 特型对象在分派前已被拦截,不会到达本判定)。
|
||
fn is_plain_object(src: &JsObject) -> bool {
|
||
src.type_tag == JsObject::OBJ_TYPE_PLAIN && !src.is_function() && !src.is_module_namespace()
|
||
}
|
||
|
||
/// 是否可克隆的装箱对象:Boolean / Number / String 盒,或 BigInt 包装
|
||
/// (PLAIN 标签加 `boxed_value` 存 bigint 载荷)。非装箱对象的 `boxed_value`
|
||
/// 恒为 undefined,BigInt 包装判定不会误命中普通 plain 对象。
|
||
fn is_boxed_cloneable(src: &JsObject) -> bool {
|
||
src.is_boolean_obj()
|
||
|| src.is_number_obj()
|
||
|| src.is_string_obj()
|
||
|| (src.type_tag == JsObject::OBJ_TYPE_PLAIN && src.boxed_value().is_bigint())
|
||
}
|
||
|
||
/// 构造 DataCloneError 对象(统一报错入口)。
|
||
fn data_clone_error<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
|
||
crate::error::create_kind_error(vm, "DataCloneError", msg)
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use std::sync::Arc;
|
||
|
||
use oxide_vm::vm::Vm;
|
||
|
||
/// 编译并执行单脚本,返回 VM 与完成值。
|
||
fn eval(source: &str) -> Result<(Vm, JsValue), String> {
|
||
let allocator = oxide_parser::Allocator::default();
|
||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("parse: {:?}", e))?;
|
||
let module = oxide_compiler::compiler::Compiler::new()
|
||
.compile(&program)
|
||
.map_err(|e| format!("compile: {e}"))?;
|
||
let mut vm = Vm::new();
|
||
let result = vm.run(&Arc::new(module))?;
|
||
Ok((vm, result))
|
||
}
|
||
|
||
/// 把 value 置入寄存器并调克隆入口,返回克隆值或 DataCloneError。
|
||
fn clone(vm: &mut Vm, value: JsValue) -> Result<JsValue, JsValue> {
|
||
vm.set_reg(1, value);
|
||
let args = [0u8, 1];
|
||
match structured_clone_entry(vm, &args) {
|
||
NativeResult::Ok(v) => Ok(v),
|
||
NativeResult::Err(e) => Err(e),
|
||
NativeResult::TailCall { .. } => unreachable!("structuredClone 不返回 TailCall"),
|
||
}
|
||
}
|
||
|
||
/// 读对象自身属性值(按名称),无则 undefined。
|
||
fn obj_prop(vm: &Vm, obj: &JsObject, name: &str) -> JsValue {
|
||
let si = vm.kernel_core().perm_interner().intern(name).0;
|
||
match vm.get_own_property_slot(obj, si) {
|
||
Some(idx) => obj.get_prop_at(idx),
|
||
None => JsValue::undefined(),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn clone_primitives_identity() {
|
||
for src in ["42", "'hello'", "true", "null", "undefined", "1.5", "10n"] {
|
||
let (mut vm, v) = eval(src).unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
assert_eq!(c, v, "原始值应原样拷贝: {src}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn clone_plain_object() {
|
||
let (mut vm, v) = eval("var o = {a: 1, b: 'x', c: null}; o").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
assert!(c.is_object());
|
||
assert!(!std::ptr::eq(v.as_js_object_ptr(), c.as_js_object_ptr()), "克隆应为新对象");
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(!cl.is_function());
|
||
assert_eq!(obj_prop(&vm, cl, "a"), JsValue::int(1));
|
||
assert_eq!(vm.lookup_str(obj_prop(&vm, cl, "b")).unwrap(), "x");
|
||
assert!(obj_prop(&vm, cl, "c").is_null());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_array() {
|
||
let (mut vm, v) = eval("var a = [1, 'two', null]; a").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_array());
|
||
assert_eq!(cl.logical_len(), 3);
|
||
assert_eq!(cl.get_prop_at(0), JsValue::int(1));
|
||
assert_eq!(vm.lookup_str(cl.get_prop_at(1)).unwrap(), "two");
|
||
assert!(cl.get_prop_at(2).is_null());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_map() {
|
||
let (mut vm, v) = eval("var m = new Map(); m.set('k', 1); m.set(2, 'v'); m").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_map());
|
||
let inner = cl.native_data() as *const MapInner;
|
||
assert!(!inner.is_null());
|
||
let entries: Vec<(SetKey, JsValue)> = unsafe { (*inner).iter() }.collect();
|
||
assert_eq!(entries.len(), 2);
|
||
// 键 'k' → 1,键 2 → 'v'(SameValueZero 键语义)。
|
||
let mut found = [false; 2];
|
||
for (key, val) in &entries {
|
||
if key.0.is_string() && vm.lookup_str(key.0).as_deref() == Some("k") {
|
||
found[0] = true;
|
||
assert_eq!(*val, JsValue::int(1));
|
||
}
|
||
if key.0.is_int() && key.0.as_int() == 2 {
|
||
found[1] = true;
|
||
assert_eq!(vm.lookup_str(*val).unwrap(), "v");
|
||
}
|
||
}
|
||
assert!(found[0] && found[1], "两个条目均应克隆: {entries:?}");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_set() {
|
||
let (mut vm, v) = eval("var s = new Set([1, 'two', 3]); s").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_set());
|
||
let inner = cl.native_data() as *const SetInner;
|
||
assert!(!inner.is_null());
|
||
let entries: Vec<SetKey> = unsafe { (*inner).iter() }.copied().collect();
|
||
assert_eq!(entries.len(), 3);
|
||
}
|
||
|
||
#[test]
|
||
fn clone_date() {
|
||
let (mut vm, v) = eval("var d = new Date(1234567890); d").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_date_obj());
|
||
assert_eq!(cl.get_prop_at(0), JsValue::float(1234567890.0));
|
||
}
|
||
|
||
#[test]
|
||
fn clone_regexp() {
|
||
let (mut vm, v) = eval("var r = /abc/gi; r").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_regexp_obj());
|
||
assert_eq!(vm.lookup_str(cl.get_regexp_source()).unwrap(), "abc");
|
||
let flags = vm.lookup_str(cl.get_regexp_flags()).unwrap();
|
||
assert!(flags.contains('g') && flags.contains('i'), "flags 应含 g 与 i: {flags}");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_error() {
|
||
let (mut vm, v) = eval("var e = new TypeError('boom'); e").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_error_obj());
|
||
assert!(!std::ptr::eq(v.as_js_object_ptr(), c.as_js_object_ptr()));
|
||
assert_eq!(vm.lookup_str(obj_prop(&vm, cl, "message")).unwrap(), "boom");
|
||
// 原型与源一致(同类型 Error)。
|
||
assert!(std::ptr::eq(unsafe { &*v.as_js_object_ptr() }.proto().as_ptr(), cl.proto().as_ptr()));
|
||
}
|
||
|
||
#[test]
|
||
fn clone_shared_reference() {
|
||
let (mut vm, v) = eval("var a = {x: 1}; var b = {ref: a, ref2: a}; b").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
let si_ref = vm.kernel_core().perm_interner().intern("ref").0;
|
||
let si_ref2 = vm.kernel_core().perm_interner().intern("ref2").0;
|
||
let ref1 = cl.get_prop_at(vm.get_own_property_slot(cl, si_ref).unwrap());
|
||
let ref2 = cl.get_prop_at(vm.get_own_property_slot(cl, si_ref2).unwrap());
|
||
assert!(ref1.is_object() && ref2.is_object());
|
||
assert!(std::ptr::eq(ref1.as_js_object_ptr(), ref2.as_js_object_ptr()), "共享引用应映射到同一克隆");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_cycle() {
|
||
let (mut vm, v) = eval("var a = {}; a.self = a; a").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
let si_self = vm.kernel_core().perm_interner().intern("self").0;
|
||
let self_ref = cl.get_prop_at(vm.get_own_property_slot(cl, si_self).unwrap());
|
||
assert!(std::ptr::eq(self_ref.as_js_object_ptr(), c.as_js_object_ptr()), "循环引用应指向克隆自身");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_function_data_clone_error() {
|
||
let (mut vm, v) = eval("function f() {}; f").unwrap();
|
||
let e = clone(&mut vm, v).unwrap_err();
|
||
assert!(e.is_object());
|
||
let err = unsafe { &*e.as_js_object_ptr() };
|
||
assert!(err.is_error_obj());
|
||
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]
|
||
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());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_promise_data_clone_error() {
|
||
let (mut vm, v) = eval("Promise.resolve(1)").unwrap();
|
||
let e = clone(&mut vm, v).unwrap_err();
|
||
assert!(e.is_object());
|
||
let err = unsafe { &*e.as_js_object_ptr() };
|
||
assert!(err.is_error_obj());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_symbol_primitive_data_clone_error() {
|
||
let (mut vm, v) = eval("Symbol('x')").unwrap();
|
||
let e = clone(&mut vm, v).unwrap_err();
|
||
let err = unsafe { &*e.as_js_object_ptr() };
|
||
assert!(err.is_error_obj());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_symbol_box_data_clone_error() {
|
||
let (mut vm, v) = eval("Object(Symbol('x'))").unwrap();
|
||
let e = clone(&mut vm, v).unwrap_err();
|
||
let err = unsafe { &*e.as_js_object_ptr() };
|
||
assert!(err.is_error_obj());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_boolean_box() {
|
||
let (mut vm, v) = eval("new Boolean(true)").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_boolean_obj());
|
||
assert!(!std::ptr::eq(v.as_js_object_ptr(), c.as_js_object_ptr()), "克隆应为新对象");
|
||
assert_eq!(cl.boxed_value(), JsValue::bool(true));
|
||
}
|
||
|
||
#[test]
|
||
fn clone_number_box() {
|
||
let (mut vm, v) = eval("new Number(42)").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_number_obj());
|
||
assert_eq!(cl.boxed_value(), JsValue::int(42));
|
||
}
|
||
|
||
#[test]
|
||
fn clone_string_box() {
|
||
let (mut vm, v) = eval("new String('hi')").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert!(cl.is_string_obj());
|
||
assert_eq!(vm.lookup_str(cl.boxed_value()).unwrap(), "hi");
|
||
// 字符索引与 length 面已物化(与构造器一致;字符索引为整数键,按物理槽位读)。
|
||
assert_eq!(obj_prop(&vm, cl, "length"), JsValue::int(2));
|
||
assert_eq!(vm.lookup_str(cl.get_prop_at(0)).unwrap(), "h");
|
||
assert_eq!(vm.lookup_str(cl.get_prop_at(1)).unwrap(), "i");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_bigint_wrapper() {
|
||
let (mut vm, v) = eval("Object(10n)").unwrap();
|
||
let src = unsafe { &*v.as_js_object_ptr() };
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
// BigInt 包装是 PLAIN 标签加大整数载荷,克隆保标签与载荷。
|
||
assert_eq!(cl.type_tag, JsObject::OBJ_TYPE_PLAIN);
|
||
assert!(cl.boxed_value().is_bigint());
|
||
assert!(oxide_runtime_api::same_value_zero(cl.boxed_value(), src.boxed_value()));
|
||
}
|
||
|
||
#[test]
|
||
fn clone_class_instance() {
|
||
let (mut vm, v) = eval("class C { constructor() { this.x = 1; } } new C()").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
// 克隆是 plain 对象:原型链丢弃,回落 Object 原型,自有属性带过。
|
||
assert_eq!(cl.type_tag, JsObject::OBJ_TYPE_PLAIN);
|
||
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *const u8;
|
||
assert!(std::ptr::eq(cl.proto().as_ptr(), object_proto));
|
||
assert_eq!(obj_prop(&vm, cl, "x"), JsValue::int(1));
|
||
}
|
||
|
||
#[test]
|
||
fn clone_null_proto_object() {
|
||
let (mut vm, v) = eval("Object.create(null)").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert_eq!(cl.type_tag, JsObject::OBJ_TYPE_PLAIN);
|
||
}
|
||
|
||
#[test]
|
||
fn clone_weak_map_data_clone_error() {
|
||
let (mut vm, v) = eval("var w = new WeakMap(); var k = {}; w.set(k, 1); w").unwrap();
|
||
let e = clone(&mut vm, v).unwrap_err();
|
||
let err = unsafe { &*e.as_js_object_ptr() };
|
||
assert!(err.is_error_obj());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_generator_data_clone_error() {
|
||
let (mut vm, v) = eval("function* g() { yield 1; } g()").unwrap();
|
||
let e = clone(&mut vm, v).unwrap_err();
|
||
let err = unsafe { &*e.as_js_object_ptr() };
|
||
assert!(err.is_error_obj());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_frozen_plain_object_flattens_descriptors() {
|
||
let (mut vm, v) = eval("var o = {a: 1}; Object.freeze(o); o").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
let si = vm.kernel_core().perm_interner().intern("a").0;
|
||
let store = vm.get_own_property_slot(cl, si).unwrap();
|
||
let meta = cl.prop_meta_at(store).unwrap();
|
||
assert_eq!(meta.attributes, PropAttributes::DEFAULT_DATA, "克隆侧描述符应 w/e/c 全真");
|
||
assert_eq!(cl.get_prop_at(store), JsValue::int(1));
|
||
}
|
||
|
||
#[test]
|
||
fn clone_skips_non_enumerable_own_property() {
|
||
let (mut vm, v) =
|
||
eval("var o = {a: 1}; Object.defineProperty(o, 'b', {value: 2, enumerable: false}); o").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
assert_eq!(obj_prop(&vm, cl, "a"), JsValue::int(1));
|
||
let si_b = vm.kernel_core().perm_interner().intern("b").0;
|
||
assert!(vm.get_own_property_slot(cl, si_b).is_none(), "不可枚举自有属性不应复制");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_enumerable_symbol_key() {
|
||
let (mut vm, v) = eval("var s = Symbol('k'); var o = {}; o[s] = 7; o").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
let sym_keys = crate::object::walk_own_symbol_keys(&vm, cl);
|
||
assert_eq!(sym_keys.len(), 1);
|
||
let (_, store) = sym_keys[0];
|
||
assert_eq!(cl.get_prop_at(store), JsValue::int(7));
|
||
}
|
||
|
||
#[test]
|
||
fn clone_error_cause_flattened_to_data_property() {
|
||
let (mut vm, v) = eval("var e = new Error('boom'); e.cause = {x: 1}; e").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
let si_cause = vm.kernel_core().perm_interner().intern("cause").0;
|
||
let store = vm.get_own_property_slot(cl, si_cause).unwrap();
|
||
let meta = cl.prop_meta_at(store).unwrap();
|
||
assert_eq!(meta.attributes, PropAttributes::DEFAULT_DATA, "克隆侧 cause 应全真数据属性");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_error_subclass_instance() {
|
||
let (mut vm, v) = eval("class E extends Error {} var e = new E('msg'); e").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
// 引擎给 Error 子类实例打 Error 家族标签,克隆走 Error 臂(同原型、message 带过)。
|
||
assert!(cl.is_error_obj());
|
||
assert!(std::ptr::eq(unsafe { &*v.as_js_object_ptr() }.proto().as_ptr(), cl.proto().as_ptr()));
|
||
assert_eq!(vm.lookup_str(obj_prop(&vm, cl, "message")).unwrap(), "msg");
|
||
}
|
||
|
||
/// 读 ArrayBuffer 载荷存储态上限(0 定长,非 0 为请求上限加 1)。
|
||
fn ab_max_byte_length(ab: &JsObject) -> Option<usize> {
|
||
let ptr = crate::array_buffer::array_buffer_payload_ptr(ab)?;
|
||
// SAFETY: ptr 经 array_buffer_payload_ptr 校验为存活载荷盒。
|
||
Some(unsafe { &*ptr }.max_byte_length)
|
||
}
|
||
|
||
/// 读 ArrayBuffer 载荷 immutable 标志。
|
||
fn ab_immutable(ab: &JsObject) -> Option<bool> {
|
||
let ptr = crate::array_buffer::array_buffer_payload_ptr(ab)?;
|
||
// SAFETY: ptr 经 array_buffer_payload_ptr 校验为存活载荷盒。
|
||
Some(unsafe { &*ptr }.immutable)
|
||
}
|
||
|
||
#[test]
|
||
fn transfer_failure_does_not_detach() {
|
||
let (mut vm, v) = eval(
|
||
"var ab = new Uint8Array([1, 2, 3]).buffer; \
|
||
var opts = {transfer: [ab]}; \
|
||
({ab: ab, fn: function f() {}, 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());
|
||
// 克隆持有者对象(含函数值,不可克隆)→ DataCloneError。
|
||
let e = clone_with_options(&mut vm, v, opts).unwrap_err();
|
||
let err = unsafe { &*e.as_js_object_ptr() };
|
||
assert!(err.is_error_obj());
|
||
// 克隆失败时转移未完成,源缓冲未 detach。
|
||
assert_eq!(ab_bytes(unsafe { &*ab.as_js_object_ptr() }), Some(vec![1, 2, 3]));
|
||
}
|
||
|
||
#[test]
|
||
fn transfer_outside_value_graph_detaches() {
|
||
let (mut vm, v) = eval(
|
||
"var ab = new Uint8Array([1, 2, 3]).buffer; var opts = {transfer: [ab]}; \
|
||
({v: 42, ab: 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_ab = vm.kernel_core().perm_interner().intern("ab").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 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, val, opts).unwrap();
|
||
assert_eq!(c, JsValue::int(42));
|
||
// 值图外转移条目同样 detach。
|
||
assert!(ab_bytes(unsafe { &*ab.as_js_object_ptr() }).is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_detached_arraybuffer_data_clone_error() {
|
||
let (mut vm, v) = eval("var ab = new ArrayBuffer(4); $262.detachArrayBuffer(ab); ab").unwrap();
|
||
let e = clone(&mut vm, v).unwrap_err();
|
||
let err = unsafe { &*e.as_js_object_ptr() };
|
||
assert!(err.is_error_obj());
|
||
}
|
||
|
||
#[test]
|
||
fn transfer_detached_buffer_data_clone_error() {
|
||
let (mut vm, v) = eval(
|
||
"var ab = new ArrayBuffer(4); $262.detachArrayBuffer(ab); \
|
||
var opts = {transfer: [ab]}; ({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());
|
||
}
|
||
|
||
#[test]
|
||
fn clone_map_key_cycle() {
|
||
let (mut vm, v) = eval("var m = new Map(); var k = {}; k.m = m; m.set(k, 1); m").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
let inner = cl.native_data() as *const MapInner;
|
||
let entries: Vec<(SetKey, JsValue)> = unsafe { (*inner).iter() }.collect();
|
||
assert_eq!(entries.len(), 1);
|
||
// 克隆键引用克隆的 map(循环保持)。
|
||
let key = &entries[0].0;
|
||
assert!(key.0.is_object());
|
||
let key_obj = unsafe { &*key.0.as_js_object_ptr() };
|
||
let si_m = vm.kernel_core().perm_interner().intern("m").0;
|
||
let key_m = key_obj.get_prop_at(vm.get_own_property_slot(key_obj, si_m).unwrap());
|
||
assert!(std::ptr::eq(key_m.as_js_object_ptr(), c.as_js_object_ptr()), "克隆键应引用克隆的 map");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_local_map_independent_calls() {
|
||
let (mut vm, v) = eval("var o = {a: 1}; o").unwrap();
|
||
let c1 = clone(&mut vm, v).unwrap();
|
||
let c2 = clone(&mut vm, v).unwrap();
|
||
assert!(
|
||
!std::ptr::eq(c1.as_js_object_ptr(), c2.as_js_object_ptr()),
|
||
"两次调用应产独立克隆(seen 映射本地)"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn clone_deep_cycle() {
|
||
let (mut vm, v) = eval("var a = {}; var b = {}; a.b = b; b.a = a; a").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
let si_b = vm.kernel_core().perm_interner().intern("b").0;
|
||
let b_clone = cl.get_prop_at(vm.get_own_property_slot(cl, si_b).unwrap());
|
||
let b_obj = unsafe { &*b_clone.as_js_object_ptr() };
|
||
let si_a = vm.kernel_core().perm_interner().intern("a").0;
|
||
let a_back = b_obj.get_prop_at(vm.get_own_property_slot(b_obj, si_a).unwrap());
|
||
assert!(std::ptr::eq(a_back.as_js_object_ptr(), c.as_js_object_ptr()), "深循环应指回克隆的 a");
|
||
}
|
||
|
||
#[test]
|
||
fn clone_shared_reference_and_cycle_mixed() {
|
||
let (mut vm, v) = eval("var a = {x: 1}; var b = {ref: a, ref2: a}; b.self = b; b").unwrap();
|
||
let c = clone(&mut vm, v).unwrap();
|
||
let cl = unsafe { &*c.as_js_object_ptr() };
|
||
let si_ref = vm.kernel_core().perm_interner().intern("ref").0;
|
||
let si_ref2 = vm.kernel_core().perm_interner().intern("ref2").0;
|
||
let si_self = vm.kernel_core().perm_interner().intern("self").0;
|
||
let ref1 = cl.get_prop_at(vm.get_own_property_slot(cl, si_ref).unwrap());
|
||
let ref2 = cl.get_prop_at(vm.get_own_property_slot(cl, si_ref2).unwrap());
|
||
let self_ref = cl.get_prop_at(vm.get_own_property_slot(cl, si_self).unwrap());
|
||
assert!(std::ptr::eq(ref1.as_js_object_ptr(), ref2.as_js_object_ptr()), "共享引用应映射到同一克隆");
|
||
assert!(std::ptr::eq(self_ref.as_js_object_ptr(), c.as_js_object_ptr()), "循环应指向克隆自身");
|
||
}
|
||
|
||
#[test]
|
||
fn transfer_resizable_preserves_max_byte_length() {
|
||
let (mut vm, v) = eval(
|
||
"var ab = new ArrayBuffer(4, {maxByteLength: 8}); 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() };
|
||
// 克隆保留存储态上限(请求上限加 1)与源字节。
|
||
assert_eq!(ab_max_byte_length(cl), Some(9));
|
||
assert_eq!(ab_bytes(cl), Some(vec![0, 0, 0, 0]));
|
||
// 源缓冲已 detach。
|
||
assert!(ab_bytes(unsafe { &*ab.as_js_object_ptr() }).is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn transfer_clone_does_not_inherit_immutable() {
|
||
let (mut vm, v) = eval(
|
||
"var ab = new ArrayBuffer(4); ab.markImmutable(); 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() };
|
||
// 转移产物是新缓冲,不继承 immutable 标志。
|
||
assert_eq!(ab_immutable(cl), Some(false));
|
||
assert_eq!(ab_bytes(cl), Some(vec![0, 0, 0, 0]));
|
||
assert!(ab_bytes(unsafe { &*ab.as_js_object_ptr() }).is_none());
|
||
}
|
||
}
|