feat: Function.prototype[Symbol.hasInstance] 绑定与 bound 函数构造语义

@@hasInstance 按 OrdinaryHasInstance 实现(非可调用 this 返回 false、
prototype 非对象抛 TypeError、bound 链递归到最内层 target);
new boundFn() 解包 [[BoundTargetFunction]] 链后转发构造:绑定实参前置、
新对象原型取 target.prototype、new.target 替换为最内层 target。
This commit is contained in:
2026-08-15 22:40:45 +08:00
parent c3d181bd32
commit c052f5a619
7 changed files with 328 additions and 4 deletions
+81
View File
@@ -54,6 +54,84 @@ fn to_string_error_value<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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::<H> as *const ())));
// 绑定实参个数记入 native_arg_count,与 Function.length 语义一致。
(*wrapper).set_native_arg_count(bound_arg_count as u8);
+14
View File
@@ -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 原型为原型。
+4
View File
@@ -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 标记位。
@@ -15,6 +15,7 @@ pub fn bind_function(core: &Arc<KernelCore>, session: &KernelSession, global: &m
apply: oxide_builtins::function::function_apply::<crate::vm::Vm> as *const (),
bind: oxide_builtins::function::function_bind::<crate::vm::Vm> as *const (),
to_string: oxide_builtins::function::function_to_string::<crate::vm::Vm> as *const (),
has_instance: oxide_builtins::function::function_symbol_has_instance::<crate::vm::Vm> as *const (),
};
session.builtin_world().bind_function_methods(
&function_methods,
+15 -2
View File
@@ -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,
+127 -2
View File
@@ -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<JsValue>,
call_window: u8,
) -> Result<bool, String> {
// 逐层解包 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<JsValue> = 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];
@@ -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);
}