diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 4388ee03..47b1e9e4 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -238,8 +238,11 @@ fn species_constructor( if !c.is_object() { return Err(crate::error::create_type_error(vm, "RegExp species constructor must be an object")); } + // Get(C, @@species) 的 receiver 是构造器 C 本身:访问器 getter 返回 + // receiver,传实例会让返回值错指到接收者本体。 let species_key = oxide_types::private_key::make_well_known_symbol_key(10); - let s = match vm.ordinary_get(unsafe { &*c.as_js_object_ptr() }, species_key, r_val) { + let c_val = JsValue::from_js_object(c.as_js_object_ptr()); + let s = match vm.ordinary_get(unsafe { &*c.as_js_object_ptr() }, species_key, c_val) { Ok(v) => v, Err(_) => return Err(crate::iterator::engine_error(vm, "cannot read @@species")), }; diff --git a/crates/oxide_vm/src/bindings/bind_array_buffer.rs b/crates/oxide_vm/src/bindings/bind_array_buffer.rs index 28e3680e..b8160884 100644 --- a/crates/oxide_vm/src/bindings/bind_array_buffer.rs +++ b/crates/oxide_vm/src/bindings/bind_array_buffer.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use crate::bind_constructor; -use crate::bindings::{apply_binding_table, configure_native_constructor}; +use crate::bindings::{apply_binding_table, bind_accessor_getter_key, configure_native_constructor}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; @@ -29,6 +29,17 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global )], ); + // ArrayBuffer[Symbol.species] 访问器:getter 返回 receiver,派生类沿静态原型链 + // 解析 @@species 得自身构造器(规范不给 class 默认 static @@species,类上无 own)。 + bind_accessor_getter_key( + core, + session, + ctor, + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_SPECIES), + "get [Symbol.species]", + oxide_builtins::array::array_species_get:: as *const (), + ); + apply_binding_table( session.builtin_world(), proto, diff --git a/crates/oxide_vm/src/bindings/bind_map.rs b/crates/oxide_vm/src/bindings/bind_map.rs index 55bf02b1..0dc79adb 100644 --- a/crates/oxide_vm/src/bindings/bind_map.rs +++ b/crates/oxide_vm/src/bindings/bind_map.rs @@ -2,7 +2,8 @@ use std::sync::Arc; use crate::bind_constructor; use crate::bindings::{ - apply_binding_table, bind_accessor_getter, bind_well_known_method_alias, configure_native_constructor, + apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, bind_well_known_method_alias, + configure_native_constructor, }; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; @@ -39,6 +40,17 @@ pub fn bind_map(core: &Arc, session: &KernelSession, global: &mut Js bind_well_known_method_alias(core, proto, "entries", 0); bind_accessor_getter(core, session, proto, "size", oxide_builtins::map::map_size:: as *const ()); + // Map[Symbol.species] 访问器:getter 返回 receiver,派生类沿静态原型链解析 + // @@species 得自身构造器(规范不给 class 默认 static @@species,类上无 own)。 + bind_accessor_getter_key( + core, + session, + ctor, + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_SPECIES), + "get [Symbol.species]", + oxide_builtins::array::array_species_get:: as *const (), + ); + // Map.groupBy 静态方法。 apply_binding_table( session.builtin_world(), diff --git a/crates/oxide_vm/src/bindings/bind_regexp.rs b/crates/oxide_vm/src/bindings/bind_regexp.rs index 0a0397b5..353bc477 100644 --- a/crates/oxide_vm/src/bindings/bind_regexp.rs +++ b/crates/oxide_vm/src/bindings/bind_regexp.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use crate::bindings::{ - apply_binding_table, bind_accessor_getter, bind_well_known_method, configure_native_constructor, + apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, bind_well_known_method, + configure_native_constructor, }; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; @@ -158,5 +159,16 @@ pub fn bind_regexp(core: &Arc, session: &KernelSession, global: &mut 1, ); + // RegExp[Symbol.species] 访问器:getter 返回 receiver,派生类沿静态原型链解析 + // @@species 得自身构造器(规范不给 class 默认 static @@species,类上无 own)。 + bind_accessor_getter_key( + core, + session, + ctor, + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_SPECIES), + "get [Symbol.species]", + oxide_builtins::array::array_species_get:: as *const (), + ); + bind_constructor!(core, global, "RegExp", ctor_ptr, oxide_builtins::regexp::regexp_constructor::, 2, hash: true); } diff --git a/crates/oxide_vm/src/bindings/bind_set.rs b/crates/oxide_vm/src/bindings/bind_set.rs index eada725d..fa841105 100644 --- a/crates/oxide_vm/src/bindings/bind_set.rs +++ b/crates/oxide_vm/src/bindings/bind_set.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use crate::bind_constructor; use crate::bindings::{ - apply_binding_table, bind_accessor_getter, bind_method_alias, bind_well_known_method_alias, - configure_native_constructor, + apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, bind_method_alias, + bind_well_known_method_alias, configure_native_constructor, }; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; @@ -53,5 +53,16 @@ pub fn bind_set(core: &Arc, session: &KernelSession, global: &mut Js bind_well_known_method_alias(core, proto, "values", 0); bind_accessor_getter(core, session, proto, "size", oxide_builtins::set::set_size:: as *const ()); + // Set[Symbol.species] 访问器:getter 返回 receiver,派生类沿静态原型链解析 + // @@species 得自身构造器(规范不给 class 默认 static @@species,类上无 own)。 + bind_accessor_getter_key( + core, + session, + ctor, + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_SPECIES), + "get [Symbol.species]", + oxide_builtins::array::array_species_get:: as *const (), + ); + bind_constructor!(core, global, "Set", ctor_ptr, oxide_builtins::set::set_constructor::, 1, hash: true); } diff --git a/crates/oxide_vm/src/bindings/bind_typed_array.rs b/crates/oxide_vm/src/bindings/bind_typed_array.rs index c0fea76c..f9b1a5fa 100644 --- a/crates/oxide_vm/src/bindings/bind_typed_array.rs +++ b/crates/oxide_vm/src/bindings/bind_typed_array.rs @@ -56,6 +56,22 @@ fn bind_typed_array_abstract_ctor(core: &Arc, session: &KernelSessio }, 1, ); + + // %TypedArray%[Symbol.species] 访问器:getter 返回 receiver,十一具体构造器 + // (及派生类)沿原型链解析 @@species 得自身(具体构造器无 own 属性)。绑定站 + // 与 TA 构造器后续消费面改造共用,先查槽位防重复绑定追加第二访问器槽。 + let species_key = + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_SPECIES); + if core.shape_forge().lookup_position(ctor.shape_id(), species_key).is_none() { + bind_accessor_getter_key( + core, + session, + ctor, + species_key, + "get [Symbol.species]", + oxide_builtins::array::array_species_get:: as *const (), + ); + } } /// 给构造器与对应原型设置 `BYTES_PER_ELEMENT`(只读、不可枚举、不可配置)。 diff --git a/crates/oxide_vm/src/promise/constructor.rs b/crates/oxide_vm/src/promise/constructor.rs index f927ad65..599473f8 100644 --- a/crates/oxide_vm/src/promise/constructor.rs +++ b/crates/oxide_vm/src/promise/constructor.rs @@ -264,6 +264,17 @@ impl Vm { // prototype 槽位在 length/name 之后(下标 2)。 ctor_mut.set_prop_at(2u32, JsValue::from_js_object(self.promise_proto.as_ptr() as *mut JsObject)); + // Promise[Symbol.species] 访问器:getter 返回 receiver,派生类沿静态原型链 + // 解析 @@species 得自身构造器。ctor 经本函数每次调用全新构造,同键槽不累积。 + crate::bindings::bind_accessor_getter_key( + &self.kernel_core, + &self.session, + ctor_mut, + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_SPECIES), + "get [Symbol.species]", + oxide_builtins::array::array_species_get:: as *const (), + ); + // 绑定 global:槽已存在则更新(full_reset 未重建 global 时旧槽指向已弃 ctor)。 let global_ptr = self.session.global_object().as_ptr() as *mut JsObject; // SAFETY: global 对象由 session 持有,存活整个 session;本函数内只改其 diff --git a/crates/oxide_vm/tests/builtins_regexp_tests.rs b/crates/oxide_vm/tests/builtins_regexp_tests.rs index 5d45f542..de068610 100644 --- a/crates/oxide_vm/tests/builtins_regexp_tests.rs +++ b/crates/oxide_vm/tests/builtins_regexp_tests.rs @@ -802,10 +802,12 @@ fn symbol_split_undefined_limit_full() { #[test] fn symbol_match_all_species_matcher_and_cached_lastindex() { let mut vm = Vm::new(); - // matcher 经物种构造;lastIndex 只从 R 读一次并写入 matcher。 + // matcher 经物种构造;lastIndex 只从 R 读一次并写入 matcher。静态 species 是 + // getter-only 访问器,简单赋值静默失效(set 为 undefined),须 defineProperty + // 重定义为数据属性才能覆写。 let result = eval( &mut vm, - "(() => { var r = /b/g; r.lastIndex = 2; var ctorCalls = 0; r.constructor[Symbol.species] = function() { ctorCalls += 1; return /b/g; }; var it = r[Symbol.matchAll]('abc'); return ctorCalls + '/' + it.__mal_re__.lastIndex; })()", + "(() => { var r = /b/g; r.lastIndex = 2; var ctorCalls = 0; Object.defineProperty(r.constructor, Symbol.species, { value: function() { ctorCalls += 1; return /b/g; }, configurable: true }); var it = r[Symbol.matchAll]('abc'); return ctorCalls + '/' + it.__mal_re__.lastIndex; })()", ) .unwrap(); assert_eq!(to_str(&vm, result), "1/2"); diff --git a/crates/oxide_vm/tests/builtins_species_tests.rs b/crates/oxide_vm/tests/builtins_species_tests.rs new file mode 100644 index 00000000..996ea7e2 --- /dev/null +++ b/crates/oxide_vm/tests/builtins_species_tests.rs @@ -0,0 +1,90 @@ +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + vm.run(&Arc::new(module)) +} + +/// 七构造器 `Symbol.species` 静态 getter 的规范描述符面: +/// `{ get, set:undefined, enumerable:false, configurable:true }`、`get.length === 0`、 +/// `get.name === "get [Symbol.species]"`,getter 返回 receiver。 +fn assert_species_accessor(ctor_name: &str) { + let mut vm = Vm::new(); + let source = format!( + "var d = Object.getOwnPropertyDescriptor({ctor_name}, Symbol.species); \ + d !== undefined && d.set === undefined && d.enumerable === false && d.configurable === true && \ + typeof d.get === 'function' && d.get.length === 0 && d.get.name === 'get [Symbol.species]' && \ + d.get.call({ctor_name}) === {ctor_name} && d.get.call('x') === 'x'" + ); + let result = eval(&mut vm, &source).unwrap(); + assert!(result.as_bool(), "{ctor_name} species accessor descriptor mismatch"); +} + +#[test] +fn array_buffer_species_accessor_descriptor() { + assert_species_accessor("ArrayBuffer"); +} + +#[test] +fn map_species_accessor_descriptor() { + assert_species_accessor("Map"); +} + +#[test] +fn set_species_accessor_descriptor() { + assert_species_accessor("Set"); +} + +#[test] +fn regexp_species_accessor_descriptor() { + assert_species_accessor("RegExp"); +} + +#[test] +fn promise_species_accessor_descriptor() { + assert_species_accessor("Promise"); +} + +#[test] +fn typed_array_abstract_species_descriptor_and_chain_read() { + // 抽象构造器 own 访问器;具体构造器无 own(gOPD 为 undefined),链上读回自身。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(TypedArray, Symbol.species); \ + d !== undefined && d.set === undefined && d.enumerable === false && d.configurable === true && \ + d.get.name === 'get [Symbol.species]' && d.get.length === 0 && \ + TypedArray[Symbol.species] === TypedArray && \ + Object.getOwnPropertyDescriptor(Uint8Array, Symbol.species) === undefined && \ + Uint8Array[Symbol.species] === Uint8Array && \ + Object.getOwnPropertyDescriptor(BigUint64Array, Symbol.species) === undefined && \ + BigUint64Array[Symbol.species] === BigUint64Array", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn species_subclasses_resolve_own_constructor() { + // 派生类无 own @@species,沿静态原型链命中基类同一 getter,receiver 为自身。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class SubMap extends Map {} \ + class SubSet extends Set {} \ + class SubBuf extends ArrayBuffer {} \ + class SubRe extends RegExp {} \ + class SubP extends Promise {} \ + SubMap[Symbol.species] === SubMap && SubSet[Symbol.species] === SubSet && \ + SubBuf[Symbol.species] === SubBuf && SubRe[Symbol.species] === SubRe && SubP[Symbol.species] === SubP && \ + Object.getOwnPropertyDescriptor(SubMap, Symbol.species) === undefined", + ) + .unwrap(); + assert!(result.as_bool()); +}