feat: 对象层惰性模块命名空间求值触发

This commit is contained in:
2026-10-09 15:13:19 +00:00
parent 7900bb92e6
commit b240cbd305
21 changed files with 615 additions and 82 deletions
Generated
+19 -19
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1226,7 +1226,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1235,7 +1235,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1255,7 +1255,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1269,7 +1269,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1284,7 +1284,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1296,7 +1296,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1305,7 +1305,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"bumpalo",
"dashmap",
@@ -1319,7 +1319,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1331,7 +1331,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"tracing",
"tracing-appender",
@@ -1340,7 +1340,7 @@ dependencies = [
[[package]]
name = "oxide_parser"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1355,7 +1355,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1371,7 +1371,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"num-bigint",
"num-traits",
@@ -1384,7 +1384,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1402,7 +1402,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"bumpalo",
"dashmap",
@@ -1415,7 +1415,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.8.105"
version = "0.8.106"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -23,7 +23,7 @@ members = [
]
[workspace.package]
version = "0.8.105"
version = "0.8.106"
edition = "2021"
license = "MIT"
rust-version = "1.80"
@@ -187,10 +187,12 @@ pub(crate) fn unit_string_value<H: VmHost>(vm: &mut H, unit: u16) -> JsValue {
/// 索引位置存在性判定(规范 HasProperty:自身与原型链任一层命中即存在,
/// 数组元素区 hole 视同缺失,TypedArray 整数索引按视图长度判在界)。
/// 数组/类数组目标非 deferred namespace,触发恒 no-op。
pub(crate) fn arraylike_index_present<H: VmHost>(vm: &mut H, ptr: *mut JsObject, i: usize) -> bool {
// SAFETY: ptr 来自 this/参数对象,指向存活对象。
let key_si = vm.string_key_si(&i.to_string());
vm.has_property(unsafe { &*ptr }, key_si)
.unwrap_or(false)
}
/// 元素读取失败时直接把异常作为 NativeResult::Err 返回。
+8 -4
View File
@@ -26,7 +26,8 @@ fn gated_get<H: VmHost>(
vm: &mut H, arr_ptr: *mut JsObject, index: usize, recv: JsValue,
) -> Result<Option<JsValue>, JsValue> {
let key_si = vm.string_key_si(&index.to_string());
if !vm.has_property(unsafe { &*arr_ptr }, key_si) {
// 数组目标非 deferred namespace,触发恒 no-op;错误按缺失处理(不可达)。
if !vm.has_property(unsafe { &*arr_ptr }, key_si).unwrap_or(false) {
return Ok(None);
}
let val = match vm.ordinary_get(unsafe { &*arr_ptr }, key_si, recv) {
@@ -40,7 +41,9 @@ fn gated_get<H: VmHost>(
/// 成功。数组元素区与命名属性双路径交由 delete_own_property_outcome 判定。
fn delete_prop_or_throw<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> Result<(), JsValue> {
match crate::object::delete_own_property_outcome(vm, obj, key_si) {
crate::object::DeleteOutcome::NonConfigurable => Err(array_type_error(vm, "Cannot delete property")),
// deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值上抛。
Err(exc) => Err(exc),
Ok(crate::object::DeleteOutcome::NonConfigurable) => Err(array_type_error(vm, "Cannot delete property")),
_ => Ok(()),
}
}
@@ -769,7 +772,8 @@ pub fn array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let lower_si = vm.string_key_si(&lower.to_string());
let upper_si = vm.string_key_si(&upper.to_string());
// 先下后上读值(getter 副作用序对齐规范);hole 与原型缺失判端不存在。
let lower_val = if vm.has_property(unsafe { &*arr_ptr }, lower_si) {
// 数组目标非 deferred namespace,触发恒 no-op;错误按缺失处理(不可达)。
let lower_val = if vm.has_property(unsafe { &*arr_ptr }, lower_si).unwrap_or(false) {
match vm.ordinary_get(unsafe { &*arr_ptr }, lower_si, recv) {
Ok(v) => Some(v),
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
@@ -782,7 +786,7 @@ pub fn array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if let Some(v) = lower_val {
vm.set_reg(0, v);
}
let upper_val = if vm.has_property(unsafe { &*arr_ptr }, upper_si) {
let upper_val = if vm.has_property(unsafe { &*arr_ptr }, upper_si).unwrap_or(false) {
match vm.ordinary_get(unsafe { &*arr_ptr }, upper_si, recv) {
Ok(v) => Some(v),
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
+2
View File
@@ -627,6 +627,8 @@ fn same_value_number(a: f64, b: f64) -> bool {
/// 均静默不抛(规范明注)。
fn apply_child_result<H: VmHost>(vm: &mut H, child_ptr: *mut JsObject, child_si: u32, new_val: JsValue) {
if new_val.is_undefined() {
// 子节点为 JSON.parse 新建的 plain 对象(非 deferred namespace),触发
// 恒 no-op;删除失败静默不抛(规范明注)。
let _ = crate::object::delete_own_property(vm, unsafe { &mut *child_ptr }, child_si);
} else {
let _ = vm.define_data_property(
+113 -7
View File
@@ -70,11 +70,14 @@ pub struct DeferredNsState {
/// undefined(哨兵:自导入/祖先引用,模块函数在导入方 prelude 期不可得)。
pub module: JsValue,
/// `[[Evaluated]]`:依赖模块是否已求值(mixed 依赖构造时即为真;
/// 首次触发成功后置真,不回退)。
/// 求值完成(成功或失败)后置真,不回退)。
pub evaluated: bool,
/// `[[Namespace]]`:求值后返回的命名空间对象(mixed 依赖构造时即为真实 ns;
/// 求值前为 undefined)。
pub namespace: JsValue,
/// 求值失败时缓存的原始错误值(再读再抛同一错误对象,保身份);
/// 成功求值与求值前恒 `None`。
pub error: Option<JsValue>,
}
/// 模块命名空间条目表:挂在 ns 对象 `native_data` 上,是 exotic [[Get]] 的权威
@@ -125,6 +128,52 @@ pub fn deferred_state(obj: &JsObject) -> Option<&DeferredNsState> {
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)
@@ -232,9 +281,10 @@ pub fn module_ns_export(obj: &JsObject, key_si: u32) -> Option<ModuleNsQuery> {
/// 收集条目表持有的 JsValue 边(已初始化 `Value` 与全部 `Cell` 内值),供 GC mark。
///
/// deferred namespace 对象额外持有 `[[Module]]` 与 `[[Namespace]]` 两条边:
/// 依赖模块函数与求值后命名空间对象须经本单一注册点入 mark,漏登则函数在
/// deferred ns 存活期间被回收,触发点解引用悬垂。
/// 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() {
@@ -261,6 +311,10 @@ pub fn module_ns_native_edges(obj: &JsObject) -> Vec<JsValue> {
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
}
@@ -844,6 +898,10 @@ pub fn module_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
/// `__moduleEval(fn)`:同步执行依赖模块(fn 为编译期 CREATE_CLOSURE 的函数对象),返回其命名空间。
///
/// # 副作用
/// - 调用前置「正在求值模块」标记为 `Some(fn)`、返回后还原(save/restore,嵌套
/// `__moduleEval` 安全):deferred namespace 求值触发的 cyclic 守卫据此判自求值。
pub fn module_eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return type_error(vm, "__moduleEval: 1 argument required");
@@ -856,10 +914,15 @@ pub fn module_eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if ptr.is_null() || !unsafe { (*ptr).is_function() } {
return type_error(vm, "__moduleEval: target is not a function");
}
match vm.call_function_sync(fn_val, JsValue::undefined(), &[]) {
// 置位标记,调用返回后无条件还原(异常展开亦不残留)。
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) => NativeResult::Ok(v),
Err(e) => NativeResult::Err(crate::error::create_error(vm, &e)),
}
};
vm.set_evaluating_module(saved);
result
}
/// `__moduleDeferObject(fn, real_ns)`:创建 deferred namespace 对象(`import defer * as` 产物)。
@@ -871,7 +934,7 @@ pub fn module_eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 2. 建 null 原型对象,定义 `@@toStringTag` = "Deferred Module"(不可写不可枚举不可配置)。
/// 3. 置 module namespace + deferred 双标志,置 non-extensible。
/// 4. 安装条目表并写状态盒:`[[Module]]` = fn、`[[Evaluated]]` = `real_ns` 为对象、
/// `[[Namespace]]` = `real_ns`(或 undefined)。
/// `[[Namespace]]` = `real_ns`(或 undefined)、失败错误缓存恒 `None`。
///
/// # 副作用
/// - 新对象创建起即标记 module namespace exotic 并置 non-extensible(同 `__moduleObject`);
@@ -912,6 +975,7 @@ pub fn module_defer_object<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
module: fn_val,
evaluated: ns_val.is_object(),
namespace: ns_val,
error: None,
});
}
NativeResult::Ok(JsValue::from_js_object(obj))
@@ -1069,4 +1133,46 @@ mod tests {
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 ns_val = module_defer_object(&mut vm, &[0, 1, 2]).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 指针应稳定");
}
}
+106 -14
View File
@@ -259,6 +259,12 @@ pub fn object_get_own_property_symbols<H: VmHost>(vm: &mut H, args: &[u8]) -> Na
Err(err) => return NativeResult::Err(err),
};
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
let obj = unsafe { &*obj_ptr };
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
return NativeResult::Err(exc);
}
let symbols: Vec<JsValue> = {
let obj = unsafe { &*obj_ptr };
own_symbol_key_values(vm, obj)
@@ -332,17 +338,28 @@ pub enum DeleteOutcome {
/// # 注意事项
/// - 数组元素存在性以 `prop_meta_at` 的 hole 标记判定,删除后重新写入元素
/// 会自动清除 hole 标记恢复存在
pub fn delete_own_property_outcome<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> DeleteOutcome {
pub fn delete_own_property_outcome<H: VmHost>(
vm: &mut H, obj: &mut JsObject, key_si: u32,
) -> Result<DeleteOutcome, JsValue> {
// deferred namespace 求值触发([[Delete]],symbol-like 键内部 no-op):
// 触发失败(cyclic / 失败缓存重抛)以异常值上抛,由调用边界恢复。
// uncaught 侧通道已还原为原错误值,原值重抛保身份。
if let Err(msg) = vm.ensure_deferred_ns_evaluation(obj, Some(key_si)) {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_from_text(vm, &msg));
return Err(exc);
}
// TA 数值键:界内索引不可配置(删除失败,元素零修改、镜像槽不同步);
// 数字无效键视为缺失(删除成功,镜像槽同步 undefined,对非全局内置 TA
// 为 no-op);非数字串 / symbol 键落下方普通属性路径。live 长 0
// (detach / 收缩越界)时全数值键自然归数字无效,与读 / 写 / 定义面同口径。
if obj.is_typed_array_obj() {
match crate::typed_array::ta_index_gate(vm, obj, key_si) {
crate::typed_array::TaIndexGate::NumericValid(_) => return DeleteOutcome::NonConfigurable,
crate::typed_array::TaIndexGate::NumericValid(_) => return Ok(DeleteOutcome::NonConfigurable),
crate::typed_array::TaIndexGate::NumericInvalid => {
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
return DeleteOutcome::Missing;
return Ok(DeleteOutcome::Missing);
}
crate::typed_array::TaIndexGate::Ordinary => {}
}
@@ -355,21 +372,21 @@ pub fn delete_own_property_outcome<H: VmHost>(vm: &mut H, obj: &mut JsObject, ke
let meta = obj.prop_meta_at(index);
// 已是 hole 视为不存在;非 configurable 不可删。
if meta.is_some_and(|m| m.is_hole()) {
return DeleteOutcome::Missing;
return Ok(DeleteOutcome::Missing);
}
if meta.is_some_and(|m| !m.attributes.configurable()) {
return DeleteOutcome::NonConfigurable;
return Ok(DeleteOutcome::NonConfigurable);
}
obj.mark_hole_at(index);
obj.bump_generation();
return DeleteOutcome::Deleted;
return Ok(DeleteOutcome::Deleted);
}
// 大索引越出元素区时是命名属性(shape 链),落穿下方查删路径。
}
// 非下标键:length 是虚拟属性(无 shape 槽、不在元素区),但描述符声明
// configurable:false,删除恒失败。
if key_si == vm.perm_intern("length") {
return DeleteOutcome::NonConfigurable;
return Ok(DeleteOutcome::NonConfigurable);
}
}
@@ -383,7 +400,7 @@ pub fn delete_own_property_outcome<H: VmHost>(vm: &mut H, obj: &mut JsObject, ke
// 属性缺失:delete 按规范返 true,镜像槽同步 undefined 保持
// "槽 = A 侧原始存储"不变式(与入口预载缺位语义幂等)。
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
return DeleteOutcome::Missing;
return Ok(DeleteOutcome::Missing);
};
// walk_own_keys 已返回绝对存储索引(数组含元素区偏移),直接使用。
if obj
@@ -391,7 +408,7 @@ pub fn delete_own_property_outcome<H: VmHost>(vm: &mut H, obj: &mut JsObject, ke
.map(|meta| !meta.attributes.configurable())
.unwrap_or(false)
{
return DeleteOutcome::NonConfigurable;
return Ok(DeleteOutcome::NonConfigurable);
}
// 数组重建前保存元素区(值 + meta),重建后恢复到命名属性之前。
@@ -456,13 +473,13 @@ pub fn delete_own_property_outcome<H: VmHost>(vm: &mut H, obj: &mut JsObject, ke
// 删除成功:镜像槽同步 undefined(成员形删除 / Reflect.deleteProperty /
// 0x9C 清槽同源收口,后两者对此幂等)。
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
DeleteOutcome::Deleted
Ok(DeleteOutcome::Deleted)
}
/// 删除对象自身属性并投影为布尔(`Reflect.deleteProperty` 语义):成功返回 true,
/// 属性不可配置返回 false,恒不抛错。
pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> bool {
delete_own_property_outcome(vm, obj, key_si) != DeleteOutcome::NonConfigurable
/// 属性不可配置返回 false;deferred namespace 求值触发失败以异常值上抛。
pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> Result<bool, JsValue> {
Ok(delete_own_property_outcome(vm, obj, key_si)? != DeleteOutcome::NonConfigurable)
}
/// JS `Object()` 构造逻辑:创建空对象(prototype 为 null,由 VM 补装内置原型)。
@@ -533,7 +550,9 @@ fn namespace_export_get(obj: &JsObject, key_si: u32) -> Result<Option<JsValue>,
if is_symbol_key(key_si) {
return Ok(None);
}
match crate::module::module_ns_export(obj, key_si) {
// deferred namespace 求值后活值在真实 ns 条目表,读路径按权威对象查询。
let target = crate::module::deferred_ns_read_target(obj);
match crate::module::module_ns_export(target, key_si) {
None => Ok(None),
Some(crate::module::ModuleNsQuery::Initialized(v)) => Ok(Some(v)),
Some(crate::module::ModuleNsQuery::Uninitialized) => Err(crate::module::NS_UNINITIALIZED_MESSAGE),
@@ -580,6 +599,10 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
};
let obj = unsafe { &*obj_ptr };
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
return NativeResult::Err(exc);
}
let owned_keys: Vec<(u32, u32)> = walk_own_keys(vm, obj)
.into_iter()
.filter(|(_si, offset)| {
@@ -685,6 +708,10 @@ pub fn object_assign<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if source_ptr.is_null() {
continue;
}
// [[OwnPropertyKeys]] 触发(源对象,deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, unsafe { &*source_ptr }) {
return NativeResult::Err(exc);
}
let source_keys: Vec<(u32, u32)> = {
let source = unsafe { &*source_ptr };
walk_own_keys(vm, source)
@@ -769,6 +796,15 @@ impl From<JsValue> for DefineDescFailure {
pub(crate) fn define_from_descriptor<H: VmHost>(
vm: &mut H, obj_ptr: *mut JsObject, key_si: u32, desc_val: JsValue,
) -> Result<(), DefineDescFailure> {
// [[DefineOwnProperty]] 触发(deferred namespace 先求值,不经 GOP 传导;
// symbol-like 键内部 no-op)。触发失败时 uncaught 侧通道已还原为原错误值,
// 原值重抛保身份(cyclic 无原值时按文本重建)。
if let Err(msg) = vm.ensure_deferred_ns_evaluation(unsafe { &*obj_ptr }, Some(key_si)) {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_from_text(vm, &msg));
return Err(DefineDescFailure::User(exc));
}
if !desc_val.is_object() {
return Err(DefineDescFailure::Engine("Property description must be an object".to_string()));
}
@@ -1060,6 +1096,16 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
/// # 副作用
/// - 在 epoch 分配一个描述符对象
fn own_descriptor_of<H: VmHost>(vm: &mut H, obj: &JsObject, key_si: u32) -> Result<Option<JsValue>, JsValue> {
// deferred namespace 求值触发([[GetOwnProperty]],symbol-like 键内部 no-op)。
// 触发失败时 uncaught 侧通道已还原为原错误值,原值重抛保身份。
if let Err(msg) = vm.ensure_deferred_ns_evaluation(obj, Some(key_si)) {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_from_text(vm, &msg));
return Err(exc);
}
// 求值后描述符读真实 ns(deferred 对象自身槽位是预注册占位,活值在真实 ns)。
let obj = crate::module::deferred_ns_read_target(obj);
// TA 数值键:界内索引返回元素值 + 常量四字段描述符(writable/enumerable/
// configurable 恒真);数字无效与非数字串键落下方槽位路径(真实 own 属性
// 原样返回,无属性则 None)。
@@ -1194,6 +1240,10 @@ pub fn object_get_own_property_descriptors<H: VmHost>(vm: &mut H, args: &[u8]) -
};
let obj = unsafe { &*obj_ptr };
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
return NativeResult::Err(exc);
}
// 三段自身键序:字符串/整数键(整键升序在前)后接 Symbol 键插入序。
let mut keys = walk_own_keys(vm, obj);
@@ -1245,6 +1295,20 @@ fn push_desc_prop(obj: &mut JsObject, shape_forge: &ShapeForge, prop_si: u32, va
obj.push_prop(val);
}
/// deferred namespace [[OwnPropertyKeys]] 触发(无键):失败时 uncaught 侧通道
/// 已还原为原错误值,原值重抛保身份(cyclic 无原值时按文本重建)。
fn ensure_deferred_own_keys<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<(), JsValue> {
match vm.ensure_deferred_ns_evaluation(obj, None) {
Ok(()) => Ok(()),
Err(msg) => {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_from_text(vm, &msg));
Err(exc)
}
}
}
/// 校验并取出 Object 类方法的目标对象:`args[0]` 为接收者,`args[1]` 为待转换值,
/// 缺参或值经 ToObject 失败(null/undefined)时抛 TypeError。
fn require_obj_arg<H: VmHost>(vm: &mut H, args: &[u8], fn_name: &str) -> Result<*mut JsObject, JsValue> {
@@ -1313,6 +1377,10 @@ pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Ok(val);
}
let obj_ptr = val.as_js_object_ptr();
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, unsafe { &*obj_ptr }) {
return NativeResult::Err(exc);
}
{
let obj = unsafe { &*obj_ptr };
// 模块命名空间导出保持 writable:true,冻结须失败(规范 SetIntegrityLevel)。
@@ -1363,6 +1431,10 @@ pub fn object_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Ok(val);
}
let obj_ptr = val.as_js_object_ptr();
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, unsafe { &*obj_ptr }) {
return NativeResult::Err(exc);
}
{
let obj = unsafe { &mut *obj_ptr };
let keys = walk_own_keys(vm, obj);
@@ -1418,6 +1490,10 @@ pub fn object_is_frozen<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Ok(JsValue::bool(true));
}
let obj = unsafe { &*obj_ptr };
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
return NativeResult::Err(exc);
}
if obj.is_frozen() {
return NativeResult::Ok(JsValue::bool(true));
}
@@ -1454,6 +1530,10 @@ pub fn object_is_sealed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Ok(JsValue::bool(true));
}
let obj = unsafe { &*obj_ptr };
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
return NativeResult::Err(exc);
}
if obj.is_sealed() {
return NativeResult::Ok(JsValue::bool(true));
}
@@ -1500,6 +1580,10 @@ pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> Nati
let obj_ptr = native_try!(require_obj_arg(vm, args, "getOwnPropertyNames"));
let obj = unsafe { &*obj_ptr };
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
return NativeResult::Err(exc);
}
let keys = walk_own_keys(vm, obj);
let n = keys.len();
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
@@ -2121,6 +2205,10 @@ pub fn object_proto_define_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeR
pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "entries"));
let obj = unsafe { &*obj_ptr };
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
return NativeResult::Err(exc);
}
let keys = walk_own_keys(vm, obj);
let owned_keys: Vec<(u32, u32)> = keys
.into_iter()
@@ -2296,6 +2384,10 @@ pub fn object_group_by<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn object_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "values"));
let obj = unsafe { &*obj_ptr };
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
return NativeResult::Err(exc);
}
let keys = walk_own_keys(vm, obj);
let owned_keys: Vec<(u32, u32)> = keys
.into_iter()
+17 -2
View File
@@ -157,7 +157,11 @@ pub fn reflect_delete_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
Err(e) => return NativeResult::Err(from_engine_error(vm, &e)),
};
let target = unsafe { &mut *target_ptr };
NativeResult::Ok(JsValue::bool(delete_own_property(vm, target, key_si)))
match delete_own_property(vm, target, key_si) {
Ok(deleted) => NativeResult::Ok(JsValue::bool(deleted)),
// deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值上抛。
Err(exc) => NativeResult::Err(exc),
}
}
/// `Reflect.get(target, key, receiver)`:读取属性(含原型链与 accessor)。
@@ -213,7 +217,11 @@ pub fn reflect_has<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
Ok(si) => si,
Err(e) => return NativeResult::Err(from_engine_error(vm, &e)),
};
NativeResult::Ok(JsValue::bool(vm.has_property(unsafe { &*target_ptr }, key_si)))
// deferred namespace 目标先触发求值(cyclic 抛错 / 失败缓存重抛)。
match vm.has_property(unsafe { &*target_ptr }, key_si) {
Ok(found) => NativeResult::Ok(JsValue::bool(found)),
Err(msg) => NativeResult::Err(from_engine_error(vm, &msg)),
}
}
/// `Reflect.isExtensible(target)`:对象是否可扩展。
@@ -240,6 +248,13 @@ pub fn reflect_own_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return type_error(vm, "Reflect.ownKeys target is not an object");
};
let target = unsafe { &*target_ptr };
// [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。
if let Err(msg) = vm.ensure_deferred_ns_evaluation(target, None) {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_from_text(vm, &msg));
return NativeResult::Err(exc);
}
// Symbol 键分流在消费端追加:底层字符串枚举源(Object.keys / JSON / for-in)
// 语义不变,不泄漏 Symbol 键。
@@ -335,6 +335,14 @@ 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);
// [[OwnPropertyKeys]] 触发(源对象,deferred namespace 先求值,cyclic 抛错)。
// plain 对象判定已排除 module namespace,此触发对实际可达路径恒 no-op。
if let Err(msg) = vm.ensure_deferred_ns_evaluation(src, None) {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_from_text(vm, &msg));
return Err(exc);
}
let str_keys = walk_own_keys(vm, src);
let sym_keys = walk_own_symbol_keys(vm, src);
for (si, store) in str_keys {
+28 -1
View File
@@ -248,7 +248,10 @@ pub trait VmHost {
fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue>;
/// 存在性判定(规范 HasProperty):自身与原型链任一层 P 为自有属性即存在;
/// 数组元素区 hole 视同缺失,TypedArray 整数索引按视图长度判在界。
fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool;
///
/// deferred namespace 目标先触发 `EnsureDeferredNamespaceEvaluation`(cyclic
/// 守卫抛 TypeError / 失败缓存原值重抛),触发失败以 `Err` 返回。
fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result<bool, String>;
fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32>;
// 属性访问
@@ -284,6 +287,30 @@ pub trait VmHost {
// 调用基础设施
fn call_function_sync(&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue]) -> Result<JsValue, String>;
/// deferred namespace 求值触发(`EnsureDeferredNamespaceEvaluation`):
/// 对 deferred 且未求值的 module namespace 同步执行依赖模块函数,缓存结果
/// 命名空间或失败错误对象。
///
/// # 步骤
/// 1. 非 module namespace / 非 deferred / 无状态盒 → no-op(`Ok`)。
/// 2. symbol-like 键(Symbol 或 deferred 的 `"then"`)→ no-op(走 Ordinary* 路径)。
/// 3. 已求值:失败缓存原值重抛(`Err`),成功 no-op。
/// 4. cyclic 守卫:`[[Module]]` 为 undefined 哨兵且标记在场 → TypeError;
/// 标记等于 `[[Module]]` 函数(自求值中)→ TypeError。
/// 5. 求值:`call_function_sync([[Module]])`,成功缓存 `[[Namespace]]`,
/// 失败缓存原错误值(还原 uncaught 侧通道供调用方原值重抛)。
///
/// # 边界与前提
/// - `key_si` 为 `None` 表示 `[[OwnPropertyKeys]]` 触发(无键,恒触发)。
///
/// # 副作用
/// - 写状态盒 `[[Evaluated]]` / `[[Namespace]]` / 失败错误缓存;可能执行用户
/// 模块代码(含分配与 GC)。
fn ensure_deferred_ns_evaluation(&mut self, obj: &JsObject, key_si: Option<u32>) -> Result<(), String>;
/// 读「正在求值模块」cyclic 守卫标记(`None` = 无模块求值中)。
fn evaluating_module(&self) -> Option<JsValue>;
/// 置「正在求值模块」cyclic 守卫标记(save/restore 由调用方负责)。
fn set_evaluating_module(&mut self, val: Option<JsValue>);
/// 构造调用(Construct(C, args)):native 构造器以新对象为 receiver 值传递调用,
/// bytecode 构造器压构造帧执行(含 derived 构造器 super() 语义与 new.target
/// 传播),返回值非对象时回退到新对象。
+3 -1
View File
@@ -70,9 +70,11 @@ pub fn bind_symbol(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
// well-known symbol 以符号原语绑定,下标与内建符号表(0..WELL_KNOWN_SYMBOL_COUNT)
// 一一对应;名称表是 id/名映射的唯一来源,属性名去掉 `Symbol.` 前缀。
// 原语按 (realm 编号, 局部下标) 编码,与键空间 `encode_symbol_key` 同口径,
// 非零 realm 下与键物化值保持同一符号。
for (id, full_name) in oxide_types::private_key::WELL_KNOWN_SYMBOL_NAMES.iter().enumerate() {
let name = full_name.strip_prefix("Symbol.").unwrap_or(full_name);
bind_well_known_symbol(core, ctor, name, JsValue::symbol(id as u32));
bind_well_known_symbol(core, ctor, name, JsValue::symbol_realm(realm_id, id as u32));
}
bind_constructor!(core, global, "Symbol", ctor_ptr, oxide_builtins::symbol::symbol_constructor::<crate::vm::Vm>, 0, hash: true);
+20 -4
View File
@@ -283,6 +283,11 @@ impl Vm {
self.pc += 1;
let value = self.regs[a];
let obj = unsafe { &mut *obj_ptr };
// 规范 PrivateFieldAdd 步 1:对象不可扩展时拒加私有字段(模块命名空间
// 等 exotic 对象创建起即 non-extensible)。
if !obj.is_extensible() {
return self.raise_type_error("Cannot add private field to a non-extensible object");
}
if self
.kernel_core
.shape_forge()
@@ -720,7 +725,11 @@ impl Vm {
// 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);严格模式分派见
// finish_member_delete。
let outcome = oxide_builtins::object::delete_own_property_outcome(self, obj, prop_name_si);
self.finish_member_delete(rd, outcome)
match outcome {
// deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值重抛。
Err(exc) => self.raise_captured(exc).map(|_| true),
Ok(outcome) => self.finish_member_delete(rd, outcome),
}
}
pub(crate) fn dispatch_delete_prop_dynamic(&mut self, rd: usize, b: usize) -> Result<bool, String> {
@@ -738,7 +747,11 @@ impl Vm {
// 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);严格模式分派见
// finish_member_delete。
let outcome = oxide_builtins::object::delete_own_property_outcome(self, obj, prop_name_si);
self.finish_member_delete(rd, outcome)
match outcome {
// deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值重抛。
Err(exc) => self.raise_captured(exc).map(|_| true),
Ok(outcome) => self.finish_member_delete(rd, outcome),
}
}
/// 成员形 delete 的结果分派:不可配置属性在严格模式抛 TypeError(规范要求
@@ -794,8 +807,11 @@ impl Vm {
// SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。
let obj = unsafe { &mut *global_ptr };
// 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);不可配置返回
// false 且属性保留。
let deleted = oxide_builtins::object::delete_own_property(self, obj, si);
// false 且属性保留。全局对象非 deferred namespace,触发恒 no-op。
let deleted = match oxide_builtins::object::delete_own_property(self, obj, si) {
Ok(d) => d,
Err(exc) => return self.raise_captured(exc),
};
// 仅删除成功才清镜像槽(无槽 0 不写,reg 0 是返回槽不可误写)。
if deleted && slot > 0 {
self.regs[slot] = JsValue::undefined();
+17 -8
View File
@@ -126,9 +126,11 @@ impl Vm {
/// 使外围 try/catch 可捕获(native 函数抛错时原值存于 `last_uncaught_value`)。
///
/// # 注意事项
/// 仅在主 dispatch(`native_call_depth == 0`)下展开——此时 try_stack 只含当前
/// 主 dispatch(`native_call_depth == 0`)就地展开——此时 try_stack 只含当前
/// 字节码的处理器,展开后 pc/regs[0] 不会被中途的原生调用栈覆盖。原生 builtin
/// 内部(depth > 0)必须传播错误,由其调用边界(dispatch_native_call)转换。
/// 内部(depth > 0)传播错误,由其调用边界(dispatch_native_call)转换;异常值
/// 同步物化入异常通道(不取 uncaught 侧通道,builtin 边界 `from_engine_error`
/// 仍须从侧通道还原原值),供内联字节码体在状态还原前由体内 try 处理器捕获。
pub(crate) fn raise_call_error(&mut self, err: &str) -> Result<JsValue, String> {
if self.native_call_depth == 0 {
let exc = self
@@ -141,27 +143,34 @@ impl Vm {
self.unwind()?;
return Ok(JsValue::undefined());
}
let exc = self
.last_uncaught_value
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, err));
let kind = self.thrown_error_kind(exc);
self.exception_value = Some(exc);
self.pending_error_kind = Some(kind);
Err(err.to_string())
}
/// 恢复已捕获的原始异常值(强转失败载荷)并走异常展开,保原值 kind:
/// 深度 0 置原值入异常通道就地展开到外围 catch;深度 >0 返回 kind 前缀
/// 文本 `Err`,由原生调用边界恢复为异常对象。
/// 文本 `Err`,由原生调用边界恢复为异常对象,异常值同步物化入异常通道
/// (供内联字节码体在状态还原前由体内 try 处理器捕获)。
///
/// # 步骤
/// 1. 深度 0:原值与 kind 写入异常通道,就地 `unwind`。
/// 2. 深度 >0:以 `Kind: message` 文本 `Err` 返回,不就地展开。
/// 2. 深度 >0:原值与 kind 写入异常通道,以 `Kind: message` 文本 `Err` 返回。
///
/// # 边界与前提
/// - `exc` 为调用方已提取的原始异常值;本入口不重取 uncaught 槽。
///
/// # 副作用
/// - 深度 0 写 `exception_value`/`pending_error_kind`,pc 经展开改写。
/// - 写 `exception_value`/`pending_error_kind`;深度 0 时 pc 经展开改写。
pub(crate) fn raise_captured(&mut self, exc: JsValue) -> Result<(), String> {
let kind = self.thrown_error_kind(exc);
self.exception_value = Some(exc);
self.pending_error_kind = Some(kind);
if self.native_call_depth == 0 {
let kind = self.thrown_error_kind(exc);
self.exception_value = Some(exc);
self.pending_error_kind = Some(kind);
return self.unwind();
}
Err(oxide_builtins::typed_array::element_error_text(self, exc))
+5
View File
@@ -242,6 +242,11 @@ pub struct Vm {
/// String 后,for-of 需要重新抛出原值。放在 VM 顶层字段(不在 InlineSyncState 中)
/// 以便跨内联调用的恢复过程存活。
pub(crate) last_uncaught_value: Option<JsValue>,
/// 「正在求值模块」cyclic 守卫标记(deferred namespace 求值触发用):
/// `run` 在 dispatch 前置 `Some(undefined)`(入口模块哨兵)、`module_eval`
/// 在 `call_function_sync` 前置 `Some(fn)`,两者均 save/restore(嵌套安全)。
/// 触发点据此判自求值/祖先求值中的 cyclic 并抛 TypeError。
pub(crate) evaluating_module: Option<JsValue>,
/// 数组 length define 强转期用户代码抛出的原始异常:与 `last_uncaught_value`
/// 分离,避免入口误取其它操作忽略调用残留的值。仅 `define_array_length` 写入,
/// 由 Object/Reflect define 入口消费。
+17 -6
View File
@@ -2,6 +2,7 @@
//! 数组下标反解、非对象基删除与属性解析及自身属性槽查找。
use oxide_runtime_api as coercion;
use oxide_runtime_api::VmHost;
use oxide_types::object::JsObject;
use oxide_types::private_key::{encode_symbol_key, int_key_value, is_int_key, make_int_key, INT_KEY_COUNT};
@@ -212,25 +213,35 @@ impl Vm {
/// 与 `resolve_property` 的差异:原型链上每层都检查数组元素区
/// (`resolve_property` 只在顶层检查元素区,链上仅走 shape 槽),
/// 继承自父数组的索引属性在此判存在。
pub(crate) fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool {
///
/// deferred namespace 目标在原型链每层触发 `EnsureDeferredNamespaceEvaluation`
/// (`in` 运算符逐级走 HasProperty;非 deferred 对象触发为 no-op),触发失败
/// (cyclic 守卫抛 TypeError / 失败缓存原值重抛)走可捕获异常展开。
pub(crate) fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result<bool, String> {
let length_si = self.length_si;
let mut current = Some(obj);
let mut depth = 0usize;
while let Some(obj) = current {
// deferred namespace 求值触发(symbol-like 键内部 no-op):链上每层
// 独立判定,`key in obj`(obj 原型为 deferred ns)在 ns 层触发。
if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) {
self.raise_call_error(&msg)?;
return Ok(false);
}
if obj.is_array() && prop_name_si == length_si {
return true;
return Ok(true);
}
// 统一数值键门(exotic [[HasProperty]]):同 `ordinary_get_inner`
// 的口径,链上每层同口径判定。
if obj.is_typed_array_obj() {
match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) {
oxide_builtins::typed_array::TaIndexGate::NumericValid(_) => return true,
oxide_builtins::typed_array::TaIndexGate::NumericInvalid => return false,
oxide_builtins::typed_array::TaIndexGate::NumericValid(_) => return Ok(true),
oxide_builtins::typed_array::TaIndexGate::NumericInvalid => return Ok(false),
oxide_builtins::typed_array::TaIndexGate::Ordinary => {}
}
}
if self.get_own_property_slot(obj, prop_name_si).is_some() {
return true;
return Ok(true);
}
if depth >= MAX_PROTO_CHAIN_DEPTH {
break;
@@ -239,7 +250,7 @@ impl Vm {
let proto = obj.proto();
current = proto.is_object().then(|| unsafe { &*proto.as_js_object_ptr() });
}
false
Ok(false)
}
/// 查找自身属性槽下标:数组 length 虚拟属性返回 `None`,元素区返回下标
+66 -1
View File
@@ -209,7 +209,7 @@ impl oxide_runtime_api::VmHost for Vm {
fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue> {
self.resolve_property(obj, prop_name_si)
}
fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool {
fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result<bool, String> {
self.has_property(obj, prop_name_si)
}
fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32> {
@@ -252,6 +252,71 @@ impl oxide_runtime_api::VmHost for Vm {
fn call_function_sync(&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue]) -> Result<JsValue, String> {
self.call_function_sync(callee, receiver, args)
}
fn ensure_deferred_ns_evaluation(&mut self, obj: &JsObject, key_si: Option<u32>) -> Result<(), String> {
// 非 deferred module namespace 直接 no-op(热路径零成本)。
if !obj.is_module_namespace() || !obj.is_deferred() {
return Ok(());
}
// symbol-like 键(Symbol 或 deferred 的 "then")走 Ordinary* 路径,不触发。
if let Some(key) = key_si {
if oxide_types::private_key::is_symbol_key(key) {
return Ok(());
}
if key == self.perm_intern("then") {
return Ok(());
}
}
// 读状态盒(经 native_data 裸指针,Box 堆分配 GC 不搬移,指针稳定)。
let Some(state) = oxide_builtins::module::deferred_state_mut(obj) else {
return Ok(());
};
if state.evaluated {
// 已求值:失败缓存原值重抛(保错误对象身份),成功 no-op。
if let Some(err) = state.error {
// 还原 uncaught 侧通道为缓存的原错误值,调用方([[Get]] 等)
// 经 raise_call_error 原值重抛,保 sameValue 身份。
self.last_uncaught_value = Some(err);
return Err(self.error_text(err));
}
return Ok(());
}
// cyclic 守卫:[[Module]] 为 undefined 哨兵(自导入/祖先 defer)时,
// 标记在场即求值中 → TypeError;标记已清(祖先已求值完毕)按已求值处理。
if state.module.is_undefined() {
if self.evaluating_module.is_some() {
return Err(self.error_message_text("TypeError", "cyclic module evaluation"));
}
state.evaluated = true;
return Ok(());
}
// [[Module]] 为函数:标记等于本函数即自求值中(安全网)→ TypeError。
if self.evaluating_module == Some(state.module) {
return Err(self.error_message_text("TypeError", "cyclic module evaluation"));
}
// EvaluateSync:调依赖模块函数,缓存结果命名空间或失败错误。
match self.call_function_sync(state.module, JsValue::undefined(), &[]) {
Ok(ns) => {
state.namespace = ns;
state.evaluated = true;
}
Err(text) => {
// 原错误值经 uncaught 侧通道取走存入状态盒,并还原侧通道供
// 调用方([[Get]] 等)原值重抛。
let exc = self.last_uncaught_value.take();
state.error = exc;
state.evaluated = true;
self.last_uncaught_value = exc;
return Err(text);
}
}
Ok(())
}
fn evaluating_module(&self) -> Option<JsValue> {
self.evaluating_module
}
fn set_evaluating_module(&mut self, val: Option<JsValue>) {
self.evaluating_module = val;
}
fn construct_ctor(&mut self, ctor: JsValue, args: &[JsValue]) -> Result<JsValue, JsValue> {
Vm::construct_ctor(self, ctor, args)
}
+12
View File
@@ -1,6 +1,7 @@
use crate::vm::{Completion, FrameArgs, FrameContinuation, PendingAsyncEscape, TryHandler, Vm};
use crate::vm_trace;
use oxide_bytecode::opcode;
use oxide_runtime_api::VmHost;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::make_private_name_id;
use oxide_types::value::JsValue;
@@ -233,6 +234,12 @@ impl Vm {
let prop_name_si = self.property_key_si(self.regs[b])?;
let value = self.regs[a];
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
// [[DefineOwnProperty]] 触发(类字段初始化 CreateDataPropertyOrThrow 路径):
// deferred namespace 先求值,触发失败走可捕获异常展开。
if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) {
self.raise_call_error(&msg)?;
return Ok(());
}
match self.define_data_property(obj, prop_name_si, value, PropAttributes::DEFAULT_DATA) {
Ok(()) => Ok(()),
Err(msg) => self.raise_error_kind("TypeError", &msg),
@@ -251,6 +258,11 @@ impl Vm {
let prop_name_si = self.property_key_si(self.regs[b])?;
let value = self.regs[a];
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
// [[DefineOwnProperty]] 触发(同 `dispatch_define_prop`,带描述符形态)。
if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) {
self.raise_call_error(&msg)?;
return Ok(());
}
match self.define_data_property(obj, prop_name_si, value, PropAttributes(attrs)) {
Ok(()) => Ok(()),
Err(msg) => self.raise_error_kind("TypeError", &msg),
+18 -5
View File
@@ -1,6 +1,6 @@
use crate::vm::{ForInIter, FrameArgs, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH};
use crate::vm_trace;
use oxide_runtime_api::{push_units_to, to_boolean, to_units_full};
use oxide_runtime_api::{push_units_to, to_boolean, to_units_full, VmHost};
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::{
encode_symbol_key, int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, INT_KEY_COUNT,
@@ -453,8 +453,8 @@ impl Vm {
let obj = unsafe { &*obj_ptr };
let prop_name_si = self.property_key_si(key_val)?;
// `in` 即 HasProperty:原型链各层都要看数组元素区与 TypedArray 整数索引,
// 读值解析不覆盖这两处。
let found = self.has_property(obj, prop_name_si);
// 读值解析不覆盖这两处。deferred namespace 目标先触发求值(cyclic 抛错)。
let found = self.has_property(obj, prop_name_si)?;
self.regs[rd] = JsValue::bool(found);
Ok(())
}
@@ -473,6 +473,17 @@ impl Vm {
obj_val = oxide_runtime_api::to_object(obj_val, self)?;
}
// [[OwnPropertyKeys]] 触发(for-in 枚举源):deferred namespace 先求值,
// cyclic 守卫抛 TypeError,触发失败走可捕获异常展开。
let obj_ptr = obj_val.as_js_object_ptr();
if !obj_ptr.is_null() {
let obj = unsafe { &*obj_ptr };
if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, None) {
self.raise_call_error(&msg)?;
return Ok(());
}
}
let mut keys_vec: Vec<(JsValue, u32)> = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut current = obj_val;
@@ -569,7 +580,8 @@ impl Vm {
// 模块命名空间 exotic:EnumerateObjectProperties 构建键表时逐键走
// `? [[GetOwnProperty]]`,未初始化导出抛 ReferenceError。校验须在排序与
// 迭代器建立之前(INIT 期),不遗留 for-in 迭代器;非 live ns 无条目表,
// `module_ns_export` 返回 None,落普通路径零行为变化。
// `module_ns_export` 返回 None,落普通路径零行为变化。deferred 对象求值后
// 校验按真实 ns 条目表(deferred 自身槽位是预注册占位)。
if obj_val.is_object() {
let ns_obj = unsafe { &*obj_val.as_js_object_ptr() };
if ns_obj.is_module_namespace() {
@@ -577,8 +589,9 @@ impl Vm {
if is_symbol_key(*si) {
continue;
}
let target = oxide_builtins::module::deferred_ns_read_target(ns_obj);
if let Some(oxide_builtins::module::ModuleNsQuery::Uninitialized) =
oxide_builtins::module::module_ns_export(ns_obj, *si)
oxide_builtins::module::module_ns_export(target, *si)
{
return self
.raise_error_kind("ReferenceError", oxide_builtins::module::NS_UNINITIALIZED_MESSAGE);
+124 -8
View File
@@ -3,6 +3,7 @@ use crate::{ic_trace, vm_trace};
use oxide_kernel::prop_forge::PropTemplate;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api as coercion;
use oxide_runtime_api::VmHost;
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::value::JsValue;
@@ -54,7 +55,15 @@ impl Vm {
// 模块命名空间 exotic [[Get]]:条目表是权威状态,预注册但未初始化的
// 导出读抛 ReferenceError;非 live ns(无表)落普通属性路径零行为变化。
if obj.is_module_namespace() {
if let Some(state) = oxide_builtins::module::module_ns_export(obj, prop_name_si) {
// deferred namespace 求值触发(symbol-like 键内部 no-op);触发
// 失败(cyclic / 失败缓存重抛)走可捕获异常展开,外围 try/catch
// 可收到原错误值。
if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) {
return self.raise_call_error(&msg);
}
// 求值后活值在真实 ns 条目表,deferred 对象自身槽位是预注册占位。
let target = oxide_builtins::module::deferred_ns_read_target(obj);
if let Some(state) = oxide_builtins::module::module_ns_export(target, prop_name_si) {
return match state {
oxide_builtins::module::ModuleNsQuery::Initialized(v) => Ok(v),
oxide_builtins::module::ModuleNsQuery::Uninitialized => {
@@ -360,8 +369,9 @@ impl Vm {
prop_name_si,
use_frame_push
);
// 模块命名空间 exotic [[Set]] 恒 false(规范 10.4.6.8):严格抛 TypeError,
// sloppy 静默 no-op;Reflect.set 以 strict=true 调用并投影为 false。
// 模块命名空间 exotic [[Set]] 恒 false(规范 10.4.6.8,deferred 形态同口径
// 不触发求值):严格抛 TypeError,sloppy 静默 no-op;Reflect.set 以
// strict=true 调用并投影为 false。
if obj.is_module_namespace() {
if strict {
return self.write_protection_failure(builtin, "Cannot assign to a module namespace export");
@@ -1713,7 +1723,7 @@ mod tests {
assert_eq!(obj.logical_len(), 2_000_001, "length 扩到 index+1");
assert_eq!(obj.array_prop_count, 1, "物理区不物化");
assert_eq!(vm.ordinary_get(obj, si, a).expect("get"), JsValue::int(9));
assert!(vm.has_property(obj, si), "in 为 true");
assert!(vm.has_property(obj, si).expect("has_property"), "in 为 true");
let keys = oxide_builtins::object::walk_own_keys(&vm, obj);
assert!(keys.iter().any(|(k, _)| *k == si), "枚举含大索引键");
}
@@ -1729,7 +1739,7 @@ mod tests {
let obj = unsafe { &*b.as_js_object_ptr() };
assert_eq!(obj.logical_len(), 0, "2^32-1 不扩展 length");
assert_eq!(vm.ordinary_get(obj, si, b).expect("get"), JsValue::int(9));
assert!(vm.has_property(obj, si));
assert!(vm.has_property(obj, si).expect("has_property"));
}
#[test]
@@ -1763,7 +1773,7 @@ mod tests {
let obj = unsafe { &*d.as_js_object_ptr() };
assert_eq!(obj.logical_len(), 5);
assert!(!vm.has_property(obj, si), "截断后 3000000 键被删");
assert!(!vm.has_property(obj, si).expect("has_property"), "截断后 3000000 键被删");
assert_eq!(vm.ordinary_get(obj, si, d).expect("get"), JsValue::undefined());
}
@@ -1839,11 +1849,12 @@ mod tests {
.expect("set");
let outcome =
oxide_builtins::object::delete_own_property_outcome(&mut vm, unsafe { &mut *a.as_js_object_ptr() }, si);
oxide_builtins::object::delete_own_property_outcome(&mut vm, unsafe { &mut *a.as_js_object_ptr() }, si)
.expect("delete");
assert_eq!(outcome, oxide_builtins::object::DeleteOutcome::Deleted);
let obj = unsafe { &*a.as_js_object_ptr() };
assert_eq!(obj.logical_len(), 2_000_001, "length 不变");
assert!(!vm.has_property(obj, si));
assert!(!vm.has_property(obj, si).expect("has_property"));
}
#[test]
@@ -1941,4 +1952,109 @@ mod tests {
let obj = unsafe { &*boxed.as_js_object_ptr() };
assert_eq!(vm.ordinary_get(obj, length_si, boxed).expect("get"), JsValue::int(4), "length 不变");
}
// ---- deferred namespace 求值触发测试 ----
use std::sync::Arc;
/// 建带编译服务的 VM 与一个依赖模块函数(deferred ns 的 `[[Module]]` 载荷)。
fn deferred_vm_with_function() -> (Vm, JsValue) {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(oxide_compiler::DefaultCompilerService));
let fn_val = vm
.create_dynamic_function(&[], "return 42;", false, false)
.expect("create function");
(vm, fn_val)
}
/// 建 deferred namespace 对象(`fn_val` 为依赖模块函数,未求值态)。
fn make_deferred_ns(vm: &mut Vm, fn_val: JsValue) -> JsValue {
vm.set_reg(1, fn_val);
vm.set_reg(2, JsValue::undefined());
oxide_builtins::module::module_defer_object(vm, &[0, 1, 2]).unwrap()
}
#[test]
fn deferred_ns_get_triggers_evaluation() {
let (mut vm, fn_val) = deferred_vm_with_function();
let ns_val = make_deferred_ns(&mut vm, fn_val);
let ns_ptr = ns_val.as_js_object_ptr();
// 未求值态。
let ns = unsafe { &*ns_ptr };
assert!(!oxide_builtins::module::deferred_state(ns).unwrap().evaluated, "求值前应为 false");
// 读属性触发求值(模块函数返回 42,非对象无妨,只求值完成)。
let x_si = vm.kernel_core.perm_interner().intern("x").0;
let ns = unsafe { &*ns_ptr };
let _ = vm.ordinary_get(ns, x_si, ns_val);
// 求值后 [[Evaluated]] 置真。
let ns = unsafe { &*ns_ptr };
assert!(oxide_builtins::module::deferred_state(ns).unwrap().evaluated, "求值后应为 true");
}
#[test]
fn deferred_ns_cyclic_get_throws_type_error() {
let (mut vm, _fn_val) = deferred_vm_with_function();
// 哨兵形态:[[Module]] = undefined(自导入/祖先 defer)。
vm.set_reg(1, JsValue::undefined());
vm.set_reg(2, JsValue::undefined());
let ns_val = oxide_builtins::module::module_defer_object(&mut vm, &[0, 1, 2]).unwrap();
let ns_ptr = ns_val.as_js_object_ptr();
// 模拟「正在求值模块」:置 cyclic 守卫标记。
vm.set_evaluating_module(Some(JsValue::undefined()));
// 读属性触发 cyclic 守卫 → TypeError(builtin 上下文返 Err)。
let x_si = vm.kernel_core.perm_interner().intern("x").0;
let ns = unsafe { &*ns_ptr };
let result = vm.ordinary_get(ns, x_si, ns_val);
assert!(result.is_err(), "cyclic 应抛 TypeError");
}
#[test]
fn deferred_ns_failure_rethrows_same_error() {
let (mut vm, _fn_val) = deferred_vm_with_function();
// 模块函数抛错。
let throwing_fn = vm
.create_dynamic_function(&[], "throw new Error('boom');", false, false)
.expect("create function");
vm.set_reg(1, throwing_fn);
vm.set_reg(2, JsValue::undefined());
let ns_val = oxide_builtins::module::module_defer_object(&mut vm, &[0, 1, 2]).unwrap();
let ns_ptr = ns_val.as_js_object_ptr();
let x_si = vm.kernel_core.perm_interner().intern("x").0;
// 首次读:触发求值,模块函数抛错,缓存错误。
let ns = unsafe { &*ns_ptr };
assert!(vm.ordinary_get(ns, x_si, ns_val).is_err(), "首次读应抛错");
let ns = unsafe { &*ns_ptr };
let cached_err = oxide_builtins::module::deferred_state(ns).unwrap().error.expect("失败应缓存错误");
// 再读:重抛同一错误对象(sameValue 身份)。
let ns = unsafe { &*ns_ptr };
assert!(vm.ordinary_get(ns, x_si, ns_val).is_err(), "再读应再抛");
// 重抛经 uncaught 侧通道还原,取回验证指针身份。
let rethrown = vm.take_uncaught_value().expect("重抛应还原 uncaught 侧通道");
assert!(
std::ptr::eq(rethrown.as_js_object_ptr(), cached_err.as_js_object_ptr()),
"再读应重抛同一错误对象"
);
}
#[test]
fn deferred_ns_symbol_like_key_does_not_trigger() {
let (mut vm, fn_val) = deferred_vm_with_function();
let ns_val = make_deferred_ns(&mut vm, fn_val);
let ns_ptr = ns_val.as_js_object_ptr();
// 读 "then" 键(deferred 的 symbol-like 键)不触发求值。
let then_si = vm.kernel_core.perm_interner().intern("then").0;
let ns = unsafe { &*ns_ptr };
let _ = vm.ordinary_get(ns, then_si, ns_val);
// 求值未触发:[[Evaluated]] 仍为 false。
let ns = unsafe { &*ns_ptr };
assert!(!oxide_builtins::module::deferred_state(ns).unwrap().evaluated, "symbol-like 键不应触发求值");
}
}
+27 -1
View File
@@ -519,7 +519,26 @@ impl Vm {
sub.n_args,
sub.is_arrow
);
let result = self.dispatch();
// 体自身 try 处理器基线:异常逃出体内时先由体内处理器捕获
// (catch_pc 在体字节码内,状态还原后指向调用方字节码即悬空)。
let try_base = self.try_stack.len();
let mut result = self.dispatch();
loop {
if result.is_ok() || self.try_stack.len() <= try_base {
break;
}
// 体内处理器捕获:pc 落体字节码 catch/finally,继续执行体余部。
// 捕获体再抛(re-throw)时展开会消费外层处理器并跳外字节码 catch_pc,
// 与活动字节码不一致——re-dispatch 返回 Err 即该形态,还原状态后由
// 调用边界按外层处理器正常展开(与修复前同口径)。
if self.unwind().is_err() {
break;
}
result = self.dispatch();
if result.is_err() {
break;
}
}
self.native_call_depth -= 1;
vm_trace!("call_bytecode: restoring state pc={} result={:?}", saved.pc, result.as_ref().ok());
@@ -724,7 +743,14 @@ impl Vm {
self.top_level_this = this_val;
self.regs[254] = this_val;
// 「正在求值模块」cyclic 守卫标记:入口模块函数对象在 run 不可得(顶层
// CREATE_CLOSURE 产物),用 undefined 哨兵——sentinel 检查只看 is_some(),
// 依赖模块的 real-function 检查恒不匹配 undefined,安全。save/restore 保
// 嵌套/重入安全,dispatch 返回即还原(微任务 drain 前模块已非求值中)。
let saved_evaluating = self.evaluating_module.take();
self.evaluating_module = Some(JsValue::undefined());
let result = self.execute_task(|vm| vm.dispatch());
self.evaluating_module = saved_evaluating;
// 顶层执行结束后 drain 微任务队列:Promise reactions 与 thenable 委托在此执行。
self.drain_microtasks();
// 指令周期采样:run 末聚合本 run 的样本并输出 top-K 直方图(关闭时零开销
+2
View File
@@ -135,6 +135,7 @@ impl Vm {
try_stack: Vec::new(),
exception_value: None,
last_uncaught_value: None,
evaluating_module: None,
pending_length_exception: None,
pending_exception: None,
pending_error_kind: None,
@@ -295,6 +296,7 @@ impl Vm {
try_stack: Vec::new(),
exception_value: None,
last_uncaught_value: None,
evaluating_module: None,
pending_length_exception: None,
pending_exception: None,
pending_error_kind: None,