mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: 顶层 var 声明带初始化撞不可写全局用户属性 strict 抛 TypeError——用户名声明处写点改 session 解析 PutValue 语义,builtin 名保 GDI 写点;序言缺失分支全局不可扩展两模式对齐抛 TypeError
This commit is contained in:
@@ -1586,6 +1586,20 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
/// 顶层 var 声明带初始化的 A 侧同步:PutValue 语义——既有不可写数据属性
|
||||
/// strict 抛 TypeError / sloppy 静默 no-op;可写照原描述符仅更值。脚本与
|
||||
/// eval 均经 session 解析全局对象,不依赖顶层 this。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 仅顶层模块上下文调用;builtin 名不走本写点(保持 GDI 零动作写点)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 更新全局对象数据属性,失败面抛 TypeError(strict 不可写)。
|
||||
pub(crate) fn emit_global_put_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
|
||||
let idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::define_global_prop_c(Operand::Reg(val_reg), idx));
|
||||
}
|
||||
|
||||
/// 名字是否为顶层已声明 var(A 侧单一真值):在顶层 var 名集内,且当前解析
|
||||
/// 绑定落在全局作用域(scope 0)——嵌套函数内的局部同名遮蔽命中更高作用域,
|
||||
/// 判定为局部而非顶层,走既有 cell/寄存器路径。
|
||||
|
||||
@@ -108,7 +108,13 @@ impl Emitter {
|
||||
ctx.init_var(name);
|
||||
// 脚本顶层 var:同步写全局对象,使 globalThis.x 反射声明值。
|
||||
if ctx.is_global_scope && matches!(kind, VariableDeclarationKind::Var) {
|
||||
self.emit_global_prop_write(name, src_reg, ctx);
|
||||
if CompileCtx::is_known_builtin(name) {
|
||||
// builtin 名保持 GDI 零动作写点(序言镜像值语义),不扩 strict 面。
|
||||
self.emit_global_prop_write(name, src_reg, ctx);
|
||||
} else {
|
||||
// 用户名既有属性面:声明带初始化是 PutValue,0x98 带 strict 分派。
|
||||
self.emit_global_put_write(name, src_reg, ctx);
|
||||
}
|
||||
}
|
||||
// REPL 持久模式:let/const 也写全局对象属性,使跨轮次读取可见。
|
||||
if ctx.repl_persist && ctx.is_global_scope && !matches!(kind, VariableDeclarationKind::Var) {
|
||||
|
||||
@@ -371,9 +371,12 @@ impl Vm {
|
||||
///
|
||||
/// CreateGlobalVarBinding 对既有属性零动作:属性已存在(数据或 accessor)直接
|
||||
/// 返回——可写/不可写/配置位与值均不更新。缺失新建可写/可枚举/不可配置数据
|
||||
/// 属性;全局对象不可扩展时静默 no-op(GDI 面,基线同形)。
|
||||
/// 属性;全局对象不可扩展 → TypeError(两模式均抛,GDI CanDeclareGlobalVar
|
||||
/// 面,同形 eval 变体)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - rd 槽固定为顶层 `this`:本形以所有脚本(含严格模式)顶层 this 即全局
|
||||
/// 对象为成立前提。
|
||||
/// - rd 非对象(非对象 this)时按 no-op 处理,不抛错。
|
||||
pub(crate) fn dispatch_define_global_prop_if_absent(
|
||||
&mut self, rd: usize, a: usize, b: usize,
|
||||
@@ -395,9 +398,12 @@ impl Vm {
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
// 缺失:新建可写/可枚举/不可配置;不可扩展静默(GDI 面,基线同形)。
|
||||
let _ = self.define_data_property(obj, prop_name_si, value, PropAttributes::new(true, true, false));
|
||||
Ok(())
|
||||
// 缺失:新建可写/可枚举/不可配置;唯一失败面是全局对象不可扩展(两模式
|
||||
// 均抛 TypeError,GDI CanDeclareGlobalVar 面,同形 eval 变体)。
|
||||
match self.define_data_property(obj, prop_name_si, value, PropAttributes::new(true, true, false)) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(msg) => self.raise_error_kind("TypeError", &msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// eval 脚本顶层 var 声明的全局属性 define-if-absent:ext 字 = 键常量池下标
|
||||
|
||||
@@ -138,6 +138,15 @@ fn var_builtin_object_with_init() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_var_builtin_object_with_init() {
|
||||
// #10 同形(strict):builtin 名保 GDI 写点,行为与模式无关。
|
||||
run_truthy(
|
||||
"'use strict'; var Math = 6; \
|
||||
Object.getOwnPropertyDescriptor(globalThis, 'Math').value === 6",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_var_readonly_builtin() {
|
||||
// #11:eval var 路径(0x98 判 writable 而非 configurable)——sloppy no-op、
|
||||
|
||||
@@ -4,6 +4,7 @@ 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_truthy(source: &str) {
|
||||
@@ -39,6 +40,30 @@ fn eval_two_phases(phase1: &str, phase2: &str) -> bool {
|
||||
result.is_bool() && result.as_bool()
|
||||
}
|
||||
|
||||
/// 两阶段 run-Err 变体:phase2 返回运行结果,供未捕获异常(TypeError 等)断言。
|
||||
fn run_two_phases(phase1: &str, phase2: &str) -> Result<JsValue, String> {
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&Arc::new(compile(phase1))).expect("run1");
|
||||
vm.reset();
|
||||
vm.run(&Arc::new(compile(phase2)))
|
||||
}
|
||||
|
||||
/// 两阶段真值断言:phase2 须以 true 完成。
|
||||
fn assert_two_phases_truthy(phase1: &str, phase2: &str) {
|
||||
match run_two_phases(phase1, phase2) {
|
||||
Ok(v) => assert!(v.is_bool() && v.as_bool(), "expected true, got: {v:?}\nphase2: {phase2}"),
|
||||
Err(e) => panic!("phase2 run failed: {e}\nphase2: {phase2}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// 取 phase2 未捕获异常文本(phase2 正常完成时 panic)。
|
||||
fn phase2_err_text(phase1: &str, phase2: &str) -> String {
|
||||
match run_two_phases(phase1, phase2) {
|
||||
Err(e) => e,
|
||||
Ok(v) => panic!("expected uncaught error, got: {v:?}\nphase2: {phase2}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_var_lands_on_global_this() {
|
||||
eval_truthy("var x = 5; globalThis.x === 5");
|
||||
@@ -343,3 +368,139 @@ fn tier_declaration_respects_existing_property() {
|
||||
"var rw = 99; rw === 7 && globalThis.rw === 7",
|
||||
));
|
||||
}
|
||||
|
||||
// ── 声明带初始化的 A 侧写是 PutValue:strict 撞既有不可写用户属性抛 TypeError ──
|
||||
|
||||
#[test]
|
||||
fn strict_var_init_on_nonwritable_user_property_throws() {
|
||||
// strict 声明初始化撞既有不可写(可配置)用户属性抛 TypeError;
|
||||
// 序言对既有属性零动作(抛点仅在声明处 PutValue)。
|
||||
let err = phase2_err_text(
|
||||
"Object.defineProperty(globalThis, 'u', {value: 1, writable: false, configurable: true})",
|
||||
"'use strict'; var u = 5;",
|
||||
);
|
||||
assert!(err.contains("TypeError"), "应抛 TypeError,实际: {err}");
|
||||
assert!(err.contains("read-only"), "消息应指明只读属性,实际: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_var_init_on_writable_user_property_updates_value() {
|
||||
// 既有可写用户属性(strict):PutValue 更新值不抛,裸读与反射一致。
|
||||
assert_two_phases_truthy(
|
||||
"Object.defineProperty(globalThis, 'u', {value: 1, writable: true, configurable: true})",
|
||||
"'use strict'; var u = 5; u === 5 && globalThis.u === 5",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sloppy_var_init_on_writable_user_property_updates_value() {
|
||||
// 同上(sloppy):可写属性值更新。
|
||||
assert_two_phases_truthy(
|
||||
"Object.defineProperty(globalThis, 'u', {value: 1, writable: true, configurable: true})",
|
||||
"var u = 5; u === 5 && globalThis.u === 5",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_var_init_on_nonconfigurable_nonwritable_user_property_throws() {
|
||||
// c:false + w:false:序言零动作,声明 PutValue strict 抛 TypeError。
|
||||
let err = phase2_err_text(
|
||||
"Object.defineProperty(globalThis, 'u', {value: 1, writable: false, configurable: false})",
|
||||
"'use strict'; var u = 5;",
|
||||
);
|
||||
assert!(err.contains("TypeError") && err.contains("read-only"), "实际: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sloppy_var_init_on_nonconfigurable_nonwritable_user_property_silent() {
|
||||
// c:false + w:false(sloppy):静默 no-op,值不覆盖。
|
||||
assert_two_phases_truthy(
|
||||
"Object.defineProperty(globalThis, 'u', {value: 1, writable: false, configurable: false})",
|
||||
"var u = 5; u === 1 && globalThis.u === 1",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_var_no_init_on_nonwritable_user_property_no_throw() {
|
||||
// 无初始化声明无 PutValue(防过抛守卫):strict 不抛,既有值不动。
|
||||
assert_two_phases_truthy(
|
||||
"Object.defineProperty(globalThis, 'u', {value: 1, writable: false, configurable: true})",
|
||||
"'use strict'; var u; u === 1 && globalThis.u === 1",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_for_of_var_head_on_nonwritable_user_property_throws() {
|
||||
// for-of var 声明头与声明带 init 同走 PutValue 写点:strict 迭代值撞
|
||||
// 不可写抛 TypeError;sloppy 静默 no-op。
|
||||
let err = phase2_err_text(
|
||||
"Object.defineProperty(globalThis, 'p', {value: 1, writable: false, configurable: true})",
|
||||
"'use strict'; for (var p of [3]) {}",
|
||||
);
|
||||
assert!(err.contains("TypeError") && err.contains("read-only"), "实际: {err}");
|
||||
assert_two_phases_truthy(
|
||||
"Object.defineProperty(globalThis, 'p', {value: 1, writable: false, configurable: true})",
|
||||
"for (var p of [3]) {} p === 1 && globalThis.p === 1",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_eval_var_init_on_nonwritable_user_property_throws() {
|
||||
// eval 对照:eval 脚本声明处写点本就带 strict 分派,改前后同形。
|
||||
let err = phase2_err_text(
|
||||
"Object.defineProperty(globalThis, 'u', {value: 1, writable: false, configurable: true})",
|
||||
"eval('\"use strict\"; var u = 5')",
|
||||
);
|
||||
assert!(err.contains("TypeError") && err.contains("read-only"), "实际: {err}");
|
||||
assert_two_phases_truthy(
|
||||
"Object.defineProperty(globalThis, 'u', {value: 1, writable: false, configurable: true})",
|
||||
"eval('var u = 5'); globalThis.u === 1",
|
||||
);
|
||||
}
|
||||
|
||||
// ── GDI 序言缺失分支:全局不可扩展 → 两模式 TypeError(CanDeclareGlobalVar 面) ──
|
||||
|
||||
#[test]
|
||||
fn nonextensible_global_strict_var_prologue_throws_before_body() {
|
||||
// 序言期抛:体副作用不执行(touched 未写入)。
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&Arc::new(compile("Object.preventExtensions(globalThis)")))
|
||||
.expect("run1");
|
||||
vm.reset();
|
||||
let result = vm.run(&Arc::new(compile("'use strict'; var missing; touched = 1;")));
|
||||
let err = match result {
|
||||
Err(e) => e,
|
||||
Ok(v) => panic!("应抛 TypeError,实际: {v:?}"),
|
||||
};
|
||||
assert!(err.contains("TypeError"), "应抛 TypeError,实际: {err}");
|
||||
// 体未执行:隐式全局写点未触达,touched 未创建。
|
||||
vm.reset();
|
||||
let after = vm.run(&Arc::new(compile("typeof touched"))).expect("run3");
|
||||
assert_eq!(vm.lookup_str(after).unwrap_or_default(), "undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonextensible_global_sloppy_var_prologue_throws() {
|
||||
// var 臂无 strict 门:sloppy 同形抛 TypeError。
|
||||
let err = phase2_err_text("Object.preventExtensions(globalThis)", "var missing;");
|
||||
assert!(err.contains("TypeError"), "应抛 TypeError,实际: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonextensible_global_eval_var_throws_both_modes() {
|
||||
// eval 脚本序言不可扩展抛:sloppy/strict 两模式同抛(回归钉)。
|
||||
let err_sloppy = phase2_err_text("Object.preventExtensions(globalThis)", "eval('var missing')");
|
||||
assert!(err_sloppy.contains("TypeError"), "sloppy eval 应抛 TypeError,实际: {err_sloppy}");
|
||||
let err_strict = phase2_err_text("Object.preventExtensions(globalThis)", "eval('\"use strict\"; var missing')");
|
||||
assert!(err_strict.contains("TypeError"), "strict eval 应抛 TypeError,实际: {err_strict}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonextensible_global_var_existing_property_no_throw() {
|
||||
// 既有属性不进缺失分支:零动作不抛(防过抛守卫)。
|
||||
assert_two_phases_truthy(
|
||||
"Object.defineProperty(globalThis, 'ex', {value: 1, writable: false, configurable: true}); \
|
||||
Object.preventExtensions(globalThis); true",
|
||||
"var ex; ex === 1 && globalThis.ex === 1",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user