mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 捕获名解析增加块作用域可见性检查
This commit is contained in:
@@ -21,19 +21,20 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
// 捕获集是函数级名字并集:块级 let/const 的 cell 在块退出后仍存留,但名字
|
||||
// 已不在作用域链内。仅当名字当前可解析时才走 cell,否则落下方全局解析
|
||||
// (未声明读经 LOAD_GLOBAL 抛 ReferenceError),不得按名误读已失效的 cell。
|
||||
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
||||
let r = ctx.alloc_reg();
|
||||
if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) {
|
||||
if let Some(binding_reg) = ctx.scopes.symbols.lookup_any_binding(name).map(|(binding, _)| binding.reg) {
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_GET,
|
||||
Operand::Reg(r),
|
||||
Operand::Reg(binding.reg),
|
||||
Operand::Reg(binding_reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
} else {
|
||||
ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(r), Operand::None, Operand::Imm(cell_idx as u16)));
|
||||
return Ok(r);
|
||||
}
|
||||
return Ok(r);
|
||||
}
|
||||
|
||||
let var_reg = match ctx.lookup_or_builtin(name) {
|
||||
@@ -100,6 +101,9 @@ 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) {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_GET,
|
||||
@@ -107,13 +111,17 @@ impl Emitter {
|
||||
Operand::Reg(binding.reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
} else {
|
||||
} else if self.is_global_tier_name(ctx, name) {
|
||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_GET,
|
||||
OpCode::LOAD_GLOBAL,
|
||||
Operand::Reg(result_reg),
|
||||
Operand::Const(key_idx),
|
||||
Operand::None,
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.inst(Inst::load_const(Operand::Reg(result_reg), undef_idx));
|
||||
}
|
||||
} else if self.is_global_tier_name(ctx, name) {
|
||||
// 顶层已声明 var:with 对象无该属性时回退读全局对象属性(顶层 var 的唯一
|
||||
@@ -183,15 +191,19 @@ impl Emitter {
|
||||
));
|
||||
return;
|
||||
}
|
||||
// 目标若是被捕获 cell,走 CELL_SET
|
||||
// 目标若是被捕获 cell,走 CELL_SET;仅当名字当前可解析时才写 cell——捕获集
|
||||
// 按名保留的块级绑定在块退出后不可解析,须落下方全局写(sloppy 物化隐式
|
||||
// 全局属性,strict 抛 ReferenceError)。
|
||||
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_SET,
|
||||
Operand::None,
|
||||
Operand::Reg(val_reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
return;
|
||||
if ctx.scopes.symbols.lookup_any_binding(name).is_some() {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_SET,
|
||||
Operand::None,
|
||||
Operand::Reg(val_reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
let var_reg = ctx.lookup_or_global(name);
|
||||
if ctx.targets_readonly_builtin(name, var_reg) {
|
||||
|
||||
@@ -348,14 +348,17 @@ impl Emitter {
|
||||
if let Expression::Identifier(ident) = arg_expr {
|
||||
let name = ident.name.as_str();
|
||||
let in_with_dynamic = !ctx.with_stack.is_empty() && !ctx.is_with_internal_binding(name);
|
||||
let captured =
|
||||
ctx.current_upvalue_captures.iter().any(|u| u.name == name) || ctx.captured_bindings.contains_key(name);
|
||||
let binding = ctx.scopes.symbols.lookup_any_binding(name);
|
||||
// 捕获集按名保留块级绑定,但块退出后名字已不可解析:此时不再视为
|
||||
// captured(typeof 对未解析引用求值 "undefined",不得 CELL_GET 误读
|
||||
// 已失效 cell)。
|
||||
let captured = ctx.current_upvalue_captures.iter().any(|u| u.name == name)
|
||||
|| (ctx.captured_bindings.contains_key(name) && binding.is_some());
|
||||
// 顶层已声明 var:typeof 读全局对象属性(顶层 var 的唯一存储,引擎侧不保留
|
||||
// 镜像副本),缺失 → "undefined"(非抛,IsUnresolvableReference 语义)。未声明名
|
||||
// 同走此路(lookup 未命中)。隐式全局槽(未声明名读写登记)同走属性路:
|
||||
// delete 真删后缺失 → "undefined" 而非经引擎侧镜像副本读旧值或抛
|
||||
// ReferenceError(typeof 对 unresolvable 引用不抛)。
|
||||
let binding = ctx.scopes.symbols.lookup_any_binding(name);
|
||||
let implicit_global = binding.is_some_and(|(b, _)| ctx.is_implicit_global_reg(b.reg));
|
||||
let is_tier = self.is_global_tier_name(ctx, name);
|
||||
if !in_with_dynamic && !captured && (is_tier || binding.is_none() || implicit_global) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! 两类型经 lib.rs 重导出供各语法域构造实参;编译链为 `Emitter` 方法,
|
||||
//! 捕获分析经自由函数调用 capture 族,与所在文件无关。
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
use oxide_bytecode::module::{Constant, UpvalueCapture};
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
@@ -568,10 +568,20 @@ impl Emitter {
|
||||
body_context,
|
||||
FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow | FunctionBodyContext::ClassElement
|
||||
) {
|
||||
// 只有创建点父作用域链内可解析的父捕获名才可能成为子函数 upvalue:
|
||||
// 捕获集是函数级名字并集,块级 let/const 在块退出后 cell 仍存留但名字
|
||||
// 已不可解析,块外嵌套函数按名命中会错误读取已失效的 cell。父自身
|
||||
// upvalue 代表的祖父绑定在父函数体内恒可见,不参与过滤。
|
||||
let visible_parent_captured: BTreeMap<String, u8> = parent_ctx
|
||||
.captured_bindings
|
||||
.iter()
|
||||
.filter(|(n, _)| parent_ctx.scopes.symbols.lookup_any_binding(n).is_some())
|
||||
.map(|(n, &i)| (n.clone(), i))
|
||||
.collect();
|
||||
ctx.current_upvalue_captures = collect_upvalue_names(
|
||||
body_stmts,
|
||||
&capture_exprs,
|
||||
&parent_ctx.captured_bindings,
|
||||
&visible_parent_captured,
|
||||
&parent_ctx.current_upvalue_captures,
|
||||
&ctx.own_bindings,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
//! 块作用域退出后的名字解析钉:块级 `let`/`const` 退出后名字不可解析,读取
|
||||
//! 须落全局解析(ReferenceError / typeof 为 "undefined"),不得按名命中残留
|
||||
//! 捕获 cell 读到已失效的值。
|
||||
//!
|
||||
//! 覆盖同函数直读、嵌套函数捕获、`typeof`、严格模式写与 direct eval 各面,
|
||||
//! 并锁住合法块内捕获与参数遮蔽不回归。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {e}"),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&Arc::new(module)) {
|
||||
Ok(result) => format!("{result}"),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catch_nested_function_bare_read_of_popped_block_let_throws_reference_error() {
|
||||
// 语料主形:catch 内嵌套函数裸读 try 内已退出块的 let,须抛 ReferenceError
|
||||
// 而非命中残留 cell 读到 18。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var ref = false; \
|
||||
try { { let xx = 18; throw 25; } } catch (e) { \
|
||||
(function () { try { xx; } catch (e2) { ref = (e2 instanceof ReferenceError); } })(); \
|
||||
} ref"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typeof_popped_block_let_in_nested_function_is_undefined() {
|
||||
// 块外嵌套函数对已退出块 let 的 typeof:未解析引用求值 "undefined",
|
||||
// 不得命中残留 cell 得到 "number"。
|
||||
assert_eq!(eval("{ let zz = 7; } (function () { return typeof zz === \"undefined\"; })()"), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_read_of_popped_block_let_throws_reference_error_with_legal_capture() {
|
||||
// 同函数内既有合法块内捕获,块外直读仍须抛 ReferenceError;捕获闭包本身
|
||||
// 读到块内值不受影响。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var f; { let aa = 1; f = function () { return aa; }; } \
|
||||
var ref = false; try { aa; } catch (e) { ref = (e instanceof ReferenceError); } \
|
||||
ref && f() === 1"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typeof_popped_block_let_with_legal_capture_is_undefined() {
|
||||
// 同函数 typeof:块外 typeof 已退出块 let 为 "undefined"(非抛非旧值)。
|
||||
assert_eq!(
|
||||
eval("{ let aa = 1; var f = function () { return aa; }; } typeof aa === \"undefined\""),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_write_to_popped_block_let_throws_reference_error() {
|
||||
// 严格模式对已退出块 let 的块外写须抛 ReferenceError,不得按名 CELL_SET
|
||||
// 污染残留 cell;块内闭包捕获值不变。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"\"use strict\"; var f; { let aa = 1; f = function () { return aa; }; } \
|
||||
var ref = false; try { aa = 99; } catch (e) { ref = (e instanceof ReferenceError); } \
|
||||
ref && f() === 1"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_of_popped_block_let_throws_reference_error() {
|
||||
// direct eval 面锁:调用方块外的 eval('xx') 看不到已退出块绑定,抛
|
||||
// ReferenceError(当前已绿,锁不回归)。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var ref = false; \
|
||||
try { { let xx = 18; throw 25; } } catch (e) { \
|
||||
(function () { try { eval(\"xx\"); } catch (e2) { ref = (e2 instanceof ReferenceError); } })(); \
|
||||
} ref"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_created_inside_block_reads_let_after_block() {
|
||||
// 合法块内捕获守卫:块内创建的闭包在块外调用仍读到块绑定值。
|
||||
assert_eq!(eval("var r; { let bb = 7; r = function () { return bb; }; } r() === 7"), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chained_closure_reads_grandparent_block_let() {
|
||||
// 链式捕获守卫:跨两层闭包读取祖父块内绑定不受可见性过滤影响。
|
||||
assert_eq!(eval("var h; { let cc = 5; h = () => () => cc; } h()() === 5"), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catch_block_let_shadows_parameter() {
|
||||
// 参数遮蔽守卫:catch/try 块内 let 只遮蔽块内引用,catch 外仍读参数值。
|
||||
assert_eq!(
|
||||
eval("(function (x) { try { let x = \"in\"; throw 0; } catch (e) { return x === \"out\"; } })(\"out\")"),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_of_uncaptured_popped_block_let_throws_reference_error() {
|
||||
// 无捕获对照守卫:无任何闭包捕获时块外直读同样抛 ReferenceError。
|
||||
assert_eq!(
|
||||
eval("var ref = false; { let aa = 1; } try { aa; } catch (e) { ref = (e instanceof ReferenceError); } ref"),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user