mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 未初始化 var 在赋值前读取不再取寄存器残留
This commit is contained in:
@@ -427,6 +427,12 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
// `var` 绑定入口实例化(非捕获名):预声明只登记槽位、不发射定义指令,
|
||||
// 声明点对已绑定名又跳过 undefined 写,缺失入口写会让首次写入前的读取
|
||||
// 取到调用方遗留的寄存器值。捕获名已由参数 prologue 的
|
||||
// MAKE_CELL(undefined) 实例化,此处只补未捕获名。
|
||||
self.instantiate_var_bindings(body_stmts, &mut ctx);
|
||||
|
||||
// 生成器:body 起点标记——调用时参数初始化(emit_params_prologue)结束后挂起于此,
|
||||
// 参数副作用/异常在 `g()` 调用时刻生效,首次 next() 从这继续执行 body。
|
||||
if is_generator {
|
||||
@@ -685,6 +691,35 @@ impl Emitter {
|
||||
Ok(param_base)
|
||||
}
|
||||
|
||||
/// 非捕获 `var` 名的函数入口实例化:把每个未捕获 var 槽在入口写为 undefined。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 须在 `var` 预声明之后、body 语句发射之前调用:预声明只登记槽位不发定义
|
||||
/// 指令,声明语句对已绑定名又跳过 undefined 写,缺失入口写会让首次写入前的
|
||||
/// 读取取到调用方遗留的寄存器值。
|
||||
/// - 形参与 `arguments` 由参数 prologue 写入实参,跳过以免覆盖。
|
||||
/// - 捕获名由参数 prologue 的 MAKE_CELL(undefined) 实例化;STORE_VAR 不更新
|
||||
/// cell,跳过以免冗余。
|
||||
/// - 符号表查不到的名字(如解构 pattern 叶名未被预声明)跳过,不报错。
|
||||
fn instantiate_var_bindings(&self, body_stmts: &[Statement], ctx: &mut CompileCtx) {
|
||||
let mut names: Vec<String> = collect_var_binding_names(body_stmts)
|
||||
.into_iter()
|
||||
.filter(|n| !ctx.param_names.contains(n) && n != "arguments")
|
||||
.filter(|n| !ctx.captured_bindings.contains_key(n))
|
||||
.collect();
|
||||
if names.is_empty() {
|
||||
return;
|
||||
}
|
||||
// 按名排序发射:哈希集迭代序随进程变化,排序使字节码确定。
|
||||
names.sort();
|
||||
let undef_reg = self.emit_undefined(ctx);
|
||||
for name in names {
|
||||
if let Some(reg) = ctx.scopes.symbols.lookup_any(&name) {
|
||||
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(reg), Operand::Reg(undef_reg), Operand::Imm(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 双 sub-pass emit body:先函数声明(hoisting),再其余语句。返回最后结果寄存器。
|
||||
fn emit_body_stmts(&self, body_stmts: &[Statement], ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
|
||||
let mut last_result_reg = None;
|
||||
|
||||
@@ -238,7 +238,41 @@ fn strict_flag_inherited() {
|
||||
assert!(top.is_strict, "脚本顶层 use strict 应置位");
|
||||
}
|
||||
|
||||
/// 面 9:source_encoded 口径下正则源池键为 marker 形态(与静态口径的明文键不同形)。
|
||||
/// 面 9:未捕获 `var` 名在函数入口补发 undefined 定义(预声明只登记槽位,
|
||||
/// 声明点对已绑定名跳过 undefined 写,入口缺定义会使首次写入前读取取到残留)。
|
||||
#[test]
|
||||
fn var_entry_init_emits_undefined_store() {
|
||||
let ir = emit_ir("function f() { var x; return typeof x; }");
|
||||
let f = &ir.nested[0];
|
||||
assert_eq!(count_op(&f.insts, OpCode::STORE_VAR), 1, "未捕获 var 应在函数入口恰写一次 undefined");
|
||||
}
|
||||
|
||||
/// 面 10:入口补写的过滤面——形参、`arguments`、捕获名不触发补写,
|
||||
/// 多个未捕获 var 名各自补一条。
|
||||
#[test]
|
||||
fn var_entry_init_filters_params_arguments_captured() {
|
||||
let param = emit_ir("function f(a) { var x; return typeof a + typeof x; }");
|
||||
assert_eq!(count_op(¶m.nested[0].insts, OpCode::STORE_VAR), 1, "形参名不补写,仅未捕获 var 补写");
|
||||
|
||||
let arguments = emit_ir("function f() { var x; return arguments.length + typeof x; }");
|
||||
assert_eq!(
|
||||
count_op(&arguments.nested[0].insts, OpCode::STORE_VAR),
|
||||
1,
|
||||
"arguments 名不补写,未捕获 var 补写"
|
||||
);
|
||||
|
||||
let captured = emit_ir("function f() { var x; return function g() { return typeof x; }; }");
|
||||
assert_eq!(
|
||||
count_op(&captured.nested[0].insts, OpCode::STORE_VAR),
|
||||
0,
|
||||
"捕获名由入口 MAKE_CELL(undefined) 实例化,不再补 STORE_VAR"
|
||||
);
|
||||
|
||||
let multi = emit_ir("function f() { var a, b; return typeof a + typeof b; }");
|
||||
assert_eq!(count_op(&multi.nested[0].insts, OpCode::STORE_VAR), 2, "多 var 名各自补一条入口定义");
|
||||
}
|
||||
|
||||
/// 面 11:source_encoded 口径下正则源池键为 marker 形态(与静态口径的明文键不同形)。
|
||||
#[test]
|
||||
fn source_encoded_regex_pool_key() {
|
||||
let src = "var r = /\\ud800/g;";
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
//! 函数入口 `var` 绑定实例化:未捕获 `var` 槽在首次写入前读取须见 undefined,
|
||||
//! 不得取到调用方遗留的寄存器值。
|
||||
//!
|
||||
//! 覆盖:赋值前 typeof 读主形、纯声明无后续写、死分支/for 头 var、直接读、
|
||||
//! 多 var、赋值后读与带初始化器守卫、嵌套函数自身 var、捕获名守卫、形参与
|
||||
//! `arguments` 守卫、`x=5; var x` 声明 no-op 形、多形参移位染色形。
|
||||
|
||||
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 truthy(source: &str) {
|
||||
let result = eval(source).unwrap_or_else(|e| panic!("{source} -> {e}"));
|
||||
assert!(result.as_bool(), "{source} -> 期望 true,得 {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_read_before_assignment_is_undefined() {
|
||||
// 主形:赋值前 `typeof x` 不得取到调用方残留(残留曾使结果为 'string')。
|
||||
truthy("function f(){ var x; var r=typeof x; x=1; return r } f() === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_typeof_before_any_write_is_undefined() {
|
||||
// 纯声明形:函数内 var 只声明不写,读者见 undefined。
|
||||
truthy("function f(){ var x; return typeof x } f() === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_direct_read_before_assignment_is_undefined() {
|
||||
// 直接读(非 typeof)同样见 undefined。
|
||||
truthy("function f(){ var x; var r=x; x=1; return r } f() === undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_in_dead_branch_read_is_undefined() {
|
||||
// 死分支内声明:var 仍提升到函数入口实例化。
|
||||
truthy("function f(){ if(false) var x; return typeof x } f() === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_in_for_head_zero_iterations_read_is_undefined() {
|
||||
// C-for 头 var 零迭代:入口实例化兜底,无运行期写入也见 undefined。
|
||||
truthy("function f(){ for (var x; false;) {} return typeof x } f() === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_var_all_undefined() {
|
||||
// 多 var 名每个槽都实例化(曾一名残留、一名 undefined)。
|
||||
truthy("function f(){ var a,b; return typeof a + typeof b } f() === 'undefinedundefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_assignment_before_read_keeps_value() {
|
||||
// 守卫:赋值后读仍是赋值结果,入口实例化不覆写运行期写入。
|
||||
truthy("function f(){ var x; x=1; return typeof x } f() === 'number'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_initializer_before_read_keeps_value() {
|
||||
// 守卫:带初始化器的声明在入口实例化之后执行,保留初始化值。
|
||||
truthy("function f(){ var x=1; return typeof x } f() === 'number'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_function_own_var_is_undefined() {
|
||||
// 嵌套函数自身的 var 同样在入口实例化。
|
||||
truthy("function f(){ function g(){ var y; return typeof y } return g() } f() === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn captured_var_stays_undefined() {
|
||||
// 守卫:被闭包捕获的 var 由 cell 入口实例化,读取仍为 undefined。
|
||||
truthy("function f(){ var x; var g=function(){ return typeof x }; return g() } f() === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param_value_not_overwritten_by_entry_init() {
|
||||
// 守卫:形参名过滤,入口实例化不冲毁实参写入。
|
||||
truthy("function f(a){ var x; return a + typeof x } f(5) === '5undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arguments_not_overwritten_by_entry_init() {
|
||||
// 守卫:arguments 名过滤,入口实例化不冲毁 arguments 对象。
|
||||
truthy("function f(){ var x; return arguments.length + typeof x } f(1,2) === '2undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declaration_after_assignment_is_noop() {
|
||||
// 规范:`var x;` 在既有运行期写入后是空操作,保留值 5。
|
||||
truthy("function f(){ x=5; var x; return x } f() === 5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_params_do_not_change_result() {
|
||||
// 多形参移位染色:结果与寄存器布局无关,恒为 undefined。
|
||||
truthy("function f(a,b,c,d,e,g,h,i){ var x; var r=typeof x; x=1; return r } f() === 'undefined'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn captured_and_plain_var_both_undefined() {
|
||||
// 捕获名与非捕获名并存:两条实例化路径互不干扰。
|
||||
truthy(
|
||||
"function f(){ var x,y; var g=function(){ return x }; g(); return typeof x + typeof y } f() === 'undefinedundefined'",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user