mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 19:21:11 +08:00
fix: upvalue cell 改独立堆分配消除 GC sweep 后悬垂
upvalue cell 原先分配于 session_epoch bump arena,对象 sweep 替换 arena 后旧 Bump drop,全部闭包 cell 悬垂:后续分配复用其内存导致 upvalue 值 被覆盖(私有字段 brand 检查跨语句误报、release 下可致 SIGSEGV)。 cell 改为 Box::into_raw 独立堆分配并登记进 session_cell_ptrs 追踪表, 与 arena 生命周期解耦:sweep 搬移对象时 cell 指针稳定,只重写 cell.value 中的对象引用;full_reset 统一释放,无 double-free。
This commit is contained in:
@@ -741,8 +741,11 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use super::*;
|
||||
use crate::vm::{CallFrame, FrameContinuation};
|
||||
@@ -1358,6 +1361,89 @@ mod tests {
|
||||
Vm::with_kernel_core(core)
|
||||
}
|
||||
|
||||
// ── upvalue cell 独立堆分配:跨对象 sweep 存活 ─────────────────────────
|
||||
|
||||
fn compile(source: &str) -> oxide_bytecode::module::CompiledModule {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||
Compiler::new().compile(&program).expect("compile")
|
||||
}
|
||||
|
||||
fn global_prop_opt(vm: &Vm, name: &str) -> Option<JsValue> {
|
||||
let global = vm.session.global_object();
|
||||
let si = vm.kernel_core().perm_interner().intern(name).0;
|
||||
vm.resolve_property(global, si)
|
||||
}
|
||||
|
||||
/// 闭包捕获变量跨对象 sweep 存活:reset 触发完整收集(session_epoch 替换),
|
||||
/// 修复前 cell 分配于旧 arena、随 Bump drop 悬垂;修复后独立堆分配、
|
||||
/// 地址稳定,sweep 只重写 cell.value 中的对象引用,值跨搬移保留。
|
||||
#[test]
|
||||
fn closure_cell_survives_object_sweep() {
|
||||
let mut vm = vm_with_threshold(1);
|
||||
vm.run(&compile("var counter = 0; function inc() { return ++counter; } globalThis.inc = inc; 0"))
|
||||
.expect("run1");
|
||||
assert!(!vm.gc_state.session_cell_ptrs.borrow().is_empty(), "run1 应分配 upvalue cell");
|
||||
|
||||
// reset 触发对象 sweep:存活对象搬到新 arena,旧 arena 释放。
|
||||
vm.reset();
|
||||
assert!(vm.session_gc_stats().total_collections > 0, "reset 应触发对象收集");
|
||||
|
||||
// 跨搬移后从 global 重新取 inc 函数对象:upvalue cell 指针稳定、值保留。
|
||||
let inc_val = global_prop_opt(&vm, "inc").expect("inc 应挂在 global 上");
|
||||
let obj = unsafe { &*inc_val.as_js_object_ptr() };
|
||||
let cells = obj.upvalues_slice();
|
||||
assert_eq!(cells.len(), 1, "inc 应捕获 counter 一个 cell");
|
||||
let cell = unsafe { &*cells[0] };
|
||||
assert_eq!(cell.value, JsValue::int(0), "sweep 后 cell 值应保留为 counter 初值");
|
||||
assert!(cell.is_initialized(), "sweep 后 cell 初始化位应保留");
|
||||
}
|
||||
|
||||
/// 私有字段类的 brand cell 跨对象 sweep 存活:`@@class_brand` upvalue cell
|
||||
/// 存类 brand 对象,sweep 后其值经 forwarding 重写为搬移后的新对象地址,
|
||||
/// cell 指针仍稳定(修复前 cell 悬垂、值被复用覆盖)。
|
||||
#[test]
|
||||
fn private_brand_cell_survives_object_sweep() {
|
||||
let mut vm = vm_with_threshold(1);
|
||||
vm.run(&compile(
|
||||
"class C { #x = 0; set(v){ this.#x = v; } get(){ return this.#x; } } \
|
||||
globalThis.c = new C(); globalThis.c.set(4); 0",
|
||||
))
|
||||
.expect("run1");
|
||||
|
||||
vm.reset();
|
||||
assert!(vm.session_gc_stats().total_collections > 0, "reset 应触发对象收集");
|
||||
|
||||
// 实例 c 的方法 get(类原型上)捕获 @@class_brand cell:值须仍为对象。
|
||||
let c_val = global_prop_opt(&vm, "c").expect("c 应挂在 global 上");
|
||||
let c_obj = unsafe { &*c_val.as_js_object_ptr() };
|
||||
let proto_ptr = c_obj.proto().as_js_object_ptr();
|
||||
let get_val = vm
|
||||
.resolve_property(unsafe { &*proto_ptr }, vm.kernel_core().perm_interner().intern("get").0)
|
||||
.expect("类原型应有 get 方法");
|
||||
let get_obj = unsafe { &*get_val.as_js_object_ptr() };
|
||||
let cells = get_obj.upvalues_slice();
|
||||
assert!(!cells.is_empty(), "get 应捕获 @@class_brand cell");
|
||||
let brand = unsafe { &*cells[0] }.value;
|
||||
assert!(brand.is_object(), "sweep 后 brand cell 值应仍为 brand 对象");
|
||||
}
|
||||
|
||||
/// full_reset 统一释放全部 cell box 且可重新分配:追踪表清空(无 double-free),
|
||||
/// 重置后新闭包正常建立新 cell。
|
||||
#[test]
|
||||
fn cells_freed_by_full_reset_and_reallocatable() {
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&compile("var x = 1; function f() { return x; } globalThis.f = f; f()"))
|
||||
.expect("run1");
|
||||
assert!(!vm.gc_state.session_cell_ptrs.borrow().is_empty());
|
||||
|
||||
vm.full_reset();
|
||||
assert!(vm.gc_state.session_cell_ptrs.borrow().is_empty(), "full_reset 应释放全部 cell");
|
||||
|
||||
let result = vm.run(&compile("var y = 2; function g() { return y; } g()")).expect("run2");
|
||||
assert_eq!(format!("{result}"), "2", "重置后新 cell 应正常分配与读取");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allocation_does_not_trigger_gc_before_safe_point() {
|
||||
let mut vm = vm_with_threshold(1);
|
||||
|
||||
Reference in New Issue
Block a user