diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 1a0f9f48..74e514a0 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -134,9 +134,10 @@ const BUILTIN_GLOBALS: &[&str] = &[ "$262", ]; -/// 不可写全局内置名:全局对象上的数据属性为 {writable:false, configurable:false}, +/// 只读全局内置名:全局对象上的数据属性为 {writable:false, configurable:false}, /// 对它们的 put 永不成功。写路径命中这些名字的全局绑定(非局部遮蔽)时编译期 -/// 拦截:sloppy 静默丢弃、strict 抛 TypeError;其余内置名属性可写,写路径放行。 +/// 拦截:sloppy 静默丢弃、strict 抛 TypeError;BUILTIN_GLOBALS 内其余名属性可写 +/// (writable:true),标识符写须双写全局属性(见 targets_writable_builtin)。 const NON_WRITABLE_GLOBAL_BUILTINS: &[&str] = &["undefined", "NaN", "Infinity"]; pub(crate) struct FieldBuffer { @@ -675,20 +676,37 @@ impl CompileCtx { false } - /// 写目标是否为不可写全局内置(undefined/NaN/Infinity 的全局绑定)。 - /// 名字在名单内且解析寄存器是全局内置槽(builtin_reg_map 登记),或名字落到 - /// 全局作用域绑定(顶层 var 预声明/隐式全局,含继承的全局槽)——局部 + /// 名字是否解析到全局内置槽:builtin_reg_map 登记(预注册镜像槽),或绑定 + /// 落全局作用域(scope 0,顶层 var 预声明/隐式全局,含继承的全局槽)。局部 /// var/let/参数遮蔽同名时解析到局部寄存器/局部作用域,两条件均不命中。 - pub(crate) fn targets_readonly_builtin(&self, name: &str, reg: u32) -> bool { - if !NON_WRITABLE_GLOBAL_BUILTINS.contains(&name) { - return false; - } + fn resolves_to_global_builtin_slot(&self, name: &str, reg: u32) -> bool { if self.scopes.builtin_reg_map.iter().any(|(n, r)| n == name && *r == reg) { return true; } matches!(self.scopes.symbols.lookup_any_binding(name), Some((_, 0))) } + /// 写目标是否为只读全局内置(undefined/NaN/Infinity 的全局绑定——全局对象上 + /// 数据属性 {writable:false, configurable:false},put 永不成功)。名字在只读 + /// 名单内且解析到全局内置槽;声明臂与赋值臂共用本谓词。 + pub(crate) fn targets_readonly_builtin(&self, name: &str, reg: u32) -> bool { + NON_WRITABLE_GLOBAL_BUILTINS.contains(&name) && self.resolves_to_global_builtin_slot(name, reg) + } + + /// 写目标是否为可写全局内置(全局对象上数据属性 {writable:true, + /// configurable:true})。标识符写须双写镜像槽 + 全局对象属性,否则裸读 + /// (镜像)与 globalThis 反射(属性)失步。 + pub(crate) fn targets_writable_builtin(&self, name: &str, reg: u32) -> bool { + Self::is_writable_builtin_global(name) && self.resolves_to_global_builtin_slot(name, reg) + } + + /// 规范可写全局名:BUILTIN_GLOBALS 减只读三常量集——11 个 w:true 函数、 + /// 48 个构造器/命名空间、globalThis 与宿主名,描述符皆 + /// {writable:true, enumerable:false, configurable:true}。 + pub(crate) fn is_writable_builtin_global(name: &str) -> bool { + BUILTIN_GLOBALS.contains(&name) && !NON_WRITABLE_GLOBAL_BUILTINS.contains(&name) + } + pub(crate) fn is_known_builtin(name: &str) -> bool { BUILTIN_GLOBALS.contains(&name) } @@ -1802,7 +1820,7 @@ impl Default for Emitter { #[cfg(test)] mod tests { - use super::BUILTIN_GLOBALS; + use super::{BUILTIN_GLOBALS, NON_WRITABLE_GLOBAL_BUILTINS}; use crate::prepass::RESTRICTED_GLOBAL_LEXICAL_NAMES; /// 漂移守卫:受限全局名集与 put 写拦截名单交叠名恒同步(两名单语义独立、 @@ -1813,4 +1831,18 @@ mod tests { assert!(BUILTIN_GLOBALS.contains(name), "受限全局名缺失于 builtin 名单:{name}"); } } + + /// 漂移守卫:只读名单是 builtin 母集子集,且只读/可写两谓词对母集构成 + /// 划分(不重不漏)——防三常量集或母集改名/删名时拦截面与双写面单边漂移。 + #[test] + fn readonly_and_writable_partition_builtin_globals() { + for name in NON_WRITABLE_GLOBAL_BUILTINS { + assert!(BUILTIN_GLOBALS.contains(name), "只读全局名缺失于 builtin 名单:{name}"); + } + for name in BUILTIN_GLOBALS { + let readonly = NON_WRITABLE_GLOBAL_BUILTINS.contains(name); + let writable = crate::CompileCtx::is_writable_builtin_global(name); + assert!(readonly ^ writable, "builtin 名只读/可写归属漂移:{name}"); + } + } } diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index 25458ebf..b100e08d 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -40,6 +40,10 @@ impl Emitter { return Ok(val_reg); } ctx.inst(Inst::new(op, Operand::Reg(reg), Operand::Reg(rhs), Operand::None)); + // 可写内置名:RMW 新值同步落全局对象属性。 + if ctx.targets_writable_builtin(name, reg) { + self.emit_global_put_write(name, reg, ctx); + } return Ok(reg); } } @@ -116,9 +120,11 @@ impl Emitter { if is_tier { // 顶层已声明 var 复合赋值:新值落全局对象属性(A 侧单一真值)。 self.emit_tier_global_write(name, var_reg, ctx); - } - if is_implicit { + } else if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:RMW 新值同步落全局对象属性。 + self.emit_global_put_write(name, var_reg, ctx); } Ok(var_reg) } @@ -452,6 +458,9 @@ impl Emitter { )); if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:同步落全局对象属性(仅短路通过时到达)。 + self.emit_global_put_write(name, var_reg, ctx); } } } diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index 6cadbddd..9cbba90f 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -159,6 +159,10 @@ impl Emitter { return; } ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(reg), Operand::Reg(val_reg), Operand::Imm(0))); + // 可写内置名:值同步落全局对象属性,裸读(镜像)与反射不失步。 + if ctx.targets_writable_builtin(name, reg) { + self.emit_global_put_write(name, reg, ctx); + } return; } } @@ -211,6 +215,10 @@ impl Emitter { )); if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:值同步落全局对象属性(0x98 对既有可写属性仅更值、 + // 保 e/c 位),否则镜像槽与 globalThis 反射失步。 + self.emit_global_put_write(name, var_reg, ctx); } } diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 2c941c23..bcf36350 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -548,6 +548,10 @@ impl Emitter { } let result_reg = ctx.alloc_reg(); ctx.inst(Inst::new(op, Operand::Reg(reg), Operand::Reg(result_reg), Operand::Reg(result_reg))); + // 可写内置名:RMW 新值同步落全局对象属性。 + if ctx.targets_writable_builtin(name, reg) { + self.emit_global_put_write(name, reg, ctx); + } return Ok(result_reg); } } @@ -663,9 +667,11 @@ impl Emitter { if is_tier { // 顶层已声明 var 自增/自减:新值落全局对象属性(A 侧单一真值)。 self.emit_tier_global_write(name, var_reg, ctx); - } - if is_implicit { + } else if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:RMW 新值同步落全局对象属性。 + self.emit_global_put_write(name, var_reg, ctx); } Ok(result_reg) } diff --git a/crates/oxide_emit/src/prepass.rs b/crates/oxide_emit/src/prepass.rs index 0104e1fc..f7ae3299 100644 --- a/crates/oxide_emit/src/prepass.rs +++ b/crates/oxide_emit/src/prepass.rs @@ -9,12 +9,13 @@ use oxide_parser::{ BindingPattern, Declaration, ExportDefaultDeclarationKind, Expression, Statement, VariableDeclarationKind, }; -/// 脚本顶层 lexical 声明禁止使用的受限全局名:规范 HasRestrictedGlobalProperty -/// 判定「全局对象不可配置自有属性名」,现规范全局对象该自有属性恰为三常量。 -/// 名基静态集(编译期不可查运行时描述符);eval 与各 builtin 属性均可配置 -/// (合法遮蔽),不在此列。与 BUILTIN_GLOBALS 语义不同(后者是 put 写拦截名单), -/// 不互相派生,交叠名由漂移守卫单测断言恒同步。 -pub(crate) const RESTRICTED_GLOBAL_LEXICAL_NAMES: &[&str] = &["undefined", "NaN", "Infinity"]; +/// 脚本顶层 lexical 声明(let/const/class)禁止使用的受限全局名:4 名静态集。 +/// 三常量是全局对象上 {configurable:false} 的自有属性名(声明实例化无法建立 +/// 遮蔽绑定);eval 属静态受限臂(顶层 lexical 声明不得遮蔽,var/function +/// 路径不受本集约束)。名基静态集(编译期不可查运行时描述符)。与 +/// BUILTIN_GLOBALS 语义不同(后者是 put 写拦截/双写名单),不互相派生,交叠 +/// 名由漂移守卫单测断言恒同步。 +pub(crate) const RESTRICTED_GLOBAL_LEXICAL_NAMES: &[&str] = &["undefined", "eval", "NaN", "Infinity"]; /// 脚本顶层 lexical 声明撞受限全局名 → SyntaxError(声明实例化期拒绝,整程序 /// 编译失败)。错误消息与既有重复声明错同形;非顶层或名不在受限集 → Ok。 diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index 9c79657e..4cbac747 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -161,6 +161,9 @@ impl Emitter { )); if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:值同步落全局对象属性。 + self.emit_global_put_write(name, var_reg, ctx); } Ok(()) } @@ -369,6 +372,9 @@ impl Emitter { )); if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:值同步落全局对象属性。 + self.emit_global_put_write(name, var_reg, ctx); } Ok(()) } @@ -459,6 +465,9 @@ impl Emitter { )); if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:值同步落全局对象属性(其余属性继续赋值)。 + self.emit_global_put_write(name, var_reg, ctx); } } AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => { diff --git a/crates/oxide_emit/src/stmt/iteration/for_in.rs b/crates/oxide_emit/src/stmt/iteration/for_in.rs index d71d90c3..1783577e 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_in.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_in.rs @@ -72,8 +72,9 @@ impl Emitter { Operand::None, )); // 顶层 for-in var 头:迭代值落全局对象属性(A 侧单一真值)。 - // builtin 名(writable 如 Math)保留既有全局属性值,不 clobber。 - if self.is_global_tier_name(ctx, name) && !CompileCtx::is_known_builtin(name) { + // 只读三常量已在上方拦截臂跳过;其余 builtin 名属性可写, + // 迭代键照规范覆写既有全局属性。 + if self.is_global_tier_name(ctx, name) { self.emit_tier_global_write(name, key_reg, ctx); } } @@ -99,9 +100,10 @@ impl Emitter { if is_implicit && ctx.is_strict { // 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。 self.emit_strict_undeclared_write(name, ctx)?; - } else if is_tier && !CompileCtx::is_known_builtin(name) { + } else if is_tier { // 顶层 for-in 赋值头:迭代值落全局对象属性(A 侧单一真值)。 - // builtin 名保留既有全局属性值,不 clobber。 + // 只读三常量已在上方拦截臂跳过;其余 builtin 名属性可写, + // 迭代键照规范覆写既有全局属性。 self.emit_tier_global_write(name, key_reg, ctx); } else { ctx.inst(Inst::new( @@ -112,6 +114,9 @@ impl Emitter { )); if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:迭代键同步落全局对象属性。 + self.emit_global_put_write(name, var_reg, ctx); } } } diff --git a/crates/oxide_emit/src/stmt/iteration/for_of.rs b/crates/oxide_emit/src/stmt/iteration/for_of.rs index 941b95b3..b4732724 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_of.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_of.rs @@ -129,6 +129,9 @@ impl Emitter { )); if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); + } else if ctx.targets_writable_builtin(name, var_reg) { + // 可写内置名:迭代值同步落全局对象属性。 + self.emit_global_put_write(name, var_reg, ctx); } } } diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index 3844fff6..ec5cddc1 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -625,16 +625,16 @@ impl KernelSession { let si_undef = core.perm_interner.intern("undefined").0; let si_infinity = core.perm_interner.intern("Infinity").0; - // 全局三常量描述符:{ writable:false, enumerable:false, configurable:true }。 + // 全局三常量描述符:{ writable:false, enumerable:false, configurable:false }。 // e:false 是枚举面守卫(Object.keys / for-in 由 enumerable 决定); - // c:true 是 delete / defineProperty 可观察的规范值(delete globalThis.undefined - // 属性真删、返回 true)。 + // c:false 是 delete / defineProperty 可观察的规范值(delete globalThis.undefined + // 返回 false、属性不可删,后续声明/定义均不得 reconfigure)。 let nan_shape = core.shape_forge.make_shape(EMPTY_SHAPE_ID, si_nan); global_obj.set_shape_id(nan_shape); global_obj.ensure_hash_props().push(JsValue::float(f64::NAN)); global_obj.set_data_meta( global_obj.prop_vec_len().saturating_sub(1) as u32, - oxide_types::object::PropAttributes::new(false, false, true), + oxide_types::object::PropAttributes::new(false, false, false), ); let undef_shape = core.shape_forge.make_shape(nan_shape, si_undef); @@ -642,7 +642,7 @@ impl KernelSession { global_obj.ensure_hash_props().push(JsValue::undefined()); global_obj.set_data_meta( global_obj.prop_vec_len().saturating_sub(1) as u32, - oxide_types::object::PropAttributes::new(false, false, true), + oxide_types::object::PropAttributes::new(false, false, false), ); let inf_shape = core.shape_forge.make_shape(undef_shape, si_infinity); @@ -650,7 +650,7 @@ impl KernelSession { global_obj.ensure_hash_props().push(JsValue::float(f64::INFINITY)); global_obj.set_data_meta( global_obj.prop_vec_len().saturating_sub(1) as u32, - oxide_types::object::PropAttributes::new(false, false, true), + oxide_types::object::PropAttributes::new(false, false, false), ); P::new(global_obj) diff --git a/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs b/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs index dcbda3e6..3a415c3c 100644 --- a/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs +++ b/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs @@ -1,7 +1,7 @@ //! 脚本顶层 lexical 声明撞受限全局名的编译期拒绝:let/const/class 声明名撞 -//! 全局对象不可配置自有属性(undefined/NaN/Infinity)→ SyntaxError(整程序 -//! 编译失败)。局部作用域(函数体/块/try)与 eval 代码为合法遮蔽不拒;三常量 -//! 值使用不受影响。 +//! 受限集(undefined/eval/NaN/Infinity——三常量不可配置自有属性 + eval 静态 +//! 受限臂)→ SyntaxError(整程序编译失败)。局部作用域(函数体/块/try)与 +//! eval 代码为合法遮蔽不拒;三常数值使用不受影响。 use std::sync::Arc; @@ -108,10 +108,22 @@ fn top_level_let_uses_nan_value_allowed() { } #[test] -fn top_level_let_eval_name_allowed() { - // eval 是全局对象可配置自有属性:合法遮蔽,不在受限集。 - let r = assert_ok("let eval; 1"); - assert_eq!(r.as_int(), 1); +fn top_level_let_eval_name_rejected() { + // eval 属静态受限臂:顶层 lexical 声明不得遮蔽(var/function 路径不受 + // 本集约束)。 + assert_compile_err("let eval;"); +} + +#[test] +fn top_level_const_eval_name_rejected() { + assert_compile_err("const eval = 1;"); +} + +#[test] +fn top_level_class_eval_name_rejected() { + // 若解析器更早拒绝(early error 族),亦接受该拒绝相位(整程序拒绝可观察 + // 行为同形)。 + assert!(eval("class eval {}").is_err(), "class eval 类名应被拒绝"); } #[test] diff --git a/crates/oxide_vm/tests/global_builtin_constant_descriptor_tests.rs b/crates/oxide_vm/tests/global_builtin_constant_descriptor_tests.rs index ea22316c..193731d5 100644 --- a/crates/oxide_vm/tests/global_builtin_constant_descriptor_tests.rs +++ b/crates/oxide_vm/tests/global_builtin_constant_descriptor_tests.rs @@ -1,7 +1,7 @@ //! 全局三常量(undefined/NaN/Infinity)描述符与 var 声明交互的回归测试。 //! -//! 覆盖规范全局对象属性描述符 { writable:false, enumerable:false, configurable:true }: -//! 描述符读、delete 真删、delete 后镜像裸读、var 无初始化声明的描述符不漂移与值 +//! 覆盖规范全局对象属性描述符 { writable:false, enumerable:false, configurable:false }: +//! 描述符读、delete 拒绝且属性保留、delete 后镜像裸读、var 无初始化声明的描述符不漂移与值 //! 保持(writable 不翻位、NaN/Infinity 不被抹成 undefined)、var 初始化声明的值 //! 落槽、枚举面不泄漏、任务只读拦截与 strict TypeError、eval 路径、for-in 枚举面。 @@ -45,31 +45,31 @@ const CONSTANTS: [&str; 3] = ["undefined", "NaN", "Infinity"]; #[test] fn three_constants_descriptor_matches_spec() { - // #1:描述符 { writable:false, enumerable:false, configurable:true } ×3 常量。 + // #1:描述符 { writable:false, enumerable:false, configurable:false } ×3 常量。 for name in CONSTANTS { let src = format!( "var d = Object.getOwnPropertyDescriptor(globalThis, '{name}'); \ - d.writable === false && d.enumerable === false && d.configurable === true" + d.writable === false && d.enumerable === false && d.configurable === false" ); run_truthy(&src); } } #[test] -fn delete_three_constants_removes_property() { - // #2:configurable → delete 返 true 且属性真删(描述符读为 undefined)×3 常量。 +fn delete_three_constants_refused_property_kept() { + // #2:configurable:false → delete 返 false 且属性保留(描述符仍在、值不变)×3 常量。 for name in CONSTANTS { let src = format!( - "delete globalThis.{name} === true \ - && Object.getOwnPropertyDescriptor(globalThis, '{name}') === undefined" + "delete globalThis.{name} === false \ + && Object.getOwnPropertyDescriptor(globalThis, '{name}') !== undefined" ); run_truthy(&src); } } #[test] -fn delete_then_bare_read_via_mirror() { - // #3:删后裸读走镜像预载(入口值),无 ReferenceError。 +fn refused_delete_then_bare_read_via_mirror() { + // #3:delete 拒绝后裸读走镜像预载(入口值),无 ReferenceError。 assert_eq!(run_string("delete globalThis.undefined; typeof undefined"), "undefined"); } @@ -86,7 +86,7 @@ fn var_no_init_descriptor_unchanged() { let src = format!( "var {name}; var d = Object.getOwnPropertyDescriptor(globalThis, '{name}'); \ {value_check} && d.writable === false && d.enumerable === false \ - && d.configurable === true", + && d.configurable === false", ); run_truthy(&src); } @@ -157,7 +157,7 @@ fn eval_var_readonly_builtin() { run_truthy( "eval('var undefined = 3'); \ var d = Object.getOwnPropertyDescriptor(globalThis, 'undefined'); \ - d.writable === false && d.configurable === true", + d.writable === false && d.configurable === false", ); } @@ -199,10 +199,10 @@ fn function_declaration_builtin_name() { } #[test] -fn for_in_var_head_builtin_name_compiles_and_keeps_property() { - // 顶层 for-in var 头撞 builtin 名:序言预登记槽复用后可编译, - // 迭代键不触及全局属性(Math 对象保)。 - run_truthy("var m = Math; for (var Math in {a:1,b:2}) {} globalThis.Math === m"); +fn for_in_var_head_writable_builtin_name_clobbers_property() { + // 顶层 for-in var 头撞可写 builtin 名:序言预登记槽复用后可编译, + // 迭代键照规范覆写既有全局属性(末键 'b' 落 Math 属性)。 + run_truthy("var m = Math; for (var Math in {a:1,b:2}) {} globalThis.Math === 'b'"); } #[test] diff --git a/crates/oxide_vm/tests/global_builtin_writable_tests.rs b/crates/oxide_vm/tests/global_builtin_writable_tests.rs new file mode 100644 index 00000000..b31dca9d --- /dev/null +++ b/crates/oxide_vm/tests/global_builtin_writable_tests.rs @@ -0,0 +1,125 @@ +//! 可写 builtin 全局双写(镜像槽 + 全局对象属性)回归测试。 +//! +//! 规范可写全局(11 个 w:true 函数 + 48 个构造器/命名空间 + globalThis + 宿主名, +//! 描述符 {writable:true, enumerable:false, configurable:true})的标识符写须同步 +//! 全局对象属性,裸读(镜像)与 globalThis 反射(属性)不失步。覆盖:简单/复合/ +//! 更新/逻辑赋值、for-in/for-of 标识符目标、解构两臂、局部遮蔽不误伤、strict 可写 +//! put 成功、eval 路径与 TypedArray 宿主名抽样。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval_truthy(source: &str) { + 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"); + assert!(result.is_bool() && result.as_bool(), "expected true, got: {result:?}\nsource: {source}"); +} + +// ── 简单赋值:镜像与属性同步落值 ── +#[test] +fn simple_assign_math_dual_writes() { + eval_truthy("var m = Math; Math = 5; Math === 5 && globalThis.Math === 5 && m !== 5"); +} + +#[test] +fn simple_assign_writable_names_dual_write() { + for name in ["Object", "JSON", "Symbol", "Promise"] { + let src = format!("var m = {name}; {name} = 5; {name} === 5 && globalThis.{name} === 5"); + eval_truthy(&src); + } +} + +// ── w:true 函数族:strict 下可写 put 成功(无 TypeError) ── +#[test] +fn strict_writable_function_put_succeeds() { + eval_truthy("'use strict'; parseInt = 42; parseInt === 42 && globalThis.parseInt === 42"); +} + +#[test] +fn sloppy_writable_function_put_succeeds() { + eval_truthy("parseInt = 42; parseInt === 42 && globalThis.parseInt === 42"); +} + +#[test] +fn uri_family_dual_write() { + eval_truthy("var m = encodeURI; encodeURI = 1; encodeURI === 1 && globalThis.encodeURI === 1"); +} + +// ── 复合/更新/逻辑赋值:RMW 新值两侧同步 ── +#[test] +fn compound_assign_dual_writes() { + eval_truthy("var m = Math; Math = 5; Math += 1; Math === 6 && globalThis.Math === 6"); +} + +#[test] +fn update_assign_dual_writes() { + eval_truthy("var m = Math; Math = 5; var r = (Math++); r === 5 && Math === 6 && globalThis.Math === 6"); +} + +#[test] +fn logical_assign_dual_writes() { + // ||=:LHS falsy 触发 put;??=:LHS nullish 触发 put。 + eval_truthy("var m = Math; Math = 0; Math ||= 9; Math === 9 && globalThis.Math === 9"); + eval_truthy("var m = Math; Math = null; Math ??= 2; Math === 2 && globalThis.Math === 2"); +} + +#[test] +fn logical_assign_short_circuit_no_put() { + // 短路未通过不发生 put(&&= 遇 falsy、??= 遇非 nullish):值与属性均保持。 + eval_truthy("var m = Math; Math = 0; Math &&= 1; Math === 0 && globalThis.Math === 0"); + eval_truthy("var m = Math; Math = 5; Math ??= 2; Math === 5 && globalThis.Math === 5"); +} + +// ── for-in/for-of 标识符目标:迭代值两侧同步 ── +#[test] +fn for_in_target_dual_writes() { + eval_truthy("var m = Math; Math = 5; for (Math in {a:1,b:2}) {} Math === 'b' && globalThis.Math === 'b'"); +} + +#[test] +fn for_of_target_dual_writes() { + eval_truthy("var m = Math; for (Math of [1,2]) {} Math === 2 && globalThis.Math === 2"); +} + +// ── 解构赋值两臂:值两侧同步 ── +#[test] +fn destructure_assign_dual_writes() { + eval_truthy("var m = Math; ({Math} = {Math: 1}); Math === 1 && globalThis.Math === 1"); + eval_truthy("var m = Math; ([Math = 1] = [2]); Math === 2 && globalThis.Math === 2"); +} + +// ── 局部遮蔽不误伤:函数内 var 同名写不触全局 ── +#[test] +fn local_shadow_not_dual_written() { + eval_truthy( + "var m = Math; function g(){ var Math = 7; return Math; } \ + g() === 7 && Math === m && globalThis.Math === m", + ); +} + +// ── globalThis 自身可写:镜像与属性同步 ── +#[test] +fn global_this_self_dual_writes() { + eval_truthy( + "var m = globalThis; globalThis = 1; \ + globalThis === 1 && Object.getOwnPropertyDescriptor(m, 'globalThis').value === 1", + ); +} + +// ── 宿主名(TypedArray 抽象运算对象)同双写面 ── +#[test] +fn typed_array_host_name_dual_writes() { + eval_truthy("var m = TypedArray; TypedArray = 1; TypedArray === 1 && globalThis.TypedArray === 1"); +} + +// ── eval 路径:同一 emit 管线双写同效(外层镜像预载不重载,只钉属性面) ── +#[test] +fn eval_code_dual_writes_property() { + eval_truthy("eval('Math = 5'); globalThis.Math === 5"); +} diff --git a/crates/oxide_vm/tests/global_prop_desc_tests.rs b/crates/oxide_vm/tests/global_prop_desc_tests.rs index baf7f65c..0c42d7c0 100644 --- a/crates/oxide_vm/tests/global_prop_desc_tests.rs +++ b/crates/oxide_vm/tests/global_prop_desc_tests.rs @@ -1,6 +1,6 @@ //! 内置全局属性描述符测试:规范要求全局构造器/命名空间对象属性 -//! enumerable=false(不泄漏进 Object.keys(globalThis) / for-in), -//! 全局 NaN/undefined/Infinity 三常量不可写不可枚举可配置。 +//! enumerable=false(不泄漏进 Object.keys(globalThis) / for-in)且可写可配置, +//! 全局 NaN/undefined/Infinity 三常量不可写不可枚举不可配置。 use std::sync::Arc; @@ -63,11 +63,11 @@ fn for_in_global_this_excludes_builtins() { } #[test] -fn nan_undefined_infinity_are_non_writable_configurable() { +fn nan_undefined_infinity_are_non_writable_non_configurable() { for name in ["NaN", "undefined", "Infinity"] { let src = format!( "var d = Object.getOwnPropertyDescriptor(globalThis, '{name}'); \ - d.writable === false && d.enumerable === false && d.configurable === true" + d.writable === false && d.enumerable === false && d.configurable === false" ); eval_truthy(&src); }