feat: Function 构造器(动态编译字符串生成函数)

This commit is contained in:
2026-08-08 03:07:26 +08:00
parent 0b68baa802
commit d186c4d9f2
6 changed files with 252 additions and 4 deletions
+42 -1
View File
@@ -2,7 +2,7 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr};
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, VmHost};
use oxide_runtime_api::{to_string_full, NativeResult, VmHost};
fn invoke_target<H: VmHost>(vm: &mut H, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult {
let args: Vec<JsValue> = arg_regs.iter().map(|&r| vm.reg(r)).collect();
@@ -13,6 +13,47 @@ fn invoke_target<H: VmHost>(vm: &mut H, target_val: JsValue, this_val: JsValue,
}
}
/// `Function(...)` / `new Function(...)` 构造器:动态编译一个匿名函数。
///
/// 除最后一个实参为函数体外,其余实参为形参名;无实参时函数体为空串。
/// 编译成功返回函数对象,语法错误抛 SyntaxError,实参 ToString 失败抛 TypeError。
pub fn function_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let arg_regs = &args[1..];
let (param_regs, body_reg) = if arg_regs.is_empty() {
(vec![], None)
} else {
let (head, tail) = arg_regs.split_at(arg_regs.len() - 1);
(head.to_vec(), Some(tail[0]))
};
let mut params = Vec::with_capacity(param_regs.len());
for &r in &param_regs {
match to_string_full(vm.reg(r), vm) {
Ok(s) => params.push(s),
Err(e) => return NativeResult::Err(to_string_error_value(vm, &e)),
}
}
let body = match body_reg {
Some(r) => match to_string_full(vm.reg(r), vm) {
Ok(s) => s,
Err(e) => return NativeResult::Err(to_string_error_value(vm, &e)),
},
None => String::new(),
};
match vm.create_dynamic_function(&params, &body) {
Ok(func) => NativeResult::Ok(func),
Err(msg) => NativeResult::Err(crate::error::create_syntax_error(vm, &msg)),
}
}
/// 把 ToString 强制转换错误转成可抛异常值:用户回调抛出的原始值优先原样传播
/// (经 `last_uncaught_value` 侧通道),否则按普通 TypeError 处理。
fn to_string_error_value<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
vm.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, msg))
}
fn bind_dispatcher<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let wrapper_val = vm.reg(254);
let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() };
+3
View File
@@ -117,6 +117,9 @@ pub trait VmHost {
fn step_rng(&mut self);
fn math_rng_value(&self) -> f64;
fn sub_module_function_name(&self, sub_idx: u16) -> String;
/// 动态编译一个函数体(`Function` 构造器用):把参数列表与函数体编译为可调用
/// 函数对象。编译或解析失败返回 `Err`,由调用方转为 `SyntaxError`。
fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result<JsValue, String>;
fn symbol_intern(&mut self, desc: String) -> u32;
fn symbol_description(&self, idx: u32) -> Option<&str>;
fn symbol_lookup_global(&self, key: &str) -> Option<u32>;
+2 -2
View File
@@ -13,6 +13,8 @@ oxide_runtime_api = { path = "../oxide_runtime_api" }
oxide_builtins = { path = "../oxide_builtins" }
oxide_types = { path = "../oxide_types" }
oxide_log = { path = "../oxide_log" }
oxide_compiler = { path = "../oxide_compiler" }
oxide_parser = { path = "../oxide_parser" }
bumpalo.workspace = true
fast-float.workspace = true
ryu.workspace = true
@@ -26,9 +28,7 @@ unicode-normalization.workspace = true
rustc-hash.workspace = true
[dev-dependencies]
oxide_compiler = { path = "../oxide_compiler" }
proptest.workspace = true
oxide_parser = { path = "../oxide_parser" }
oxide_ir = { path = "../oxide_ir" }
criterion = { version = "0.5", features = ["html_reports"] }
+16 -1
View File
@@ -2,10 +2,11 @@ use std::sync::Arc;
use oxide_kernel::builtin::FunctionMethods;
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::object::JsObject;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
use super::bind_global_value;
use super::configure_native_constructor;
/// 把 Function 构造器与原型方法绑定到 global。
pub fn bind_function(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
@@ -22,5 +23,19 @@ pub fn bind_function(core: &Arc<KernelCore>, session: &KernelSession, global: &m
);
let function_ctor = session.builtin_world().function_constructor.as_ptr() as *mut JsObject;
configure_native_constructor(
unsafe { &mut *function_ctor },
oxide_builtins::function::function_constructor::<crate::vm::Vm> as *const (),
1,
);
// 设置 Function.length = 1(configure_native_constructor 不写 length 属性),
// 属性为不可写不可枚举可配置。
let length_si = core.perm_interner().intern("length").0;
let length_shape = core.shape_forge().make_shape(unsafe { &*function_ctor }.shape_id(), length_si);
let ctor = unsafe { &mut *function_ctor };
ctor.set_shape_id(length_shape);
ctor.ensure_hash_props().push(JsValue::int(1));
let length_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
bind_global_value(core, global, "Function", JsValue::from_js_object(function_ctor));
}
+85
View File
@@ -1475,6 +1475,9 @@ impl oxide_runtime_api::VmHost for Vm {
.and_then(|m| m.function_name.clone())
.unwrap_or_default()
}
fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result<JsValue, String> {
self.create_dynamic_function(params, body)
}
fn symbol_intern(&mut self, desc: String) -> u32 {
self.symbols.intern(desc)
}
@@ -1492,6 +1495,88 @@ impl oxide_runtime_api::VmHost for Vm {
}
}
impl Vm {
/// 动态编译函数(`Function` 构造器路径):把参数列表与函数体 wrap 成匿名函数
/// 源码,走完整编译链后取匿名函数模块追加进 VM 平表,返回对应函数对象。
///
/// # 步骤
/// 1. wrap 源码 `function anonymous(p...) { body }`,parse + compile。
/// 2. 取 `sub_modules[0]`(匿名函数模块),把其子树 flat_id 重编号到平表末尾
/// 并重写子树内每条 `CREATE_CLOSURE` 的 imm16。
/// 3. 同步 resize `immutables_cache`,建函数对象并设置 name/length。
///
/// # 边界与前提
/// - 编译或解析失败返回 `Err`(由 builtin 层转 SyntaxError)。
/// - 追加的子树原 flat_id 自 1 连续(flatten 后 1=匿名体,2…=其嵌套函数);
/// 新 id = 平表长度 + (old - 1)。
/// - 动态函数只在本次 `run()` 内有效:下次 run 重建平表,跨 run 引用会越界。
///
/// # 副作用
/// - 修改 `self.sub_modules` 与 `self.immutables_cache`。
pub fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result<JsValue, String> {
// wrap 源码:末尾换行防止 body 以行注释结尾吞掉右花括号。
let params_str = params.join(", ");
let source = format!("function anonymous({params_str}) {{\n{body}\n}}");
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, &source)
.map_err(|errs| errs.into_iter().map(|e| e.message).collect::<Vec<_>>().join("\n"))?;
let mut module = oxide_compiler::compiler::Compiler::new().compile(&program)?;
let anonymous = module.sub_modules.remove(0);
// 形参数以编译结果为准:单个实参 "a,b,c" 拼接后解析为 3 个形参
// (ES 动态函数把非末位实参以逗号连接成参数串再解析)。
let formal_count = anonymous.n_args as i32;
// 子树重编号 + 追加:base = 当前平表长度,DFS 前序压入,push 序即新 flat_id。
let base = self.sub_modules.len() as u32;
let mut added = Vec::new();
rehome_subtree(&anonymous, base, &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()));
let func_val = self.create_function_object(base, false, false, false, false);
let func_obj = unsafe { &mut *func_val.as_js_object_ptr() };
let length_si = self.kernel_core.perm_interner().intern("length").0;
let name_si = self.kernel_core.perm_interner().intern("name").0;
// length/name 为不可写不可枚举可配置,且 length 先于 name(规范属性顺序)。
let attrs = PropAttributes::new(false, false, true);
let length_val = JsValue::int(formal_count);
let name_val = self.new_string("anonymous");
self.define_data_property(func_obj, length_si, length_val, attrs)?;
self.define_data_property(func_obj, name_si, name_val, attrs)?;
Ok(func_val)
}
}
/// 把 flatten 后的子模块子树重编号到平表偏移 `base`:DFS 前序拷贝进 `out`,
/// 新 flat_id = base + (old - 1),子树内每条 `CREATE_CLOSURE` 的 imm16 同步重写。
/// 原子树 flat_id 自 1 连续,因此拷贝顺序即新 id 顺序,`out` 下标对齐平表槽位。
fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec<CompiledModule>) {
let new_id = base + module.flat_id - 1;
let mut bytecode = module.bytecode.clone();
for instr in &mut bytecode {
if opcode::opcode(*instr) == OpCode::CREATE_CLOSURE {
let old = opcode::imm16(*instr) as u32;
let new_flat = base + (old - 1);
*instr = opcode::encode(
OpCode::CREATE_CLOSURE,
opcode::rd(*instr),
(new_flat & 0xFF) as u8,
((new_flat >> 8) & 0xFF) as u8,
);
}
}
let mut rehomed = module.clone();
rehomed.bytecode = bytecode;
rehomed.flat_id = new_id;
out.push(rehomed);
for sub in &module.sub_modules {
rehome_subtree(sub, base, out);
}
}
#[cfg(test)]
mod tests {
use super::{opcode, JsValue, TryHandler, Vm};
+104
View File
@@ -497,6 +497,110 @@ mod tests {
// 子模块常量改走 temp_immutables(避免缓存下标冲突)
}
#[test]
fn dynamic_function_basic_arity() {
let mut vm = Vm::new();
// 与等价静态函数返回值逐位一致(引擎函数调用统一产 Double 数值)。
let expected = run_source(&mut vm, "function f(a,b){return a+b} f(3,4)");
let result = run_source(&mut vm, "new Function('a','b','return a+b')(3,4)");
assert_eq!(result, expected);
}
#[test]
fn dynamic_function_called_without_new() {
let mut vm = Vm::new();
let expected = run_source(&mut vm, "function f(a,b){return a*b} f(6,7)");
let result = run_source(&mut vm, "Function('a','b','return a*b')(6,7)");
assert_eq!(result, expected);
}
#[test]
fn dynamic_function_empty_body_returns_undefined() {
let mut vm = Vm::new();
assert!(run_source(&mut vm, "Function()()").is_undefined());
}
#[test]
fn dynamic_function_syntax_error_throws_syntax_error() {
let mut vm = Vm::new();
let result = run_source(&mut vm, "try{new Function('return {{')}catch(e){e.name}");
assert!(result.is_string());
assert_eq!(vm.lookup_str(result).as_deref(), Some("SyntaxError"));
}
#[test]
fn dynamic_function_nested_closure_renumbering() {
let mut vm = Vm::new();
// 匿名函数体声明嵌套函数 g,返回值是引用 g 的闭包:验证子树 flat_id 重编号
// 与 CREATE_CLOSURE imm16 重写后嵌套调用仍指向正确的子模块。
let expected = run_source(
&mut vm,
"function outer(){var g=function(n){return n*2}; return function(){return g(21)}} outer()()",
);
let result = run_source(
&mut vm,
"new Function('var g=function(n){return n*2}; return function(){return g(21)}')()()",
);
assert_eq!(result, expected);
}
#[test]
fn dynamic_function_multiple_in_one_run() {
let mut vm = Vm::new();
// 同一 run 内连续创建多个动态函数:验证 base 偏移累计正确。
let expected = run_source(
&mut vm,
"function f1(){return 1} function f2(){return 2} function f3(a){return a*3} f1()+f2()+f3(4)",
);
let result = run_source(
&mut vm,
"new Function('return 1')() + new Function('return 2')() + new Function('a','return a*3')(4)",
);
assert_eq!(result, expected);
}
#[test]
fn dynamic_function_name_and_length() {
let mut vm = Vm::new();
let name = run_source(&mut vm, "var f=new Function('a','b','return a'); f.name");
assert!(name.is_string());
assert_eq!(vm.lookup_str(name).as_deref(), Some("anonymous"));
let len = run_source(&mut vm, "var f=new Function('a','b','return a'); f.length");
assert_eq!(len, JsValue::int(2));
}
#[test]
fn dynamic_function_comma_split_params_count() {
let mut vm = Vm::new();
// 单个实参 "a,b,c" 拼接解析为 3 个形参,length 应为解析后的形参数。
let result = run_source(&mut vm, "new Function('a,b,c','null').length");
assert_eq!(result, JsValue::int(3));
}
#[test]
fn dynamic_function_name_and_length_attributes() {
let mut vm = Vm::new();
// length/name 为不可写、不可枚举、可配置的数据属性。
let attrs = run_source(
&mut vm,
"var d=Object.getOwnPropertyDescriptor(new Function('a','return a'),'length'); String(d.value)+d.writable+d.enumerable+d.configurable",
);
assert_eq!(vm.lookup_str(attrs).as_deref(), Some("1falsefalsetrue"));
let name_attrs = run_source(
&mut vm,
"var d=Object.getOwnPropertyDescriptor(Function(),'name'); String(d.writable)+d.enumerable+d.configurable",
);
assert_eq!(vm.lookup_str(name_attrs).as_deref(), Some("falsefalsetrue"));
}
#[test]
fn dynamic_function_rethrows_to_string_exception() {
let mut vm = Vm::new();
// 形参 ToString 回调抛出的原始值须原样传播,而非包成 TypeError。
let result = run_source(&mut vm, "try{new Function({toString:function(){throw 7}})}catch(e){e}");
assert_eq!(result, JsValue::int(7));
}
#[test]
fn session_epoch_reset_is_only_in_full_reset_state_clear() {
let src = include_str!("vm_support.rs");