feat: eval 脚本模式保留完成值与全局 var(create_dynamic_script)

This commit is contained in:
2026-08-17 00:14:47 +08:00
parent f6f5628cba
commit c523bdcb41
4 changed files with 122 additions and 14 deletions
+6 -7
View File
@@ -1,10 +1,10 @@
//! eval 全局函数:非字符串原样返回;字符串按脚本/表达式动态编译执行。 //! eval 全局函数:非字符串原样返回;字符串按脚本模式动态编译执行。
//! //!
//! # 契约 //! # 契约
//! - 非字符串实参**不** ToString(`eval(new String("1+1"))` 返回对象本身)。 //! - 非字符串实参**不** ToString(`eval(new String("1+1"))` 返回对象本身)。
//! - 字符串经动态编译以 global 为 this 同步调用;编译失败 → SyntaxError; //! - 字符串经动态编译以 global 为 this 同步调用;编译失败 → SyntaxError;
//! 运行异常 → 优先重抛原始异常值(`take_uncaught_value`,`eval("throw 1")` 捕获到 `1`)。 //! 运行异常 → 优先重抛原始异常值(`take_uncaught_value`,`eval("throw 1")` 捕获到 `1`)。
//! - 档 1 函数模式:eval 内 var 不落全局;档 2 切 `create_dynamic_script` 后 var 落全局。 //! - 脚本模式:完成值保留(`eval("1+2")` → 3);var/函数声明落全局对象;let/const 词法隔离。
use oxide_runtime_api::{NativeResult, VmHost}; use oxide_runtime_api::{NativeResult, VmHost};
use oxide_types::object::JsObject; use oxide_types::object::JsObject;
@@ -12,15 +12,15 @@ use oxide_types::value::JsValue;
use crate::error; use crate::error;
/// `eval(x)`:非字符串实参原样返回;字符串按脚本编译执行(档 1 走函数模式)。 /// `eval(x)`:非字符串实参原样返回;字符串按脚本模式编译执行。
/// ///
/// # 步骤 /// # 步骤
/// 1. 实参非字符串 → 原样返回(规范不 ToString)。 /// 1. 实参非字符串 → 原样返回(规范不 ToString)。
/// 2. 字符串 → 动态编译为匿名函数(`create_dynamic_function`),以 global 为 this 同步调用。 /// 2. 字符串 → 动态编译为脚本模块(`create_dynamic_script`),以 global 为 this 同步调用。
/// 3. 编译失败 → SyntaxError;运行异常 → 优先重抛原始异常值(`take_uncaught_value`)。 /// 3. 编译失败 → SyntaxError;运行异常 → 优先重抛原始异常值(`take_uncaught_value`)。
/// ///
/// # 边界与前提 /// # 边界与前提
/// - 档 1 函数模式:eval 内 var 不落全局;档 2 切 `create_dynamic_script` 后 var 落全局。 /// - 脚本模式:var/函数声明落全局对象,完成值保留,let/const 词法隔离。
/// - 无实参等价于 `eval(undefined)` → undefined。 /// - 无实参等价于 `eval(undefined)` → undefined。
pub fn eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let arg = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() }; let arg = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
@@ -28,8 +28,7 @@ pub fn eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Ok(arg); return NativeResult::Ok(arg);
} }
let code = vm.string_ref(arg).to_string(); let code = vm.string_ref(arg).to_string();
// 档 2 起把此分支换成 `vm.create_dynamic_script(&code)`。 match vm.create_dynamic_script(&code) {
match vm.create_dynamic_function(&[], &code) {
Ok(func) => { Ok(func) => {
let global = JsValue::from_js_object(vm.session().global_object().as_ptr() as *mut JsObject); let global = JsValue::from_js_object(vm.session().global_object().as_ptr() as *mut JsObject);
match vm.call_function_sync(func, global, &[]) { match vm.call_function_sync(func, global, &[]) {
+3
View File
@@ -189,6 +189,9 @@ pub trait VmHost {
/// 动态编译一个函数体(`Function` 构造器用):把参数列表与函数体编译为可调用 /// 动态编译一个函数体(`Function` 构造器用):把参数列表与函数体编译为可调用
/// 函数对象。编译或解析失败返回 `Err`,由调用方转为 `SyntaxError`。 /// 函数对象。编译或解析失败返回 `Err`,由调用方转为 `SyntaxError`。
fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result<JsValue, String>; fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result<JsValue, String>;
/// 动态编译脚本(`eval` 字符串模式):按脚本模式编译,var/函数声明落全局对象。
/// 编译或解析失败返回 `Err`,由调用方转为 `SyntaxError`。
fn create_dynamic_script(&mut self, code: &str) -> Result<JsValue, String>;
/// `None` 表示无描述(`Symbol()`/`Symbol(undefined)`),`Some(desc)` 为字符串描述。 /// `None` 表示无描述(`Symbol()`/`Symbol(undefined)`),`Some(desc)` 为字符串描述。
fn symbol_intern(&mut self, desc: Option<String>) -> u32; fn symbol_intern(&mut self, desc: Option<String>) -> u32;
fn symbol_description(&self, idx: u32) -> Option<&str>; fn symbol_description(&self, idx: u32) -> Option<&str>;
+34
View File
@@ -2228,6 +2228,9 @@ impl oxide_runtime_api::VmHost for Vm {
fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result<JsValue, String> { fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result<JsValue, String> {
self.create_dynamic_function(params, body) self.create_dynamic_function(params, body)
} }
fn create_dynamic_script(&mut self, code: &str) -> Result<JsValue, String> {
self.create_dynamic_script(code)
}
fn symbol_intern(&mut self, desc: Option<String>) -> u32 { fn symbol_intern(&mut self, desc: Option<String>) -> u32 {
self.symbols.intern(desc) self.symbols.intern(desc)
} }
@@ -2298,6 +2301,37 @@ impl Vm {
self.define_data_property(func_obj, name_si, name_val, attrs)?; self.define_data_property(func_obj, name_si, name_val, attrs)?;
Ok(func_val) Ok(func_val)
} }
/// 动态编译脚本(eval 脚本模式):把源码按脚本模式编译,var/函数声明落全局对象。
///
/// # 步骤
/// 1. parse(脚本模式)→ compile(emit_program 置 is_global_scope=true)。
/// 2. 整棵模块树(根 flat_id=0 + 嵌套函数)追加进平表:`rehome_subtree(&module, base+1)`,
/// 使根落 base、子函数 old→base+old,CREATE_CLOSURE imm16 同步重写。
/// 3. 扩容 immutables_cache,建函数对象(sub_module_index = base)返回。
///
/// # 边界与前提
/// - 顶层 return 不报 SyntaxError(emit 无此检查,与 CLI 脚本路径一致)——已知偏差。
/// - 动态模块只在本次 run() 内有效(同 create_dynamic_function)。
/// - 返回函数对象仅供内部同步调用,不设 name/length(用户不可见)。
///
/// # 副作用
/// - 修改 `self.sub_modules` 与 `self.immutables_cache`。
pub fn create_dynamic_script(&mut self, code: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, code)
.map_err(|errs| errs.into_iter().map(|e| e.message).collect::<Vec<_>>().join("\n"))?;
let module = oxide_compiler::compiler::Compiler::new().compile(&program)?;
let base = self.sub_modules.len() as u32;
let mut added = Vec::new();
// 根模块 flat_id=0 传 base+1,重编号后落 base(避开 sub_module_index()==0 守卫)。
rehome_subtree(&module, base + 1, &mut added);
Arc::make_mut(&mut self.sub_modules).extend(added);
// 平表变长后同步扩容常量缓存,否则激活新模块常量时越界 panic。
self.immutables_cache
.extend((0..self.sub_modules.len().saturating_sub(self.immutables_cache.len())).map(|_| OnceLock::new()));
Ok(self.create_function_object(base, false, false, false, false))
}
} }
/// 把 flatten 后的子模块子树重编号到平表偏移 `base`:DFS 前序拷贝进 `out`, /// 把 flatten 后的子模块子树重编号到平表偏移 `base`:DFS 前序拷贝进 `out`,
+79 -7
View File
@@ -19,26 +19,27 @@ fn assert_num(result: JsValue, expected: f64) {
#[test] #[test]
fn eval_string_expression_completion_value() { fn eval_string_expression_completion_value() {
let mut vm = Vm::new(); let mut vm = Vm::new();
// 档 1 函数模式:body 包装为语句体匿名函数,隐式返回 undefined,完成值不保留。 // 脚本模式完成值保留:`eval('1+2')` 返回 3(档 2 起,不再被函数模式 body wrap 吞掉)。
// 档 2 脚本模式(create_dynamic_script)后 `eval('1+2')` 返回 3。
let result = eval(&mut vm, "eval('1+2')").unwrap(); let result = eval(&mut vm, "eval('1+2')").unwrap();
assert_eq!(result, JsValue::undefined()); assert_num(result, 3.0);
} }
#[test] #[test]
fn eval_string_number_completion_value() { fn eval_string_number_completion_value() {
let mut vm = Vm::new(); let mut vm = Vm::new();
// 同档 1 函数模式上限:完成值不保留。 // 脚本模式完成值保留:`eval('42')` 返回 42。
let result = eval(&mut vm, "eval('42')").unwrap(); let result = eval(&mut vm, "eval('42')").unwrap();
assert_eq!(result, JsValue::undefined()); assert_num(result, 42.0);
} }
#[test] #[test]
fn eval_string_var_declaration_inside() { fn eval_string_var_declaration_inside() {
let mut vm = Vm::new(); let mut vm = Vm::new();
// 档 1:eval 内 var 声明只存在于匿名函数作用域,完成值不保留、不泄漏外层。 // 档 2:eval 内 var 声明落全局对象,完成值保留为末表达式值。
let result = eval(&mut vm, "eval('var y = 1; y')").unwrap(); let result = eval(&mut vm, "eval('var y = 1; y')").unwrap();
assert_eq!(result, JsValue::undefined()); assert_num(result, 1.0);
let result = eval(&mut vm, "eval('var y = 1; y') && this.y === 1").unwrap();
assert_eq!(result, JsValue::bool(true));
} }
#[test] #[test]
@@ -150,3 +151,74 @@ fn eval_survives_full_reset_rebuild() {
assert_num(result, 123.0); assert_num(result, 123.0);
assert!(!vm.session().is_dirty_since_snapshot()); assert!(!vm.session().is_dirty_since_snapshot());
} }
#[test]
fn eval_script_var_lands_on_global() {
let mut vm = Vm::new();
// 间接 eval 脚本模式:var 声明落全局对象,外层 this 可见。
let result = eval(&mut vm, "(0,eval)('var q = 9'); this.q === 9").unwrap();
assert_eq!(result, JsValue::bool(true));
}
#[test]
fn eval_script_function_decl_lands_on_global() {
let mut vm = Vm::new();
// 脚本模式:顶层函数声明落全局对象。
let result = eval(&mut vm, "(0,eval)('function f(){}'); typeof f === 'function'").unwrap();
assert_eq!(result, JsValue::bool(true));
}
#[test]
fn eval_script_let_is_isolated() {
let mut vm = Vm::new();
// let/const 词法隔离:不落全局,不泄漏到外层。
let result = eval(&mut vm, "(0,eval)('let z = 1'); typeof z === 'undefined'").unwrap();
assert_eq!(result, JsValue::bool(true));
// 词法声明自身仍参与脚本完成值。
let result = eval(&mut vm, "(0,eval)('let z2 = 2; z2')").unwrap();
assert_num(result, 2.0);
}
#[test]
fn eval_script_this_is_global() {
let mut vm = Vm::new();
// 脚本模式:this 与 globalThis 恒等(inline 调用 receiver 传 global)。
let result = eval(&mut vm, "(0,eval)('this === globalThis')").unwrap();
assert_eq!(result, JsValue::bool(true));
}
#[test]
fn eval_nested_eval() {
let mut vm = Vm::new();
// 嵌套 eval:内层完成值透传为外层脚本值。
let result = eval(&mut vm, "eval(\"eval('1+1')\")").unwrap();
assert_num(result, 2.0);
}
#[test]
fn eval_script_completion_value() {
let mut vm = Vm::new();
// 间接 eval 脚本模式完成值:var 初始化 + 表达式。
let result = eval(&mut vm, "(0,eval)('var a = 1; a')").unwrap();
assert_num(result, 1.0);
let result = eval(&mut vm, "(0,eval)('1 + 2')").unwrap();
assert_num(result, 3.0);
}
#[test]
fn eval_script_empty_and_decl_only() {
let mut vm = Vm::new();
// 空脚本 / 纯声明脚本:完成值为 undefined。
let result = eval(&mut vm, "eval('') === undefined").unwrap();
assert_eq!(result, JsValue::bool(true));
let result = eval(&mut vm, "eval('var x;') === undefined").unwrap();
assert_eq!(result, JsValue::bool(true));
}
#[test]
fn eval_script_throw_still_rethrows() {
let mut vm = Vm::new();
// 脚本模式异常路径回归:eval 内 throw 原始值,外层 catch 捕获同一值。
let result = eval(&mut vm, "try { (0,eval)('throw 5') } catch(e) { e }").unwrap();
assert_num(result, 5.0);
}