diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 796f439f..3385f4a0 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1004,14 +1004,15 @@ pub fn object_get_own_property_descriptor(vm: &mut H, args: &[u8]) -> /// /// # 步骤 /// 1. ToObject 装箱接收者(null/undefined 抛 TypeError) -/// 2. 按规范三段序枚举自身键:整数索引数值升序 + 非整数串键插入序 -/// (walk_own_keys),Symbol 键插入序(walk_own_symbol_keys)随后 -/// 3. 逐键取描述符(缺失键跳过),以数据属性定义到结果对象 +/// 2. String 盒:按规范枚举虚拟键(整数下标 0..len-1 + "length"),构造描述符 +/// 3. 普通对象:按规范三段序枚举自身键(walk_own_keys + walk_own_symbol_keys) +/// 4. 逐键取描述符(缺失键跳过),以数据属性定义到结果对象 /// /// # 边界与前提 /// - 结果对象 proto = %Object.prototype%,各键属性恒 writable/enumerable/ /// configurable 全真(fresh 普通对象的 CreateDataPropertyOrThrow 形态) /// - 描述符取值与 gOPD 同核;模块命名空间未初始化导出抛 ReferenceError 原值 +/// - String 盒虚拟属性:字符下标 enumerable=true,length enumerable=false /// /// # 副作用 /// - 分配结果对象与每个描述符对象 @@ -1020,6 +1021,12 @@ pub fn object_get_own_property_descriptors(vm: &mut H, args: &[u8]) - Ok(ptr) => ptr, Err(err) => return NativeResult::Err(err), }; + + // String 盒:自身键为虚拟属性(从被包字符串值派生),不走 shape 链枚举。 + if unsafe { &*obj_ptr }.is_string_obj() { + return string_exotic_get_own_property_descriptors(vm, obj_ptr); + } + let obj = unsafe { &*obj_ptr }; // 三段自身键序:字符串/整数键(整键升序在前)后接 Symbol 键插入序。 @@ -1046,6 +1053,71 @@ pub fn object_get_own_property_descriptors(vm: &mut H, args: &[u8]) - NativeResult::Ok(result_val) } +/// String 盒的 `getOwnPropertyDescriptors`:枚举虚拟键(字符下标 + length)。 +/// +/// # 步骤 +/// 1. 取被包字符串值(boxed_value 或 hash_props[0] fallback) +/// 2. 按 UTF-16 单元枚举:整数下标 0..len-1(enumerable=true)+ "length"(enumerable=false) +/// 3. 逐键构造描述符对象并写入结果 +/// +/// # 边界与前提 +/// - 空串:仅返回 length 描述符 +/// - 非字符串被包值:退化为零键 +fn string_exotic_get_own_property_descriptors( + vm: &mut H, obj_ptr: *mut JsObject, +) -> NativeResult { + let obj = unsafe { &*obj_ptr }; + // 取被包字符串值:优先 boxed_value,回退 hash_props[0]。 + let raw = obj.boxed_value(); + let raw = if raw.is_undefined() { obj.get_prop_at(0) } else { raw }; + let units = if raw.is_string() { vm.string_units(raw).to_vec() } else { Vec::new() }; + let len = units.len(); + + // 结果对象:proto = %Object.prototype%。 + let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; + let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto))); + let result_val = JsValue::from_js_object(result); + + // 预计算 interned 键。 + let intern = vm.kernel_core().perm_interner(); + let value_si = intern.intern("value").0; + let writable_si = intern.intern("writable").0; + let enumerable_si = intern.intern("enumerable").0; + let configurable_si = intern.intern("configurable").0; + let length_si = intern.intern("length").0; + + // 整数下标键 0..len-1:enumerable=true, writable=false, configurable=false。 + for (i, unit) in units.iter().enumerate() { + let si = make_int_key(i as u32); + let char_val = vm.new_string_units(std::slice::from_ref(unit)); + let desc = alloc_desc_object(vm); + let desc_obj = unsafe { &mut *desc }; + let desc_obj_ptr = JsValue::from_js_object(desc); + // 构造描述符对象:value + writable/enumerable/configurable。 + vm.define_data_property(desc_obj, value_si, char_val, PropAttributes::DEFAULT_DATA).ok(); + vm.define_data_property(desc_obj, writable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA).ok(); + vm.define_data_property(desc_obj, enumerable_si, JsValue::bool(true), PropAttributes::DEFAULT_DATA).ok(); + vm.define_data_property(desc_obj, configurable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA).ok(); + if let Err(err) = vm.define_data_property(unsafe { &mut *result }, si, desc_obj_ptr, PropAttributes::DEFAULT_DATA) { + return NativeResult::Err(crate::error::create_type_error(vm, &err)); + } + } + + // "length" 键:enumerable=false, writable=false, configurable=false。 + let desc = alloc_desc_object(vm); + let desc_obj = unsafe { &mut *desc }; + let desc_obj_ptr = JsValue::from_js_object(desc); + vm.define_data_property(desc_obj, value_si, JsValue::int(len as i32), PropAttributes::DEFAULT_DATA).ok(); + vm.define_data_property(desc_obj, writable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA).ok(); + vm.define_data_property(desc_obj, enumerable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA).ok(); + vm.define_data_property(desc_obj, configurable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA).ok(); + if let Err(err) = vm.define_data_property(unsafe { &mut *result }, length_si, desc_obj_ptr, PropAttributes::DEFAULT_DATA) { + return NativeResult::Err(crate::error::create_type_error(vm, &err)); + } + + NativeResult::Ok(result_val) +} + /// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经 /// ordinary_get 取值(触发 accessor getter,receiver 为描述符对象本身)。 fn own_field(vm: &mut H, desc: JsValue, prop_si: u32) -> Option { diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index b018b0db..65a0b672 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -357,11 +357,10 @@ pub fn regexp_constructor(vm: &mut H, args: &[u8]) -> NativeResult { } set_prop(&mut obj, "lastIndex", JsValue::int(0), vm); - // 单元口径物化 source/flags(孤立 surrogate 单元保持往返)。7 个 flag 布尔 - // 不再作实例数据属性:读侧走原型只读访问器,以 flags 串为唯一数据源。 - set_prop(&mut obj, "source", vm.new_string_units_owned(pattern_units), vm); - set_prop(&mut obj, "flags", vm.new_string_units_owned(flags_units), vm); obj.type_tag = JsObject::OBJ_TYPE_REGEXP; + // source/flags 存实例字段:原型访问器从本字段读取,不作为自身属性枚举。 + obj.set_regexp_source(vm.new_string_units_owned(pattern_units)); + obj.set_regexp_flags(vm.new_string_units_owned(flags_units)); let obj_ptr = vm.alloc_object(obj); NativeResult::Ok(JsValue::from_js_object(obj_ptr)) @@ -605,14 +604,8 @@ pub fn regexp_to_string(vm: &mut H, args: &[u8]) -> NativeResult { Err(err) => return NativeResult::Err(err), }; let re = unsafe { &*re_ptr }; - let source = { - let val = get_prop(re, 1); - vm.lookup_str(val).unwrap_or_default() - }; - let flags = { - let val = get_prop(re, 2); - vm.lookup_str(val).unwrap_or_default() - }; + let source = vm.lookup_str(re.get_regexp_source()).unwrap_or_default(); + let flags = vm.lookup_str(re.get_regexp_flags()).unwrap_or_default(); let result = format!("/{}/{}", source, flags); NativeResult::Ok(vm.new_string_owned(result)) } @@ -732,14 +725,10 @@ const HEX_DIGITS: [u16; 16] = [ /// # 边界与前提 /// - `re` 须为持有编译正则的对象(RegExp 实例或 matchAll 载体);flags 缺失 /// 时各 flag 一律 false。 -/// - 经自身+原型链解析 flags 键:flags 是实例数据属性,原型链回退仅覆盖 -/// 用户删改自身属性后的读法。 -pub(crate) fn regexp_has_flag(vm: &H, re: &JsObject, unit: char) -> bool { - let flags_si = vm.kernel_core().perm_interner().intern("flags").0; - match vm.resolve_property(re, flags_si) { - Some(val) => vm.lookup_str(val).unwrap_or_default().contains(unit), - None => false, - } +/// - 直接读实例 regexp_flags 字段(避免通过 ordinary_get 触发原型访问器, +/// 防止在 replace 等 builtin 调用期间触发 GC 导致悬垂)。 +pub(crate) fn regexp_has_flag(vm: &mut H, re: &JsObject, unit: char) -> bool { + vm.lookup_str(re.get_regexp_flags()).unwrap_or_default().contains(unit) } /// 8 个单 flag 只读访问器的公共核:this 为 RegExp.prototype 本身返回 @@ -803,6 +792,44 @@ pub fn regexp_get_unicode_sets(vm: &mut H, args: &[u8]) -> NativeResu regexp_flag_of(vm, args, 'v') } +/// `RegExp.prototype.flags` getter:返回实例 flags 串。 +pub fn regexp_get_flags(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(args[0]); + // proto 恒等判定:规范对 prototype 本身返回 undefined。 + if this_val.is_object() { + let this_ptr = this_val.as_js_object_ptr(); + let proto_ptr = vm.session().builtin_world().regexp_proto.as_ptr() as *mut JsObject; + if !this_ptr.is_null() && this_ptr == proto_ptr { + return NativeResult::Ok(JsValue::undefined()); + } + } + let re_ptr = match get_regexp_ptr(vm, args) { + Ok(ptr) => ptr, + Err(err) => return NativeResult::Err(err), + }; + let re = unsafe { &*re_ptr }; + NativeResult::Ok(re.get_regexp_flags()) +} + +/// `RegExp.prototype.source` getter:返回实例 source 串。 +pub fn regexp_get_source(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(args[0]); + // proto 恒等判定:规范对 prototype 本身返回 undefined。 + if this_val.is_object() { + let this_ptr = this_val.as_js_object_ptr(); + let proto_ptr = vm.session().builtin_world().regexp_proto.as_ptr() as *mut JsObject; + if !this_ptr.is_null() && this_ptr == proto_ptr { + return NativeResult::Ok(JsValue::undefined()); + } + } + let re_ptr = match get_regexp_ptr(vm, args) { + Ok(ptr) => ptr, + Err(err) => return NativeResult::Err(err), + }; + let re = unsafe { &*re_ptr }; + NativeResult::Ok(re.get_regexp_source()) +} + /// 取匹配文本参数,按 ToString 语义完整转换(对象经 toString/valueOf); /// 结果按载荷形态 owned:良形文本走 Str 臂,含孤立 surrogate 等走 Units 臂。 /// 对象 ToString 抛出的原生异常原样传播。 diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 711bf6f1..d5903ae8 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -132,6 +132,10 @@ pub struct JsObject { /// shape 槽位保持恒等映射,后续索引/命名属性写读不干扰被包值。 /// 未装箱对象恒为 undefined。 boxed_value: JsValue, + /// RegExp 实例的 source 字符串(RegExp.prototype.source 访问器的数据源)。 + regexp_source: JsValue, + /// RegExp 实例的 flags 字符串(RegExp.prototype.flags 访问器的数据源)。 + regexp_flags: JsValue, pub upvalues: *mut u8, } @@ -382,6 +386,8 @@ impl JsObject { captured_this: JsValue::undefined(), home_object: JsValue::undefined(), boxed_value: JsValue::undefined(), + regexp_source: JsValue::undefined(), + regexp_flags: JsValue::undefined(), upvalues: std::ptr::null_mut(), } } @@ -409,6 +415,8 @@ impl JsObject { captured_this: JsValue::undefined(), home_object: JsValue::undefined(), boxed_value: JsValue::undefined(), + regexp_source: JsValue::undefined(), + regexp_flags: JsValue::undefined(), upvalues: std::ptr::null_mut(), }; let vec = Box::new(vec![JsValue::undefined(); n_elements.min(MAX_DENSE_PROPS)]); @@ -508,6 +516,8 @@ impl JsObject { captured_this: self.captured_this, home_object: self.home_object, boxed_value: self.boxed_value, + regexp_source: self.regexp_source, + regexp_flags: self.regexp_flags, upvalues: self.upvalues, } } @@ -653,6 +663,12 @@ impl JsObject { if self.boxed_value.is_object() { self.boxed_value = rewrite(self.boxed_value); } + if self.regexp_source.is_object() { + self.regexp_source = rewrite(self.regexp_source); + } + if self.regexp_flags.is_object() { + self.regexp_flags = rewrite(self.regexp_flags); + } if !self.upvalues.is_null() { let cells = unsafe { &mut *(self.upvalues as *mut Vec<*mut Cell>) }; for cell_ptr in cells { @@ -969,6 +985,26 @@ impl JsObject { pub fn set_boxed_value(&mut self, v: JsValue) { self.boxed_value = v; } + + /// RegExp 实例的 source 字符串(非 RegExp 实例恒为 undefined)。 + pub fn get_regexp_source(&self) -> JsValue { + self.regexp_source + } + + /// 设置 RegExp 实例的 source 字符串。 + pub fn set_regexp_source(&mut self, v: JsValue) { + self.regexp_source = v; + } + + /// RegExp 实例的 flags 字符串(非 RegExp 实例恒为 undefined)。 + pub fn get_regexp_flags(&self) -> JsValue { + self.regexp_flags + } + + /// 设置 RegExp 实例的 flags 字符串。 + pub fn set_regexp_flags(&mut self, v: JsValue) { + self.regexp_flags = v; + } } #[cfg(test)] diff --git a/crates/oxide_vm/src/bindings/bind_regexp.rs b/crates/oxide_vm/src/bindings/bind_regexp.rs index acea8507..0a0397b5 100644 --- a/crates/oxide_vm/src/bindings/bind_regexp.rs +++ b/crates/oxide_vm/src/bindings/bind_regexp.rs @@ -36,6 +36,22 @@ pub fn bind_regexp(core: &Arc, session: &KernelSession, global: &mut ], ); + // source/flags 只读访问器(set 恒 undefined,getter 读实例字段)。 + bind_accessor_getter( + core, + session, + proto, + "source", + oxide_builtins::regexp::regexp_get_source:: as *const (), + ); + bind_accessor_getter( + core, + session, + proto, + "flags", + oxide_builtins::regexp::regexp_get_flags:: as *const (), + ); + // 8 个单 flag 只读访问器(set 恒 undefined,getter 读实例 flags 串判码元)。 bind_accessor_getter( core, diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 538b3749..f8aa5ce3 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -222,6 +222,11 @@ impl SessionGc { Self::mark_string_live(live_strings, ptr); } } + // RegExp 实例 source/flags 字段持有字符串边。 + if obj.is_regexp_obj() { + Self::process_edge(obj.get_regexp_source(), vm, stack, live_strings, live_bigints); + Self::process_edge(obj.get_regexp_flags(), vm, stack, live_strings, live_bigints); + } // 遍历 upvalue cell 中的引用。 for cell_ptr in obj.upvalues_slice() { if cell_ptr.is_null() { @@ -393,6 +398,15 @@ impl SessionGc { Self::mark_string_live(live, ptr); } } + // RegExp 实例 source/flags 字段持有字符串边。 + if obj.is_regexp_obj() { + if obj.get_regexp_source().is_string() { + Self::mark_string_live(live, obj.get_regexp_source().as_string_ptr_mut()); + } + if obj.get_regexp_flags().is_string() { + Self::mark_string_live(live, obj.get_regexp_flags().as_string_ptr_mut()); + } + } } /// 从 VM roots 标记存活的 session/epoch 对象、字符串与 BigInt,供 `sweep` 判定。 diff --git a/crates/oxide_vm/tests/builtins_regexp_tests.rs b/crates/oxide_vm/tests/builtins_regexp_tests.rs index b898ae44..d661f701 100644 --- a/crates/oxide_vm/tests/builtins_regexp_tests.rs +++ b/crates/oxide_vm/tests/builtins_regexp_tests.rs @@ -534,8 +534,9 @@ fn regexp_flag_accessor_descriptor_and_identity() { #[test] fn regexp_instance_has_no_own_flag_props() { let mut vm = Vm::new(); + // source/flags 是原型访问器,非实例自身属性;仅 lastIndex 为自身数据属性。 let result = eval(&mut vm, "Object.getOwnPropertyNames(new RegExp('a', 'g')).sort().join(',')").unwrap(); - assert_eq!(to_str(&vm, result), "flags,lastIndex,source"); + assert_eq!(to_str(&vm, result), "lastIndex"); } #[test]