mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 全局对象成员写与删后镜像槽反向同步防裸读失步
This commit is contained in:
@@ -255,6 +255,9 @@ pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u3
|
||||
all_keys.extend(walk_own_symbol_keys(vm, obj));
|
||||
}
|
||||
let Some(delete_pos) = all_keys.iter().find(|(si, _)| *si == key_si).map(|(_, pos)| *pos) else {
|
||||
// 属性缺失:delete 按规范返 true,镜像槽同步 undefined 保持
|
||||
// "槽 = A 侧原始存储"不变式(与入口预载缺位语义幂等)。
|
||||
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
|
||||
return true;
|
||||
};
|
||||
// walk_own_keys 已返回绝对存储索引(数组含元素区偏移),直接使用。
|
||||
@@ -319,6 +322,9 @@ pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u3
|
||||
}
|
||||
}
|
||||
obj.bump_generation();
|
||||
// 删除成功:镜像槽同步 undefined(成员形删除 / Reflect.deleteProperty /
|
||||
// 0x9C 清槽同源收口,后两者对此幂等)。
|
||||
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@@ -177,6 +177,15 @@ pub trait VmHost {
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, get: JsValue, set: JsValue, attributes: PropAttributes,
|
||||
) -> Result<(), String>;
|
||||
fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue);
|
||||
/// 全局 builtin 属性 A 侧写/删成功后,把当前帧对应镜像槽反向同步为 `val`
|
||||
///(维护"槽 = A 侧原始存储"不变式)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `obj` 非会话全局对象时立即 no-op(接收者指针判等,热路径零额外成本)。
|
||||
/// - 键未登记在活动模块 builtin 镜像名集时无槽可写,no-op。
|
||||
/// # 副作用
|
||||
/// - 写当前帧寄存器文件的镜像槽。
|
||||
fn sync_global_builtin_mirror(&mut self, obj: &JsObject, key_si: u32, val: JsValue);
|
||||
|
||||
// 查找 / 强制转换
|
||||
fn lookup_str(&self, val: JsValue) -> Option<String>;
|
||||
|
||||
@@ -419,6 +419,9 @@ impl Vm {
|
||||
{
|
||||
self.profiling.record_ic_hit();
|
||||
ic_trace!("IC_SET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth);
|
||||
// IC 命中快路径防御:今日全局对象带 meta 不可达,防 meta 不变量
|
||||
// 被未来优化破坏后镜像静默失步(键由 (shape, slot) 反查)。
|
||||
self.sync_global_builtin_mirror_slot(obj, cached_shape_id, cached_slot, cached_depth, value);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -443,6 +446,7 @@ impl Vm {
|
||||
self.profiling.record_ic_miss();
|
||||
prop_cache_miss();
|
||||
obj.set_prop_shape(pos, value);
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, value);
|
||||
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), pos, 0);
|
||||
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
|
||||
} else {
|
||||
|
||||
+17
-14
@@ -859,6 +859,19 @@ impl Vm {
|
||||
.map(|m| m.as_ref())
|
||||
}
|
||||
|
||||
/// 活动帧所属代际平表里 `active_flat_id` 指向的模块条目(镜像槽同步 /
|
||||
/// 重载的名集载体)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 活动代际表在执行期恒在注册表中(首 run 前为预登记的空表占位),
|
||||
/// `active_flat_id` 越界返回 None(调用方按无模块处理)。
|
||||
pub(crate) fn active_module(&self) -> Option<&CompiledModule> {
|
||||
self.active_table()
|
||||
.modules
|
||||
.get(self.active_flat_id as usize)
|
||||
.map(|m| m.as_ref())
|
||||
}
|
||||
|
||||
/// 子模块表注册表的当前代际条目总数(run 边界回收行为的测试钉)。
|
||||
pub fn table_gen_count(&self) -> usize {
|
||||
self.tables.len()
|
||||
@@ -1772,20 +1785,7 @@ impl Vm {
|
||||
self.bytecode = sub_bytecode;
|
||||
self.activate_immutables(gen, sub_idx, &sub.constants);
|
||||
self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize));
|
||||
for (name, reg) in &sub.builtin_reg_map.clone() {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
let global = self.session.global_object();
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
|
||||
self.regs[*reg as usize] = global.get_prop_at(pos);
|
||||
} else {
|
||||
vm_debug!(
|
||||
"push_frame: builtin '{}' reg={} NOT on global object (stays {})",
|
||||
name,
|
||||
reg,
|
||||
self.regs[*reg as usize]
|
||||
);
|
||||
}
|
||||
}
|
||||
self.reload_builtin_mirror_slots(&sub.builtin_reg_map);
|
||||
|
||||
self.active_reg_limit = sub_n_registers.max(1);
|
||||
self.pc = 0;
|
||||
@@ -2633,6 +2633,9 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
|
||||
self.set_or_create_prop_value(obj, prop_name_si, val)
|
||||
}
|
||||
fn sync_global_builtin_mirror(&mut self, obj: &JsObject, key_si: u32, val: JsValue) {
|
||||
self.sync_global_builtin_mirror(obj, key_si, val)
|
||||
}
|
||||
fn lookup_str(&self, val: JsValue) -> Option<String> {
|
||||
self.lookup_str(val)
|
||||
}
|
||||
|
||||
@@ -152,6 +152,95 @@ impl Vm {
|
||||
None
|
||||
}
|
||||
|
||||
/// 全局 builtin 属性 A 侧写成功后反向同步当前帧镜像槽(成员写 / define /
|
||||
/// delete 成功后调用)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 仅当接收者为会话全局对象时生效:非全局写经指针判等短路(热路径成本
|
||||
/// = 一次指针比较)。
|
||||
/// - 名集以活动模块 `builtin_reg_map` 为准(与编译期登记同源):键不在表
|
||||
/// 内即无镜像槽,不写。
|
||||
/// # 副作用
|
||||
/// - 写当前帧寄存器文件镜像槽。
|
||||
/// # 注意事项
|
||||
/// - 只可挂在真实 A 侧写发生之后;写失败路径(只读 no-op / 抛错)不得
|
||||
/// 调用,否则污染镜像槽。
|
||||
pub(crate) fn sync_global_builtin_mirror(&mut self, obj: &JsObject, key_si: u32, val: JsValue) {
|
||||
if !std::ptr::eq(obj as *const JsObject, self.session.global_object().as_ptr()) {
|
||||
return;
|
||||
}
|
||||
let Some(module) = self.active_module() else {
|
||||
return;
|
||||
};
|
||||
for (name, reg) in &module.builtin_reg_map {
|
||||
if self.kernel_core.perm_interner().intern(name.as_str()).0 == key_si {
|
||||
self.regs[*reg as usize] = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// IC 命中快路径的防御变体(命中分支不解析键):按 (shape, slot) 定位被
|
||||
/// 写属性,自镜像名集反查键后同步。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 仅当写落在全局对象自身时生效:原型链目标命中(depth > 0)不改全局
|
||||
/// 自身属性,不同步。
|
||||
/// - 全局对象自构造起恒带 prop_meta,IC 命中分支对今日不可达;本挂点防
|
||||
/// 该不变量被未来优化破坏后镜像静默失步。
|
||||
pub(crate) fn sync_global_builtin_mirror_slot(
|
||||
&mut self, obj: &JsObject, shape_id: u32, slot: u32, depth: u8, val: JsValue,
|
||||
) {
|
||||
if !std::ptr::eq(obj as *const JsObject, self.session.global_object().as_ptr()) {
|
||||
return;
|
||||
}
|
||||
if depth != 0 || shape_id != obj.shape_id() {
|
||||
return;
|
||||
}
|
||||
let Some(module) = self.active_module() else {
|
||||
return;
|
||||
};
|
||||
for (name, reg) in &module.builtin_reg_map {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
if self.kernel_core.shape_forge().lookup_position(shape_id, si) == Some(slot) {
|
||||
self.regs[*reg as usize] = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 重载活动帧模块(`active_flat_id`)的 builtin 镜像槽:先按值取出名集再
|
||||
/// 写寄存器,避免借用交叉。帧恢复 / 重执行边界调用。
|
||||
pub(crate) fn reload_active_module_mirror_slots(&mut self) {
|
||||
let map = self.active_module().map(|m| m.builtin_reg_map.clone()).unwrap_or_default();
|
||||
self.reload_builtin_mirror_slots(&map);
|
||||
}
|
||||
|
||||
/// 从全局对象 A 侧重载模块 builtin 名集的镜像槽:属性在位取原始存储值,
|
||||
/// 缺位写 undefined。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 缺位臂语义为"写 undefined",各入口(run / 帧 / inline / 恢复)统一,
|
||||
/// 使成员形删除后的裸读与入口预载看到同一值。
|
||||
/// # 副作用
|
||||
/// - 只写镜像槽寄存器(槽下标受编译期登记约束)。
|
||||
pub(crate) fn reload_builtin_mirror_slots(&mut self, map: &[(String, u32)]) {
|
||||
if map.is_empty() {
|
||||
return;
|
||||
}
|
||||
let global = self.session.global_object();
|
||||
for (name, reg) in map {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
let val = self
|
||||
.kernel_core
|
||||
.shape_forge()
|
||||
.lookup_position(global.shape_id(), si)
|
||||
.map(|pos| global.get_prop_at(pos))
|
||||
.unwrap_or_else(JsValue::undefined);
|
||||
self.regs[*reg as usize] = val;
|
||||
}
|
||||
}
|
||||
|
||||
/// `strict` 为写方(执行赋值的那个函数/脚本)的严格模式:写失败时严格抛
|
||||
/// TypeError,sloppy 静默 no-op(ECMA-262 OrdinarySetOwnProperty 失败分支)。
|
||||
pub(crate) fn ordinary_set(
|
||||
@@ -285,6 +374,7 @@ impl Vm {
|
||||
}
|
||||
// pos 是存储索引(get_own_property_slot 对数组已加元素区偏移)。
|
||||
obj.set_prop_storage(pos as usize, val);
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, val);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -469,11 +559,15 @@ impl Vm {
|
||||
}
|
||||
if crate::ic_helper::ic_set_hit_own(obj, &self.bytecode, ext_pc, val) {
|
||||
self.profiling.record_ic_hit();
|
||||
// IC 写命中快路径防御:今日全局对象带 meta 不可达,防不变量被破坏
|
||||
// 后静默失步。
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, val);
|
||||
return Ok(());
|
||||
}
|
||||
self.profiling.record_ic_miss();
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
obj.set_prop_shape(pos, val);
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, val);
|
||||
crate::ic_helper::write_ic_back(self.bytecode_mut(), ext_pc, obj.shape_id(), pos, 0);
|
||||
} else if self.named_prop_create_needs_ordinary_set(obj, prop_name_si) {
|
||||
// 数组 length / 整数索引键写新属性:分流回 ordinary_set(ArraySetLength /
|
||||
@@ -533,6 +627,7 @@ impl Vm {
|
||||
obj.set_shape_id(new_shape_id);
|
||||
obj.push_prop(val);
|
||||
obj.bump_generation();
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, val);
|
||||
crate::ic_helper::write_ic_back(self.bytecode_mut(), ext_pc, new_shape_id, slot, 0);
|
||||
self.kernel_core.prop_forge().upsert(
|
||||
new_shape_id,
|
||||
@@ -580,6 +675,7 @@ impl Vm {
|
||||
}
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
obj.set_prop_shape(pos, val);
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, val);
|
||||
} else {
|
||||
// 不可扩展对象禁止新增命名属性(兜底路径,常规入口已拦截)。
|
||||
if !obj.is_extensible() {
|
||||
@@ -590,6 +686,7 @@ impl Vm {
|
||||
// 数组对象:属性追加到 hash_props 属性区(元素之后),array_prop_count 不变。
|
||||
obj.push_prop(val);
|
||||
obj.bump_generation();
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,6 +759,7 @@ impl Vm {
|
||||
obj.set_prop_storage(pos, val);
|
||||
obj.set_data_meta(pos, attributes);
|
||||
obj.bump_generation();
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, val);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -713,6 +811,9 @@ impl Vm {
|
||||
obj.set_prop_storage(pos, JsValue::undefined());
|
||||
obj.set_accessor_meta(pos, get, set, attributes);
|
||||
obj.bump_generation();
|
||||
// accessor 化同步原始存储值(undefined),与入口预载读裸槽同不变式;
|
||||
// getter 穿透属镜像模型结构残面,不在本挂点扩面。
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, JsValue::undefined());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -331,6 +331,9 @@ impl Vm {
|
||||
let vm = self;
|
||||
let _window_regs: Vec<JsValue> = Vec::new();
|
||||
inline_core_fields!(vm, saved, inline_restore, _window_regs);
|
||||
// 窗口回拷已复活调用方陈旧镜像槽:重载调用方模块的 builtin 名集
|
||||
// (active_flat_id 已随宏回写还原到调用方)。
|
||||
vm.reload_active_module_mirror_slots();
|
||||
}
|
||||
|
||||
/// 首次执行的 VM 就绪:清空执行核心(regs/pc/bytecode/各栈段/迭代器/内联态),
|
||||
@@ -459,13 +462,7 @@ impl Vm {
|
||||
receiver
|
||||
};
|
||||
self.regs[255] = JsValue::undefined();
|
||||
for (name, reg) in &sub.builtin_reg_map {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
let global = self.session.global_object();
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
|
||||
self.regs[*reg as usize] = global.get_prop_at(pos);
|
||||
}
|
||||
}
|
||||
self.reload_builtin_mirror_slots(&sub.builtin_reg_map);
|
||||
let _ = callee;
|
||||
|
||||
vm_trace!(
|
||||
@@ -522,6 +519,9 @@ impl Vm {
|
||||
// active_reg_limit 还原为调用方真实值(caller_reg_limit 可能被存活上界截断)。
|
||||
self.active_reg_limit = frame.caller_active_reg_limit;
|
||||
self.pc = frame.return_addr;
|
||||
// 窗口回拷已复活调用方陈旧镜像槽:重载调用方模块的 builtin 名集
|
||||
// (active_flat_id 已随保存栈弹回还原到调用方)。
|
||||
self.reload_active_module_mirror_slots();
|
||||
}
|
||||
|
||||
/// 重新执行当前已加载的 bytecode:清空执行状态并重置 IC 缓存后再次 dispatch。
|
||||
@@ -535,6 +535,8 @@ impl Vm {
|
||||
self.clear_execution_state();
|
||||
self.active_reg_limit = self.root_reg_limit;
|
||||
self.regs[254] = self.top_level_this;
|
||||
// 寄存器文件清空后镜像槽须重载(与 run 入口同语义),否则裸读回陈旧值。
|
||||
self.reload_active_module_mirror_slots();
|
||||
crate::ic_helper::clear_ic_caches(self.bytecode_mut());
|
||||
self.dispatch()
|
||||
}
|
||||
@@ -606,13 +608,7 @@ impl Vm {
|
||||
self.root_reg_limit = module.n_registers.max(1);
|
||||
self.active_reg_limit = self.root_reg_limit;
|
||||
|
||||
for (name, reg) in &module.builtin_reg_map {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
let global = self.session.global_object();
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
|
||||
self.regs[*reg as usize] = global.get_prop_at(pos);
|
||||
}
|
||||
}
|
||||
self.reload_builtin_mirror_slots(&module.builtin_reg_map);
|
||||
|
||||
// 顶层 this:脚本为全局对象(ECMA-262 全局执行上下文);
|
||||
// ES module 顶层环境 GetThisBinding 返回 undefined。记录到
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
//! 成员写 globalThis 后镜像槽反向失步收口测试。
|
||||
//!
|
||||
//! 全局对象双存储:A 侧 = 全局对象属性存储,B 侧 = 镜像寄存器槽(裸标识符
|
||||
//! 读走 LOAD_VAR)。成员写 `globalThis.X = v` 只动 A 侧,同 run 内 B 侧须经
|
||||
//! 写原语族挂点反向同步;跨调用/跨帧经返回边界重预载。覆盖:同 run 成员写、
|
||||
//! 重复写、跨调用、成员删(同 run 与跨调用)、defineProperty 数据描述符形、
|
||||
//! 裸名写面不回归、写失败不过同步、三常量拦截不回归、meta 不变量白盒钉。
|
||||
//!
|
||||
//! 核心形断言方向:成员写后裸读应见新属性值,`Math === m`(m 为写前保存)
|
||||
//! 期望 false;旧行为(槽不刷新)为 true。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> Result<JsValue, String> {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&Arc::new(module))
|
||||
}
|
||||
|
||||
fn eval_str(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
let mut vm = Vm::new();
|
||||
let result = vm.run(&Arc::new(module)).expect("run");
|
||||
vm.lookup_str(result).unwrap_or_else(|| format!("{result}"))
|
||||
}
|
||||
|
||||
fn eval_truthy(source: &str) {
|
||||
let r = eval(source).expect("run");
|
||||
assert!(r.is_bool() && r.as_bool(), "expected true, got: {r:?}\nsource: {source}");
|
||||
}
|
||||
|
||||
// ── 核心形与重复写:同 run 成员写后裸读见新值 ──
|
||||
#[test]
|
||||
fn member_write_same_run_bare_read_sees_new_value() {
|
||||
eval_truthy("var m = Math; globalThis.Math = {x:1}; Math !== m && Math.x === 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeated_member_write_hot_path() {
|
||||
eval_truthy(
|
||||
"var m = Math; globalThis.Math = {a:1}; globalThis.Math = {x:2}; \
|
||||
Math !== m && Math.x === 2",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 跨调用:调用期 A 侧变更经返回边界重预载 ──
|
||||
#[test]
|
||||
fn member_write_across_call() {
|
||||
eval_truthy("var m = Math; (function(){ globalThis.Math = {x:1}; })(); Math !== m && Math.x === 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn member_delete_across_call_bare_read_undefined() {
|
||||
eval_truthy(
|
||||
"var m = Math; (function(){ delete globalThis.Math; })(); \
|
||||
Math === undefined && Math !== m",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 成员删同 run:delete 成员形后裸读 undefined ──
|
||||
#[test]
|
||||
fn member_delete_same_run_bare_read_undefined() {
|
||||
eval_truthy("var m = Math; delete globalThis.Math; Math === undefined && Math !== m");
|
||||
}
|
||||
|
||||
// ── defineProperty 数据描述符形同收口 ──
|
||||
#[test]
|
||||
fn define_property_member_form_same_run() {
|
||||
eval_truthy(
|
||||
"var m = Math; Object.defineProperty(globalThis,'Math',\
|
||||
{value:{x:1},writable:true,configurable:true,enumerable:false}); \
|
||||
Math !== m && Math.x === 1",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 裸名写面不回归 ──
|
||||
#[test]
|
||||
fn bare_write_double_write_not_regressed() {
|
||||
eval_truthy("parseInt = 42; parseInt === 42 && globalThis.parseInt === 42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_bare_then_member_write() {
|
||||
eval_truthy("var m = parseInt; parseInt = 42; globalThis.parseInt = 43; parseInt === 43");
|
||||
}
|
||||
|
||||
// ── 写失败不过同步:只读属性 no-op 写不污染镜像槽 ──
|
||||
#[test]
|
||||
fn failed_write_does_not_sync_mirror() {
|
||||
let s = eval_str("globalThis.undefined = 5; '' + undefined");
|
||||
assert_eq!(s, "undefined", "只读 no-op 写不得同步镜像槽,裸 undefined 读回 undefined");
|
||||
}
|
||||
|
||||
// ── 三常量裸名拦截不回归 ──
|
||||
#[test]
|
||||
fn constant_bare_write_still_intercepted() {
|
||||
let s = eval_str("undefined = 3; '' + undefined");
|
||||
assert_eq!(s, "undefined", "sloppy 裸写三常量静默 no-op");
|
||||
let r = eval("(function(){ 'use strict'; undefined = 3; })()");
|
||||
assert!(r.is_err(), "strict 裸写三常量须抛 TypeError,got: {r:?}");
|
||||
}
|
||||
|
||||
// ── meta 不变量白盒钉:全局对象恒带 prop_meta ──
|
||||
// 该不变量是 IC 写命中分支对全局对象不可达的前提;若被破坏(全局对象
|
||||
// meta-less 化),IC 快路径可达而镜像同步面须重审,此钉先红提示。
|
||||
#[test]
|
||||
fn global_object_always_has_prop_meta() {
|
||||
let core = oxide_kernel::KernelCore::new(oxide_kernel::KernelConfig::minimal());
|
||||
let session = oxide_kernel::KernelSession::new(&core);
|
||||
assert!(
|
||||
session.global_object().has_prop_meta(),
|
||||
"全局对象自构造起恒带 prop_meta(三常量 set_data_meta 落定)"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user