diff --git a/crates/oxide_builtins/src/error.rs b/crates/oxide_builtins/src/error.rs index 2de7b2f..d30a6cb 100644 --- a/crates/oxide_builtins/src/error.rs +++ b/crates/oxide_builtins/src/error.rs @@ -40,9 +40,7 @@ pub fn create_kind_error(host: &mut H, kind: &str, msg: &str) -> JsVa "EvalError" => P::as_ptr(&host.session().builtin_world().eval_error_proto) as *mut JsObject, _ => P::as_ptr(&host.session().builtin_world().error_proto) as *mut JsObject, }; - let obj = host - .epoch() - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); + let obj = host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); let sf = Arc::clone(host.kernel_core().perm_interner()); let sh = Arc::clone(host.kernel_core().shape_forge()); if !msg.is_empty() { @@ -129,9 +127,7 @@ macro_rules! error_ctor { /// 忽略调用方传入的 this——与规范构造器语义一致。 pub fn $name(host: &mut H, args: &[u8]) -> NativeResult { let proto_ptr = P::as_ptr(&host.session().builtin_world().$proto_field) as *mut JsObject; - let this = host - .epoch() - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); + let this = host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); set_own_message(host, this, args); NativeResult::Ok(JsValue::from_js_object(this)) } diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 544d9b3..b2e866c 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -717,9 +717,7 @@ pub fn object_get_own_property_descriptor(vm: &mut H, args: &[u8]) -> let sf_ptr = vm.kernel_core().perm_interner().as_ref() as *const PermInterner; let sh_ptr = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge; let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; - let desc = vm - .epoch() - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto))); + let desc = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto))); let sf = unsafe { &*sf_ptr }; let sh = unsafe { &*sh_ptr }; diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 7fc5398..9879276 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -245,8 +245,7 @@ pub fn regexp_exec(vm: &mut H, args: &[u8]) -> NativeResult { let n = 1 + group_count; let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let arr = - vm.epoch() - .alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump())); + vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump())); unsafe { (*arr).set_prop_at(0, vm.new_string(&haystack[range.start..range.end])); // 捕获组:未参与匹配的组为 undefined。 diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index 44532d0..f93fa77 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -192,8 +192,7 @@ pub(crate) fn make_string_array(vm: &mut H, parts: Vec) -> Js let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let n = parts.len(); let arr = - vm.epoch() - .alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump())); + vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump())); unsafe { for (i, s) in parts.into_iter().enumerate() { let sv = vm.new_string_owned(s); @@ -210,8 +209,7 @@ pub(crate) fn make_string_array_values(vm: &mut H, parts: Vec 0, "exec 结果数组应登记 epoch 追踪表"); + + let freed_before = vm.session_gc_stats().total_bytes_freed; + vm.reset(); + assert!( + vm.session_gc_stats().total_bytes_freed > freed_before, + "reset 应释放 exec 结果数组的元素区与命名属性堆数据" + ); + assert_eq!(vm.epoch_object_count(), 0, "reset 后追踪表应清空"); +} + +/// split/match 结果数组必须登记 epoch 追踪表:元素区 Box 随 reset 统一释放; +/// 未登记则每次 split/match 泄漏一个 Box。 +#[test] +fn string_split_match_result_array_tracked_and_freed_on_reset() { + let mut vm = Vm::new(); + let result = run_source( + &mut vm, + "for (var i = 0; i < 20; i++) { 'a,b,c'.split(','); 'ab'.match(/a/); } \ + 'a,b,c'.split(',').length", + ); + assert_eq!(format!("{}", result), "3", "split 结果数组长度应正常"); + assert!(vm.epoch_object_count() > 0, "split/match 结果数组应登记 epoch 追踪表"); + + let freed_before = vm.session_gc_stats().total_bytes_freed; + vm.reset(); + assert!( + vm.session_gc_stats().total_bytes_freed > freed_before, + "reset 应释放 split/match 结果数组的元素区堆数据" + ); + assert_eq!(vm.epoch_object_count(), 0, "reset 后追踪表应清空"); +} + +/// 错误对象必须登记 epoch 追踪表:message 非空时 push_prop 分配 hash_props Box, +/// 随 reset 统一释放;未登记则每次抛错(全引擎最频繁路径)泄漏一个 Box。 +/// 同时覆盖构造器路径(`new TypeError`)与内部抛错路径(builtin 内 create_type_error)。 +#[test] +fn error_object_tracked_and_freed_on_reset() { + let mut vm = Vm::new(); + let result = run_source( + &mut vm, + "for (var i = 0; i < 20; i++) { \ + try { throw new TypeError('boom'); } catch (e) {} \ + try { Error.prototype.toString.call(1); } catch (e) {} \ + } \ + try { throw new TypeError('boom'); } catch (e) { e.message }", + ); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "boom", "错误对象 message 应正常"); + assert!(vm.epoch_object_count() > 0, "错误对象应登记 epoch 追踪表"); + + let freed_before = vm.session_gc_stats().total_bytes_freed; + vm.reset(); + assert!( + vm.session_gc_stats().total_bytes_freed > freed_before, + "reset 应释放错误对象的 message 属性堆数据" + ); + assert_eq!(vm.epoch_object_count(), 0, "reset 后追踪表应清空"); +} + +/// gOPD/Reflect 描述符对象必须登记 epoch 追踪表:4 个描述符属性 push 分配 +/// hash_props Box,随 reset 统一释放;未登记则每次 getOwnPropertyDescriptor 泄漏。 +#[test] +fn gopd_descriptor_tracked_and_freed_on_reset() { + let mut vm = Vm::new(); + let result = run_source( + &mut vm, + "for (var i = 0; i < 20; i++) { \ + Object.getOwnPropertyDescriptor({x:1,y:2,z:3,w:4}, 'x'); \ + } \ + Object.getOwnPropertyDescriptor({x:5}, 'x').value", + ); + assert_eq!(format!("{}", result), "5", "gOPD 描述符值应正常"); + assert!(vm.epoch_object_count() > 0, "描述符对象应登记 epoch 追踪表"); + + let freed_before = vm.session_gc_stats().total_bytes_freed; + vm.reset(); + assert!( + vm.session_gc_stats().total_bytes_freed > freed_before, + "reset 应释放描述符对象的 hash_props 堆数据" + ); + assert_eq!(vm.epoch_object_count(), 0, "reset 后追踪表应清空"); }