From 12d1a85e44e3e3193508b66a48bb8519fd27c390 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 15 Aug 2026 19:13:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Vm=20=E5=A2=9E=E5=8A=A0=20Drop=20?= =?UTF-8?q?=E6=94=B6=E5=B0=BE=E9=87=8A=E6=94=BE=E5=85=A8=E9=83=A8=20sessio?= =?UTF-8?q?n=20=E5=A0=86=E6=95=B0=E6=8D=AE=E6=B6=88=E9=99=A4=E9=80=90?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test262 每测试新建 Vm 直接 drop,此前对象堆数据/串/BigInt/cell 全部 泄漏。新增 teardown 收尾函数(epoch+session 对象堆数据、串、BigInt、 cell 统一释放)供 full_reset 与 Drop 共用。连带补齐 promote 分支的 ArrayBuffer 字节缓冲深拷贝(与 sweep 对齐,消除共享所有权 double-free), 并修正依赖旧泄漏行为跨 Vm 解析字符串的 error 测试。 --- crates/oxide_vm/src/session_arena.rs | 5 +- crates/oxide_vm/src/session_gc.rs | 5 +- crates/oxide_vm/src/vm.rs | 8 +++ crates/oxide_vm/src/vm_support.rs | 51 ++++++++++--------- crates/oxide_vm/tests/builtins_error_tests.rs | 28 ++++++---- crates/oxide_vm/tests/generator_gc_tests.rs | 10 ++-- 6 files changed, 64 insertions(+), 43 deletions(-) diff --git a/crates/oxide_vm/src/session_arena.rs b/crates/oxide_vm/src/session_arena.rs index fc713d8..7075eea 100644 --- a/crates/oxide_vm/src/session_arena.rs +++ b/crates/oxide_vm/src/session_arena.rs @@ -6,7 +6,7 @@ use oxide_types::value::JsValue; use rustc_hash::FxBuildHasher; use crate::vm::Vm; -use oxide_builtins::{data_view, map, set, typed_array}; +use oxide_builtins::{array_buffer, data_view, map, set, typed_array}; impl Vm { pub(crate) fn is_session_escape_root_ptr(&self, target_ptr: *mut JsObject) -> bool { @@ -65,6 +65,9 @@ impl Vm { data_view::clone_data_view_native_with_rewrite(src_ref, dst_ref, |value| { self.promote_value_if_epoch_object(value, forwarding) }); + } else if src_ref.is_array_buffer_obj() { + // 字节缓冲深拷贝到新对象:源 Vec 随 epoch 释放,互不共享。 + array_buffer::clone_array_buffer_native(src_ref, dst_ref); } else if src_ref.is_generator_obj() { // 生成器状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。 crate::generator::clone_generator_native_with_rewrite(src_ref, dst_ref, |value| { diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 57c55be..7e985a8 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -578,10 +578,7 @@ impl SessionGc { live.clear(); Self::record_object_string_edges(&mut live, obj); for s_ptr in live.drain() { - assert!( - self.live_strings.contains(&s_ptr), - "存活对象持有未登记的 session 字符串边" - ); + assert!(self.live_strings.contains(&s_ptr), "存活对象持有未登记的 session 字符串边"); } } } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 6c2a2e6..3082608 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -451,6 +451,14 @@ pub struct Vm { pub(crate) cell_stack: Vec>, } +impl Drop for Vm { + fn drop(&mut self) { + // 直接 drop(test262 每测试新建即弃)不经 reset/full_reset 路径: + // 统一收尾释放全部 session 堆数据,防逐测试累积泄漏。 + self.teardown_session_heap_data(); + } +} + impl Vm { const SYNC_NATIVE_ARG_BASE: usize = 0; const SYNC_NATIVE_ARG_LIMIT: usize = 253; diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 12c7696..ea66f38 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -299,32 +299,34 @@ impl Vm { self.bytecode = Arc::default(); self.immutables_cache.clear(); self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0); - self.free_epoch_object_heap_data(); + self.teardown_session_heap_data(); self.epoch.reset(); self.gc_state.epoch_object_ptrs.clear(); - // 先释放 session 对象堆数据(属性向量 + 各原生盒),再重置 arena: - // 原生盒在 GC 搬移/晋升时已深拷贝为单所有权,此处恰好释放一次。 - let mut freed = 0u64; - for ptr in self.gc_state.session_object_ptrs.drain(..) { - freed += crate::session_gc::SessionGc::drop_object_heap_data(ptr, true); - } - if freed > 0 { - self.gc_state.session_gc.total_bytes_freed = - self.gc_state.session_gc.total_bytes_freed.saturating_add(freed); - self.gc_state.session_gc.last_collection_bytes_freed = freed; - } self.gc_state.session_epoch.reset(); self.gc_state.session_bytes_allocated = 0; self.gc_state.string_gc_watermark = self.kernel_core.config().session_gc_threshold; self.gc_state.session_gc = crate::session_gc::SessionGc::new(); - self.free_session_string_heap_data(); - self.free_session_bigint_heap_data(); - self.gc_state.free_cells(); self.symbols.reset(); self.root_reg_limit = 0; self.active_reg_limit = 0; } + /// 释放全部 session 堆数据:epoch 对象堆数据 + session 对象堆数据 + session 串 + + /// BigInt box + upvalue cell box。 + /// + /// 供 `full_reset` 与 `Drop` 共用——对象本体(bumpalo arena / epoch bump)由调用方 + /// 重置,本函数只释放手工管理的 Box 指针(属性向量、各原生盒、串、BigInt、cell)。 + /// 原生盒在 GC 搬移/晋升时已深拷贝为单所有权,此处恰好释放一次。 + pub(crate) fn teardown_session_heap_data(&mut self) { + self.free_epoch_object_heap_data(); + for ptr in self.gc_state.session_object_ptrs.drain(..) { + crate::session_gc::SessionGc::drop_object_heap_data(ptr, true); + } + self.free_session_string_heap_data(); + self.free_session_bigint_heap_data(); + self.gc_state.free_cells(); + } + fn free_epoch_object_heap_data(&mut self) { let mut freed = 0u64; for ptr in self.gc_state.epoch_object_ptrs.drain(..) { @@ -682,11 +684,14 @@ mod tests { match vm.resume_generator(gen, crate::generator::GeneratorResumeMode::Next(JsValue::undefined())) { Ok(crate::generator::GeneratorStep::Suspended { value }) => value, Ok(crate::generator::GeneratorStep::Completed { value }) => value, - Ok(other) => panic!("unexpected step: {:?}", match other { - crate::generator::GeneratorStep::Thrown { value } => format!("Thrown({value})"), - crate::generator::GeneratorStep::SuspendedRaw { value } => format!("SuspendedRaw({value})"), - _ => String::new(), - }), + Ok(other) => panic!( + "unexpected step: {:?}", + match other { + crate::generator::GeneratorStep::Thrown { value } => format!("Thrown({value})"), + crate::generator::GeneratorStep::SuspendedRaw { value } => format!("SuspendedRaw({value})"), + _ => String::new(), + } + ), Err(e) => panic!("resume failed: {e}"), } } @@ -694,10 +699,7 @@ mod tests { #[test] fn generator_survives_object_sweep_and_resumes() { let mut vm = vm_with_low_threshold(); - let _ = run_source( - &mut vm, - "function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0", - ); + let _ = run_source(&mut vm, "function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0"); // 直接触发完整收集(保留执行上下文):存活生成器克隆进新 arena, // 状态盒深拷贝为新 Box(reset 会清空模块表使恢复不可行,走收集入口等价验证)。 @@ -1069,4 +1071,3 @@ mod tests { assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError")); } } - diff --git a/crates/oxide_vm/tests/builtins_error_tests.rs b/crates/oxide_vm/tests/builtins_error_tests.rs index a6b110d..a09de7b 100644 --- a/crates/oxide_vm/tests/builtins_error_tests.rs +++ b/crates/oxide_vm/tests/builtins_error_tests.rs @@ -16,6 +16,14 @@ fn eval(source: &str) -> Result { vm.run(&module) } +/// 在既有 Vm 内编译执行:结果字符串须在同一 Vm 上下文解析(session 串随 Vm 释放)。 +fn eval_in(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?; + vm.run(&module) +} + #[test] fn error_constructor_creates_object() { let mut vm = make_vm(); @@ -286,24 +294,24 @@ fn error_to_string_basic() { #[test] fn error_to_string_empty_message() { - let result = eval("new Error().toString()").unwrap(); - let vm = make_vm(); + let mut vm = make_vm(); + let result = eval_in(&mut vm, "new Error().toString()").unwrap(); let s = vm.lookup_str(result); assert_eq!(s, Some("Error".to_string())); } #[test] fn error_to_string_type_error() { - let result = eval("new TypeError('bad').toString()").unwrap(); - let vm = make_vm(); + let mut vm = make_vm(); + let result = eval_in(&mut vm, "new TypeError('bad').toString()").unwrap(); let s = vm.lookup_str(result); assert_eq!(s, Some("TypeError: bad".to_string())); } #[test] fn error_to_string_name_only() { - let result = eval("Error.prototype.toString.call({name: 'E'})").unwrap(); - let vm = make_vm(); + let mut vm = make_vm(); + let result = eval_in(&mut vm, "Error.prototype.toString.call({name: 'E'})").unwrap(); let s = vm.lookup_str(result); assert_eq!(s, Some("E".to_string())); } @@ -355,16 +363,16 @@ fn error_stack_is_string() { #[test] fn error_stack_starts_with_header() { - let result = eval("new Error().stack()").unwrap(); - let vm = make_vm(); + let mut vm = make_vm(); + let result = eval_in(&mut vm, "new Error().stack()").unwrap(); let s = vm.lookup_str(result).unwrap(); assert!(s.starts_with("Error"), "stack should start with 'Error', got: {}", s); } #[test] fn error_stack_frame_format() { - let result = eval("(function foo() { return new Error('boom').stack(); })()").unwrap(); - let vm = make_vm(); + let mut vm = make_vm(); + let result = eval_in(&mut vm, "(function foo() { return new Error('boom').stack(); })()").unwrap(); let s = vm.lookup_str(result).unwrap(); assert!(s.contains(" at "), "stack should have 4-space indent, got: {}", s); } diff --git a/crates/oxide_vm/tests/generator_gc_tests.rs b/crates/oxide_vm/tests/generator_gc_tests.rs index b642bc8..6d8365a 100644 --- a/crates/oxide_vm/tests/generator_gc_tests.rs +++ b/crates/oxide_vm/tests/generator_gc_tests.rs @@ -18,8 +18,10 @@ fn compile(source: &str) -> oxide_bytecode::module::CompiledModule { #[test] fn generator_promoted_to_global_survives_full_reset_cleanly() { let mut vm = Vm::new(); - vm.run(&compile("function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0")) - .expect("run1"); + vm.run(&compile( + "function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0", + )) + .expect("run1"); assert!(vm.session_object_count() > 0, "生成器挂 global 应被 promote 进 session"); // global 含 session 对象即强制重建:旧 global 丢弃、it 槽不存在,全程无悬垂访问 @@ -31,6 +33,8 @@ fn generator_promoted_to_global_survives_full_reset_cleanly() { let text = vm.lookup_str(result).expect("typeof 应返回字符串").to_string(); assert_eq!(text, "undefined"); // 新会话可正常创建并推进生成器。 - let ok = vm.run(&compile("function* h(){ yield 9; } var it2 = h(); it2.next().value")).expect("run3"); + let ok = vm + .run(&compile("function* h(){ yield 9; } var it2 = h(); it2.next().value")) + .expect("run3"); assert_eq!(format!("{ok}"), "9"); }