feat: 脚本顶层词法声明三常量名合法化并登记语料跳过条目

This commit is contained in:
2026-09-28 19:06:34 +00:00
parent c363102302
commit 55133c0057
6 changed files with 134 additions and 92 deletions
+7 -11
View File
@@ -1,8 +1,6 @@
//! emit 前置 pass,声明预登记:函数/var 声明提升预声明、块级函数声明按 ES2015 //! emit 前置 pass,声明预登记:函数/var 声明提升预声明、块级函数声明按 ES2015
//! 块绑定预声明、let/const/class 建 TDZ 占位;顶层 lexical 声明撞受限全局名 //! 块绑定预声明、let/const/class 建 TDZ 占位。
//! 在声明实例化期拒绝(global_lexical 门控,门控源居 emit_program 调用点)。
use super::check_restricted_global_lexical;
use crate::{CompileCtx, Emitter}; use crate::{CompileCtx, Emitter};
use oxide_parser::{BindingPattern, Declaration, ExportDefaultDeclarationKind, Statement, VariableDeclarationKind}; use oxide_parser::{BindingPattern, Declaration, ExportDefaultDeclarationKind, Statement, VariableDeclarationKind};
@@ -294,10 +292,9 @@ impl Emitter {
/// 属 Declaration、非 Statement 子产生式,parser 按语法错误直接拒绝,这些 /// 属 Declaration、非 Statement 子产生式,parser 按语法错误直接拒绝,这些
/// 形状不会进入 emit)。 /// 形状不会进入 emit)。
/// - 跳过 for 头声明(循环作用域由 for 分支内联 declare)。 /// - 跳过 for 头声明(循环作用域由 for 分支内联 declare)。
/// - `global_lexical` 为 true 时(仅脚本顶层调用点传 `!is_eval_script`): /// - `global_lexical` 仅脚本顶层调用点传 `!is_eval_script`,其余调用点传
/// lexical 声明撞受限全局名报 SyntaxError(脚本声明实例化对全局对象受限 /// false;当前只透传不消费(死参数,待后续清理),顶层与局部的 lexical
/// 自有属性名做检查,eval 代码声明实例化无此检查);函数体/块/switch case/ /// 声明均按合法遮蔽处理。
/// try/模块调用点传 false,lexical 声明是局部绑定不查。
pub(crate) fn predeclare_lexical_declarations( pub(crate) fn predeclare_lexical_declarations(
&self, statements: &[Statement], ctx: &mut CompileCtx, global_lexical: bool, &self, statements: &[Statement], ctx: &mut CompileCtx, global_lexical: bool,
) -> Result<(), String> { ) -> Result<(), String> {
@@ -314,7 +311,6 @@ impl Emitter {
} }
Statement::ClassDeclaration(cd) => { Statement::ClassDeclaration(cd) => {
if let Some(id) = &cd.id { if let Some(id) = &cd.id {
check_restricted_global_lexical(id.name.as_str(), global_lexical)?;
let reg = ctx.alloc_reg(); let reg = ctx.alloc_reg();
let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false); let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false);
} }
@@ -333,7 +329,6 @@ impl Emitter {
} }
Declaration::ClassDeclaration(cd) => { Declaration::ClassDeclaration(cd) => {
if let Some(id) = &cd.id { if let Some(id) = &cd.id {
check_restricted_global_lexical(id.name.as_str(), global_lexical)?;
let reg = ctx.alloc_reg(); let reg = ctx.alloc_reg();
let _ = ctx.declare_predeclared( let _ = ctx.declare_predeclared(
id.name.as_str(), id.name.as_str(),
@@ -352,7 +347,6 @@ impl Emitter {
// 此处只为具名 default 类建 lexical Let TDZ 占位。 // 此处只为具名 default 类建 lexical Let TDZ 占位。
if let ExportDefaultDeclarationKind::ClassDeclaration(cd) = &exp.declaration { if let ExportDefaultDeclarationKind::ClassDeclaration(cd) = &exp.declaration {
if let Some(id) = &cd.id { if let Some(id) = &cd.id {
check_restricted_global_lexical(id.name.as_str(), global_lexical)?;
let reg = ctx.alloc_reg(); let reg = ctx.alloc_reg();
let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false); let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false);
} }
@@ -365,12 +359,14 @@ impl Emitter {
} }
/// 递归预声明绑定 pattern 内的全部标识符(含数组/对象/默认值解构)。 /// 递归预声明绑定 pattern 内的全部标识符(含数组/对象/默认值解构)。
/// `global_lexical` 当前只透传不消费(死参数,待后续清理),递归调用
/// 必须原样下传,故仅递归使用。
#[allow(clippy::only_used_in_recursion)]
fn predeclare_lexical_pattern( fn predeclare_lexical_pattern(
&self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx, global_lexical: bool, &self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx, global_lexical: bool,
) -> Result<(), String> { ) -> Result<(), String> {
match pattern { match pattern {
BindingPattern::BindingIdentifier(bi) => { BindingPattern::BindingIdentifier(bi) => {
check_restricted_global_lexical(bi.name.as_str(), global_lexical)?;
let reg = ctx.alloc_reg(); let reg = ctx.alloc_reg();
let _ = ctx.declare_predeclared( let _ = ctx.declare_predeclared(
bi.name.as_str(), bi.name.as_str(),
-20
View File
@@ -7,23 +7,3 @@
mod block_fn; mod block_fn;
mod builtin_scan; mod builtin_scan;
mod declare; mod declare;
/// 脚本顶层词法声明(`let`/`const`/`class`)禁止使用的受限全局名:`undefined`、
/// `NaN`、`Infinity` 三名静态集。
///
/// 判定标准是全局对象上 `configurable:false` 的自有属性名:此类属性无法由声明
/// 实例化建立遮蔽绑定,现行规范下恰为三常量;`eval` 与其余内置属性皆可配置,
/// 可合法遮蔽。运行时无法查询全局对象描述符,名单必须编译期确定。
///
/// 与 `BUILTIN_GLOBALS` 语义不同:后者是写入拦截与双写名单,两份名单互不派生,
/// 交叠名由 `restricted_lexical_names_within_builtin_globals` 单测断言恒同步。
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(())
}
-10
View File
@@ -489,16 +489,6 @@ impl Emitter {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{BUILTIN_GLOBALS, NON_WRITABLE_GLOBAL_BUILTINS}; use super::{BUILTIN_GLOBALS, NON_WRITABLE_GLOBAL_BUILTINS};
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}");
}
}
/// 漂移守卫:只读名单是 builtin 母集子集,且只读/可写两谓词对母集构成 /// 漂移守卫:只读名单是 builtin 母集子集,且只读/可写两谓词对母集构成
/// 划分(不重不漏)——防三常量集或母集改名/删名时拦截面与双写面单边漂移。 /// 划分(不重不漏)——防三常量集或母集改名/删名时拦截面与双写面单边漂移。
+4
View File
@@ -110,6 +110,10 @@ pub(crate) fn pre_existing_excluded(path: &str) -> Option<&'static str> {
"built-ins/Error/prototype/stack/instance-not-enumerable.js", "built-ins/Error/prototype/stack/instance-not-enumerable.js",
"快照断言 JSON.stringify 空对象,与 Error.prototype.toJSON 规范序列化(name/message)冲突,子模块更新后自然通过", "快照断言 JSON.stringify 空对象,与 Error.prototype.toJSON 规范序列化(name/message)冲突,子模块更新后自然通过",
), ),
(
"language/global-code/decl-lex-restricted-global.js",
"语料负面 oracle 相对 V8 对齐裁定过时:引擎已合法化三常量名顶层词法声明(ES2025 规范 GDI lexNames 臂仍保留受限全局属性检查,属有意偏离;若规范日后变更并更新语料,移除本条目即重新对齐)",
),
]; ];
LIST.iter() LIST.iter()
.find(|(suffix, _)| path.ends_with(suffix)) .find(|(suffix, _)| path.ends_with(suffix))
@@ -1,10 +1,13 @@
//! 脚本顶层 lexical 声明撞受限全局名的编译期拒绝:let/const 声明名撞 //! 脚本顶层 lexical 声明合法遮蔽三常量全局名(undefined/NaN/Infinity):
//! 受限集(undefined/NaN/Infinity——三常量,全局对象 {configurable:false} //! let/const/class 声明名撞三常量合法(对齐 V8/node 现行行为;ES2025 规范
//! 自有属性名)→ SyntaxError(整程序编译失败)。局部作用域(函数体/块/try)与 //! GDI lexNames 臂仍保留受限全局属性检查,属有意偏离,语料负面 oracle 已
//! eval 代码为合法遮蔽不拒;三常数值使用不受影响。 //! 登记跳过集)。裸读读绑定槽、裸写/delete 不落全局对象,全局属性描述符
//! 不变;局部作用域(函数体/块/try)与 eval 代码为合法遮蔽不拒;三常数值
//! 使用不受影响。
//! //!
//! class 名撞 eval 不在本集(解析器 early-error 族独立拒绝,类体恒严格模式, //! class 名撞 undefined/eval 不在本集(解析器 early-error 族独立拒绝,类体
//! 与受限集检查无关,见 top_level_class_eval_name_rejected)。 //! 恒严格模式,与本集机制无关,见 top_level_class_undefined_rejected /
//! top_level_class_eval_name_rejected)。
use std::sync::Arc; use std::sync::Arc;
@@ -20,74 +23,86 @@ fn eval(source: &str) -> Result<JsValue, String> {
vm.run(&Arc::new(module)) 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 { fn assert_ok(source: &str) -> JsValue {
eval(source).unwrap_or_else(|e| panic!("expected ok, got: {e}\nsource: {source}")) eval(source).unwrap_or_else(|e| panic!("expected ok, got: {e}\nsource: {source}"))
} }
// ── 脚本顶层 let/const/class 撞三常量:编译期拒绝 ── /// 断言编译 + 运行均成功且完成值为字符串,返回字符串内容。
fn assert_ok_str(source: &str) -> String {
let value = assert_ok(source);
if value.is_string() {
// SAFETY: 值已确认字符串类别,指针指向 VM 存活期内的 JsString。
unsafe { &*value.as_string_ptr() }.as_str().to_string()
} else {
panic!("expected string, got {value:?}\nsource: {source}")
}
}
// ── 脚本顶层 let/const/class 撞三常量:合法遮蔽 ──
#[test] #[test]
fn top_level_let_undefined_rejected() { fn top_level_let_undefined_allowed() {
// 独立形(脚本内无裸 undefined 引用,无镜像槽):规范 SyntaxError。 // 独立形(脚本内无裸 undefined 引用,无镜像槽):合法,完成值 undefined。
assert_compile_err("let undefined;"); let r = assert_ok("let undefined;");
assert!(r.is_undefined(), "let undefined 完成值应为 undefined,实际 {r:?}");
} }
#[test] #[test]
fn top_level_let_nan_rejected() { fn top_level_let_nan_allowed() {
assert_compile_err("let NaN;"); let r = assert_ok("let NaN;");
assert!(r.is_undefined(), "let NaN 完成值应为 undefined,实际 {r:?}");
} }
#[test] #[test]
fn top_level_let_infinity_rejected() { fn top_level_let_infinity_allowed() {
assert_compile_err("let Infinity;"); let r = assert_ok("let Infinity;");
assert!(r.is_undefined(), "let Infinity 完成值应为 undefined,实际 {r:?}");
} }
#[test] #[test]
fn top_level_const_nan_rejected() { fn top_level_const_nan_allowed() {
assert_compile_err("const NaN = 1;"); let r = assert_ok("const NaN = 1; NaN");
assert_eq!(r.as_int(), 1);
} }
#[test] #[test]
fn top_level_const_undefined_rejected() { fn top_level_const_undefined_allowed() {
assert_compile_err("const undefined = 1;"); let r = assert_ok("const undefined = 1; undefined");
assert_eq!(r.as_int(), 1);
} }
#[test] #[test]
fn top_level_class_undefined_rejected() { fn top_level_class_undefined_rejected() {
// class 名同为 lexical 绑定(受限检查同形);undefined 类名由解析器更早拒绝 // class 名撞 undefined 由解析器 early-error 族更早拒绝(SyntaxError 族,
// (SyntaxError 族,整程序拒绝可观察行为同形),断言接受任一拒绝相位。 // 整程序拒绝可观察行为同形),与本集机制无关,断言接受任一拒绝相位。
assert!(eval("class undefined {}").is_err(), "class undefined 类名应被拒绝"); assert!(eval("class undefined {}").is_err(), "class undefined 类名应被拒绝");
} }
#[test] #[test]
fn top_level_class_infinity_rejected() { fn top_level_class_infinity_allowed() {
assert_compile_err("class Infinity {}"); // class 名同为 lexical 绑定:合法遮蔽,typeof 读绑定槽(类函数)。
assert_eq!(assert_ok_str("class Infinity {} typeof Infinity"), "function");
} }
#[test] #[test]
fn top_level_let_undefined_with_bare_use_rejected() { fn top_level_let_undefined_with_bare_use_allowed() {
// 镜像形(脚本另有裸 undefined 引用):拒绝行为不变,错误相位提前到 // 镜像形(脚本另有裸 undefined 引用):声明点之后的裸读是词法绑定读,
// 声明预登记期(消息同形)。 // 合法,完成值为绑定值 undefined。
assert_compile_err("let undefined; undefined"); let r = assert_ok("let undefined; undefined");
assert!(r.is_undefined(), "镜像形完成值应为 undefined,实际 {r:?}");
} }
#[test] #[test]
fn top_level_let_destructured_nan_rejected() { fn top_level_let_destructured_nan_allowed() {
// 解构叶子同属声明名,受限检查覆盖。 // 解构叶子同属声明名,合法遮蔽。
assert_compile_err("let [NaN] = [1];"); let r = assert_ok("let [NaN] = [1]; NaN");
assert_eq!(r.as_int(), 1);
} }
#[test] #[test]
fn top_level_switch_case_let_nan_allowed() { fn top_level_switch_case_let_nan_allowed() {
// switch case 有独立 CaseBlock 作用域:case 内 lexical 是块局部绑定,不受限检查。 // switch case 有独立 CaseBlock 作用域:case 内 lexical 是块局部绑定。
let r = assert_ok("switch (0) { case 0: let NaN; } 1"); let r = assert_ok("switch (0) { case 0: let NaN; } 1");
assert_eq!(r.as_int(), 1); assert_eq!(r.as_int(), 1);
} }
@@ -96,12 +111,68 @@ fn top_level_switch_case_let_nan_allowed() {
#[test] #[test]
fn eval_script_let_nan_allowed() { fn eval_script_let_nan_allowed() {
// eval 代码在独立 lexical 环境实例化,不受限检查约束;外层裸读仍是三常量值 // eval 代码在独立 lexical 环境实例化;外层裸读仍是三常量值
//(NaN 自反性用 isNaN 断言,=== 对 NaN 恒 false)。 //(NaN 自反性用 isNaN 断言,=== 对 NaN 恒 false)。
let r = assert_ok("eval('let NaN;'); isNaN(NaN)"); let r = assert_ok("eval('let NaN;'); isNaN(NaN)");
assert!(r.is_bool() && r.as_bool(), "eval 内 let NaN 应为合法遮蔽,实际 {r:?}"); assert!(r.is_bool() && r.as_bool(), "eval 内 let NaN 应为合法遮蔽,实际 {r:?}");
} }
// ── 值隔离与全局对象不变面:写/delete 落绑定槽,全局属性原值与描述符保留 ──
#[test]
fn top_level_let_undefined_value_isolated() {
// 声明点后的裸读读绑定槽:得声明值,非全局属性值。
let r = assert_ok("let undefined=5; undefined");
assert_eq!(r.as_int(), 5);
}
#[test]
fn top_level_let_undefined_global_this_reflection() {
// 全局对象属性原值保留:globalThis.undefined 仍是真 undefined(typeof 串形)。
assert_eq!(assert_ok_str("let undefined=5; typeof globalThis.undefined"), "undefined");
}
#[test]
fn top_level_let_undefined_delete_not_global() {
// 词法绑定 delete 返 false(DeleteBinding 非属性引用),全局属性原值保留。
let r = assert_ok("let undefined=5; delete undefined");
assert!(r.is_bool() && !r.as_bool(), "实际 {r:?}");
let r = assert_ok("let undefined=5; delete undefined; undefined");
assert_eq!(r.as_int(), 5);
}
#[test]
fn top_level_let_undefined_typeof() {
// typeof 读绑定槽:声明值 5 的 typeof 为 number。
assert_eq!(assert_ok_str("let undefined=5; typeof undefined"), "number");
}
#[test]
fn top_level_let_undefined_nested_capture() {
// 嵌套函数读顶层词法绑定经 upvalue 捕获:得声明值。
let r = assert_ok("let undefined=1; function f(){return undefined} f()");
assert_eq!(r.as_int(), 1);
}
#[test]
fn top_level_let_undefined_tdz_read_throws() {
// 声明点前裸读是 TDZ 运行期 ReferenceError(错消息含 before initialization)。
let err = eval("undefined; let undefined;").unwrap_err();
assert!(err.contains("before initialization"), "got: {err}");
}
#[test]
fn top_level_let_undefined_descriptor_unchanged() {
// 全局对象属性描述符不变:writable/configurable/enumerable 恒 false。
let r = assert_ok(
"let undefined=5; Object.getOwnPropertyDescriptor(globalThis,'undefined')
.writable === false && Object.getOwnPropertyDescriptor(globalThis,'undefined')
.configurable === false && Object.getOwnPropertyDescriptor(globalThis,'undefined')
.enumerable === false",
);
assert!(r.is_bool() && r.as_bool(), "描述符应为只读不可配置不可枚举,实际 {r:?}");
}
// ── 假阳性守卫:合法面不得误拒 ── // ── 假阳性守卫:合法面不得误拒 ──
#[test] #[test]
@@ -113,8 +184,7 @@ fn top_level_let_uses_nan_value_allowed() {
#[test] #[test]
fn top_level_let_eval_name_allowed() { fn top_level_let_eval_name_allowed() {
// eval 是全局对象可配置自有属性(受限判定走自有属性臂,eval 不命中): // eval 是全局对象可配置自有属性:合法遮蔽,不在受限集。
// 合法遮蔽,不在受限集。
let r = assert_ok("let eval; 1"); let r = assert_ok("let eval; 1");
assert_eq!(r.as_int(), 1); assert_eq!(r.as_int(), 1);
} }
@@ -129,8 +199,8 @@ fn top_level_const_eval_name_allowed() {
fn top_level_class_eval_name_rejected() { fn top_level_class_eval_name_rejected() {
// eval 不在受限集(let/const 合法遮蔽),但 class 名撞 eval 由解析器 // eval 不在受限集(let/const 合法遮蔽),但 class 名撞 eval 由解析器
// early-error 族拒绝:类体恒严格模式,类名绑定检查落在类作用域 strict // early-error 族拒绝:类体恒严格模式,类名绑定检查落在类作用域 strict
// 判定内(V8 同形:sloppy `class eval {}` 亦 SyntaxError)。与本受限集 // 判定内(V8 同形:sloppy `class eval {}` 亦 SyntaxError)。与本集
// 检查无关,整程序拒绝可观察行为同形,断言任意拒绝相位。 // 机制无关,整程序拒绝可观察行为同形,断言任意拒绝相位。
assert!(eval("class eval {}").is_err(), "class eval 类名应被拒绝"); assert!(eval("class eval {}").is_err(), "class eval 类名应被拒绝");
} }
@@ -3,9 +3,9 @@
//! 不为其登记,声明合法建立遮蔽绑定——裸读读绑定槽、裸写/delete 不落全局 //! 不为其登记,声明合法建立遮蔽绑定——裸读读绑定槽、裸写/delete 不落全局
//! 对象(全局属性原值保留),声明点前裸读按 TDZ 运行期抛 ReferenceError。 //! 对象(全局属性原值保留),声明点前裸读按 TDZ 运行期抛 ReferenceError。
//! //!
//! 回归约束:受限三常量(87 面)声明仍编译期拒绝;let+var / function+let //! 回归约束:let+var / function+let 撞 builtin 名的重复声明仍编译期拒绝
//! 撞 builtin 名的重复声明仍编译期拒绝(var 懒登记镜像与函数先登记撞点保留); //! (var 懒登记镜像与函数先登记撞点保留);未遮蔽 builtin 读、var 内置名
//! 未遮蔽 builtin 读、var 内置名写全局、函数内遮蔽读不回退。 //! 写全局、函数内遮蔽读不回退。
use std::sync::Arc; use std::sync::Arc;
@@ -153,13 +153,15 @@ fn top_level_let_shadow_tdz_second_read_throws() {
assert!(err.contains("before initialization"), "got: {err}"); assert!(err.contains("before initialization"), "got: {err}");
} }
// ── 回归约束:受限名与重复声明拒面不翻 ── // ── 回归约束:受限名已合法化(对齐 V8),重复声明拒面不翻 ──
#[test] #[test]
fn top_level_let_restricted_names_still_rejected() { fn top_level_let_restricted_names_legal_shadow() {
// 受限三常量(87 面):非受限名修面不动此面,声明仍编译期拒绝。 // 三常量名(undefined/NaN/Infinity)声明合法化:裸读读绑定槽。
assert_compile_err("let undefined;"); let r = assert_ok("let undefined;");
assert_compile_err("let NaN=1; NaN"); assert!(r.is_undefined(), "let undefined 完成值应为 undefined,实际 {r:?}");
let r = assert_ok("let NaN=1; NaN");
assert_eq!(r.as_int(), 1);
} }
#[test] #[test]