fix: 顶层受限全局名 lexical 声明编译期拒绝——let/const undefined|NaN|Infinity 撞 GDI 三常量 SyntaxError

This commit is contained in:
2026-09-12 22:45:24 +00:00
parent 41a3aef29e
commit 6aff1101f3
6 changed files with 224 additions and 22 deletions
+24 -4
View File
@@ -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}");
}
}
}
+2 -1
View File
@@ -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);
+47 -13
View File
@@ -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(())
}
}
+2 -1
View File
@@ -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)? {
@@ -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(&param.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<u32> = 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<u32> = 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);
@@ -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<JsValue, String> {
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:?}");
}