mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 03:31:12 +08:00
fix: 修正捕获可见性过滤的前向捕获与隐式全局误判
This commit is contained in:
@@ -92,6 +92,11 @@ pub struct CompileCtx {
|
||||
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
|
||||
/// 消除符号表时序依赖与 cell 索引错位。
|
||||
pub(crate) captured_bindings: BTreeMap<String, u8>,
|
||||
/// 正在发射的 C 风格 for 头 let/const 声明名(含解构叶)。头名不像块级
|
||||
/// let/const 那样在块入口预声明,init 表达式内创建的嵌套函数引用同头尚未
|
||||
/// declare 的前向名时,父捕获可见性过滤据此放行;init 发射完毕后移除,
|
||||
/// 嵌套 for 头取并集。
|
||||
pub(crate) pending_for_head_names: HashSet<String>,
|
||||
/// 顶层已声明 var 名与顶层函数声明名(脚本全局声明实例化提升名):裸读走全局
|
||||
/// 对象属性(LOAD_GLOBAL)、裸写走感知全局对象属性描述符的写;值的唯一存储是
|
||||
/// 全局对象属性(顶层 var 的唯一存储),引擎侧不再保留镜像副本。
|
||||
@@ -201,6 +206,7 @@ impl CompileCtx {
|
||||
block_fn_suppressed: HashSet::new(),
|
||||
block_fn_entry_mats: Vec::new(),
|
||||
captured_bindings: BTreeMap::new(),
|
||||
pending_for_head_names: HashSet::new(),
|
||||
global_tier_names: HashSet::new(),
|
||||
upvalue_const_flags: HashSet::new(),
|
||||
implicit_global_reads: HashSet::new(),
|
||||
@@ -347,6 +353,17 @@ impl CompileCtx {
|
||||
self.implicit_global_writes.contains(®) || self.implicit_global_reads.contains(®)
|
||||
}
|
||||
|
||||
/// 名字在当前作用域链内是否存在真实词法绑定(排除隐式全局登记),命中时返回其寄存器。
|
||||
///
|
||||
/// 未声明名的隐式全局登记(写侧 `lookup_or_global`、读侧 `lookup_or_builtin` 经
|
||||
/// `pre_register_global`)会在全局作用域插入占位绑定,使 `lookup_any_binding` 按名
|
||||
/// 命中;但它不是词法声明。捕获名退出作用域后若曾被登记为隐式全局,按名命中会
|
||||
/// 误读残留 cell——捕获 cell 的可见性判定统一经本入口。
|
||||
pub(crate) fn visible_binding_reg(&self, name: &str) -> Option<u32> {
|
||||
let (binding, _) = self.scopes.symbols.lookup_any_binding(name)?;
|
||||
(!self.is_implicit_global_reg(binding.reg)).then_some(binding.reg)
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_const_flag(&self, name: &str) -> bool {
|
||||
// upvalue 捕获的 const:子 ctx 符号表不含父函数作用域绑定,查快照标志。
|
||||
self.scopes.symbols.lookup_is_const(name) || self.upvalue_const_flags.contains(name)
|
||||
|
||||
@@ -22,10 +22,11 @@ impl Emitter {
|
||||
}
|
||||
|
||||
// 捕获集是函数级名字并集:块级 let/const 的 cell 在块退出后仍存留,但名字
|
||||
// 已不在作用域链内。仅当名字当前可解析时才走 cell,否则落下方全局解析
|
||||
// (未声明读经 LOAD_GLOBAL 抛 ReferenceError),不得按名误读已失效的 cell。
|
||||
// 已不在作用域链内。仅当名字当前可解析为真实词法绑定时才走 cell(隐式全局
|
||||
// 登记不算),否则落下方全局解析(未声明读经 LOAD_GLOBAL 抛 ReferenceError),
|
||||
// 不得按名误读已失效的 cell。
|
||||
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
||||
if let Some(binding_reg) = ctx.scopes.symbols.lookup_any_binding(name).map(|(binding, _)| binding.reg) {
|
||||
if let Some(binding_reg) = ctx.visible_binding_reg(name) {
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_GET,
|
||||
@@ -101,14 +102,14 @@ impl Emitter {
|
||||
Operand::None,
|
||||
));
|
||||
} else if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
||||
// 回退分支的捕获 cell 同样只在名字当前可解析时才有效;否则与静态解析
|
||||
// 一致:顶层 tier 名读全局对象属性,其余读 undefined(with 回退不抛
|
||||
// 未解析引用)。
|
||||
if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) {
|
||||
// 回退分支的捕获 cell 同样只在名字当前可解析为真实词法绑定时才有效;
|
||||
// 否则与静态解析一致:顶层 tier 名读全局对象属性,其余读 undefined
|
||||
// (with 回退不抛未解析引用)。
|
||||
if let Some(binding_reg) = ctx.visible_binding_reg(name) {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_GET,
|
||||
Operand::Reg(result_reg),
|
||||
Operand::Reg(binding.reg),
|
||||
Operand::Reg(binding_reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
} else if self.is_global_tier_name(ctx, name) {
|
||||
@@ -191,11 +192,11 @@ impl Emitter {
|
||||
));
|
||||
return;
|
||||
}
|
||||
// 目标若是被捕获 cell,走 CELL_SET;仅当名字当前可解析时才写 cell——捕获集
|
||||
// 按名保留的块级绑定在块退出后不可解析,须落下方全局写(sloppy 物化隐式
|
||||
// 全局属性,strict 抛 ReferenceError)。
|
||||
// 目标若是被捕获 cell,走 CELL_SET;仅当名字当前可解析为真实词法绑定时才写
|
||||
// cell——捕获集按名保留的块级绑定在块退出后不可解析(隐式全局登记不算),
|
||||
// 须落下方全局写(sloppy 物化隐式全局属性,strict 抛 ReferenceError)。
|
||||
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
||||
if ctx.scopes.symbols.lookup_any_binding(name).is_some() {
|
||||
if ctx.visible_binding_reg(name).is_some() {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_SET,
|
||||
Operand::None,
|
||||
|
||||
@@ -351,9 +351,9 @@ impl Emitter {
|
||||
let binding = ctx.scopes.symbols.lookup_any_binding(name);
|
||||
// 捕获集按名保留块级绑定,但块退出后名字已不可解析:此时不再视为
|
||||
// captured(typeof 对未解析引用求值 "undefined",不得 CELL_GET 误读
|
||||
// 已失效 cell)。
|
||||
// 已失效 cell)。隐式全局登记同样不算真实绑定。
|
||||
let captured = ctx.current_upvalue_captures.iter().any(|u| u.name == name)
|
||||
|| (ctx.captured_bindings.contains_key(name) && binding.is_some());
|
||||
|| (ctx.captured_bindings.contains_key(name) && ctx.visible_binding_reg(name).is_some());
|
||||
// 顶层已声明 var:typeof 读全局对象属性(顶层 var 的唯一存储,引擎侧不保留
|
||||
// 镜像副本),缺失 → "undefined"(非抛,IsUnresolvableReference 语义)。未声明名
|
||||
// 同走此路(lookup 未命中)。隐式全局槽(未声明名读写登记)同走属性路:
|
||||
|
||||
@@ -572,10 +572,15 @@ impl Emitter {
|
||||
// 捕获集是函数级名字并集,块级 let/const 在块退出后 cell 仍存留但名字
|
||||
// 已不可解析,块外嵌套函数按名命中会错误读取已失效的 cell。父自身
|
||||
// upvalue 代表的祖父绑定在父函数体内恒可见,不参与过滤。
|
||||
// C 风格 for 头 let/const 名在 init 内尚未 declare,但同头声明会为其
|
||||
// 建 cell,前向捕获须按父捕获集保留(见 `pending_for_head_names`)。
|
||||
let visible_parent_captured: BTreeMap<String, u8> = parent_ctx
|
||||
.captured_bindings
|
||||
.iter()
|
||||
.filter(|(n, _)| parent_ctx.scopes.symbols.lookup_any_binding(n).is_some())
|
||||
.filter(|(n, _)| {
|
||||
parent_ctx.visible_binding_reg(n.as_str()).is_some()
|
||||
|| parent_ctx.pending_for_head_names.contains(n.as_str())
|
||||
})
|
||||
.map(|(n, &i)| (n.clone(), i))
|
||||
.collect();
|
||||
ctx.current_upvalue_captures = collect_upvalue_names(
|
||||
|
||||
@@ -53,6 +53,17 @@ impl Emitter {
|
||||
if let Some(expr) = init.as_expression() {
|
||||
self.emit_expression(expr, ctx)?;
|
||||
} else if let ForStatementInit::VariableDeclaration(decl) = init {
|
||||
// C 风格 for 头 let/const 名在 init 表达式发射期间视为可见:头名不像
|
||||
// 块级 let/const 那样在块入口预声明,init 内闭包创建点头名尚不在符号表,
|
||||
// 可见性过滤会误删其前向捕获(见 `CompileCtx::pending_for_head_names`)。
|
||||
let saved_pending = ctx.pending_for_head_names.clone();
|
||||
if !matches!(decl.kind, VariableDeclarationKind::Var) {
|
||||
for d in &decl.declarations {
|
||||
let mut head_names = Vec::new();
|
||||
self.collect_pattern_binding_names(&d.id, &mut head_names);
|
||||
ctx.pending_for_head_names.extend(head_names);
|
||||
}
|
||||
}
|
||||
for d in &decl.declarations {
|
||||
let is_const = matches!(decl.kind, VariableDeclarationKind::Const);
|
||||
if let Some(init_expr) = &d.init {
|
||||
@@ -116,6 +127,7 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.pending_for_head_names = saved_pending;
|
||||
}
|
||||
}
|
||||
ctx.labels.set_label_pos(start_label, ctx.insts.len());
|
||||
|
||||
Reference in New Issue
Block a user