fix: 执行期字符串 GC 触发加 native 深度守卫防嵌套 dispatch 回调悬垂
This commit is contained in:
@@ -1320,10 +1320,14 @@ impl Vm {
|
|||||||
let mut steps: u64 = 0;
|
let mut steps: u64 = 0;
|
||||||
loop {
|
loop {
|
||||||
steps += 1;
|
steps += 1;
|
||||||
// 执行期字符串 GC 安全点:指令边界上无 builtin 局部活值(分配点触发
|
// 执行期字符串 GC 安全点:仅在顶层 dispatch(native_call_depth == 0)
|
||||||
// 会释放仅存于 Rust 局部/构造中未登记对象的串),此处触发保证回收
|
// 的指令边界触发——嵌套 dispatch(builtin 经 call_function_sync 重入
|
||||||
// 时所有存活串都已落地为执行根。账目未超水位时仅 2 次字段比较。
|
// 执行 JS 回调、generator/async 恢复)期间,调用方寄存器窗口副本存于
|
||||||
if self.gc_state.session_bytes_allocated >= self.gc_state.string_gc_watermark {
|
// 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();
|
self.maybe_collect_session_strings();
|
||||||
}
|
}
|
||||||
if let Some(max_steps) = max_steps {
|
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");
|
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 内存:执行期回收释放过的死串不与完全重置的
|
/// full_reset 清空 session 内存:执行期回收释放过的死串不与完全重置的
|
||||||
/// 整体释放路径重复释放(无双重释放崩溃),重置后引擎可继续运行。
|
/// 整体释放路径重复释放(无双重释放崩溃),重置后引擎可继续运行。
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user