mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
1420 lines
64 KiB
Rust
1420 lines
64 KiB
Rust
//! 模块命名空间与求值辅助(import 实现内部用,非 JS 可见标准 API)。
|
||
//!
|
||
//! 命名约定 `__module*`:由编译器模块 prelude 发出(`lookup_or_builtin` 解析为全局槽,
|
||
//! VM 在 frame push 时从 global 对象取回 native 函数)。依赖模块先整体求值并返回
|
||
//! 命名空间对象,导入方从中读取导出值;依赖含可重赋导出时命名/默认导入经
|
||
//! `__moduleGet` 活读,被捕获导出由 `__moduleSetCell` 挂共享 cell。
|
||
//! source-phase / defer 语义留待后续轮次。
|
||
|
||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||
use oxide_runtime_api::{to_string, NativeResult, VmHost};
|
||
use oxide_types::object::{Cell, JsObject, PropAttributes, PropMetaEntry};
|
||
use oxide_types::private_key::encode_symbol_key;
|
||
use oxide_types::value::JsValue;
|
||
|
||
/// `Symbol.toStringTag` 在 well-known symbol 表中的序号,命名空间标签键按其编码。
|
||
const TO_STRING_TAG_SYMBOL_ID: u32 = 9;
|
||
|
||
/// 未初始化导出被读时的 ReferenceError 文本。
|
||
///
|
||
/// exotic `[[Get]]` 与 `[[GetOwnProperty]]` 消费端(`Object.*`、for-in)共用同一
|
||
/// 文本,错误类型由消费端的 `create_reference_error` / `create_from_text` 恢复。
|
||
pub const NS_UNINITIALIZED_MESSAGE: &str = "Cannot access module export before initialization";
|
||
|
||
/// 模块命名空间导出属性描述符:可写、可枚举、不可配置(module namespace exotic)。
|
||
///
|
||
/// 规范 10.4.6.5 返回 `{writable: true, enumerable: true, configurable: false}`;
|
||
/// 写保护由命名空间的 `[[Set]]`/`[[DefineOwnProperty]]` 专属语义承担,不靠描述符位。
|
||
fn ns_attrs() -> PropAttributes {
|
||
PropAttributes::new(true, true, false)
|
||
}
|
||
|
||
fn type_error<H: VmHost>(vm: &mut H, msg: &str) -> NativeResult {
|
||
NativeResult::Err(crate::error::create_type_error(vm, msg))
|
||
}
|
||
|
||
/// 命名空间条目表的来源身份(provenance)。
|
||
///
|
||
/// `Local` 为本模块直接声明的导出;`Reexport` 描述跨模块再导出的 `(模块, 绑定)`
|
||
/// 身份,`direct` 区分显式再导出(`export { x } from`)与 star 复制的间接导出;
|
||
/// `Ambiguous` 是 star 冲突后的省略哨兵,该名不再出现在命名空间且一旦歧义恒歧义。
|
||
#[derive(Clone, Copy)]
|
||
pub enum ModuleNsOrigin {
|
||
Local,
|
||
Reexport { path_id: u32, binding_id: u32, direct: bool },
|
||
Ambiguous,
|
||
}
|
||
|
||
/// 单个导出名的活值状态。
|
||
pub enum ModuleNsState {
|
||
/// 预注册后未初始化的一般导出;`initialized` 为 true 时 `value` 是当前活值。
|
||
Value { value: JsValue, initialized: bool },
|
||
/// 被捕获导出挂的共享 cell,活值由 cell 承载。
|
||
Cell(*mut Cell),
|
||
}
|
||
|
||
/// 命名空间条目:导出名 interned 键、活值状态与来源身份一一对应。
|
||
pub struct ModuleNsEntry {
|
||
pub name_si: u32,
|
||
pub origin: ModuleNsOrigin,
|
||
pub state: ModuleNsState,
|
||
}
|
||
|
||
/// deferred namespace 状态盒:`[[Module]]`/`[[Evaluated]]`/`[[Namespace]]` 三内部槽。
|
||
///
|
||
/// 以 `Option` 内嵌进 `ModuleNsTable`(`native_data` 的唯一分配),不新建 JsObject
|
||
/// 字段、不建第二个 Box——GC 边、字节核算、释放全部复用 `ModuleNamespace` 家族链,
|
||
/// 零新增注册点。
|
||
pub struct DeferredNsState {
|
||
/// `[[Module]]`:依赖模块函数(编译期 CREATE_CLOSURE 的闭包对象),或
|
||
/// undefined(哨兵:自导入/祖先引用,模块函数在导入方 prelude 期不可得)。
|
||
pub module: JsValue,
|
||
/// `[[Evaluated]]`:依赖模块是否已求值(mixed 依赖构造时即为真;
|
||
/// 求值完成(成功或失败)后置真,不回退)。
|
||
pub evaluated: bool,
|
||
/// `[[Namespace]]`:求值后返回的命名空间对象(mixed 依赖构造时即为真实 ns;
|
||
/// 求值前为 undefined)。
|
||
pub namespace: JsValue,
|
||
/// 求值失败时缓存的原始错误值(再读再抛同一错误对象,保身份);
|
||
/// 成功求值与求值前恒 `None`。
|
||
pub error: Option<JsValue>,
|
||
/// 依赖模块规范路径:求值触发点据此查 per-module 状态(提升求值过的 TLA
|
||
/// 模块已结算,直接取缓存命名空间,防双求值)。
|
||
pub path: String,
|
||
}
|
||
|
||
/// 模块命名空间条目表:挂在 ns 对象 `native_data` 上,是 exotic [[Get]] 的权威
|
||
/// 状态。键为 `PermInterner` interned 键,线性查找(导出名通常少于 50)。
|
||
///
|
||
/// `deferred` 仅对 deferred namespace 对象为 `Some`(`__moduleDeferObject` 安装),
|
||
/// 其余 ns 对象恒 `None`;求值触发点(后续轮次)经 `deferred_state` 只读消费。
|
||
pub struct ModuleNsTable {
|
||
entries: Vec<ModuleNsEntry>,
|
||
deferred: Option<DeferredNsState>,
|
||
}
|
||
|
||
/// 导出名状态查询结果。
|
||
pub enum ModuleNsQuery {
|
||
/// 未初始化(预注册未赋值,或未初始化的共享 cell)。
|
||
Uninitialized,
|
||
/// 已初始化活值(`Value` 条目或已初始化 cell)。
|
||
Initialized(JsValue),
|
||
}
|
||
|
||
/// 取 ns 对象的条目表指针;非 module namespace 或无表返回空指针。
|
||
///
|
||
/// # 注意事项
|
||
/// - 返回指针由 ns 对象 `native_data` 持有,在该对象存活期内有效;消费方须在
|
||
/// 同一 VM 世代内使用。
|
||
fn ns_table_ptr(obj: &JsObject) -> *mut ModuleNsTable {
|
||
if !obj.is_module_namespace() {
|
||
return std::ptr::null_mut();
|
||
}
|
||
obj.native_data() as *mut ModuleNsTable
|
||
}
|
||
|
||
/// 只读访问 deferred namespace 状态盒(`[[Module]]`/`[[Evaluated]]`/`[[Namespace]]`)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 返回 `None`:非 module namespace / 无条目表 / 非 deferred 对象(`deferred` 为 `None`)。
|
||
/// - 返回 `Some`:deferred 对象且已安装状态盒,供求值触发点消费三内部槽。
|
||
///
|
||
/// # 注意事项
|
||
/// - 写入点唯一(`module_defer_object` 安装),本访问器只读,不改状态盒。
|
||
pub fn deferred_state(obj: &JsObject) -> Option<&DeferredNsState> {
|
||
let table = ns_table_ptr(obj);
|
||
if table.is_null() {
|
||
return None;
|
||
}
|
||
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
|
||
let table_ref = unsafe { &*table };
|
||
table_ref.deferred.as_ref()
|
||
}
|
||
|
||
/// 可变访问 deferred namespace 状态盒(求值触发点写回 `[[Evaluated]]` /
|
||
/// `[[Namespace]]` / 失败错误缓存)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 返回 `None`:非 module namespace / 无条目表 / 非 deferred 对象(`deferred` 为 `None`)。
|
||
/// - 返回 `Some`:deferred 对象且已安装状态盒,供求值触发点读写内部槽。
|
||
///
|
||
/// # 注意事项
|
||
/// - 经 `native_data` 裸指针取 `&mut`(Box 堆分配、GC 不搬移,指针跨调用稳定),
|
||
/// 不借 `&mut JsObject` 本体。
|
||
#[expect(clippy::mut_from_ref)]
|
||
pub fn deferred_state_mut(obj: &JsObject) -> Option<&mut DeferredNsState> {
|
||
let table = ns_table_ptr(obj);
|
||
if table.is_null() {
|
||
return None;
|
||
}
|
||
// SAFETY: table 归本 ns 对象持有,生命周期见 `deferred_state`。
|
||
let table_ref = unsafe { &mut *table };
|
||
table_ref.deferred.as_mut()
|
||
}
|
||
|
||
/// deferred namespace 读路径的权威命名空间对象:已求值且 `[[Namespace]]` 为
|
||
/// 对象时返回真实 ns(活值与真实槽位都在真实 ns 的条目表,deferred 对象自身
|
||
/// 槽位是逐名预注册占位);其余形态(非 deferred / 未求值 / 哨兵)返回对象自身。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 非 deferred 对象恒返回自身,调用方无需先判 deferred 标志。
|
||
/// - 求值成功后 `[[Namespace]]` 为 `__moduleEval` 返回的真实 ns 对象;求值失败
|
||
/// 或哨兵(自导入/祖先)形态下无真实 ns,读路径回落对象自身。
|
||
pub fn deferred_ns_read_target(obj: &JsObject) -> &JsObject {
|
||
if let Some(state) = deferred_state(obj) {
|
||
if state.evaluated {
|
||
let ns = state.namespace;
|
||
if ns.is_object() {
|
||
let ptr = ns.as_js_object_ptr();
|
||
if !ptr.is_null() {
|
||
// SAFETY: ns 由 [[Namespace]] 槽持有(GC 边已登记),
|
||
// 与 deferred 对象同代存活。
|
||
return unsafe { &*ptr };
|
||
}
|
||
}
|
||
}
|
||
}
|
||
obj
|
||
}
|
||
|
||
/// 在条目表中按 interned 键定位条目下标。
|
||
fn entry_index(table: &ModuleNsTable, key_si: u32) -> Option<usize> {
|
||
table.entries.iter().position(|e| e.name_si == key_si)
|
||
}
|
||
|
||
/// 分配空条目表并挂到 ns 对象的 `native_data`。
|
||
fn install_ns_table(obj: &mut JsObject) -> *mut ModuleNsTable {
|
||
let table = Box::into_raw(Box::new(ModuleNsTable {
|
||
entries: Vec::new(),
|
||
deferred: None,
|
||
}));
|
||
obj.set_native_data(table as *mut u8);
|
||
table
|
||
}
|
||
|
||
/// 追加一条未初始化的 `Value` 条目(来源为本地导出)。
|
||
///
|
||
/// # 注意事项
|
||
/// - 调用方保证 `name_si` 尚未入表(入口均为先查后插)。
|
||
fn push_uninitialized_entry(table: *mut ModuleNsTable, name_si: u32) {
|
||
push_entry(
|
||
table,
|
||
name_si,
|
||
ModuleNsOrigin::Local,
|
||
ModuleNsState::Value {
|
||
value: JsValue::undefined(),
|
||
initialized: false,
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 追加一条已初始化为 undefined 的 `Value` 条目(来源为本地导出)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 供 var 作用域导出(`VarScopedDeclarations`)在实例化期预初始化:读时为
|
||
/// undefined 而非 TDZ,写点由 `__moduleSet` 就地覆盖。
|
||
fn push_var_initialized_entry(table: *mut ModuleNsTable, name_si: u32) {
|
||
push_entry(
|
||
table,
|
||
name_si,
|
||
ModuleNsOrigin::Local,
|
||
ModuleNsState::Value {
|
||
value: JsValue::undefined(),
|
||
initialized: true,
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 向条目表追加一条带来源身份的条目。
|
||
///
|
||
/// # 注意事项
|
||
/// - table 归调用方持有的 ns 对象所有,在同一 VM 世代内有效。
|
||
fn push_entry(table: *mut ModuleNsTable, name_si: u32, origin: ModuleNsOrigin, state: ModuleNsState) {
|
||
let entry = ModuleNsEntry { name_si, origin, state };
|
||
// SAFETY: table 归调用方持有的 ns 对象所有,在同一 VM 世代内有效。
|
||
unsafe {
|
||
(*table).entries.push(entry);
|
||
}
|
||
}
|
||
|
||
/// 模块命名空间字符串导出名的状态查询(不抛错、不分配、不写状态)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 返回 `None`:非 module namespace / 无条目表 / 键非条目(含 symbol 键)。
|
||
/// - 返回 `Uninitialized`:预注册未赋值,或条目挂在未初始化的共享 cell 上。
|
||
/// - 返回 `Initialized(v)`:`Value` 已初始化,或 cell 已初始化。
|
||
///
|
||
/// # 注意事项
|
||
/// - `Cell` 读依赖「cell 由 `alloc_cell` 分配并登记,参与 mark-sweep 回收、
|
||
/// `full_reset` 为收尾兜底」的生命周期契约;同一 VM 世代内指针有效。
|
||
pub fn module_ns_export(obj: &JsObject, key_si: u32) -> Option<ModuleNsQuery> {
|
||
let table = ns_table_ptr(obj);
|
||
if table.is_null() {
|
||
return None;
|
||
}
|
||
// SAFETY: table 指向本 ns 对象持有的 Box<ModuleNsTable>,随对象存活。
|
||
let table_ref = unsafe { &*table };
|
||
let entry = table_ref.entries.get(entry_index(table_ref, key_si)?)?;
|
||
// 歧义哨兵条目:该名已从命名空间省略,查询一律视为非导出(落普通路径)。
|
||
if matches!(entry.origin, ModuleNsOrigin::Ambiguous) {
|
||
return None;
|
||
}
|
||
match &entry.state {
|
||
ModuleNsState::Value { value, initialized } => {
|
||
if *initialized {
|
||
Some(ModuleNsQuery::Initialized(*value))
|
||
} else {
|
||
Some(ModuleNsQuery::Uninitialized)
|
||
}
|
||
}
|
||
ModuleNsState::Cell(cell) => {
|
||
if cell.is_null() {
|
||
return Some(ModuleNsQuery::Uninitialized);
|
||
}
|
||
// SAFETY: cell 由 alloc_cell 分配并登记,参与 mark-sweep、full_reset 收尾兜底。
|
||
let cell_ref = unsafe { &**cell };
|
||
if cell_ref.is_initialized() {
|
||
Some(ModuleNsQuery::Initialized(cell_ref.value))
|
||
} else {
|
||
Some(ModuleNsQuery::Uninitialized)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 收集条目表持有的 JsValue 边(已初始化 `Value` 与全部 `Cell` 内值),供 GC mark。
|
||
///
|
||
/// deferred namespace 对象额外持有 `[[Module]]`、`[[Namespace]]` 与失败缓存
|
||
/// `error`(Some 时)三条边:依赖模块函数、求值后命名空间对象与失败错误对象
|
||
/// 须经本单一注册点入 mark,漏登则对应值在 deferred ns 存活期间被回收,
|
||
/// 触发点解引用悬垂。
|
||
pub fn module_ns_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||
let table = ns_table_ptr(obj);
|
||
if table.is_null() {
|
||
return Vec::new();
|
||
}
|
||
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
|
||
let table_ref = unsafe { &*table };
|
||
let mut edges = Vec::new();
|
||
for entry in &table_ref.entries {
|
||
match &entry.state {
|
||
ModuleNsState::Value { value, initialized } => {
|
||
if *initialized {
|
||
edges.push(*value);
|
||
}
|
||
}
|
||
ModuleNsState::Cell(cell) => {
|
||
if !cell.is_null() {
|
||
// SAFETY: cell 生命周期见 `module_ns_export`。
|
||
edges.push(unsafe { (**cell).value });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if let Some(deferred) = &table_ref.deferred {
|
||
edges.push(deferred.module);
|
||
edges.push(deferred.namespace);
|
||
// 失败缓存的错误对象是 GC 边:漏登则 deferred ns 存活期被回收,再读解引用悬垂。
|
||
if let Some(err) = &deferred.error {
|
||
edges.push(*err);
|
||
}
|
||
}
|
||
edges
|
||
}
|
||
|
||
/// 收集条目表持有的共享 cell 指针(`Cell` 条目),供 GC mark cell 边。
|
||
/// cell 指针不经对象图、须独立入存活集(与 `module_ns_native_edges` 的值边互补)。
|
||
pub fn module_ns_cell_edges(obj: &JsObject) -> Vec<*mut Cell> {
|
||
let table = ns_table_ptr(obj);
|
||
if table.is_null() {
|
||
return Vec::new();
|
||
}
|
||
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
|
||
let table_ref = unsafe { &*table };
|
||
let mut out = Vec::new();
|
||
for entry in &table_ref.entries {
|
||
if let ModuleNsState::Cell(cell) = &entry.state {
|
||
if !cell.is_null() {
|
||
out.push(*cell);
|
||
}
|
||
}
|
||
}
|
||
out
|
||
}
|
||
|
||
/// 只读核算条目表的 native 数据字节(不释放),与 `drop_module_ns_native` 的
|
||
/// capacity 口径一致,供 GC 账目核算。
|
||
pub fn module_ns_native_size(obj: &JsObject) -> u64 {
|
||
let table = ns_table_ptr(obj);
|
||
if table.is_null() {
|
||
return 0;
|
||
}
|
||
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
|
||
let table_ref = unsafe { &*table };
|
||
(std::mem::size_of::<ModuleNsTable>() + table_ref.entries.capacity() * std::mem::size_of::<ModuleNsEntry>()) as u64
|
||
}
|
||
|
||
/// 释放条目表(`Box<ModuleNsTable>`),返回释放字节数供泄漏统计。
|
||
///
|
||
/// # 注意事项
|
||
/// - 每对象的表至多释放一次;释放后 `native_data` 置空保证幂等。
|
||
pub fn drop_module_ns_native(obj: &mut JsObject) -> u64 {
|
||
let table = ns_table_ptr(obj);
|
||
if table.is_null() {
|
||
return 0;
|
||
}
|
||
let bytes = module_ns_native_size(obj);
|
||
// SAFETY: table 由 install_ns_table 的 Box::into_raw 分配,本处恰好释放一次。
|
||
unsafe {
|
||
drop(Box::from_raw(table));
|
||
}
|
||
obj.set_native_data(std::ptr::null_mut());
|
||
bytes
|
||
}
|
||
|
||
/// `__modulePreRegister(ns, name, var_like)`:live 模块在 body 求值前预注册静态导出名。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 校验 ns 为 module namespace 对象,取导出名的 interned 键。
|
||
/// 2. 无表时分配;键已有条目时 no-op(幂等)。
|
||
/// 3. 经瞬态可扩展窗口在 ns 上定义真实数据属性 `undefined`(可写可枚举不可配置)。
|
||
/// 4. 追加条目:`var_like` 为真(var/函数类导出,属 `VarScopedDeclarations`)时
|
||
/// 初始化为 undefined;否则保持未初始化(lexical/class 的 TDZ)。随后按规范顺序
|
||
/// 重排导出键。
|
||
///
|
||
/// # 副作用
|
||
/// - 在 ns 上定义真实属性槽、挂载/扩展条目表,并重排 `[[OwnPropertyKeys]]` 顺序。
|
||
pub fn module_pre_register<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 4 {
|
||
return type_error(vm, "__modulePreRegister: 3 arguments required");
|
||
}
|
||
let ns_val = vm.reg(args[1]);
|
||
let ns_ptr = match vm.checked_object_ptr(ns_val, "__modulePreRegister: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__modulePreRegister: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let name_val = vm.reg(args[2]);
|
||
let name_si = vm.property_key_si(name_val);
|
||
let var_like = oxide_runtime_api::to_boolean(vm.reg(args[3]));
|
||
let obj = unsafe { &mut *ns_ptr };
|
||
if !obj.is_module_namespace() {
|
||
return type_error(vm, "__modulePreRegister: target is not a module namespace");
|
||
}
|
||
|
||
// 幂等:同名条目已存在时不再定义真实槽或重复入表。
|
||
let existing = ns_table_ptr(obj);
|
||
if !existing.is_null() && unsafe { entry_index(&*existing, name_si) }.is_some() {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
}
|
||
|
||
// 导出定义窗口:同 module_set,窗口内不运行 JS,错误路径先复位扩展性。
|
||
obj.set_extensible(true);
|
||
let result = vm.define_data_property(obj, name_si, JsValue::undefined(), ns_attrs());
|
||
obj.set_extensible(false);
|
||
if let Err(e) = result {
|
||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||
}
|
||
|
||
let table = if existing.is_null() { install_ns_table(obj) } else { existing };
|
||
if var_like {
|
||
push_var_initialized_entry(table, name_si);
|
||
} else {
|
||
push_uninitialized_entry(table, name_si);
|
||
}
|
||
crate::object::sort_namespace_exports(vm, obj);
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `__moduleObject()`:创建模块命名空间对象(null 原型,带 @@toStringTag Symbol 键)。
|
||
///
|
||
/// # 副作用
|
||
/// - 对象创建起即标记 module namespace exotic 并置 non-extensible:导出定义由
|
||
/// `module_set`/`module_star` 的瞬态可扩展窗口旁路,其余写/定义一律拒绝。
|
||
pub fn module_object<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||
let tag_si = encode_symbol_key(vm.realm_id(), TO_STRING_TAG_SYMBOL_ID);
|
||
let tag_val = vm.new_string("Module");
|
||
let obj_ref = unsafe { &mut *obj };
|
||
if let Err(e) = vm.define_data_property(obj_ref, tag_si, tag_val, PropAttributes::new(false, false, false)) {
|
||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||
}
|
||
obj_ref.set_module_namespace(true);
|
||
obj_ref.set_extensible(false);
|
||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||
}
|
||
|
||
/// 写入命名空间导出:命中条目则更新活值、真实属性槽与来源身份;否则经瞬态可扩展
|
||
/// 窗口定义真实属性槽,并在 ns 有条目表时追加条目。
|
||
///
|
||
/// # 边界与前提
|
||
/// - `Cell` 条目 no-op:被捕获导出的活值由共享 cell 承载,直接写槽会与 cell 分裂。
|
||
/// - 条目存在但真实槽缺失(歧义省略后显式导出重新定义):落新键定义路径补回槽。
|
||
///
|
||
/// # 副作用
|
||
/// - 修改条目表状态/来源身份与 ns 属性槽;新键或删槽重定义时重排
|
||
/// `[[OwnPropertyKeys]]` 顺序。
|
||
fn set_export<H: VmHost>(
|
||
vm: &mut H, obj: &mut JsObject, name_si: u32, value: JsValue, origin: ModuleNsOrigin,
|
||
) -> Result<(), String> {
|
||
let table = ns_table_ptr(obj);
|
||
let existing = if table.is_null() {
|
||
None
|
||
} else {
|
||
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
|
||
unsafe { entry_index(&*table, name_si) }
|
||
};
|
||
|
||
// Cell 条目 no-op:被捕获导出的活值由共享 cell 承载,写槽会与 cell 分裂。
|
||
if let Some(idx) = existing {
|
||
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
|
||
if matches!(unsafe { &(&(*table).entries)[idx].state }, ModuleNsState::Cell(_)) {
|
||
return Ok(());
|
||
}
|
||
}
|
||
|
||
// 条目已存在且真实槽仍在:原地更新活值/来源,键集不变无需重排。
|
||
if let Some(idx) = existing {
|
||
if let Some(pos) = vm.get_own_property_slot(obj, name_si) {
|
||
obj.set_prop_at(pos, value);
|
||
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
|
||
unsafe {
|
||
(&mut (*table).entries)[idx].origin = origin;
|
||
(&mut (*table).entries)[idx].state = ModuleNsState::Value { value, initialized: true };
|
||
}
|
||
return Ok(());
|
||
}
|
||
// 歧义省略删槽后显式导出重新定义:落下方定义路径补回真实槽。
|
||
}
|
||
|
||
// 导出定义窗口:命名空间创建起不可扩展,定义新导出须瞬态放开扩展性。窗口内
|
||
// 不运行 JS,无可观察副作用;错误路径先复位以维持 non-extensible 不变式。
|
||
obj.set_extensible(true);
|
||
let result = vm.define_data_property(obj, name_si, value, ns_attrs());
|
||
obj.set_extensible(false);
|
||
result?;
|
||
|
||
if !table.is_null() {
|
||
if let Some(idx) = existing {
|
||
// SAFETY: idx 来自刚完成的表内查找;重定义后同步条目,勿重复入表。
|
||
unsafe {
|
||
(&mut (*table).entries)[idx].origin = origin;
|
||
(&mut (*table).entries)[idx].state = ModuleNsState::Value { value, initialized: true };
|
||
}
|
||
} else {
|
||
push_entry(table, name_si, origin, ModuleNsState::Value { value, initialized: true });
|
||
}
|
||
}
|
||
// 每次键集变化后重排:自导入在 body 内即可见有序的 [[OwnPropertyKeys]]。
|
||
crate::object::sort_namespace_exports(vm, obj);
|
||
Ok(())
|
||
}
|
||
|
||
/// 从命名空间属性表移除一个导出槽(歧义省略用),保留其余字符串与 Symbol 键及其
|
||
/// 描述符。
|
||
///
|
||
/// # 注意事项
|
||
/// - 命名空间导出属性为 non-configurable,本函数不经 `[[Delete]]`,直接重建属性表
|
||
/// 以同时保住 `@@toStringTag` Symbol 键。
|
||
fn remove_export_slot<H: VmHost>(vm: &mut H, obj: &mut JsObject, name_si: u32) {
|
||
let mut retained: Vec<(u32, JsValue, Option<PropMetaEntry>)> = crate::object::walk_own_keys(vm, obj)
|
||
.into_iter()
|
||
.filter(|(si, _)| *si != name_si)
|
||
.map(|(si, pos)| (si, obj.get_prop_at(pos), obj.prop_meta_at(pos)))
|
||
.collect();
|
||
let symbols: Vec<(u32, JsValue, Option<PropMetaEntry>)> = crate::object::walk_own_symbol_keys(vm, obj)
|
||
.into_iter()
|
||
.map(|(si, pos)| (si, obj.get_prop_at(pos), obj.prop_meta_at(pos)))
|
||
.collect();
|
||
obj.set_shape_id(EMPTY_SHAPE_ID);
|
||
obj.clear_props();
|
||
for (si, value, meta) in retained.drain(..).chain(symbols) {
|
||
let shape = vm.make_shape(obj.shape_id(), si);
|
||
obj.set_shape_id(shape);
|
||
let pos = obj.push_prop(value);
|
||
if let Some(meta) = meta {
|
||
if meta.is_accessor {
|
||
obj.set_accessor_meta(pos, meta.get, meta.set, meta.attributes);
|
||
} else {
|
||
obj.set_data_meta(pos, meta.attributes);
|
||
}
|
||
}
|
||
}
|
||
obj.bump_generation();
|
||
}
|
||
|
||
/// `__moduleSet(ns, name, value)`:在命名空间上定义或更新本地导出属性。
|
||
///
|
||
/// # 副作用
|
||
/// - 同 `set_export`;本地导出来源身份恒为 `Local`,覆盖先前 star 条目时复位来源。
|
||
pub fn module_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 4 {
|
||
return type_error(vm, "__moduleSet: 3 arguments required");
|
||
}
|
||
let ns_val = vm.reg(args[1]);
|
||
let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleSet: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleSet: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let name_val = vm.reg(args[2]);
|
||
let value = vm.reg(args[3]);
|
||
let name_si = vm.property_key_si(name_val);
|
||
let obj = unsafe { &mut *ns_ptr };
|
||
if let Err(e) = set_export(vm, obj, name_si, value, ModuleNsOrigin::Local) {
|
||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||
}
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `__moduleSetCell(ns, name, cell_idx)`:把导出条目挂到模块顶层帧的共享 cell。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 校验 ns 为 module namespace 且已有条目表与同名条目(预注册保证存在)。
|
||
/// 2. 取当前字节码帧第 `cell_idx` 个 cell(`VmHost::module_frame_cell`)。
|
||
/// 3. 条目状态置 `Cell`,并把真实槽值同步为 cell 当前值一次。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 条目必须已预注册;无表、条目未命中或 cell 缺失均抛 TypeError,不静默退化。
|
||
/// - `cell_idx` 来自 emit 期捕获索引,同一模块顶层帧内稳定。
|
||
///
|
||
/// # 副作用
|
||
/// - 修改条目表状态与 ns 真实槽值;后续活值由共享 cell 承载。
|
||
pub fn module_set_cell<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 4 {
|
||
return type_error(vm, "__moduleSetCell: 3 arguments required");
|
||
}
|
||
let ns_val = vm.reg(args[1]);
|
||
let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleSetCell: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleSetCell: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let name_val = vm.reg(args[2]);
|
||
let name_si = vm.property_key_si(name_val);
|
||
let cell_idx = oxide_runtime_api::to_int32(vm.reg(args[3])) as u32;
|
||
let obj = unsafe { &mut *ns_ptr };
|
||
if !obj.is_module_namespace() {
|
||
return type_error(vm, "__moduleSetCell: target is not a module namespace");
|
||
}
|
||
let table = ns_table_ptr(obj);
|
||
if table.is_null() {
|
||
return type_error(vm, "__moduleSetCell: export is not registered");
|
||
}
|
||
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
|
||
let Some(idx) = (unsafe { entry_index(&*table, name_si) }) else {
|
||
return type_error(vm, "__moduleSetCell: export is not registered");
|
||
};
|
||
let Some(cell) = vm.module_frame_cell(cell_idx) else {
|
||
return type_error(vm, "__moduleSetCell: cell is not available");
|
||
};
|
||
// SAFETY: cell 由 alloc_cell 分配并登记,参与 mark-sweep、full_reset 收尾兜底。
|
||
let value = unsafe { (*cell).value };
|
||
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
|
||
unsafe {
|
||
(&mut (*table).entries)[idx].state = ModuleNsState::Cell(cell);
|
||
}
|
||
// 真实槽同步一次 cell 当前值:条目表是活值权威,槽仅保留存在性与占位。
|
||
if let Some(pos) = vm.get_own_property_slot(obj, name_si) {
|
||
obj.set_prop_at(pos, value);
|
||
}
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `__moduleSetReexport(ns, name, value, dep_path, imported)`:注册显式再导出
|
||
/// (`export { x } from` / `export * as ns from` / 导入绑定再导出),记录跨模块
|
||
/// 来源身份 `(dep_path, imported)`,使同一绑定经不同路径转发时来源 token 相等。
|
||
///
|
||
/// # 副作用
|
||
/// - 同 `module_set`;ns 无条目表时惰性安装,来源标为 `direct` 再导出。
|
||
pub fn module_set_reexport<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 6 {
|
||
return type_error(vm, "__moduleSetReexport: 5 arguments required");
|
||
}
|
||
let ns_val = vm.reg(args[1]);
|
||
let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleSetReexport: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleSetReexport: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let name_val = vm.reg(args[2]);
|
||
let value = vm.reg(args[3]);
|
||
let path_val = vm.reg(args[4]);
|
||
let imported_val = vm.reg(args[5]);
|
||
let name_si = vm.property_key_si(name_val);
|
||
let Some(dep_path) = vm.lookup_str(path_val) else {
|
||
return type_error(vm, "__moduleSetReexport: module path is not a string");
|
||
};
|
||
let Some(imported) = vm.lookup_str(imported_val) else {
|
||
return type_error(vm, "__moduleSetReexport: imported name is not a string");
|
||
};
|
||
let obj = unsafe { &mut *ns_ptr };
|
||
if !obj.is_module_namespace() {
|
||
return type_error(vm, "__moduleSetReexport: target is not a module namespace");
|
||
}
|
||
if ns_table_ptr(obj).is_null() {
|
||
install_ns_table(obj);
|
||
}
|
||
let path_id = vm.perm_intern(&dep_path);
|
||
let binding_id = vm.perm_intern(&imported);
|
||
let origin = ModuleNsOrigin::Reexport {
|
||
path_id,
|
||
binding_id,
|
||
direct: true,
|
||
};
|
||
if let Err(e) = set_export(vm, obj, name_si, value, origin) {
|
||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||
}
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `__moduleAlias(ns, name, fallback)`:自别名收尾刷新用。命名空间存在该导出时
|
||
/// 返回其值,否则返回传入的 `fallback`(不抛错):star 未提供该名时占位值原样
|
||
/// 保留,与现行「星号未提供该名时静默 undefined」行为一致。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 命名空间无该导出时不抛错(区别于 `__moduleGet` 的 TypeError 与
|
||
/// `__moduleLinkGet` 的 SyntaxError),缺失臂返回 `fallback` 原值。
|
||
///
|
||
/// # 副作用
|
||
/// - 无:只读命名空间属性,不修改条目表。
|
||
pub fn module_alias<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 4 {
|
||
return type_error(vm, "__moduleAlias: 3 arguments required");
|
||
}
|
||
let ns_val = vm.reg(args[1]);
|
||
let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleAlias: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleAlias: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let name_val = vm.reg(args[2]);
|
||
let name_si = vm.property_key_si(name_val);
|
||
let fallback = vm.reg(args[3]);
|
||
let obj = unsafe { &*ns_ptr };
|
||
if vm.get_own_property_slot(obj, name_si).is_some() {
|
||
match vm.ordinary_get(obj, name_si, ns_val) {
|
||
Ok(v) => NativeResult::Ok(v),
|
||
Err(e) => NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
}
|
||
} else {
|
||
NativeResult::Ok(fallback)
|
||
}
|
||
}
|
||
|
||
/// `__moduleGet(ns, name)`:读取命名空间导出属性。
|
||
pub fn module_get<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 3 {
|
||
return type_error(vm, "__moduleGet: 2 arguments required");
|
||
}
|
||
let ns_val = vm.reg(args[1]);
|
||
let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleGet: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleGet: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let name_val = vm.reg(args[2]);
|
||
let name_si = vm.property_key_si(name_val);
|
||
let obj = unsafe { &*ns_ptr };
|
||
// 模块命名空间 exotic Get:未导出属性抛 TypeError(而非 undefined)。
|
||
if vm.get_own_property_slot(obj, name_si).is_none() {
|
||
return type_error(vm, "__moduleGet: requested export is not exported");
|
||
}
|
||
match vm.ordinary_get(obj, name_si, ns_val) {
|
||
Ok(v) => NativeResult::Ok(v),
|
||
Err(e) => NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
}
|
||
}
|
||
|
||
/// `__moduleLinkGet(ns, name)`:import 绑定初始化(链接期语义)。
|
||
/// 与 `__moduleGet` 的区别:缺失导出抛 SyntaxError(模块声明实例化期的
|
||
/// 绑定解析错误),而非命名空间访问的 TypeError。
|
||
pub fn module_link_get<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 3 {
|
||
return type_error(vm, "__moduleLinkGet: 2 arguments required");
|
||
}
|
||
let ns_val = vm.reg(args[1]);
|
||
let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleLinkGet: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleLinkGet: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let name_val = vm.reg(args[2]);
|
||
let name_si = vm.property_key_si(name_val);
|
||
let obj = unsafe { &*ns_ptr };
|
||
if vm.get_own_property_slot(obj, name_si).is_none() {
|
||
return NativeResult::Err(crate::error::create_syntax_error(vm, "requested module export is not exported"));
|
||
}
|
||
match vm.ordinary_get(obj, name_si, ns_val) {
|
||
Ok(v) => NativeResult::Ok(v),
|
||
Err(e) => NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
}
|
||
}
|
||
|
||
/// 取 src 命名空间某导出名的来源身份。
|
||
///
|
||
/// # 边界与前提
|
||
/// - src 条目表命中且来源为 `Reexport`:沿用其 `(模块, 绑定)`,`direct` 归一为
|
||
/// false(经 star 复制到 dst 的是间接提供)。
|
||
/// - 其余(无表 / `Local` / 条目缺失):回退 `(src_path_id, name_si)`。只声明本地
|
||
/// 导出的源不建条目表,回退 token 使不同源的同名本地导出身份不同。
|
||
fn star_source_origin(src: &JsObject, name_si: u32, src_path_id: u32) -> ModuleNsOrigin {
|
||
let table = ns_table_ptr(src);
|
||
if !table.is_null() {
|
||
// SAFETY: table 归 src 持有,生命周期见 `module_ns_export`。
|
||
if let Some(idx) = unsafe { entry_index(&*table, name_si) } {
|
||
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
|
||
if let ModuleNsOrigin::Reexport { path_id, binding_id, .. } = unsafe { (&(*table).entries)[idx].origin } {
|
||
return ModuleNsOrigin::Reexport {
|
||
path_id,
|
||
binding_id,
|
||
direct: false,
|
||
};
|
||
}
|
||
}
|
||
}
|
||
ModuleNsOrigin::Reexport {
|
||
path_id: src_path_id,
|
||
binding_id: name_si,
|
||
direct: false,
|
||
}
|
||
}
|
||
|
||
/// `__moduleStar(dst, src, src_path)`:把 src 命名空间的可枚举导出(除 default)
|
||
/// 复制到 dst,并按来源身份解析同名冲突。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 每个 src 导出名取来源 `(模块, 绑定)`;`default` 跳过。
|
||
/// 2. dst 条目为 `Ambiguous`:恒跳过(一旦歧义恒省略)。
|
||
/// 3. dst 条目为显式导出(`Local` / direct `Reexport`):显式恒胜,跳过。
|
||
/// 4. dst 条目为间接 `Reexport`:`(模块, 绑定)` 相同则保留;不同则删槽并记
|
||
/// `Ambiguous`。
|
||
/// 5. dst 无条目:真实槽已存在视为显式导出跳过;否则复制槽并记间接来源。
|
||
///
|
||
/// # 副作用
|
||
/// - 在 dst 上定义/删除真实属性槽,惰性安装条目表,并按规范顺序重排导出键。
|
||
pub fn module_star<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 4 {
|
||
return type_error(vm, "__moduleStar: 3 arguments required");
|
||
}
|
||
let dst_val = vm.reg(args[1]);
|
||
let src_val = vm.reg(args[2]);
|
||
let src_path_val = vm.reg(args[3]);
|
||
let dst_ptr = match vm.checked_object_ptr(dst_val, "__moduleStar: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleStar: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let src_ptr = match vm.checked_object_ptr(src_val, "__moduleStar: source is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleStar: source is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let Some(src_path) = vm.lookup_str(src_path_val) else {
|
||
return type_error(vm, "__moduleStar: source path is not a string");
|
||
};
|
||
let src_path_id = vm.perm_intern(&src_path);
|
||
let src = unsafe { &*src_ptr };
|
||
let default_si = vm.perm_intern("default");
|
||
let keys = crate::object::walk_own_keys(vm, src);
|
||
let dst = unsafe { &mut *dst_ptr };
|
||
for (name_si, pos) in keys {
|
||
if name_si == default_si {
|
||
continue;
|
||
}
|
||
let src_origin = star_source_origin(src, name_si, src_path_id);
|
||
// live dst:star 复制的新导出名须一并入表,否则活值读/枚举看不到条目。
|
||
let dst_table = ns_table_ptr(dst);
|
||
let dst_idx = if dst_table.is_null() {
|
||
None
|
||
} else {
|
||
unsafe { entry_index(&*dst_table, name_si) }
|
||
};
|
||
match dst_idx {
|
||
Some(idx) => {
|
||
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
|
||
let dst_origin = unsafe { (&(*dst_table).entries)[idx].origin };
|
||
match dst_origin {
|
||
ModuleNsOrigin::Ambiguous => continue,
|
||
ModuleNsOrigin::Local | ModuleNsOrigin::Reexport { direct: true, .. } => continue,
|
||
ModuleNsOrigin::Reexport { path_id, binding_id, .. } => {
|
||
let ModuleNsOrigin::Reexport {
|
||
path_id: sp, binding_id: sb, ..
|
||
} = src_origin
|
||
else {
|
||
continue;
|
||
};
|
||
if path_id == sp && binding_id == sb {
|
||
continue;
|
||
}
|
||
// 不同绑定同名:删除导出槽并记歧义哨兵,清空可回收的活值边。
|
||
remove_export_slot(vm, dst, name_si);
|
||
// SAFETY: idx 仍指向本表条目,删除槽不改动条目表。
|
||
unsafe {
|
||
(&mut (*dst_table).entries)[idx].origin = ModuleNsOrigin::Ambiguous;
|
||
(&mut (*dst_table).entries)[idx].state = ModuleNsState::Value {
|
||
value: JsValue::undefined(),
|
||
initialized: false,
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
None => {
|
||
// 真实槽已存在但无条目:视为先前显式导出(本地导出不建表),显式恒胜。
|
||
if vm.get_own_property_slot(dst, name_si).is_some() {
|
||
continue;
|
||
}
|
||
let value = src.get_prop_at(pos);
|
||
let table = if dst_table.is_null() { install_ns_table(dst) } else { dst_table };
|
||
// star 导出同 module_set:定义新导出走瞬态可扩展窗口,错误路径先复位。
|
||
dst.set_extensible(true);
|
||
let result = vm.define_data_property(dst, name_si, value, ns_attrs());
|
||
dst.set_extensible(false);
|
||
if let Err(e) = result {
|
||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||
}
|
||
// 快照语义:star 名立即视为已初始化。
|
||
push_entry(table, name_si, src_origin, ModuleNsState::Value { value, initialized: true });
|
||
}
|
||
}
|
||
}
|
||
// star 复制后重排:直接导出与 star 导出的合并键序保持规范顺序。
|
||
crate::object::sort_namespace_exports(vm, dst);
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `__moduleSeal(ns)`:封冻命名空间(不可扩展)。
|
||
pub fn module_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 2 {
|
||
return type_error(vm, "__moduleSeal: 1 argument required");
|
||
}
|
||
let ns_val = vm.reg(args[1]);
|
||
let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleSeal: target is not an object") {
|
||
Ok(Some(p)) => p,
|
||
Ok(None) => return type_error(vm, "__moduleSeal: target is not an object"),
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
let obj = unsafe { &mut *ns_ptr };
|
||
obj.set_extensible(false);
|
||
NativeResult::Ok(ns_val)
|
||
}
|
||
|
||
/// `__moduleEval(fn, path)`:求值依赖模块(fn 为编译期 CREATE_CLOSURE 的函数对象,
|
||
/// path 为模块规范路径),返回其命名空间。有状态:per-module 状态按 path 键防双求值。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 查 per-module 状态(按 path 键):已求值返缓存命名空间(no-op);
|
||
/// 启动中(capability promise)已结算取结算值返命名空间,未结算返 promise
|
||
/// 本体(调用方聚合 gate 据 async 标志 await 之,不重跑、不抛错)。
|
||
/// 2. 未求值:启动模块(`call_function_sync`,TLA 模块挂起返 promise、同步模块
|
||
/// 返命名空间),置状态(pending 载 promise / evaluated 载命名空间),返返值。
|
||
///
|
||
/// # 副作用
|
||
/// - 调用前置「正在求值模块」标记为 `Some(fn)`、返回后还原(save/restore,嵌套
|
||
/// `__moduleEval` 安全):deferred namespace 求值触发的 cyclic 守卫据此判自求值。
|
||
/// - 写 per-module 状态(键 = path)。
|
||
pub fn module_eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 3 {
|
||
return type_error(vm, "__moduleEval: 2 arguments required");
|
||
}
|
||
let fn_val = vm.reg(args[1]);
|
||
let path_val = vm.reg(args[2]);
|
||
if !fn_val.is_object() {
|
||
return type_error(vm, "__moduleEval: target is not a function");
|
||
}
|
||
let ptr = fn_val.as_js_object_ptr();
|
||
if ptr.is_null() || !unsafe { (*ptr).is_function() } {
|
||
return type_error(vm, "__moduleEval: target is not a function");
|
||
}
|
||
let path = to_string(path_val);
|
||
// 已求值:直接返缓存命名空间(no-op,防 hoisted TLA 模块与 deferred 模块
|
||
// prelude 的双求值)。
|
||
if let Some(state) = vm.get_module_eval_state(&path) {
|
||
if state.evaluated {
|
||
return NativeResult::Ok(state.namespace);
|
||
}
|
||
// 启动中:capability promise 已结算(调用方在聚合 gate 之后),取结算值;
|
||
// 未结算(提升 TLA 模块互为依赖,先启动者尚未被外层 gate 排空)返 promise
|
||
// 本体,调用方聚合 gate 据 async 标志 await 之——不重跑、不抛错。
|
||
let promise = state.namespace;
|
||
return match vm.promise_settled_value(promise) {
|
||
Some((true, value)) => {
|
||
vm.set_module_eval_state(&path, true, value);
|
||
NativeResult::Ok(value)
|
||
}
|
||
Some((false, reason)) => {
|
||
let text = to_string(reason);
|
||
NativeResult::Err(crate::error::create_from_text(vm, &text))
|
||
}
|
||
None => NativeResult::Ok(promise),
|
||
};
|
||
}
|
||
// 置位标记,调用返回后无条件还原(异常展开亦不残留)。
|
||
let saved = vm.evaluating_module();
|
||
vm.set_evaluating_module(Some(fn_val));
|
||
let result = match vm.call_function_sync(fn_val, JsValue::undefined(), &[]) {
|
||
Ok(v) => {
|
||
// TLA 模块返 capability promise(挂起未结算),同步模块返命名空间。
|
||
if vm.is_promise_value(v) {
|
||
vm.set_module_eval_state(&path, false, v);
|
||
} else {
|
||
vm.set_module_eval_state(&path, true, v);
|
||
}
|
||
NativeResult::Ok(v)
|
||
}
|
||
Err(e) => NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
vm.set_evaluating_module(saved);
|
||
result
|
||
}
|
||
|
||
/// `__moduleDeferObject(fn, real_ns, path)`:创建 deferred namespace 对象
|
||
/// (`import defer * as` 产物)。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 校验 `fn` 为函数对象(依赖模块函数,编译期 CREATE_CLOSURE 的闭包对象)
|
||
/// 或 undefined(哨兵:自导入/祖先引用,模块函数在导入方 prelude 期不可得);
|
||
/// 校验 `real_ns` 为对象(mixed:模块已急切求值)或 undefined。
|
||
/// 2. 建 null 原型对象,定义 `@@toStringTag` = "Deferred Module"(不可写不可枚举不可配置)。
|
||
/// 3. 置 module namespace + deferred 双标志,置 non-extensible。
|
||
/// 4. 安装条目表并写状态盒:`[[Module]]` = fn、`[[Evaluated]]` = `real_ns` 为对象、
|
||
/// `[[Namespace]]` = `real_ns`(或 undefined)、失败错误缓存恒 `None`、
|
||
/// `path` = 依赖模块规范路径(求值触发点查 per-module 状态防双求值)。
|
||
///
|
||
/// # 副作用
|
||
/// - 新对象创建起即标记 module namespace exotic 并置 non-extensible(同 `__moduleObject`);
|
||
/// 状态盒内部槽由本入口唯一安装。
|
||
pub fn module_defer_object<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 4 {
|
||
return type_error(vm, "__moduleDeferObject: 3 arguments required");
|
||
}
|
||
let fn_val = vm.reg(args[1]);
|
||
let ns_val = vm.reg(args[2]);
|
||
let path = to_string(vm.reg(args[3]));
|
||
// fn 为函数或 undefined(哨兵);其余抛 TypeError。
|
||
let fn_is_function = fn_val.is_object() && {
|
||
let ptr = fn_val.as_js_object_ptr();
|
||
!ptr.is_null() && unsafe { (*ptr).is_function() }
|
||
};
|
||
if !fn_is_function && !fn_val.is_undefined() {
|
||
return type_error(vm, "__moduleDeferObject: first argument must be a function or undefined");
|
||
}
|
||
// real_ns 为对象(mixed 已求值态)或 undefined;其余抛 TypeError。
|
||
if !ns_val.is_object() && !ns_val.is_undefined() {
|
||
return type_error(vm, "__moduleDeferObject: second argument must be an object or undefined");
|
||
}
|
||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||
let tag_si = encode_symbol_key(vm.realm_id(), TO_STRING_TAG_SYMBOL_ID);
|
||
let tag_val = vm.new_string("Deferred Module");
|
||
let obj_ref = unsafe { &mut *obj };
|
||
if let Err(e) = vm.define_data_property(obj_ref, tag_si, tag_val, PropAttributes::new(false, false, false)) {
|
||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||
}
|
||
obj_ref.set_module_namespace(true);
|
||
obj_ref.set_deferred(true);
|
||
obj_ref.set_extensible(false);
|
||
let table = install_ns_table(obj_ref);
|
||
// SAFETY: table 由 install_ns_table 的 Box::into_raw 分配,本处恰好写入一次。
|
||
unsafe {
|
||
(*table).deferred = Some(DeferredNsState {
|
||
module: fn_val,
|
||
evaluated: ns_val.is_object(),
|
||
namespace: ns_val,
|
||
error: None,
|
||
path,
|
||
});
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||
}
|
||
|
||
/// `__moduleData(kind, content)`:构造数据模块命名空间(json/text;bytes 未支持)。
|
||
///
|
||
/// # 副作用
|
||
/// - 同 `__moduleObject`:标记 module namespace exotic 并置 non-extensible。
|
||
pub fn module_data<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 3 {
|
||
return type_error(vm, "__moduleData: 2 arguments required");
|
||
}
|
||
let kind_val = vm.reg(args[1]);
|
||
let content_val = vm.reg(args[2]);
|
||
if !kind_val.is_string() || !content_val.is_string() {
|
||
return type_error(vm, "__moduleData: kind/content must be strings");
|
||
}
|
||
let kind = oxide_runtime_api::to_string(kind_val);
|
||
let default_val = match kind.as_str() {
|
||
"json" => {
|
||
// 复用 JSON.parse 的 serde 管线:args[1] 即内容字符串。
|
||
let parse_result = crate::json::json_parse(vm, &[args[0], args[2]]);
|
||
match parse_result {
|
||
NativeResult::Ok(v) => v,
|
||
NativeResult::Err(e) => return NativeResult::Err(e),
|
||
NativeResult::TailCall { .. } => return type_error(vm, "__moduleData: unexpected tail call"),
|
||
}
|
||
}
|
||
"text" => {
|
||
// lossy 文本拷贝出 owned String 再入堆(模块数据文本桥接,131.2 边界)。
|
||
let text = vm.lookup_str(content_val).unwrap_or_default();
|
||
vm.new_string_owned(text)
|
||
}
|
||
_ => return type_error(vm, "__moduleData: unsupported data kind"),
|
||
};
|
||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||
let default_si = vm.perm_intern("default");
|
||
let obj_ref = unsafe { &mut *obj };
|
||
if let Err(e) = vm.define_data_property(obj_ref, default_si, default_val, ns_attrs()) {
|
||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||
}
|
||
obj_ref.set_module_namespace(true);
|
||
obj_ref.set_extensible(false);
|
||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||
}
|
||
|
||
/// `%AbstractModuleSource%` 构造器入口:被调用(含 `new`)恒抛 TypeError。
|
||
///
|
||
/// 规范 28.1.1.1:该内建不可被构造,实例只能由宿主(source-phase 导入的
|
||
/// ModuleSource 提供)产生。
|
||
pub fn abstract_module_source_ctor<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||
NativeResult::Err(crate::error::create_type_error(vm, "AbstractModuleSource is not a constructor"))
|
||
}
|
||
|
||
/// `%AbstractModuleSource%.prototype` 上 `Symbol.toStringTag` 访问器的 getter:
|
||
/// 返回实例的模块源类名,非实例(非对象 this 或无类名标记)返回 undefined。
|
||
///
|
||
/// # 步骤
|
||
/// 1. `this` 非对象 → undefined(规范 28.3.3.2 第 2 步)。
|
||
/// 2. 查全局符号注册表取类名符号键;未登记或 `this` 无该 own 属性 → undefined。
|
||
/// 3. 有类名标记 → 返回其值(宿主模块源类名,如 `AbstractModuleSource`)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 类名标记是 `__moduleSourceObject` 在实例上定义的符号键 own 属性(引擎内
|
||
/// 表示宿主 `[[ModuleSourceClassName]]` 内部槽),非实例对象无此属性。
|
||
pub fn abstract_module_source_to_string_tag<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
// 访问器 getter 的 `this` 在 args[0](receiver 槽 253),实参区从 args[1] 起。
|
||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||
if !this_val.is_object() {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
}
|
||
let Some(idx) = vm.symbol_lookup_global(MODULE_SOURCE_CLASS_NAME_SYMBOL_KEY) else {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
};
|
||
let key = encode_symbol_key(vm.realm_id(), idx);
|
||
let obj = unsafe { &*this_val.as_js_object_ptr() };
|
||
if vm.get_own_property_slot(obj, key).is_none() {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
}
|
||
match vm.ordinary_get(obj, key, this_val) {
|
||
Ok(v) => NativeResult::Ok(v),
|
||
Err(e) => NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
}
|
||
}
|
||
|
||
/// ModuleSource 类名符号的宿主注册表键(`Symbol.for` 全局注册表口径)。
|
||
const MODULE_SOURCE_CLASS_NAME_SYMBOL_KEY: &str = "oxide.moduleSourceClassName";
|
||
|
||
/// `__moduleSourceObject()`:返回一个 ModuleSource 对象(`%AbstractModuleSource%`
|
||
/// 实例),供 source-phase 导入的局部名绑定。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 经 `$262.AbstractModuleSource.prototype` 取内建原型(宿主绑定在绑定期
|
||
/// 安装);宿主或内建缺失时抛 TypeError。
|
||
/// 2. 以该原型分配新对象。
|
||
/// 3. 定义类名符号 own 属性(非枚举),供 `@@toStringTag` getter 读回类名。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 每次调用产生新实例:宿主对同一 `<module source>` 模块记录的 ModuleSource
|
||
/// 身份由依赖求值缓存(per-module 状态)承载,对象本体不跨调用共享。
|
||
///
|
||
/// # 副作用
|
||
/// - 分配新对象并定义符号键属性;首次调用时登记类名符号入全局注册表。
|
||
pub fn module_source_object<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||
let global = vm.global_object();
|
||
let global_val = JsValue::from_js_object(global.as_ptr() as *mut JsObject);
|
||
let host_si = vm.perm_intern("$262");
|
||
let host = match vm.ordinary_get(&global, host_si, global_val) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
if !host.is_object() {
|
||
return type_error(vm, "__moduleSourceObject: $262 host is not bound");
|
||
}
|
||
let host_obj = unsafe { &*host.as_js_object_ptr() };
|
||
let ctor_si = vm.perm_intern("AbstractModuleSource");
|
||
let ctor = match vm.ordinary_get(host_obj, ctor_si, host) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
if !ctor.is_object() {
|
||
return type_error(vm, "__moduleSourceObject: AbstractModuleSource is not bound");
|
||
}
|
||
let ctor_obj = unsafe { &*ctor.as_js_object_ptr() };
|
||
let proto_si = vm.perm_intern("prototype");
|
||
let proto = match vm.ordinary_get(ctor_obj, proto_si, ctor) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
|
||
};
|
||
if !proto.is_object() {
|
||
return type_error(vm, "__moduleSourceObject: AbstractModuleSource.prototype is not an object");
|
||
}
|
||
|
||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto));
|
||
let obj_ref = unsafe { &mut *obj };
|
||
let idx = match vm.symbol_lookup_global(MODULE_SOURCE_CLASS_NAME_SYMBOL_KEY) {
|
||
Some(idx) => idx,
|
||
None => {
|
||
let idx = vm.symbol_intern(Some(MODULE_SOURCE_CLASS_NAME_SYMBOL_KEY.to_string()));
|
||
vm.symbol_register_global(MODULE_SOURCE_CLASS_NAME_SYMBOL_KEY.to_string(), idx);
|
||
idx
|
||
}
|
||
};
|
||
let key = encode_symbol_key(vm.realm_id(), idx);
|
||
let class_name = vm.new_string("AbstractModuleSource");
|
||
if let Err(e) = vm.define_data_property(obj_ref, key, class_name, PropAttributes::new(true, false, true)) {
|
||
return NativeResult::Err(crate::error::create_error(vm, &e));
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use oxide_compiler::DefaultCompilerService;
|
||
use oxide_vm::vm::Vm;
|
||
use std::sync::Arc;
|
||
|
||
/// 建一个带编译服务的 VM 与一个依赖模块函数(deferred ns 的 `[[Module]]` 载荷)。
|
||
fn vm_with_function() -> (Vm, JsValue) {
|
||
let mut vm = Vm::new();
|
||
vm.set_compiler_service(Arc::new(DefaultCompilerService));
|
||
let fn_val = vm
|
||
.create_dynamic_function(&[], "return 42;", false, false)
|
||
.expect("create function");
|
||
(vm, fn_val)
|
||
}
|
||
|
||
#[test]
|
||
fn module_defer_object_installs_state_box() {
|
||
let (mut vm, fn_val) = vm_with_function();
|
||
vm.set_reg(1, fn_val);
|
||
vm.set_reg(2, JsValue::undefined());
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let ns_val = module_defer_object(&mut vm, &[0, 1, 2, 3]).unwrap();
|
||
assert!(ns_val.is_object());
|
||
let ns_ptr = ns_val.as_js_object_ptr();
|
||
assert!(!ns_ptr.is_null());
|
||
let ns_obj = unsafe { &*ns_ptr };
|
||
assert!(ns_obj.is_module_namespace(), "deferred ns 应标记 module namespace");
|
||
assert!(ns_obj.is_deferred(), "deferred ns 应标记 deferred");
|
||
assert!(!ns_obj.is_extensible(), "deferred ns 应 non-extensible");
|
||
|
||
// 状态盒三内部槽安装正确:[[Module]] = fn、[[Evaluated]] = false、[[Namespace]] = undefined。
|
||
let state = deferred_state(ns_obj).expect("deferred state 应已安装");
|
||
assert_eq!(state.module, fn_val, "[[Module]] 应存依赖模块函数");
|
||
assert!(!state.evaluated, "[[Evaluated]] 初始应为 false");
|
||
assert_eq!(state.namespace, JsValue::undefined(), "[[Namespace]] 求值前应为 undefined");
|
||
}
|
||
|
||
#[test]
|
||
fn module_defer_object_mixed_carries_evaluated_state() {
|
||
let (mut vm, fn_val) = vm_with_function();
|
||
// 真实 ns 对象(mixed:模块已急切求值)。
|
||
let real_ns = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||
let ns_val = JsValue::from_js_object(real_ns);
|
||
vm.set_reg(1, fn_val);
|
||
vm.set_reg(2, ns_val);
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let defer_ns = module_defer_object(&mut vm, &[0, 1, 2, 3]).unwrap();
|
||
let ns_obj = unsafe { &*defer_ns.as_js_object_ptr() };
|
||
let state = deferred_state(ns_obj).expect("deferred state 应已安装");
|
||
assert_eq!(state.module, fn_val, "[[Module]] 应存依赖模块函数");
|
||
assert!(state.evaluated, "mixed 依赖 [[Evaluated]] 构造时应为 true");
|
||
assert_eq!(state.namespace, ns_val, "[[Namespace]] 应存真实 ns");
|
||
}
|
||
|
||
#[test]
|
||
fn module_defer_object_sentinel_accepts_undefined_module() {
|
||
let (mut vm, _) = vm_with_function();
|
||
// 哨兵形态:fn 与 real_ns 均为 undefined(自导入/祖先 defer)。
|
||
vm.set_reg(1, JsValue::undefined());
|
||
vm.set_reg(2, JsValue::undefined());
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let ns_val = module_defer_object(&mut vm, &[0, 1, 2, 3]).unwrap();
|
||
let ns_obj = unsafe { &*ns_val.as_js_object_ptr() };
|
||
let state = deferred_state(ns_obj).expect("deferred state 应已安装");
|
||
assert_eq!(state.module, JsValue::undefined(), "哨兵 [[Module]] 应为 undefined");
|
||
assert!(!state.evaluated, "哨兵 [[Evaluated]] 应为 false");
|
||
assert_eq!(state.namespace, JsValue::undefined(), "哨兵 [[Namespace]] 应为 undefined");
|
||
}
|
||
|
||
#[test]
|
||
fn module_defer_object_rejects_non_function() {
|
||
let (mut vm, _) = vm_with_function();
|
||
vm.set_reg(1, JsValue::int(42));
|
||
vm.set_reg(2, JsValue::undefined());
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let result = module_defer_object(&mut vm, &[0, 1, 2, 3]);
|
||
assert!(matches!(result, NativeResult::Err(_)), "非函数应抛 TypeError");
|
||
}
|
||
|
||
#[test]
|
||
fn module_defer_object_rejects_non_object_ns() {
|
||
let (mut vm, fn_val) = vm_with_function();
|
||
vm.set_reg(1, fn_val);
|
||
vm.set_reg(2, JsValue::int(42));
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let result = module_defer_object(&mut vm, &[0, 1, 2, 3]);
|
||
assert!(matches!(result, NativeResult::Err(_)), "非对象 real_ns 应抛 TypeError");
|
||
}
|
||
|
||
#[test]
|
||
fn module_defer_ns_gc_edge_keeps_module_function_alive() {
|
||
let (mut vm, fn_val) = vm_with_function();
|
||
let fn_ptr = fn_val.as_js_object_ptr();
|
||
|
||
// 创建 deferred ns(持有 fn);fn + ns 两个 session 对象。
|
||
vm.set_reg(1, fn_val);
|
||
vm.set_reg(2, JsValue::undefined());
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let ns_val = module_defer_object(&mut vm, &[0, 1, 2, 3]).unwrap();
|
||
let count_after_ns = vm.session_object_count();
|
||
|
||
// 仅 ns 为根:清掉持有 fn 的寄存器,fn 唯一可达路径是 ns 的 [[Module]] 边。
|
||
vm.set_reg(1, JsValue::undefined());
|
||
vm.set_reg(0, ns_val);
|
||
|
||
// 完整收集:若 [[Module]] 边漏登,fn 被回收、计数回落。
|
||
vm.collect_session_gc();
|
||
|
||
assert_eq!(vm.session_object_count(), count_after_ns, "依赖模块函数应跨收集存活(GC 边已登记)");
|
||
// 原地 sweep 不搬移:ns 与状态盒原地保留,[[Module]] 指针稳定。
|
||
let ns_obj = unsafe { &*ns_val.as_js_object_ptr() };
|
||
let state = deferred_state(ns_obj).expect("deferred state 应存活");
|
||
assert_eq!(state.module.as_js_object_ptr(), fn_ptr, "[[Module]] 指针应稳定");
|
||
}
|
||
|
||
#[test]
|
||
fn module_defer_ns_gc_edge_keeps_error_alive() {
|
||
let (mut vm, _fn_val) = vm_with_function();
|
||
// 失败缓存的错误对象(模拟求值失败后缓存的原值)。
|
||
let err_val = crate::error::create_type_error(&mut vm, "cached failure");
|
||
let err_ptr = err_val.as_js_object_ptr();
|
||
|
||
// 创建 deferred ns(哨兵形态)。
|
||
vm.set_reg(1, JsValue::undefined());
|
||
vm.set_reg(2, JsValue::undefined());
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let ns_val = module_defer_object(&mut vm, &[0, 1, 2, 3]).unwrap();
|
||
|
||
// 直接写入 error 字段(模拟失败缓存),置已求值。
|
||
{
|
||
let ns_obj = unsafe { &*ns_val.as_js_object_ptr() };
|
||
let state = deferred_state_mut(ns_obj).expect("deferred state 应已安装");
|
||
state.error = Some(err_val);
|
||
state.evaluated = true;
|
||
}
|
||
|
||
// 仅 ns 为根:清掉持有 err 与 fn 的寄存器,err 唯一可达路径是 ns 的 error 边。
|
||
vm.set_reg(0, ns_val);
|
||
vm.set_reg(1, JsValue::undefined());
|
||
vm.set_reg(2, JsValue::undefined());
|
||
|
||
// 首收集达稳态(fn 无根被回收),记录稳态计数。
|
||
vm.collect_session_gc();
|
||
let steady_count = vm.session_object_count();
|
||
|
||
// 再收集一次:若 error 边漏登,err 被回收、计数回落。
|
||
vm.collect_session_gc();
|
||
assert_eq!(vm.session_object_count(), steady_count, "失败缓存错误应跨收集存活(GC 边已登记)");
|
||
// 原地 sweep 不搬移:ns 与状态盒原地保留,error 指针稳定。
|
||
let ns_obj = unsafe { &*ns_val.as_js_object_ptr() };
|
||
let state = deferred_state(ns_obj).expect("deferred state 应存活");
|
||
assert_eq!(state.error.unwrap().as_js_object_ptr(), err_ptr, "error 指针应稳定");
|
||
}
|
||
|
||
#[test]
|
||
fn module_eval_state_ns_survives_session_gc() {
|
||
let (mut vm, fn_val) = vm_with_function();
|
||
// 依赖模块函数经寄存器钉为根,首收集达稳态后记录基线。
|
||
vm.set_reg(0, fn_val);
|
||
vm.collect_session_gc();
|
||
let baseline = vm.session_object_count();
|
||
|
||
// 命名空间对象为唯一新增 session 对象:per-module 求值状态表是其唯一根。
|
||
let ns = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||
let ns_val = JsValue::from_js_object(ns);
|
||
let ns_ptr = ns_val.as_js_object_ptr();
|
||
vm.set_module_eval_state("p", true, ns_val);
|
||
assert_eq!(vm.session_object_count(), baseline + 1, "命名空间对象应已分配");
|
||
|
||
// 完整收集:若状态表边漏登,命名空间对象被回收、计数回落。
|
||
vm.collect_session_gc();
|
||
|
||
assert_eq!(
|
||
vm.session_object_count(),
|
||
baseline + 1,
|
||
"per-module 状态命名空间对象应跨收集存活(GC 边已登记)"
|
||
);
|
||
// 原地 sweep 不搬移:状态表原地保留,命名空间指针稳定。
|
||
let state = vm.get_module_eval_state("p").expect("per-module 状态应存活");
|
||
assert!(state.evaluated, "evaluated 标志应稳定");
|
||
assert_eq!(state.namespace.as_js_object_ptr(), ns_ptr, "命名空间指针应稳定");
|
||
}
|
||
|
||
#[test]
|
||
fn deferred_ns_read_target_falls_back_for_non_object_namespace() {
|
||
let (mut vm, fn_val) = vm_with_function();
|
||
// 模块函数返回非对象(42):求值成功后 [[Namespace]] 为非对象值。
|
||
vm.set_reg(1, fn_val);
|
||
vm.set_reg(2, JsValue::undefined());
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let ns_val = module_defer_object(&mut vm, &[0, 1, 2, 3]).unwrap();
|
||
let ns_ptr = ns_val.as_js_object_ptr();
|
||
|
||
// 模拟求值完成:[[Evaluated]] 置真、[[Namespace]] 为非对象值 42。
|
||
{
|
||
let ns_obj = unsafe { &*ns_ptr };
|
||
let state = deferred_state_mut(ns_obj).expect("deferred state 应已安装");
|
||
state.evaluated = true;
|
||
state.namespace = JsValue::int(42);
|
||
}
|
||
|
||
// 权威读目标回落对象自身(预注册占位槽),非对象 [[Namespace]] 不作读目标。
|
||
let ns_obj = unsafe { &*ns_ptr };
|
||
let target = deferred_ns_read_target(ns_obj);
|
||
assert!(
|
||
std::ptr::eq(target as *const JsObject, ns_ptr as *const JsObject),
|
||
"非对象 [[Namespace]] 应回落对象自身"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn deferred_ns_read_target_returns_real_ns_for_object_namespace() {
|
||
let (mut vm, fn_val) = vm_with_function();
|
||
// 真实 ns 对象(求值成功且 [[Namespace]] 为对象,mixed 形态)。
|
||
let real_ns = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||
let real_ns_val = JsValue::from_js_object(real_ns);
|
||
vm.set_reg(1, fn_val);
|
||
vm.set_reg(2, real_ns_val);
|
||
let path_str = vm.new_string("p");
|
||
vm.set_reg(3, path_str);
|
||
let ns_val = module_defer_object(&mut vm, &[0, 1, 2, 3]).unwrap();
|
||
let ns_ptr = ns_val.as_js_object_ptr();
|
||
|
||
// 权威读目标返回真实 ns(活值与真实槽位在真实 ns 条目表)。
|
||
let ns_obj = unsafe { &*ns_ptr };
|
||
let target = deferred_ns_read_target(ns_obj);
|
||
assert!(
|
||
std::ptr::eq(target as *const JsObject, real_ns as *const JsObject),
|
||
"对象 [[Namespace]] 应返回真实 ns"
|
||
);
|
||
}
|
||
}
|