mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 19:21:11 +08:00
fix: 块级函数声明闭包物化提前至块入口,声明点退化为外层 var 写回
This commit is contained in:
@@ -14,6 +14,11 @@ impl Emitter {
|
|||||||
self.predeclare_block_function_declarations(&block.body, ctx);
|
self.predeclare_block_function_declarations(&block.body, ctx);
|
||||||
// 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。
|
// 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。
|
||||||
let _ = self.predeclare_lexical_declarations(&block.body, ctx, false);
|
let _ = self.predeclare_lexical_declarations(&block.body, ctx, false);
|
||||||
|
// 块级函数声明的块入口初始化:闭包物化入预声明块槽,声明语句前读命中
|
||||||
|
// 函数对象(sloppy 与 strict 同形);声明点退化为其守语义。
|
||||||
|
for s in &block.body {
|
||||||
|
self.emit_block_fn_entry_init_stmt(s, ctx)?;
|
||||||
|
}
|
||||||
let mut r = None;
|
let mut r = None;
|
||||||
for s in &block.body {
|
for s in &block.body {
|
||||||
if let Some(rr) = self.emit_statement(s, ctx)? {
|
if let Some(rr) = self.emit_statement(s, ctx)? {
|
||||||
@@ -24,6 +29,39 @@ impl Emitter {
|
|||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 块入口初始化的单语句走法:函数声明就地物化(同名重复声明共享块绑定,
|
||||||
|
/// 源序物化使末次声明天然成为入口值);if/labeled/while/do-while/with 体
|
||||||
|
/// 不推作用域、随本块绑定递归;嵌套块/循环/try 各自压作用域、由其自身块
|
||||||
|
/// 入口处理,不进入。
|
||||||
|
///
|
||||||
|
/// # 前提
|
||||||
|
/// - 预声明已为各声明名建立块 Let 绑定;`materialize_function_declaration`
|
||||||
|
/// 经 `lookup` 解析槽位。
|
||||||
|
fn emit_block_fn_entry_init_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<(), String> {
|
||||||
|
match stmt {
|
||||||
|
Statement::FunctionDeclaration(fd) => {
|
||||||
|
let Some(id) = &fd.id else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let name = id.name.to_string();
|
||||||
|
self.materialize_function_declaration(fd, &name, ctx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Statement::IfStatement(is) => {
|
||||||
|
self.emit_block_fn_entry_init_stmt(&is.consequent, ctx)?;
|
||||||
|
if let Some(alt) = &is.alternate {
|
||||||
|
self.emit_block_fn_entry_init_stmt(alt, ctx)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Statement::WhileStatement(wh) => self.emit_block_fn_entry_init_stmt(&wh.body, ctx),
|
||||||
|
Statement::DoWhileStatement(dw) => self.emit_block_fn_entry_init_stmt(&dw.body, ctx),
|
||||||
|
Statement::LabeledStatement(ls) => self.emit_block_fn_entry_init_stmt(&ls.body, ctx),
|
||||||
|
Statement::WithStatement(ws) => self.emit_block_fn_entry_init_stmt(&ws.body, ctx),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn emit_block_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
|
pub(crate) fn emit_block_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::BlockStatement(_) => self.emit_block_statement(stmt, ctx),
|
Statement::BlockStatement(_) => self.emit_block_statement(stmt, ctx),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//! 函数声明语句 emit:`emit_function_declaration_statement` 创建闭包并绑定函数名。
|
//! 函数声明语句 emit:`emit_function_declaration_statement` 创建闭包并绑定函数名。
|
||||||
|
|
||||||
|
use crate::symbol_table::ScopeKind;
|
||||||
use crate::{CompileCtx, Emitter, ParamSpec};
|
use crate::{CompileCtx, Emitter, ParamSpec};
|
||||||
use oxide_bytecode::opcode::OpCode;
|
use oxide_bytecode::opcode::OpCode;
|
||||||
use oxide_ir::inst::Inst;
|
use oxide_ir::inst::Inst;
|
||||||
@@ -25,49 +26,19 @@ impl Emitter {
|
|||||||
} else {
|
} else {
|
||||||
return Err("FunctionDeclaration without name".into());
|
return Err("FunctionDeclaration without name".into());
|
||||||
};
|
};
|
||||||
let mut param_names = Vec::new();
|
// 块级函数声明:闭包已在块入口物化写入预声明块槽(块绑定块入口即持有
|
||||||
for (idx, param) in fd.params.items.iter().enumerate() {
|
// 函数对象,声明语句前读命中它),声明点不重编闭包,只处理下方外层
|
||||||
match ¶m.pattern {
|
// var 写回。
|
||||||
oxide_parser::BindingPattern::BindingIdentifier(bi) => param_names.push(ParamSpec::Identifier {
|
let binding_scope_kind = ctx
|
||||||
name: bi.name.to_string(),
|
.scopes
|
||||||
initializer: param.initializer.as_deref(),
|
.symbols
|
||||||
}),
|
.lookup_any_binding(&name)
|
||||||
pattern => param_names.push(ParamSpec::Pattern {
|
.map(|(_, scope_idx)| ctx.scopes.symbols.scopes[scope_idx].kind);
|
||||||
synthetic_name: format!("@@param_{idx}"),
|
let var_reg = if binding_scope_kind == Some(ScopeKind::BlockScope) {
|
||||||
pattern,
|
ctx.lookup(&name)?
|
||||||
initializer: param.initializer.as_deref(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(rest) = &fd.params.rest {
|
|
||||||
self.push_rest_param(&rest.rest.argument, &mut param_names)?;
|
|
||||||
}
|
|
||||||
let body_stmts: &[Statement] = if let Some(body) = &fd.body { &body.statements } else { &[] };
|
|
||||||
let own_strict = fd.has_use_strict_directive();
|
|
||||||
let mut sub_module = if fd.generator && fd.r#async {
|
|
||||||
self.compile_async_generator_body(¶m_names, body_stmts, ctx, own_strict)?
|
|
||||||
} else if fd.generator {
|
|
||||||
self.compile_generator_body(¶m_names, body_stmts, ctx, own_strict)?
|
|
||||||
} else if fd.r#async {
|
|
||||||
self.compile_async_body(¶m_names, body_stmts, ctx, false, false, own_strict)?
|
|
||||||
} else {
|
} else {
|
||||||
self.compile_function_body(¶m_names, body_stmts, ctx, false, false, own_strict)?
|
self.materialize_function_declaration(fd, &name, ctx)?
|
||||||
};
|
};
|
||||||
sub_module.function_name = Some(name.clone());
|
|
||||||
ctx.nested.push(sub_module);
|
|
||||||
let var_reg = ctx.lookup(&name)?;
|
|
||||||
ctx.reserve_reg(var_reg);
|
|
||||||
ctx.inst(Inst::create_closure(Operand::Reg(var_reg), ctx.nested.len() as u16));
|
|
||||||
if let Some(&cell_idx) = ctx.captured_bindings.get(&name) {
|
|
||||||
ctx.inst(Inst::new(
|
|
||||||
OpCode::MAKE_CELL,
|
|
||||||
Operand::Reg(var_reg),
|
|
||||||
Operand::Imm(cell_idx as u16),
|
|
||||||
Operand::None,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None));
|
|
||||||
}
|
|
||||||
// 块内函数声明求值写回外层 var 绑定(sloppy web-compat):预声明期为该
|
// 块内函数声明求值写回外层 var 绑定(sloppy web-compat):预声明期为该
|
||||||
// 名实例化的外层 var 绑定(函数作用域)在此写入块槽位里的函数对象,
|
// 名实例化的外层 var 绑定(函数作用域)在此写入块槽位里的函数对象,
|
||||||
// 求值一次写回一次(循环体内每迭代覆写头绑定)。形参/词法声明同名
|
// 求值一次写回一次(循环体内每迭代覆写头绑定)。形参/词法声明同名
|
||||||
@@ -104,4 +75,67 @@ impl Emitter {
|
|||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 函数声明物化:建形参 spec、编译体为子模块、创建闭包并把函数对象写入
|
||||||
|
/// 声明绑定槽(名被捕获时同步建 cell)。顶层声明点与块级块入口初始化共用。
|
||||||
|
///
|
||||||
|
/// # 前提
|
||||||
|
/// - 声明名已有已初始化绑定(顶层为 var 预声明、块为 Let 预声明);
|
||||||
|
/// `lookup` 对未声明名失败。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 子模块登记进嵌套模块表;当前指令流发 CREATE_CLOSURE(及 MAKE_CELL /
|
||||||
|
/// STORE_VAR)。
|
||||||
|
///
|
||||||
|
/// # 注意事项
|
||||||
|
/// - 闭包的 upvalue/cell 捕获在函数入口的捕获分析期固定,本调用点的时序
|
||||||
|
/// 只决定 CREATE_CLOSURE 在指令流中的落点。
|
||||||
|
pub(crate) fn materialize_function_declaration(
|
||||||
|
&self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx,
|
||||||
|
) -> Result<u32, String> {
|
||||||
|
let mut param_names = Vec::new();
|
||||||
|
for (idx, param) in fd.params.items.iter().enumerate() {
|
||||||
|
match ¶m.pattern {
|
||||||
|
oxide_parser::BindingPattern::BindingIdentifier(bi) => param_names.push(ParamSpec::Identifier {
|
||||||
|
name: bi.name.to_string(),
|
||||||
|
initializer: param.initializer.as_deref(),
|
||||||
|
}),
|
||||||
|
pattern => param_names.push(ParamSpec::Pattern {
|
||||||
|
synthetic_name: format!("@@param_{idx}"),
|
||||||
|
pattern,
|
||||||
|
initializer: param.initializer.as_deref(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(rest) = &fd.params.rest {
|
||||||
|
self.push_rest_param(&rest.rest.argument, &mut param_names)?;
|
||||||
|
}
|
||||||
|
let body_stmts: &[Statement] = if let Some(body) = &fd.body { &body.statements } else { &[] };
|
||||||
|
let own_strict = fd.has_use_strict_directive();
|
||||||
|
let mut sub_module = if fd.generator && fd.r#async {
|
||||||
|
self.compile_async_generator_body(¶m_names, body_stmts, ctx, own_strict)?
|
||||||
|
} else if fd.generator {
|
||||||
|
self.compile_generator_body(¶m_names, body_stmts, ctx, own_strict)?
|
||||||
|
} else if fd.r#async {
|
||||||
|
self.compile_async_body(¶m_names, body_stmts, ctx, false, false, own_strict)?
|
||||||
|
} else {
|
||||||
|
self.compile_function_body(¶m_names, body_stmts, ctx, false, false, own_strict)?
|
||||||
|
};
|
||||||
|
sub_module.function_name = Some(name.to_string());
|
||||||
|
ctx.nested.push(sub_module);
|
||||||
|
let var_reg = ctx.lookup(name)?;
|
||||||
|
ctx.reserve_reg(var_reg);
|
||||||
|
ctx.inst(Inst::create_closure(Operand::Reg(var_reg), ctx.nested.len() as u16));
|
||||||
|
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
||||||
|
ctx.inst(Inst::new(
|
||||||
|
OpCode::MAKE_CELL,
|
||||||
|
Operand::Reg(var_reg),
|
||||||
|
Operand::Imm(cell_idx as u16),
|
||||||
|
Operand::None,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None));
|
||||||
|
}
|
||||||
|
Ok(var_reg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,10 @@ fn block_function_decl_call_after_decl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_function_decl_hoisted_call_before_decl_is_not_callable() {
|
fn block_function_decl_call_before_decl() {
|
||||||
// 阶段 1:声明点之前调用读到未初始化槽 undefined,报 not callable
|
// 声明点之前调用:块绑定块入口即持有函数对象,提前调用正常返回。
|
||||||
// (比修复前的编译错好;完整 hoisting 属后续阶段)。
|
let result = eval("{ g(); function g(){return 1;} }").unwrap();
|
||||||
let err = eval("{ g(); function g(){return 1;} }").unwrap_err();
|
assert_eq!(result.as_int(), 1);
|
||||||
assert!(err.contains("not callable") || err.contains("not an object"), "got: {err}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -58,8 +57,8 @@ fn block_function_decl_capture_escape() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn block_function_decl_leaks_outer_in_sloppy_script() {
|
fn block_function_decl_leaks_outer_in_sloppy_script() {
|
||||||
// sloppy 块级函数声明名泄漏外层(web-compat 外层 var 绑定 + 求值写回):
|
// sloppy 块级函数声明名泄漏外层(web-compat 外层 var 绑定 + 求值写回):
|
||||||
// 块后读见函数对象而非未声明名。块内声明前读仍为 undefined(块入口初始化
|
// 块后读见函数对象而非未声明名;块内声明前读同样为函数对象(块入口
|
||||||
// 未建,见 hoisted_call 测试),块内声明后读为函数。
|
// 初始化),块内声明后读为函数。
|
||||||
let result = eval("{ { function g(){return 2;} } } typeof g === 'function'").unwrap();
|
let result = eval("{ { function g(){return 2;} } } typeof g === 'function'").unwrap();
|
||||||
assert!(result.as_bool(), "sloppy 块函数名应泄漏外层,got {:?}", result);
|
assert!(result.as_bool(), "sloppy 块函数名应泄漏外层,got {:?}", result);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user