mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: 块级异步与生成器函数声明不再泄漏全局
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
//! emit 前置 pass,块级函数名收集:web-compat 外层 var 绑定实例化支撑——
|
||||
//! 语句树内块级函数声明名源序去重收集、词法声明名抑制集(块函数名与词法名
|
||||
//! 语句树内块级普通函数声明名源序去重收集、词法声明名抑制集(块函数名与词法名
|
||||
//! 同名时退化为纯块作用域)、形参名 ∪ 词法名抑制集构建。
|
||||
|
||||
use std::collections::HashSet;
|
||||
@@ -9,10 +9,13 @@ use crate::Emitter;
|
||||
use oxide_parser::{Declaration, Statement, VariableDeclarationKind};
|
||||
|
||||
impl Emitter {
|
||||
/// 递归收集语句树内块级函数声明名(源序去重),供 sloppy 模式浏览器兼容行为
|
||||
/// (块级函数声明建外层 var 绑定)下的外层 var 绑定实例化。
|
||||
/// 递归收集语句树内块级普通函数声明名(源序去重),供 sloppy 模式浏览器兼容
|
||||
/// 行为(Annex B.3.3 为块级普通函数声明建外层 var 绑定)下的外层 var 绑定实例化。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 只收普通 `FunctionDeclaration`:Annex B.3.3 的 web-compat 仅改写普通函数
|
||||
/// 声明,async 函数、生成器与 async 生成器是各自独立的块级词法绑定,块外
|
||||
/// 不可见,不入名集。
|
||||
/// - 调用点直接子级(函数体/程序顶层直接子句)是提升 var 声明,非块级
|
||||
/// 函数,不收集;进入嵌套语句(块/if/循环/switch case/try 各区)后的
|
||||
/// 函数声明才是块级。
|
||||
@@ -29,7 +32,7 @@ impl Emitter {
|
||||
for statement in statements {
|
||||
match statement {
|
||||
Statement::FunctionDeclaration(f) => {
|
||||
if block_level {
|
||||
if block_level && !f.generator && !f.r#async {
|
||||
if let Some(id) = &f.id {
|
||||
let name = id.name.to_string();
|
||||
if !names.contains(&name) {
|
||||
|
||||
@@ -49,17 +49,20 @@ impl Emitter {
|
||||
} else {
|
||||
self.materialize_function_declaration(fd, &name, ctx)?
|
||||
};
|
||||
// 块内函数声明求值后按三种情形处理:
|
||||
//
|
||||
// sloppy 模式下按浏览器/web 实现惯例为块级函数声明建外层 var 绑定并写回:
|
||||
// sloppy 模式下按 Annex B.3.3 为块级普通函数声明建外层 var 绑定并写回:
|
||||
// 预声明期为该名实例化的函数作用域 var 绑定在此写入块槽位里的函数对象,
|
||||
// 求值一次写回一次(循环体内每迭代覆写头绑定)。
|
||||
//
|
||||
// async 函数、生成器与 async 生成器是块级词法绑定,不在 Annex B.3.3 改写
|
||||
// 范围,块外不可见,不写回外层 var。
|
||||
//
|
||||
// 形参或函数作用域树内同名词法声明(抑制集)不写回:该名被同名声明遮蔽。
|
||||
//
|
||||
// 顶层不可写内置名不写回:sloppy 下对其赋值(put)永不成功,跳过与执行等价;
|
||||
// 顶层情形另补全局对象属性写(与 for-in 循环头的顶层写同形)。
|
||||
if !ctx.is_strict
|
||||
&& !fd.generator
|
||||
&& !fd.r#async
|
||||
&& ctx.scopes.symbols.scopes.len() > 1
|
||||
&& !ctx.block_fn_suppressed.contains(&name)
|
||||
&& !(ctx.is_global_scope && CompileCtx::is_non_writable_global_builtin(&name))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! try/catch/finally 语句 emit:`emit_try_statement` 生成 TRY_BEGIN 与处理入口。
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_ir::inst::Inst;
|
||||
@@ -32,13 +34,21 @@ impl Emitter {
|
||||
// try block 是独立块作用域:lexical 声明(let/const/class)限定于 try 内,
|
||||
// 与外层同名绑定互不干扰;块级预声明使声明点前读取编译为 TDZ 抛错。
|
||||
ctx.push_scope();
|
||||
// 函数预声明先于 lexical:同名 let 命中已有函数绑定报重复声明错。
|
||||
self.predeclare_block_function_declarations(&ts.block.body, ctx, false);
|
||||
// try 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。
|
||||
let _ = self.predeclare_lexical_declarations(&ts.block.body, ctx, false);
|
||||
// 块级函数声明的块入口初始化:声明点前读命中函数对象,声明点复用入口槽。
|
||||
ctx.block_fn_entry_mats.push(HashMap::new());
|
||||
for s in &ts.block.body {
|
||||
self.emit_block_fn_entry_init_stmt(s, ctx)?;
|
||||
}
|
||||
for s in &ts.block.body {
|
||||
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||
last_try_result = Some(r);
|
||||
}
|
||||
}
|
||||
ctx.block_fn_entry_mats.pop();
|
||||
ctx.pop_scope();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_VAR,
|
||||
@@ -64,14 +74,21 @@ 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)?;
|
||||
}
|
||||
// catch 块函数预声明先于 lexical,块入口物化与 try/普通块同口径。
|
||||
self.predeclare_block_function_declarations(&catch.body.body, ctx, false);
|
||||
// catch 块 lexical 声明同为局部绑定:不做受限全局名检查。
|
||||
let _ = self.predeclare_lexical_declarations(&catch.body.body, ctx, false);
|
||||
ctx.block_fn_entry_mats.push(HashMap::new());
|
||||
for s in &catch.body.body {
|
||||
self.emit_block_fn_entry_init_stmt(s, ctx)?;
|
||||
}
|
||||
let mut last_catch_result: Option<u32> = None;
|
||||
for s in &catch.body.body {
|
||||
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||
last_catch_result = Some(r);
|
||||
}
|
||||
}
|
||||
ctx.block_fn_entry_mats.pop();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_VAR,
|
||||
Operand::Reg(result_reg),
|
||||
@@ -91,11 +108,18 @@ impl Emitter {
|
||||
// try/catch 收敛值保持 try 体或 catch 体值,finally 值不渗入。
|
||||
// finally block 同为独立块作用域,lexical 声明互不泄漏。
|
||||
ctx.push_scope();
|
||||
// finally 块函数预声明先于 lexical,块入口物化与 try/普通块同口径。
|
||||
self.predeclare_block_function_declarations(&ts.finalizer.as_ref().unwrap().body, ctx, false);
|
||||
// finally 块 lexical 声明同为局部绑定:不做受限全局名检查。
|
||||
let _ = self.predeclare_lexical_declarations(&ts.finalizer.as_ref().unwrap().body, ctx, false);
|
||||
ctx.block_fn_entry_mats.push(HashMap::new());
|
||||
for s in &ts.finalizer.as_ref().unwrap().body {
|
||||
self.emit_block_fn_entry_init_stmt(s, ctx)?;
|
||||
}
|
||||
for s in &ts.finalizer.as_ref().unwrap().body {
|
||||
self.emit_statement(s, ctx)?;
|
||||
}
|
||||
ctx.block_fn_entry_mats.pop();
|
||||
ctx.pop_scope();
|
||||
ctx.inst(Inst::new(OpCode::TRY_FINALLY_END, Operand::None, Operand::None, Operand::None));
|
||||
ctx.pop_open_try_handler();
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
//! 块级 async/生成器函数声明不泄漏:Annex B.3.3 的 web-compat 外层 var 绑定
|
||||
//! 仅覆盖普通函数声明。async 函数、生成器与 async 生成器是块级词法绑定,
|
||||
//! 块外不可见,也不覆写同名外层 var;块内读取仍命中块绑定。
|
||||
//!
|
||||
//! 覆盖脚本顶层块、函数体内块、直接/间接 eval、嵌套块、switch case、try 块,
|
||||
//! 以及同名 var 碰撞与普通函数泄漏正例守卫。
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
fn assert_true(source: &str) {
|
||||
let result = eval(source).unwrap();
|
||||
assert!(result.as_bool(), "期望 true,源: {source},得 {result:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_async_fn_decl_not_leaked_in_script() {
|
||||
// 脚本顶层块:块级 async 函数声明不建外层绑定,块外 typeof 为 undefined。
|
||||
assert_true("{async function a(){}} typeof a === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_generator_decl_not_leaked_in_script() {
|
||||
// 生成器声明同为块级词法绑定,不泄漏。
|
||||
assert_true("{function* g(){}} typeof g === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_async_generator_decl_not_leaked_in_script() {
|
||||
// async 生成器声明不泄漏。
|
||||
assert_true("{async function* ag(){}} typeof ag === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_async_fn_decl_not_reflected_on_global() {
|
||||
// 不泄漏即不建全局对象属性:globalThis 反射不到块级 async 名。
|
||||
assert_true("{async function a(){}} !('a' in globalThis)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_async_fn_decl_not_leaked_in_function_body() {
|
||||
// 函数体内块与脚本面同口径:块外读为 undefined。
|
||||
assert_true("function q(){ {async function a(){}} return typeof a === 'undefined' } q()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_generator_decl_not_leaked_in_function_body() {
|
||||
// 函数体内块生成器同口径。
|
||||
assert_true("function q(){ {function* g(){}} return typeof g === 'undefined' } q()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_async_fn_decl_not_leaked_in_direct_eval() {
|
||||
// 直接 eval 顶层块:不泄漏到 eval 的 var 环境。
|
||||
assert_true(r#"function q(){ eval('{async function a(){}}'); return typeof a === 'undefined' } q()"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_generator_decl_not_leaked_in_indirect_eval() {
|
||||
// 间接 eval 顶层块:生成器名不落全局。
|
||||
assert_true(r#"(0,eval)('{function* g(){}}'); typeof g === 'undefined'"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_block_async_fn_decl_not_leaked() {
|
||||
// 嵌套块:任一深度都不泄漏。
|
||||
assert_true("{{async function a(){}}} typeof a === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_case_async_fn_decl_not_leaked() {
|
||||
// switch CaseBlock 内 async 声明不泄漏到 switch 外。
|
||||
assert_true("switch(0){case 0:async function a(){}} typeof a === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_block_async_fn_decl_not_leaked() {
|
||||
// try 块内 async 声明不泄漏到语句外,且块内可读。
|
||||
assert_true("try{async function a(){}}catch(e){} typeof a === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_block_function_decl_entry_visible() {
|
||||
// try 块作为独立块作用域:直接子函数声明的块入口物化,声明点前读可用。
|
||||
assert_true("var t; try{ t = typeof f; function f(){} }catch(e){} t === 'function'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_async_fn_var_collision_keeps_var() {
|
||||
// 同名外层 var 不被块级 async 声明覆写。
|
||||
assert_true("var a=1; {async function a(){}} typeof a === 'number'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_async_fn_var_collision_in_function_body() {
|
||||
// 函数体内同名 var 碰撞:块级 async 声明不写回外层 var,仍为 undefined。
|
||||
assert_true("(function q(){ {async function a(){}} var a; return typeof a === 'undefined' })()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_generator_var_collision_keeps_var() {
|
||||
// 生成器同名 var 碰撞同口径。
|
||||
assert_true("{function* g(){}} var g; typeof g === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_async_fn_readable_inside_block() {
|
||||
// 块内读取仍命中块级绑定(声明点后为 function)。
|
||||
assert_true("(function(){ {async function a(){}; return typeof a === 'function'} })()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_function_block_still_leaks() {
|
||||
// 正例守卫:普通块级函数声明仍按 Annex B.3.3 泄漏外层。
|
||||
assert_true("{function f(){}} typeof f === 'function'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_function_var_collision_still_overwritten() {
|
||||
// 正例守卫:普通块级函数声明求值写回同名外层 var。
|
||||
assert_true("var f=1; {function f(){}} typeof f === 'function'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_block_async_fn_not_leaked() {
|
||||
// strict 面不建外层绑定,块级 async 声明不泄漏。
|
||||
assert_true("'use strict'; {async function a(){}} typeof a === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_async_fn_still_global() {
|
||||
// 守卫:脚本顶层 async 函数声明(非块级)仍建全局绑定并可反射。
|
||||
assert_true("async function top(){} typeof top === 'function' && ('top' in globalThis)");
|
||||
}
|
||||
Reference in New Issue
Block a user