diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index 0c51001..b173bf2 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -54,6 +54,84 @@ fn to_string_error_value(vm: &mut H, msg: &str) -> JsValue { .unwrap_or_else(|| crate::error::create_type_error(vm, msg)) } +/// `Function.prototype[Symbol.hasInstance]`:OrdinaryHasInstance 语义,供 +/// `instanceof` 运算符经 @@hasInstance 属性调用。 +/// +/// # 步骤 +/// 1. this(C)非可调用 → false(非对象或非函数对象,不抛) +/// 2. C 是 bound 包装 → 逐层解包 [[BoundTargetFunction]],按最内层 target 判定 +/// 3. 实参(O)非对象 → false +/// 4. C.prototype 不是对象 → TypeError +/// 5. 沿 O 原型链与 C.prototype 指针比对(深度上限防环) +/// +/// # 边界与前提 +/// - 唯一抛错点:C 可调用但其 `prototype` 属性非对象(含无 prototype 的 native 方法) +/// - bound 链解包后 prototype 取最内层 target 的(bound 包装自身无 prototype) +/// - 无实参调用(O 缺失)视为 undefined → false +pub fn function_symbol_has_instance(vm: &mut H, args: &[u8]) -> NativeResult { + let c_val = if args.is_empty() { JsValue::undefined() } else { vm.reg(args[0]) }; + let o_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + + // 非对象 / 非函数 this:OrdinaryHasInstance 返回 false(不抛 TypeError)。 + if !c_val.is_object() || c_val.as_js_object_ptr().is_null() { + return NativeResult::Ok(JsValue::bool(false)); + } + let mut c = c_val; + let mut c_obj = unsafe { &*c.as_js_object_ptr() }; + if !c_obj.is_function() { + return NativeResult::Ok(JsValue::bool(false)); + } + + // bound 包装:[[BoundTargetFunction]] 递归到最内层 target 判定 + // (InstanceofOperator 语义,target 链上的 @@hasInstance 解析到本函数)。 + while c_obj.type_tag == oxide_types::object::JsObject::OBJ_TYPE_BOUND { + let props = c_obj.hash_props_vec().cloned().unwrap_or_default(); + let target = props.get(4).copied().unwrap_or(JsValue::undefined()); + if !target.is_object() + || target.as_js_object_ptr().is_null() + || !unsafe { &*target.as_js_object_ptr() }.is_function() + { + return NativeResult::Ok(JsValue::bool(false)); + } + c = target; + c_obj = unsafe { &*c.as_js_object_ptr() }; + } + + // 左操作数非对象 → false。 + if !o_val.is_object() { + return NativeResult::Ok(JsValue::bool(false)); + } + + // prototype 非对象 → TypeError(OrdinaryHasInstance 唯一抛错点)。 + let proto_si = vm.kernel_core().perm_interner().intern("prototype").0; + let Some(proto_val) = vm.resolve_property(c_obj, proto_si) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Function has non-object prototype in instanceof check", + )); + }; + if !proto_val.is_object() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Function has non-object prototype in instanceof check", + )); + } + let proto_ptr = proto_val.as_js_object_ptr(); + + // 沿 O 原型链与 C.prototype 比对;深度上限防止原型环死循环。 + let mut cur = unsafe { &*o_val.as_js_object_ptr() }.proto(); + for _ in 0..1024 { + if !cur.is_object() { + return NativeResult::Ok(JsValue::bool(false)); + } + if cur.as_js_object_ptr() == proto_ptr { + return NativeResult::Ok(JsValue::bool(true)); + } + cur = unsafe { &*cur.as_js_object_ptr() }.proto(); + } + NativeResult::Ok(JsValue::bool(false)) +} + fn bind_dispatcher(vm: &mut H, args: &[u8]) -> NativeResult { let wrapper_val = vm.reg(254); let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() }; @@ -149,6 +227,9 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); unsafe { (*wrapper).set_function(true); + // type_tag 标记 bound 包装:构造路径(dispatch_new_expression)与 + // instanceof 路径(@@hasInstance)据此识别并转发到 target。 + (*wrapper).type_tag = JsObject::OBJ_TYPE_BOUND; (*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher:: as *const ()))); // 绑定实参个数记入 native_arg_count,与 Function.length 语义一致。 (*wrapper).set_native_arg_count(bound_arg_count as u8); diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index fcf81fc..0882e88 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -174,6 +174,8 @@ pub struct FunctionMethods { pub apply: *const (), pub bind: *const (), pub to_string: *const (), + /// `@@hasInstance`(well-known symbol id 6):instanceof 运算符的默认判定。 + pub has_instance: *const (), } /// 全部内置对象(原型、构造器、全局单例 Math/JSON、well-known symbol 与 stub 对象)的持有者。 @@ -1483,6 +1485,18 @@ impl BuiltinWorld { ("bind", methods.bind, 1), ("toString", methods.to_string, 0), ); + // @@hasInstance 走 well-known symbol 键(id 6):instanceof 运算符经 + // dispatch_instanceof 读该属性调用,绑定后属性存在性测试与全局改写生效。 + let _ = Self::bind_method_key_static( + proto, + shape_forge, + string_forge, + make_well_known_symbol_key(6), + "[Symbol.hasInstance]", + unsafe { NativeFnPtr::from_raw(methods.has_instance) }, + 1, + fp, + ); } /// 在指定原型上安装一个 native 方法,wrapper 函数以本 world 的 Function 原型为原型。 diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 3522d1f..0238172 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -653,6 +653,10 @@ impl JsObject { /// Error 家族对象(Error 及 NativeError 实例):`Object.prototype.toString` 返回 /// `[object Error]`,与 [[ErrorData]] 内部槽对应。 pub const OBJ_TYPE_ERROR: u8 = 22; + /// bound 函数包装(`Function.prototype.bind` 产物):[[BoundTargetFunction]] / + /// [[BoundThis]] / [[BoundArguments]] 存于 dense 槽 4/5/6+,构造与 instanceof + /// 语义转发到 target。 + pub const OBJ_TYPE_BOUND: u8 = 23; /// `is_session_epoch` 字段中的 session 标记位。 pub const SESSION_EPOCH_BIT: u8 = 0x01; /// `is_session_epoch` 字段中的 GC 标记位。 diff --git a/crates/oxide_vm/src/bindings/bind_function.rs b/crates/oxide_vm/src/bindings/bind_function.rs index a8e6a07..04a6276 100644 --- a/crates/oxide_vm/src/bindings/bind_function.rs +++ b/crates/oxide_vm/src/bindings/bind_function.rs @@ -15,6 +15,7 @@ pub fn bind_function(core: &Arc, session: &KernelSession, global: &m apply: oxide_builtins::function::function_apply:: as *const (), bind: oxide_builtins::function::function_bind:: as *const (), to_string: oxide_builtins::function::function_to_string:: as *const (), + has_instance: oxide_builtins::function::function_symbol_has_instance:: as *const (), }; session.builtin_world().bind_function_methods( &function_methods, diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index db9f17f..ce90c5c 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -785,8 +785,11 @@ impl Vm { .raise_type_error("arrow functions cannot be used as constructors") .map(|_| true); } - // native 方法(非构造器)不可 new。 - if ctor_obj.native_fn().is_some() && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR { + // native 方法(非构造器)不可 new;bound 包装除外(其构造语义转发到 target)。 + if ctor_obj.native_fn().is_some() + && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR + && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_BOUND + { return self.raise_type_error("object is not a constructor").map(|_| true); } @@ -796,6 +799,16 @@ impl Vm { None => return Ok(true), }; + // bound 包装:解包链后转发到最内层 target([[Construct]] 语义)。 + if ctor_obj.type_tag == oxide_types::object::JsObject::OBJ_TYPE_BOUND { + let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject; + let new_obj = self.alloc_object(JsObject::new_empty( + oxide_kernel::shape_forge::EMPTY_SHAPE_ID, + JsValue::from_js_object(proto_ptr), + )); + return self.dispatch_new_bound(rd, constructor, new_obj, args, 0); + } + let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject; let new_obj = self.alloc_object(JsObject::new_empty( oxide_kernel::shape_forge::EMPTY_SHAPE_ID, diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index ac74a23..2357f1f 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -33,8 +33,11 @@ impl Vm { .raise_type_error("arrow functions cannot be used as constructors") .map(|_| true); } - // native 方法(非构造器)不可 new。 - if ctor_obj.native_fn().is_some() && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR { + // native 方法(非构造器)不可 new;bound 包装除外(其构造语义转发到 target)。 + if ctor_obj.native_fn().is_some() + && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR + && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_BOUND + { return self.raise_type_error("object is not a constructor").map(|_| true); } @@ -44,6 +47,20 @@ impl Vm { // ext 高 8 位 = 调用点存活上界(0 = 未编码/全量),压帧窗口按此截断。 let call_window = (ext >> 8) as u8; + // bound 包装:解包链后转发到最内层 target([[Construct]] 语义)。 + if ctor_obj.type_tag == oxide_types::object::JsObject::OBJ_TYPE_BOUND { + let mut args = Vec::with_capacity(arg_count); + for i in 0..arg_count.min(256) { + args.push(self.regs[first_arg_reg.wrapping_add(i as u8) as usize]); + } + let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject; + let new_obj = self.alloc_object(JsObject::new_empty( + oxide_kernel::shape_forge::EMPTY_SHAPE_ID, + JsValue::from_js_object(proto_ptr), + )); + return self.dispatch_new_bound(rd, constructor, new_obj, args, call_window); + } + let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject; let new_obj = self.alloc_object(JsObject::new_empty( oxide_kernel::shape_forge::EMPTY_SHAPE_ID, @@ -135,6 +152,114 @@ impl Vm { } } + /// bound 函数构造分支:按 bound [[Construct]] 语义解包链后转发到最内层 target。 + /// + /// # 步骤 + /// 1. 逐层解包 [[BoundTargetFunction]]:每层绑定实参(dense 槽 6+)拼到调用 + /// 实参之前(外层先解包 → 最终顺序为内层绑定实参先、外层后、调用实参尾) + /// 2. 校验最内层 target 可构造(native 须 CONSTRUCTOR 标记;字节码须非 + /// arrow/async/generator),否则抛 TypeError + /// 3. 新对象原型取 target.prototype(new 表达式路径 newTarget 恒等于构造器, + /// 规范 SameValue 替换后 newTarget = target) + /// 4. 构造调用:native target 值传递(receiver = 新对象);字节码 target 压 + /// 构造帧(this = 新对象,派生 target 为 undefined,new.target = target) + /// + /// # 边界与前提 + /// - `call_args` 为调用点实参(寄存器连续区或 spread 物化),绑定实参前置拼接 + /// - 原型属性非对象时保持 object_proto 默认(OrdinaryCreateFromConstructor 回退) + pub(crate) fn dispatch_new_bound( + &mut self, rd: usize, wrapper_val: JsValue, new_obj: *mut JsObject, mut call_args: Vec, + call_window: u8, + ) -> Result { + // 逐层解包 bound 链,绑定实参前置拼接。 + let mut target_val = wrapper_val; + loop { + let wrapper_obj = unsafe { &*target_val.as_js_object_ptr() }; + let props = wrapper_obj.hash_props_vec().cloned().unwrap_or_default(); + let target = props.get(4).copied().unwrap_or(JsValue::undefined()); + let mut combined: Vec = props.iter().skip(6).copied().collect(); + combined.extend_from_slice(&call_args); + call_args = combined; + let is_bound = target.is_object() + && !target.as_js_object_ptr().is_null() + && unsafe { &*target.as_js_object_ptr() }.type_tag == JsObject::OBJ_TYPE_BOUND; + target_val = target; + if !is_bound { + break; + } + } + + // 校验最内层 target 可构造。 + if !target_val.is_object() || target_val.as_js_object_ptr().is_null() { + return self.raise_type_error("object is not a constructor").map(|_| true); + } + let target_obj = unsafe { &*target_val.as_js_object_ptr() }; + if !target_obj.is_function() || target_obj.is_arrow() { + return self.raise_type_error("object is not a constructor").map(|_| true); + } + if target_obj.native_fn().is_some() && target_obj.type_tag != JsObject::OBJ_TYPE_CONSTRUCTOR { + return self.raise_type_error("object is not a constructor").map(|_| true); + } + let sub_idx = target_obj.sub_module_index() as usize; + if sub_idx > 0 && sub_idx < self.sub_modules.len() { + let sub = &self.sub_modules[sub_idx]; + if sub.is_generator || sub.is_async { + return self.raise_type_error("object is not a constructor").map(|_| true); + } + } + + // 新对象原型取最内层 target 的 prototype(bound 包装自身无 prototype)。 + let proto_si = self.kernel_core.perm_interner().intern("prototype").0; + if let Some(proto_val) = self.resolve_property(target_obj, proto_si) { + if proto_val.is_object() { + let new_obj_mut = unsafe { &mut *new_obj }; + let proto_obj_ptr = proto_val.as_js_object_ptr(); + let _ = new_obj_mut.set_proto(JsValue::from_js_object(proto_obj_ptr)); + } + } + let new_obj_val = JsValue::object(new_obj as *mut u8); + + if target_obj.native_fn().is_some() { + // native 构造器:receiver = 新对象,值传递调用(错误原值恢复后展开)。 + match self.call_function_sync(target_val, new_obj_val, &call_args) { + Ok(v) => { + self.regs[rd] = if v.is_object() { v } else { new_obj_val }; + Ok(false) + } + Err(_) => { + let exc = self + .last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_error(self, "constructor call failed")); + self.exception_value = Some(exc); + self.pending_error_kind = Some(self.thrown_error_kind(exc)); + self.unwind().map(|_| true) + } + } + } else if sub_idx > 0 { + // 字节码构造器:this = 新对象(派生 target 为 undefined,super() 装配), + // new.target = 最内层 target。 + let this_value = if target_obj.is_derived_constructor() { + JsValue::undefined() + } else { + new_obj_val + }; + self.push_bytecode_frame( + target_val, + this_value, + FrameArgs::Slice(&call_args), + Some(rd as u8), + Some(new_obj_val), + target_val, + FrameContinuation::None, + call_window, + )?; + Ok(true) + } else { + self.raise_type_error("object is not a constructor").map(|_| true) + } + } + pub(crate) fn dispatch_template_str(&mut self, rd: usize) -> Result<(), String> { vm_trace!("TEMPLATE_STR rd={}", rd); let header = self.bytecode[self.pc]; diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index 8e1b028..329f13b 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -161,3 +161,89 @@ fn getter_returns_object_stays_valid() { let result = eval(&mut vm, "var o = { get p() { return {z: 7}; } }; o.p.z").unwrap(); assert_eq!(result, JsValue::int(7)); } + +#[test] +fn function_symbol_has_instance_bound() { + let mut vm = Vm::new(); + // @@hasInstance 已绑定在 Function.prototype 上且可调用。 + let result = eval(&mut vm, "typeof Function.prototype[Symbol.hasInstance]").unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "function"); + // 非对象 / 非可调用 this → false(不抛)。 + let cases = [ + ("Function.prototype[Symbol.hasInstance].call(42, {})", false), + ("Function.prototype[Symbol.hasInstance].call({}, {})", false), + ("Function.prototype[Symbol.hasInstance].call()", false), + // 左操作数非对象 → false。 + ("(function(){}).constructor[Symbol.hasInstance](42)", false), + // 原型链命中 / 未命中。 + ("var f = function(){}; var o = new f(); f[Symbol.hasInstance](o)", true), + ("var f = function(){}; f[Symbol.hasInstance]({})", false), + ("var f = function(){}; var o = Object.create(new f()); f[Symbol.hasInstance](o)", true), + // bound 递归到 target。 + ("var BC = function(){}; var bc = new BC(); BC.bind()[Symbol.hasInstance](bc)", true), + ("function C(){} C.bind(null)[Symbol.hasInstance]({})", false), + ]; + for (src, expected) in cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn function_symbol_has_instance_poisoned_prototype_throws() { + let mut vm = Vm::new(); + // 可调用但 prototype 非对象 → TypeError(OrdinaryHasInstance 唯一抛错点)。 + let err = eval(&mut vm, "var f = function(){}; f.prototype = 1; try { f[Symbol.hasInstance]({}) } catch (e) { e }").unwrap(); + let name = eval(&mut vm, "var f = function(){}; f.prototype = null; try { f[Symbol.hasInstance]({}) } catch (e) { e.name }").unwrap(); + let _ = err; + assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError"); +} + +#[test] +fn bound_function_construct_semantics() { + let mut vm = Vm::new(); + let num_cases = [ + // 绑定实参 + 新对象 this。 + ("function C(v){ this.v = v; } var B = C.bind({}, 1); new B().v", 1.0), + ("function C(v){ this.v = v; } var B = C.bind({}, 1); new B(2).v", 1.0), + ("function C(a, b){ this.s = a + b; } var B = C.bind(null, 2); new B(3).s", 5.0), + // 多层 bound 链:绑定实参按 内层先、外层后 拼接。 + ("function C(v){ this.v = v; } var B = C.bind({}, 1); var D = B.bind({}, 2); new D().v", 1.0), + // native 构造器 target。 + ("var arr = Array.bind(null); new arr(3).length", 3.0), + ]; + for (src, expected) in num_cases { + let result = eval(&mut vm, src).unwrap(); + assert_num(result, expected); + } + let bool_cases = [ + // 构造实例原型链指向 target.prototype(new.target 替换为 target)。 + ("function C(){} var B = C.bind({}); new B() instanceof C", true), + // instanceof bound 递归到 target:newB 链含 C.prototype,故对 B 也为 true。 + ("function C(){} var B = C.bind({}); new B() instanceof B", true), + ("function C(){} var B = C.bind({}); B instanceof Function", true), + // native 构造器 target。 + ("var arr = Array.bind(null); new arr(3) instanceof Array", true), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn bound_function_construct_errors() { + let mut vm = Vm::new(); + // 不可构造 target(arrow / native 方法)经 bound 构造 → TypeError。 + let err = eval(&mut vm, "var a = (()=>{}).bind(null); try { new a() } catch (e) { e.name }").unwrap(); + assert_eq!(vm.lookup_str(err).unwrap_or_default(), "TypeError"); + let err = eval(&mut vm, "var m = Math.max.bind(null); try { new m() } catch (e) { e.name }").unwrap(); + assert_eq!(vm.lookup_str(err).unwrap_or_default(), "TypeError"); + // 派生类构造器经 bound 构造:super() 装配 this,实例属派生类。 + let result = eval( + &mut vm, + "class A { constructor(v){ this.v = v; } } class D extends A { constructor(){ super(9); } } var B = D.bind({}); new B().v", + ) + .unwrap(); + assert_num(result, 9.0); +}