//! 不可写全局内置(undefined/NaN/Infinity)写路径拦截测试:写命中全局不可写 //! 内置绑定时编译期拦截——sloppy 静默 no-op(后续读回原值,全局对象不变), //! strict 抛 TypeError。覆盖简单/复合/更新/逻辑赋值、声明(var 无初始化与 //! 带初始化)、解构赋值、for-in/for-of 标识符目标与嵌套函数形态;局部遮蔽 //! 绑定(var/let/参数)与其他可写内置(Math)不受影响。逻辑赋值额外钉住 //! 短路时序:短路未通过不发生 put(strict 不抛),通过才在写点拦截。 use std::sync::Arc; use oxide_compiler::compiler::Compiler; use oxide_compiler::DefaultCompilerService; use oxide_types::value::JsValue; use oxide_vm::vm::Vm; fn eval(source: &str) -> Result { let allocator = oxide_parser::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.set_compiler_service(Arc::new(DefaultCompilerService)); vm.run(&Arc::new(module)) } fn eval_many(lines: &[&str]) -> Result { eval(&lines.join("; ")) } fn assert_err_contains(result: Result, expected: &str) { match result { Ok(_) => panic!("expected error containing '{}', got Ok", expected), Err(e) => assert!(e.contains(expected), "expected error containing '{}', got: {}", expected, e), } } /// 求值并把结果按字符串提取(`JsValue` Display 不携带字符串内容)。 fn eval_str(source: &str) -> String { let allocator = oxide_parser::Allocator::default(); let program = oxide_parser::parse(&allocator, source).expect("parse"); let module = Compiler::new().compile(&program).expect("compile"); let mut vm = Vm::new(); vm.set_compiler_service(Arc::new(DefaultCompilerService)); let result = vm.run(&Arc::new(module)).expect("run"); vm.lookup_str(result).unwrap_or_else(|| format!("{result}")) } // ── sloppy 顶层:三个不可写内置赋值后读回原值(静默 no-op) ── #[test] fn sloppy_assign_undefined_silently_keeps_value() { let s = eval_str("undefined = 5; '' + undefined"); assert_eq!(s, "undefined", "sloppy 顶层 undefined 赋值应静默 no-op,读回原值"); } #[test] fn sloppy_assign_nan_silently_keeps_value() { let s = eval_str("NaN = 1; '' + NaN"); assert_eq!(s, "NaN", "sloppy 顶层 NaN 赋值应静默 no-op,读回原值"); } #[test] fn sloppy_assign_infinity_silently_keeps_value() { let s = eval_str("Infinity = 2; '' + Infinity"); assert_eq!(s, "Infinity", "sloppy 顶层 Infinity 赋值应静默 no-op,读回原值"); } // ── strict 顶层:赋值抛 TypeError(不可写属性 put 失败) ── #[test] fn strict_assign_undefined_throws_type_error() { let result = eval("'use strict'; undefined = 5"); assert_err_contains(result, "read-only"); } #[test] fn strict_assign_nan_throws_type_error() { let result = eval("'use strict'; NaN = 1"); assert_err_contains(result, "read-only"); } // ── 可写内置(Math)重定义放行:不得被过度拦截 ── #[test] fn writable_builtin_reassignment_still_works() { let r = eval("Math = 5; Math === 5").unwrap(); assert!(r.is_bool() && r.as_bool(), "Math 可写属性重定义应保持可用,实际 {:?}", r); } // ── var 无初始化:声明不抹槽值(保留内置原值) ── #[test] fn var_no_init_undefined_keeps_value() { let s = eval_str("var undefined; '' + undefined"); assert_eq!(s, "undefined", "顶层 var undefined 无初始化应保留原值"); } #[test] fn var_no_init_nan_keeps_value() { let s = eval_str("var NaN; '' + NaN"); assert_eq!(s, "NaN", "顶层 var NaN 无初始化应保留原值"); } // ── 带初始化 var 撞全局不可写内置:sloppy 静默、strict 抛错 ── #[test] fn var_with_init_sloppy_silently_keeps_value() { let s = eval_str("var undefined = 3; '' + undefined"); assert_eq!(s, "undefined", "sloppy var undefined = 3 应静默 no-op,读回原值"); } #[test] fn var_with_init_strict_throws_type_error() { let result = eval("'use strict'; var undefined = 3"); assert_err_contains(result, "read-only"); } // ── 函数内无遮蔽写:命中继承的全局槽,sloppy 静默、strict 抛错 ── #[test] fn function_scoped_write_silently_keeps_value() { let s = eval_str("function g(){ undefined = 1; return undefined; } '' + g()"); assert_eq!(s, "undefined", "函数内未遮蔽的 undefined 写应静默 no-op,读回原值"); } #[test] fn function_scoped_write_strict_throws_type_error() { let r = eval_many(&[ "var threw = false", "function g(){ 'use strict'; try { undefined = 1 } catch (e) { threw = true } }", "g()", "threw", ]) .unwrap(); assert!(r.is_bool() && r.as_bool(), "严格函数内 undefined 写应抛 TypeError,实际 {:?}", r); } // ── 全局对象属性不变:寄存器镜像写不得穿透到 global object ── #[test] fn global_object_property_untouched() { let r = eval("undefined = 5; globalThis.undefined === undefined").unwrap(); assert!(r.is_bool() && r.as_bool(), "寄存器镜像写不应修改全局对象属性,实际 {:?}", r); } // ── 局部遮蔽绑定保持可写(var/参数同名不命中全局拦截) ── #[test] fn local_var_shadow_writable() { let r = eval("function g(){ var undefined = 3; undefined = 5; return undefined; } g()").unwrap(); assert!(r.is_int() && r.as_int() == 5, "函数内 var undefined 局部遮蔽应可写,实际 {:?}", r); } #[test] fn param_shadow_writable() { let r = eval("function g(undefined){ undefined = 1; return undefined; } g(9)").unwrap(); assert!(r.is_int() && r.as_int() == 1, "参数名 undefined 局部遮蔽应可写,实际 {:?}", r); } // ── 复合/更新赋值:槽值保留,表达式值按规范计算 ── #[test] fn compound_assign_keeps_value() { let s = eval_str("undefined += 1; '' + undefined"); assert_eq!(s, "undefined", "sloppy undefined += 1 应静默 no-op,读回原值"); } #[test] fn update_assign_keeps_value() { let s = eval_str("undefined++; '' + undefined"); assert_eq!(s, "undefined", "sloppy undefined++ 应静默 no-op,读回原值"); } #[test] fn postfix_update_returns_old_value() { let r = eval("var v = Infinity--; isFinite(v)").unwrap(); assert!(r.is_bool() && !r.as_bool(), "Infinity-- 后缀形式应返回旧值 Infinity,实际 {:?}", r); } // ── 逻辑赋值(||=/&&=/??=)标识符目标:短路时序与 PutValue 对齐——短路未通过 // 不发生 put(strict 不抛、表达式值 = LHS 旧值),通过才在写点拦截 ── #[test] fn logical_assign_sloppy_matrix_keeps_value() { // 三常量 × 三逻辑符:短路通过者静默 no-op 读回原值,短路未通过者不写。 let cases: &[(&str, &str)] = &[ ("undefined ||= 5; '' + undefined", "undefined"), ("undefined &&= 5; '' + undefined", "undefined"), ("undefined ??= 5; '' + undefined", "undefined"), ("NaN ||= 1; '' + NaN", "NaN"), ("NaN &&= 1; '' + NaN", "NaN"), ("NaN ??= 1; '' + NaN", "NaN"), ("Infinity ||= 2; '' + Infinity", "Infinity"), ("Infinity &&= 2; '' + Infinity", "Infinity"), ("Infinity ??= 2; '' + Infinity", "Infinity"), ]; for &(source, expected) in cases { let s = eval_str(source); assert_eq!(s, expected, "sloppy 逻辑赋值命中不可写内置应静默 no-op,读回原值,源码: {source}"); } } #[test] fn logical_assign_strict_write_path_throws() { // 短路通过(||=/??= 遇 falsy/nullish、&&= 遇 truthy)到达写点:put 失败抛 TypeError。 let sources = [ "'use strict'; undefined ||= 5", "'use strict'; undefined ??= 5", "'use strict'; NaN ||= 1", "'use strict'; Infinity &&= 2", ]; for source in sources { let result = eval(source); assert_err_contains(result, "read-only"); } } #[test] fn logical_assign_strict_short_circuit_no_throw() { // 短路未通过不发生 put:strict 不抛错,表达式值 = LHS 旧值。 let cases: &[(&str, &str)] = &[ ("'use strict'; '' + (undefined &&= 5)", "undefined"), ("'use strict'; '' + (NaN &&= 1)", "NaN"), ("'use strict'; '' + (NaN ??= 1)", "NaN"), ("'use strict'; '' + (Infinity ||= 2)", "Infinity"), ("'use strict'; '' + (Infinity ??= 2)", "Infinity"), ]; for &(source, expected) in cases { let s = eval_str(source); assert_eq!( s, expected, "逻辑赋值短路未通过不 put,strict 不应抛错且表达式值 = LHS 旧值,源码: {source}" ); } } #[test] fn logical_or_assign_expression_value_is_rhs() { let s = eval_str("(undefined ||= 5) + ':' + undefined"); assert_eq!(s, "5:undefined", "逻辑赋值写点表达式值应为 RHS,槽值保持原值"); } // ── 解构赋值目标:命中项静默跳过,strict 抛错 ── #[test] fn destructure_assign_keeps_value() { let s = eval_str("({undefined} = {undefined: 9}); '' + undefined"); assert_eq!(s, "undefined", "sloppy 解构赋值命中 undefined 应静默 no-op,读回原值"); } #[test] fn destructure_assign_strict_throws_type_error() { let result = eval("'use strict'; ({undefined} = {undefined: 9})"); assert_err_contains(result, "read-only"); } // ── for-in/for-of 标识符目标:迭代写不污染槽值 ── #[test] fn for_of_target_keeps_value() { let s = eval_str("for (undefined of [1, 2]) {} '' + undefined"); assert_eq!(s, "undefined", "sloppy for-of 目标 undefined 应静默 no-op,读回原值"); } #[test] fn for_of_target_strict_throws_type_error() { let result = eval("'use strict'; for (undefined of [1]) {}"); assert_err_contains(result, "read-only"); } #[test] fn for_of_target_strict_empty_no_throw() { let r = eval("'use strict'; for (undefined of []) {} true").unwrap(); assert!(r.is_bool() && r.as_bool(), "空集合 for-of 不发生 put,strict 不应抛错,实际 {:?}", r); } // ── for-in 标识符目标:键写不污染槽值,写仅在 NEXT 产生键后发生 ── #[test] fn for_in_target_keeps_value() { let s = eval_str("for (undefined in {a: 1}) {} '' + undefined"); assert_eq!(s, "undefined", "sloppy for-in 目标 undefined 应静默 no-op,读回原值"); } #[test] fn for_in_target_strict_throws_type_error() { let result = eval("'use strict'; for (undefined in {a: 1}) {}"); assert_err_contains(result, "read-only"); } #[test] fn for_in_target_strict_empty_no_throw() { let r = eval("'use strict'; for (undefined in {}) {} true").unwrap(); assert!(r.is_bool() && r.as_bool(), "空对象 for-in 不发生 put,strict 不应抛错,实际 {:?}", r); } // ── for 头 var 无初始化:不抹预载槽值(NaN 保留) ── #[test] fn for_header_var_no_init_keeps_slot_value() { let s = eval_str("for (var NaN; false;) {} '' + NaN"); assert_eq!(s, "NaN", "for 头 var NaN 无初始化应保留槽值"); }