diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 69f121e9..921d8f6b 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -1190,8 +1190,9 @@ impl Emitter { self.predeclare_var_declarations(body_stmts, &mut ctx); // 预声明 body 级 `let`/`const`/`class`(未初始化 TDZ 占位), - // 使声明点前读取可编译为运行时 ReferenceError。 - self.predeclare_lexical_declarations(body_stmts, &mut ctx); + // 使声明点前读取可编译为运行时 ReferenceError。函数体 lexical 声明是 + // 局部绑定,不做受限全局名检查;重复声明错在 emit 期报。 + let _ = self.predeclare_lexical_declarations(body_stmts, &mut ctx, false); // 生成器:body 起点标记——调用时参数初始化(emit_params_prologue)结束后挂起于此, // 参数副作用/异常在 `g()` 调用时刻生效,首次 next() 从这继续执行 body。 @@ -1666,8 +1667,12 @@ impl Emitter { // 预声明顶层 `var` 名,使首个 sub-pass 中提升的函数声明能解析外层 var。 self.predeclare_var_declarations(&program.body, &mut ctx); - // 预声明顶层 `let`/`const`/`class`(未初始化 TDZ 占位)。 - self.predeclare_lexical_declarations(&program.body, &mut ctx); + // 预声明顶层 `let`/`const`/`class`(未初始化 TDZ 占位)。受限全局名检查 + // 仅对脚本代码启用:脚本声明实例化查全局对象受限自有属性名,eval 代码 + // 声明实例化不查——门控随 is_eval_script 而非作用域标志(嵌套块内 + // is_global_scope 仍为 true,不能作门控)。 + let global_lexical = !ctx.is_eval_script; + self.predeclare_lexical_declarations(&program.body, &mut ctx, global_lexical)?; // 闭包捕获分析(AST 级,emit 前确定) ctx.own_bindings = self.collect_own_binding_names(&[], &program.body); @@ -1767,3 +1772,18 @@ impl Default for Emitter { Self::new() } } + +#[cfg(test)] +mod tests { + use super::BUILTIN_GLOBALS; + use crate::prepass::RESTRICTED_GLOBAL_LEXICAL_NAMES; + + /// 漂移守卫:受限全局名集与 put 写拦截名单交叠名恒同步(两名单语义独立、 + /// 不互相派生,靠本断言防止改名/删名时单边漂移)。 + #[test] + fn restricted_lexical_names_within_builtin_globals() { + for name in RESTRICTED_GLOBAL_LEXICAL_NAMES { + assert!(BUILTIN_GLOBALS.contains(name), "受限全局名缺失于 builtin 名单:{name}"); + } + } +} diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index c437bd22..b5451268 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -155,7 +155,8 @@ impl Emitter { self.predeclare_function_declarations(body, ctx); self.pre_register_builtin_references(body, ctx); self.predeclare_var_declarations(body, ctx); - self.predeclare_lexical_declarations(body, ctx); + // 模块 lexical 声明入模块环境(非全局对象),不做受限全局名检查。 + let _ = self.predeclare_lexical_declarations(body, ctx, false); // 闭包捕获分析(import 绑定名纳入 own_bindings,供嵌套函数 cell 捕获)。 ctx.own_bindings = self.collect_own_binding_names(&[], body); diff --git a/crates/oxide_emit/src/prepass.rs b/crates/oxide_emit/src/prepass.rs index 2b98525f..0104e1fc 100644 --- a/crates/oxide_emit/src/prepass.rs +++ b/crates/oxide_emit/src/prepass.rs @@ -9,6 +9,22 @@ 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 声明撞受限全局名 → SyntaxError(声明实例化期拒绝,整程序 +/// 编译失败)。错误消息与既有重复声明错同形;非顶层或名不在受限集 → Ok。 +fn check_restricted_global_lexical(name: &str, global_lexical: bool) -> Result<(), String> { + if global_lexical && RESTRICTED_GLOBAL_LEXICAL_NAMES.contains(&name) { + return Err(format!("Identifier '{name}' has already been declared")); + } + Ok(()) +} + impl Emitter { /// 在临时寄存器池之前分配 builtin 槽位。 pub(crate) fn pre_register_builtin_references(&self, stmts: &[Statement], ctx: &mut CompileCtx) { @@ -553,7 +569,13 @@ impl Emitter { /// 单语句 body 不接受 lexical 声明——lexical 属 Declaration、非 Statement /// 子产生式,parser 按语法错误直接拒绝,这些形状不会进入 emit)。 /// - 跳过 for 头声明(循环作用域由 for 分支内联 declare)。 - pub(crate) fn predeclare_lexical_declarations(&self, statements: &[Statement], ctx: &mut CompileCtx) { + /// - `global_lexical` 为 true 时(仅脚本顶层调用点传 `!is_eval_script`): + /// lexical 声明撞受限全局名报 SyntaxError(脚本声明实例化对全局对象受限 + /// 自有属性名做检查,eval 代码声明实例化无此检查);函数体/块/try/模块 + /// 调用点传 false,lexical 声明是局部绑定不查。 + pub(crate) fn predeclare_lexical_declarations( + &self, statements: &[Statement], ctx: &mut CompileCtx, global_lexical: bool, + ) -> Result<(), String> { for statement in statements { match statement { Statement::VariableDeclaration(decl) => { @@ -562,11 +584,12 @@ impl Emitter { } let is_const = matches!(decl.kind, VariableDeclarationKind::Const); for d in &decl.declarations { - self.predeclare_lexical_pattern(&d.id, is_const, ctx); + self.predeclare_lexical_pattern(&d.id, is_const, ctx, global_lexical)?; } } Statement::ClassDeclaration(cd) => { if let Some(id) = &cd.id { + check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); } @@ -574,7 +597,7 @@ impl Emitter { Statement::SwitchStatement(sw) => { for case in &sw.cases { for s in &case.consequent { - self.predeclare_lexical_stmt(s, ctx); + self.predeclare_lexical_stmt(s, ctx, global_lexical)?; } } } @@ -587,11 +610,12 @@ impl Emitter { } let is_const = matches!(vd.kind, VariableDeclarationKind::Const); for d in &vd.declarations { - self.predeclare_lexical_pattern(&d.id, is_const, ctx); + self.predeclare_lexical_pattern(&d.id, is_const, ctx, global_lexical)?; } } Declaration::ClassDeclaration(cd) => { if let Some(id) = &cd.id { + check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared( id.name.as_str(), @@ -608,6 +632,7 @@ impl Emitter { Statement::ExportDefaultDeclaration(exp) => match &exp.declaration { ExportDefaultDeclarationKind::ClassDeclaration(cd) => { if let Some(id) = &cd.id { + check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); @@ -625,34 +650,42 @@ impl Emitter { _ => {} } } + Ok(()) } /// 预声明单个语句中的 lexical 声明(供 switch case 递归;`let`/`const`/`class` 分支)。 - fn predeclare_lexical_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) { + fn predeclare_lexical_stmt( + &self, stmt: &Statement, ctx: &mut CompileCtx, global_lexical: bool, + ) -> Result<(), String> { match stmt { Statement::VariableDeclaration(decl) => { if matches!(decl.kind, VariableDeclarationKind::Var) { - return; + return Ok(()); } let is_const = matches!(decl.kind, VariableDeclarationKind::Const); for d in &decl.declarations { - self.predeclare_lexical_pattern(&d.id, is_const, ctx); + self.predeclare_lexical_pattern(&d.id, is_const, ctx, global_lexical)?; } } Statement::ClassDeclaration(cd) => { if let Some(id) = &cd.id { + check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); } } _ => {} } + Ok(()) } /// 递归预声明绑定 pattern 内的全部标识符(含数组/对象/默认值解构)。 - fn predeclare_lexical_pattern(&self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx) { + fn predeclare_lexical_pattern( + &self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx, global_lexical: bool, + ) -> Result<(), String> { match pattern { BindingPattern::BindingIdentifier(bi) => { + check_restricted_global_lexical(bi.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared( bi.name.as_str(), @@ -667,23 +700,24 @@ impl Emitter { } BindingPattern::ArrayPattern(ap) => { for e in ap.elements.iter().flatten() { - self.predeclare_lexical_pattern(e, is_const, ctx); + self.predeclare_lexical_pattern(e, is_const, ctx, global_lexical)?; } if let Some(rest) = &ap.rest { - self.predeclare_lexical_pattern(&rest.argument, is_const, ctx); + self.predeclare_lexical_pattern(&rest.argument, is_const, ctx, global_lexical)?; } } BindingPattern::ObjectPattern(op) => { for prop in &op.properties { - self.predeclare_lexical_pattern(&prop.value, is_const, ctx); + self.predeclare_lexical_pattern(&prop.value, is_const, ctx, global_lexical)?; } if let Some(rest) = &op.rest { - self.predeclare_lexical_pattern(&rest.argument, is_const, ctx); + self.predeclare_lexical_pattern(&rest.argument, is_const, ctx, global_lexical)?; } } BindingPattern::AssignmentPattern(ap) => { - self.predeclare_lexical_pattern(&ap.left, is_const, ctx); + self.predeclare_lexical_pattern(&ap.left, is_const, ctx, global_lexical)?; } } + Ok(()) } } diff --git a/crates/oxide_emit/src/stmt/block.rs b/crates/oxide_emit/src/stmt/block.rs index 3d227c8b..981a0260 100644 --- a/crates/oxide_emit/src/stmt/block.rs +++ b/crates/oxide_emit/src/stmt/block.rs @@ -12,7 +12,8 @@ impl Emitter { // 函数预声明先于 lexical:`{ let g; function g(){} }` 时 lexical 的预声明 // 命中已存在的函数绑定自然报重复声明错,不被静默覆盖破坏 TDZ。 self.predeclare_block_function_declarations(&block.body, ctx); - self.predeclare_lexical_declarations(&block.body, ctx); + // 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。 + let _ = self.predeclare_lexical_declarations(&block.body, ctx, false); let mut r = None; for s in &block.body { if let Some(rr) = self.emit_statement(s, ctx)? { diff --git a/crates/oxide_emit/src/stmt/exception/try_catch.rs b/crates/oxide_emit/src/stmt/exception/try_catch.rs index 8cb4df3d..6f281ac0 100644 --- a/crates/oxide_emit/src/stmt/exception/try_catch.rs +++ b/crates/oxide_emit/src/stmt/exception/try_catch.rs @@ -32,7 +32,8 @@ impl Emitter { // try block 是独立块作用域:lexical 声明(let/const/class)限定于 try 内, // 与外层同名绑定互不干扰;块级预声明使声明点前读取编译为 TDZ 抛错。 ctx.push_scope(); - self.predeclare_lexical_declarations(&ts.block.body, ctx); + // try 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。 + let _ = self.predeclare_lexical_declarations(&ts.block.body, ctx, false); for s in &ts.block.body { if let Some(r) = self.emit_statement(s, ctx)? { last_try_result = Some(r); @@ -63,7 +64,8 @@ impl Emitter { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(src_reg), Operand::None, Operand::None)); self.emit_binding_pattern(¶m.pattern, src_reg, VariableDeclarationKind::Let, false, false, ctx)?; } - self.predeclare_lexical_declarations(&catch.body.body, ctx); + // catch 块 lexical 声明同为局部绑定:不做受限全局名检查。 + let _ = self.predeclare_lexical_declarations(&catch.body.body, ctx, false); let mut last_catch_result: Option = None; for s in &catch.body.body { if let Some(r) = self.emit_statement(s, ctx)? { @@ -88,7 +90,8 @@ impl Emitter { let mut last_finally_result: Option = None; // finally block 同为独立块作用域,lexical 声明互不泄漏。 ctx.push_scope(); - self.predeclare_lexical_declarations(&ts.finalizer.as_ref().unwrap().body, ctx); + // finally 块 lexical 声明同为局部绑定:不做受限全局名检查。 + let _ = self.predeclare_lexical_declarations(&ts.finalizer.as_ref().unwrap().body, ctx, false); for s in &ts.finalizer.as_ref().unwrap().body { if let Some(r) = self.emit_statement(s, ctx)? { last_finally_result = Some(r); diff --git a/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs b/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs new file mode 100644 index 00000000..dcbda3e6 --- /dev/null +++ b/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs @@ -0,0 +1,143 @@ +//! 脚本顶层 lexical 声明撞受限全局名的编译期拒绝:let/const/class 声明名撞 +//! 全局对象不可配置自有属性(undefined/NaN/Infinity)→ SyntaxError(整程序 +//! 编译失败)。局部作用域(函数体/块/try)与 eval 代码为合法遮蔽不拒;三常量 +//! 值使用不受影响。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +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: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?; + let mut vm = Vm::new(); + vm.run(&Arc::new(module)) +} + +/// 断言整程序编译失败且错消息为声明实例化期的重复声明错形(SyntaxError 族, +/// 消息实现自定,只断结构)。 +fn assert_compile_err(source: &str) { + let err = eval(source).unwrap_err(); + assert!(err.contains("already been declared"), "got: {err}"); +} + +/// 断言编译 + 运行均成功。 +fn assert_ok(source: &str) -> JsValue { + eval(source).unwrap_or_else(|e| panic!("expected ok, got: {e}\nsource: {source}")) +} + +// ── 脚本顶层 let/const/class 撞三常量:编译期拒绝 ── + +#[test] +fn top_level_let_undefined_rejected() { + // 独立形(脚本内无裸 undefined 引用,无镜像槽):规范 SyntaxError。 + assert_compile_err("let undefined;"); +} + +#[test] +fn top_level_let_nan_rejected() { + assert_compile_err("let NaN;"); +} + +#[test] +fn top_level_let_infinity_rejected() { + assert_compile_err("let Infinity;"); +} + +#[test] +fn top_level_const_nan_rejected() { + assert_compile_err("const NaN = 1;"); +} + +#[test] +fn top_level_const_undefined_rejected() { + assert_compile_err("const undefined = 1;"); +} + +#[test] +fn top_level_class_undefined_rejected() { + // class 名同为 lexical 绑定(受限检查同形);undefined 类名由解析器更早拒绝 + // (SyntaxError 族,整程序拒绝可观察行为同形),断言接受任一拒绝相位。 + assert!(eval("class undefined {}").is_err(), "class undefined 类名应被拒绝"); +} + +#[test] +fn top_level_class_infinity_rejected() { + assert_compile_err("class Infinity {}"); +} + +#[test] +fn top_level_let_undefined_with_bare_use_rejected() { + // 镜像形(脚本另有裸 undefined 引用):拒绝行为不变,错误相位提前到 + // 声明预登记期(消息同形)。 + assert_compile_err("let undefined; undefined"); +} + +#[test] +fn top_level_let_destructured_nan_rejected() { + // 解构叶子同属声明名,受限检查覆盖。 + assert_compile_err("let [NaN] = [1];"); +} + +#[test] +fn top_level_switch_case_let_nan_rejected() { + // switch 不推作用域:case 内 lexical 声明属全局 lexical,仍受检。 + assert_compile_err("switch (0) { case 0: let NaN; }"); +} + +// ── eval 门控:eval 代码声明实例化无受限检查 ── + +#[test] +fn eval_script_let_nan_allowed() { + // eval 代码在独立 lexical 环境实例化,不受限检查约束;外层裸读仍是三常量值 + //(NaN 自反性用 isNaN 断言,=== 对 NaN 恒 false)。 + let r = assert_ok("eval('let NaN;'); isNaN(NaN)"); + assert!(r.is_bool() && r.as_bool(), "eval 内 let NaN 应为合法遮蔽,实际 {r:?}"); +} + +// ── 假阳性守卫:合法面不得误拒 ── + +#[test] +fn top_level_let_uses_nan_value_allowed() { + // 三常量值使用(非声明名)不受检查影响(isNaN 断言,=== 对 NaN 恒 false)。 + let r = assert_ok("let x = NaN; isNaN(x)"); + assert!(r.is_bool() && r.as_bool(), "let x = NaN 应正常执行,实际 {r:?}"); +} + +#[test] +fn top_level_let_eval_name_allowed() { + // eval 是全局对象可配置自有属性:合法遮蔽,不在受限集。 + let r = assert_ok("let eval; 1"); + assert_eq!(r.as_int(), 1); +} + +#[test] +fn top_level_let_hasownproperty_allowed() { + // hasOwnProperty 非全局对象自有属性(Object.prototype 继承):合法遮蔽。 + let r = assert_ok("let hasOwnProperty; 1"); + assert_eq!(r.as_int(), 1); +} + +#[test] +fn block_level_let_nan_allowed() { + // 顶层块内 lexical 声明是块局部绑定:不拒。 + let r = assert_ok("{ let NaN; } 1"); + assert_eq!(r.as_int(), 1); +} + +#[test] +fn try_block_let_nan_allowed() { + // try 块 lexical 声明是 try 局部绑定:不拒。 + let r = assert_ok("try { let NaN; } catch (e) {} 1"); + assert_eq!(r.as_int(), 1); +} + +#[test] +fn function_body_let_nan_allowed() { + // 函数体 lexical 声明是局部绑定:不拒。 + let r = assert_ok("function f(){ let NaN; return 1; } f() === 1"); + assert!(r.is_bool() && r.as_bool(), "函数体 let NaN 应为合法局部,实际 {r:?}"); +}