fix: 执行期字符串 GC 触发加 native 深度守卫防嵌套 dispatch 回调悬垂
This commit is contained in:
@@ -1320,10 +1320,14 @@ impl Vm {
|
||||
let mut steps: u64 = 0;
|
||||
loop {
|
||||
steps += 1;
|
||||
// 执行期字符串 GC 安全点:指令边界上无 builtin 局部活值(分配点触发
|
||||
// 会释放仅存于 Rust 局部/构造中未登记对象的串),此处触发保证回收
|
||||
// 时所有存活串都已落地为执行根。账目未超水位时仅 2 次字段比较。
|
||||
if self.gc_state.session_bytes_allocated >= self.gc_state.string_gc_watermark {
|
||||
// 执行期字符串 GC 安全点:仅在顶层 dispatch(native_call_depth == 0)
|
||||
// 的指令边界触发——嵌套 dispatch(builtin 经 call_function_sync 重入
|
||||
// 执行 JS 回调、generator/async 恢复)期间,调用方寄存器窗口副本存于
|
||||
// inline 状态(非 GC 根),此时回收会把调用方 regs 中的活串当死串释放。
|
||||
// 返回顶层后检查恢复,存活串此时已回拷为执行根。账目未超水位时仅
|
||||
// 3 次字段比较。
|
||||
if self.native_call_depth == 0 && self.gc_state.session_bytes_allocated >= self.gc_state.string_gc_watermark
|
||||
{
|
||||
self.maybe_collect_session_strings();
|
||||
}
|
||||
if let Some(max_steps) = max_steps {
|
||||
|
||||
@@ -132,6 +132,84 @@ fn replace_replacer_args_survive_runtime_gc() {
|
||||
assert!(vm.session_gc_stats().total_collections > 0, "执行期应触发字符串 GC");
|
||||
}
|
||||
|
||||
/// 嵌套 dispatch(map 回调经 call_function_sync → 内联字节码执行)内分配超水位时,
|
||||
/// 调用方寄存器窗口(`save_inline_state` 存入 inline 状态,非 GC 根)持的 session
|
||||
/// 串不得被执行期回收:回调期间触发收集会把它当死串释放,回拷后成悬垂。
|
||||
/// `held` 经 middle 参数位于低号寄存器,恰落在回调写寄存器区(0..n_registers),
|
||||
/// 修复前回调内触发收集即把它释放并复用(结果变垃圾串),修复后回调期间不回收。
|
||||
#[test]
|
||||
fn map_callback_caller_reg_strings_survive_nested_dispatch() {
|
||||
let mut vm = vm_with_threshold(512);
|
||||
let module = compile(
|
||||
"var g = {}; for (var i = 0; i < 20; i++) { g['k' + i] = 'v'.repeat(64); } \
|
||||
function middle(held) { \
|
||||
var r = [1, 2, 3].map(function(x) { var t; for (var j = 0; j < 300; j++) { t = 'z' + j; } return x * 2; }); \
|
||||
return held; } \
|
||||
var out = middle('mid' + 'str'); out",
|
||||
);
|
||||
let result = vm.run(&module).expect("run");
|
||||
let text = vm.lookup_str(result).expect("held 应为字符串").to_string();
|
||||
assert_eq!(text, "midstr");
|
||||
assert!(vm.session_gc_stats().total_collections > 0, "执行期应触发字符串 GC");
|
||||
}
|
||||
|
||||
/// 函数 replacer 回调同样重入嵌套 dispatch:调用方 regs 持串跨回调存活,
|
||||
/// 回调体内分配不回收调用方窗口中的串。
|
||||
#[test]
|
||||
fn replace_replacer_caller_reg_strings_survive_nested_dispatch() {
|
||||
let mut vm = vm_with_threshold(512);
|
||||
let module = compile(
|
||||
"var g = {}; for (var i = 0; i < 20; i++) { g['k' + i] = 'v'.repeat(64); } \
|
||||
function middle(held) { \
|
||||
var out = 'a-b-c'.replace(/-/g, function(m, pos, str) { var t; for (var j = 0; j < 300; j++) { t = 'y' + j; } return '[' + m + ']'; }); \
|
||||
return held + '|' + out; } \
|
||||
var out = middle('repl' + 'acer'); out",
|
||||
);
|
||||
let result = vm.run(&module).expect("run");
|
||||
let text = vm.lookup_str(result).expect("结果应为字符串").to_string();
|
||||
assert_eq!(text, "replacer|a[-]b[-]c");
|
||||
assert!(vm.session_gc_stats().total_collections > 0, "执行期应触发字符串 GC");
|
||||
}
|
||||
|
||||
/// generator.next() 经生成器恢复重入嵌套 dispatch:调用方 regs(参数串与累加器)
|
||||
/// 在生成器体执行期间不得被回收。修复前该场景直接段错误(悬垂指针解引用)。
|
||||
#[test]
|
||||
fn generator_next_loop_caller_reg_strings_survive_nested_dispatch() {
|
||||
let mut vm = vm_with_threshold(512);
|
||||
let module = compile(
|
||||
"var g = {}; for (var i = 0; i < 20; i++) { g['k' + i] = 'v'.repeat(64); } \
|
||||
function middle(held) { \
|
||||
function* gen() { for (var i = 0; i < 5; i++) { var t; for (var j = 0; j < 200; j++) { t = 'g' + j; } yield i; } } \
|
||||
var it = gen(); var acc = 0; for (var i = 0; i < 5; i++) { acc += it.next().value; } \
|
||||
return held + '|' + acc; } \
|
||||
var out = middle('gen' + 'str'); out",
|
||||
);
|
||||
let result = vm.run(&module).expect("run");
|
||||
let text = vm.lookup_str(result).expect("结果应为字符串").to_string();
|
||||
assert_eq!(text, "genstr|10");
|
||||
assert!(vm.session_gc_stats().total_collections > 0, "执行期应触发字符串 GC");
|
||||
}
|
||||
|
||||
/// 嵌套回调组合(map 回调内再调 map):两层内联 dispatch 叠加,任一层指令边界
|
||||
/// 触发收集都不回收调用方窗口中的串。
|
||||
#[test]
|
||||
fn nested_inline_callbacks_caller_reg_strings_survive_runtime_gc() {
|
||||
let mut vm = vm_with_threshold(512);
|
||||
let module = compile(
|
||||
"var g = {}; for (var i = 0; i < 20; i++) { g['k' + i] = 'v'.repeat(64); } \
|
||||
function middle(held) { \
|
||||
var r = [1, 2].map(function(x) { \
|
||||
var inner = [10, 20].map(function(y) { var t; for (var j = 0; j < 200; j++) { t = 'n' + j; } return y; }); \
|
||||
return inner[0] + inner[1] + x; }); \
|
||||
return held + '|' + r.join(','); } \
|
||||
var out = middle('nest' + 'ed'); out",
|
||||
);
|
||||
let result = vm.run(&module).expect("run");
|
||||
let text = vm.lookup_str(result).expect("结果应为字符串").to_string();
|
||||
assert_eq!(text, "nested|31,32");
|
||||
assert!(vm.session_gc_stats().total_collections > 0, "执行期应触发字符串 GC");
|
||||
}
|
||||
|
||||
/// full_reset 清空 session 内存:执行期回收释放过的死串不与完全重置的
|
||||
/// 整体释放路径重复释放(无双重释放崩溃),重置后引擎可继续运行。
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user