From 97779b901aa75994b4a4b756495b2af5bd873793 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 16 Oct 2026 12:22:01 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Function.prototype=20=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=20undefined=20=E4=B8=94=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=87=BD=E6=95=B0=20prototype=20=E4=B8=8D=E5=8F=AF=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 ++++++------- Cargo.toml | 2 +- crates/oxide_vm/src/dispatch/call.rs | 25 +++++--- crates/oxide_vm/src/vm/call.rs | 6 ++ crates/oxide_vm/src/vm/dynamic.rs | 4 +- crates/oxide_vm/src/vm_dispatch_ctrl.rs | 7 +++ crates/oxide_vm/src/vm_support.rs | 24 +++++--- .../oxide_vm/tests/builtins_function_tests.rs | 60 +++++++++++++++++++ 8 files changed, 133 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2931862..d6d3853e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.19.28" +version = "0.19.29" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.19.28" +version = "0.19.29" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.19.28" +version = "0.19.29" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.19.28" +version = "0.19.29" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.19.28" +version = "0.19.29" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.19.28" +version = "0.19.29" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.19.28" +version = "0.19.29" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.19.28" +version = "0.19.29" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.19.28" +version = "0.19.29" [[package]] name = "oxide_parser" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.19.28" +version = "0.19.29" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.19.28" +version = "0.19.29" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.19.28" +version = "0.19.29" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.19.28" +version = "0.19.29" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 1c222c2d..6e396178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.19.28" +version = "0.19.29" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index dfeb87fc..eb96bc18 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -100,12 +100,18 @@ impl Vm { NativeResult::TailCall { callee, this, args } => { if callee.is_object() { let obj = unsafe { &*callee.as_js_object_ptr() }; - // 不可调用目标(apply/call/bound 转发的 thisArg 不可调用等)抛 + // 非函数目标(apply/call/bound 转发的 thisArg 非函数等)抛 // 可捕获 TypeError;落入 push_bytecode_frame 会产生引擎级错误, // 绕过 JS try/catch。 - if !obj.is_function() || (obj.native_fn().is_none() && obj.sub_module_index() == 0) { + if !obj.is_function() { return self.raise_type_error("CALL target is not callable"); } + // 无本体函数对象(Function.prototype):[[Call]] 接受任意实参 + // 返回 undefined,不执行函数体。 + if obj.native_fn().is_none() && obj.sub_module_index() == 0 { + self.regs[0] = JsValue::undefined(); + return Ok(()); + } if obj.native_fn().is_some() { // 尾调用转发为普通调用形态。 self.constructing_native = false; @@ -967,6 +973,12 @@ impl Vm { .raise_type_error("class constructor cannot be invoked without 'new'") .map(|_| true); } + // 无本体函数对象(Function.prototype):[[Call]] 接受任意实参 + // 返回 undefined,不执行函数体。 + if obj.native_fn().is_none() && obj.sub_module_index() == 0 { + self.regs[0] = JsValue::undefined(); + return Ok(false); + } if obj.native_fn().is_some() { // spread 普通调用形态:清零构造形态标记。 self.constructing_native = false; @@ -1174,11 +1186,10 @@ impl Vm { )?; Ok(true) } else { - let error = - oxide_builtins::error::create_error(self, "NEW_EXPRESSION: bytecode constructors not yet supported"); - self.exception_value = Some(error); - self.pending_error_kind = Some(self.thrown_error_kind(error)); - self.unwind().map(|_| true) + // 无函数体的函数对象(如 Function.prototype):IsConstructor 为假, + // 按规范抛 TypeError(与非 spread 的 NEW_EXPRESSION 臂同形)。 + self.raise_type_error("NEW_EXPRESSION: constructor is not a constructor") + .map(|_| true) } } diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index 79e301b6..4ccc63c0 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -191,6 +191,12 @@ impl Vm { }; } + // 无本体函数对象(Function.prototype):[[Call]] 接受任意实参返回 + // undefined,不执行函数体。此处 callee 已确认为非 native 的函数对象, + // sub_module_index == 0 即无本体(accessor 非函数,已在上方拦截)。 + if callee_obj.sub_module_index() == 0 { + return Ok(JsValue::undefined()); + } // 字节码函数在自身(同一 epoch)内联执行,防止 use-after-free。 // 独立子 VM 拥有不同 epoch:返回含子 VM epoch 指针的 JsValue 后再销毁子 VM, // 会在 release 构建中产生悬垂指针 / 访问违规。 diff --git a/crates/oxide_vm/src/vm/dynamic.rs b/crates/oxide_vm/src/vm/dynamic.rs index 065e4c96..dd4c73cb 100644 --- a/crates/oxide_vm/src/vm/dynamic.rs +++ b/crates/oxide_vm/src/vm/dynamic.rs @@ -98,7 +98,9 @@ impl Vm { table.immutables.resize(table.modules.len(), OnceLock::new()); table.si_tables.resize(table.modules.len(), OnceLock::new()); - let func_val = self.create_function_object(base, self.current_gen, FunctionFlags::none()); + // 动态函数对象置 DYNAMIC 旗:`prototype` 属性按规范不可配置 + // (区别于普通函数的可配置面)。 + let func_val = self.create_function_object(base, self.current_gen, FunctionFlags(FunctionFlags::DYNAMIC)); 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; diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index cf22a344..5bcabb28 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -41,6 +41,13 @@ impl Vm { let call_window = (ext >> 8) as u8; crate::vm_debug!("CALL rd={} this={} args={} depth={}", rd, this_reg, arg_count, self.frames.len()); + // 无本体函数对象(Function.prototype):[[Call]] 接受任意实参 + // 返回 undefined,不执行函数体。 + if obj.native_fn().is_none() && obj.sub_module_index() == 0 { + self.regs[0] = JsValue::undefined(); + return Ok(false); + } + if obj.native_fn().is_some() { self.dispatch_native_call(obj, callee, this_reg, first_arg_reg, arg_count)?; return Ok(true); diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index bd479cec..cafac2ee 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -26,9 +26,10 @@ fn gc_pressure_mode_from_env() -> bool { std::env::var_os("OXIDE_GC_PRESSURE").is_some() } -/// 函数种类标志位集合:箭头、类构造器、派生构造器,压缩在单个 `u8` 中。 +/// 函数种类标志位集合:箭头、类构造器、派生构造器、动态函数,压缩在单个 `u8` 中。 /// -/// 位定义:bit0 = arrow,bit1 = class_constructor,bit2 = derived_constructor。 +/// 位定义:bit0 = arrow,bit1 = class_constructor,bit2 = derived_constructor, +/// bit3 = dynamic。 #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct FunctionFlags(pub u8); @@ -39,6 +40,9 @@ impl FunctionFlags { pub(crate) const CLASS_CONSTRUCTOR: u8 = 0b010; /// 派生构造器标志位(bit2)。 pub(crate) const DERIVED_CONSTRUCTOR: u8 = 0b100; + /// 动态函数标志位(bit3):经 Function 构造器创建的函数对象,其 + /// `prototype` 属性不可配置。 + pub(crate) const DYNAMIC: u8 = 0b1000; /// 无标志(普通函数)。 pub(crate) const fn none() -> Self { @@ -74,6 +78,11 @@ impl FunctionFlags { pub(crate) const fn is_derived_constructor(self) -> bool { self.0 & Self::DERIVED_CONSTRUCTOR != 0 } + + /// 是否动态函数(经 Function 构造器创建)。 + pub(crate) const fn is_dynamic(self) -> bool { + self.0 & Self::DYNAMIC != 0 + } } /// 模块级装载缓存条目三件套:已转换常量池、BigInt 自持存储、si 侧表。 @@ -891,16 +900,17 @@ impl Vm { prototype.bump_generation(); } - // 函数自身 `prototype` 属性:生成器 writable:true / enumerable:false / - // configurable:false;普通函数与构造器 writable:true / enumerable:false / - // configurable:true。显式落元数据——缺元数据按默认描述符(可枚举) - // 判定,`prototype` 会泄漏进 keys / for-in / 聚合枚举。 + // 函数自身 `prototype` 属性:生成器与动态函数(Function 构造器产物) + // writable:true / enumerable:false / configurable:false;普通函数与 + // 构造器 writable:true / enumerable:false / configurable:true。显式 + // 落元数据——缺元数据按默认描述符(可枚举)判定,`prototype` 会泄漏 + // 进 keys / for-in / 聚合枚举。 let prototype_si = self.kernel_core.perm_interner().intern("prototype").0; let func = unsafe { &mut *obj_ptr }; let prototype_shape = self.kernel_core.shape_forge().make_shape(func.shape_id(), prototype_si); func.set_shape_id(prototype_shape); let prototype_pos = func.push_prop(prototype_val); - if is_generator || is_async_generator { + if is_generator || is_async_generator || flags.is_dynamic() { func.set_data_meta(prototype_pos, PropAttributes::new(true, false, false)); } else { func.set_data_meta(prototype_pos, PropAttributes::new(true, false, true)); diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index 0c076a88..da06b9bf 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -448,6 +448,66 @@ fn function_bind_async_generator_target_returns_iterator() { assert_eq!(vm.lookup_str(result).unwrap_or_default(), "object"); } +#[test] +fn function_prototype_call_returns_undefined() { + // Function.prototype 是函数对象:[[Call]] 接受任意实参返回 undefined, + // 不执行函数体。三个调用入口(CALL、CALL_SPREAD、TailCall 转发)同口径。 + let mut vm = Vm::new(); + // 直调(CALL 指令)。 + let result = eval(&mut vm, "Function.prototype()").unwrap(); + assert!(result.is_undefined()); + // 带任意实参。 + let result = eval(&mut vm, "Function.prototype(null, void 0, 7)").unwrap(); + assert!(result.is_undefined()); + // 展开调用(CALL_SPREAD 指令)。 + let result = eval(&mut vm, "Function.prototype(...[1, 2, 3])").unwrap(); + assert!(result.is_undefined()); + // call/apply 尾调用转发(TailCall 分支)。 + let result = eval(&mut vm, "Function.prototype.call.call(Function.prototype)").unwrap(); + assert!(result.is_undefined()); + let result = eval(&mut vm, "Function.prototype.apply.call(Function.prototype, [7])").unwrap(); + assert!(result.is_undefined()); +} + +#[test] +fn function_prototype_new_throws_catchable_type_error() { + // Function.prototype 无 [[Construct]]:new 表达式抛可捕获 TypeError, + // 不得绕过 try/catch 成为引擎级错误。 + let mut vm = Vm::new(); + let name = eval(&mut vm, "try { new Function.prototype() } catch (e) { e.name }").unwrap(); + assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError"); + // 展开构造形态(NEW_EXPRESSION_SPREAD)同口径:抛 TypeError。 + let name = eval(&mut vm, "try { new Function.prototype(...[]) } catch (e) { e.name }").unwrap(); + assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError"); +} + +#[test] +fn dynamic_function_prototype_descriptor_not_configurable() { + // 动态函数(Function 构造器产物)的 prototype 属性为 + // {writable:true, enumerable:false, configurable:false},delete 失败且值不变; + // 普通函数的 prototype 属性保持可配置,不回归。 + let mut vm = Vm::new(); + vm.set_compiler_service(Arc::new(DefaultCompilerService)); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(new Function('a', 'return a;'), 'prototype'); d.writable + ',' + d.enumerable + ',' + d.configurable", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "true,false,false"); + let result = eval( + &mut vm, + "var f = new Function('a', 'return a;'); var p = f.prototype; delete f.prototype; f.prototype === p", + ) + .unwrap(); + assert!(result.as_bool()); + let result = eval( + &mut vm, + "function g() {} var d = Object.getOwnPropertyDescriptor(g, 'prototype'); d.writable + ',' + d.enumerable + ',' + d.configurable", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "true,false,true"); +} + #[test] fn function_call_generator_param_default_throw_keeps_catch_value() { // 生成器目标带抛错的参数默认值,经 call/apply/bind 调用且调用点在 try/catch 内: