fix: 迭代器包装对象分配走统一入口登记 epoch 对象表——执行期 epoch 换新只晋升表内对象,绕过登记的包装器/结果对象在换新后悬垂,闭合

This commit is contained in:
2026-09-13 00:41:48 +00:00
parent 6aff1101f3
commit c49cdbf9c5
5 changed files with 199 additions and 24 deletions
+1 -3
View File
@@ -1755,9 +1755,7 @@ pub(crate) fn make_array_iterator<H: VmHost>(vm: &mut H, this_val: JsValue, kind
Err(msg) => return Err(array_type_error(vm, &msg)),
};
let array_iter_proto = vm.session().builtin_world().array_iterator_proto.as_ptr() as *mut JsObject;
let iter = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(array_iter_proto)));
let iter = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(array_iter_proto)));
let target_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_TARGET_PROP).0;
let index_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_INDEX_PROP).0;
let kind_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_KIND_PROP).0;
+4 -12
View File
@@ -434,9 +434,7 @@ fn make_iterator_helper<H: VmHost>(
vm: &mut H, inner: JsValue, next: JsValue, kind: IteratorHelperKind, callback: JsValue, counter: JsValue,
) -> JsValue {
let helper_proto = vm.session().builtin_world().iterator_helper_proto.as_ptr() as *mut JsObject;
let wrapper = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(helper_proto)));
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(helper_proto)));
let wrapper_obj = unsafe { &mut *wrapper };
let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0;
let next_si = vm.kernel_core().perm_interner().intern(NEXT_CACHE_PROP).0;
@@ -1232,9 +1230,7 @@ pub(crate) fn try_make_iterator_inner_proto<H: VmHost>(
Some(proto) => proto,
None => vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject,
};
let wrapper = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(iterator_proto)));
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(iterator_proto)));
let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0;
let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0;
@@ -1576,9 +1572,7 @@ fn current_index<H: VmHost>(vm: &mut H, wrapper: &JsObject, index_si: u32) -> us
/// 构造迭代器结果对象 `{value, done}`(生成器 next/return 结果复用)。
pub fn make_iter_result<H: VmHost>(vm: &mut H, value: JsValue, done: bool) -> JsValue {
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let obj = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
let value_si = vm.kernel_core().perm_interner().intern("value").0;
let done_si = vm.kernel_core().perm_interner().intern("done").0;
let obj_ref = unsafe { &mut *obj };
@@ -1854,9 +1848,7 @@ pub(crate) fn make_collection_iterator<H: VmHost>(
} else {
std::ptr::null_mut()
};
let wrapper = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0;
let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0;
let mode_si = vm.kernel_core().perm_interner().intern(MODE_PROP).0;
+1 -3
View File
@@ -563,9 +563,7 @@ pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
// 复用 String.prototype.matchAll 的迭代器包装(next 经 string_match_all_next 推进,
// 挂 %RegExpStringIteratorPrototype% 原型,不设实例 own next)。
let regexp_iter_proto = vm.session().builtin_world().regexp_string_iterator_proto.as_ptr() as *mut JsObject;
let wrapper = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
let wrapper_obj = unsafe { &mut *wrapper };
let input_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_INPUT).0;
let index_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_INDEX).0;
+2 -6
View File
@@ -1522,9 +1522,7 @@ fn builder_wrapper<H: VmHost>(vm: &mut H, input: &str, re_obj: JsValue) -> Nativ
// matchAll 迭代器挂 %RegExpStringIteratorPrototype%(链到 %IteratorPrototype%),
// next 由原型提供(不挂实例 own)。
let regexp_iter_proto = vm.session().builtin_world().regexp_string_iterator_proto.as_ptr() as *mut JsObject;
let wrapper = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
let wrapper_obj = unsafe { &mut *wrapper };
let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0;
@@ -1642,9 +1640,7 @@ pub fn string_match_all_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
fn make_match_done_result<H: VmHost>(vm: &mut H, value: JsValue) -> NativeResult {
let done = value.is_undefined();
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let obj = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
let value_si = vm.kernel_core().perm_interner().intern("value").0;
let done_si = vm.kernel_core().perm_interner().intern("done").0;
let obj_ref = unsafe { &mut *obj };
+191
View File
@@ -0,0 +1,191 @@
//! 并行负载下内存面腐化的稳定判别探针(执行期两档收集全路径覆盖)。
//!
//! 每个测试独享 `Vm`(低 session GC 阈值强制单 run 内反复触发执行期
//! epoch 晋升 + session 原地 sweep),JS 负载覆盖:对象元素数组读回、
//! 原生盒(Set/Map)对象边、Promise 结算值、挂起帧(生成器/异步)
//! 持串/BigInt 边跨收集边界恢复;断言读回值的 tag 位与载荷,
//! 悬垂读命中复用块即红。
//!
//! 盒键只用对象键:字符串键的 SameValueZero 值等值在 SetKey 位比较下
//! 未闭合(动态构造串键按指针判异),属独立语义缺陷,不在此面覆盖。
//!
//! 运行纪律:与负载子集二进制同批 `--test-threads` 梯度并行,
//! `MALLOC_PERTURB_` 放大已释放块扰动;先串行基线确认扰动不引入新红。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_kernel::kernel::{KernelConfig, KernelCore};
use oxide_types::value::JsValue;
use oxide_vm::promise::promise_settled_value;
use oxide_vm::vm::Vm;
fn vm_with_threshold(bytes: usize) -> Vm {
let mut config = KernelConfig::minimal();
config.set_session_gc_threshold(bytes);
Vm::with_kernel_core(KernelCore::new(config))
}
/// 执行源码并格式化完成值:串取内容、Promise 取 drain 后结算值。
fn eval_src(vm: &mut Vm, source: &str) -> String {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse");
let module = Compiler::new().compile(&program).expect("compile");
let result = vm.run(&Arc::new(module)).expect("run");
format_value(vm, result)
}
fn format_value(vm: &Vm, val: JsValue) -> String {
if val.is_string() {
vm.lookup_str(val).expect("串值").to_string()
} else if val.is_object() {
// SAFETY: 完成值对象由 VM 自有,本 session 内有效。
let obj = unsafe { &*val.as_js_object_ptr() };
if obj.is_promise_obj() {
match promise_settled_value(obj) {
Some((true, v)) => format_value(vm, v),
Some((false, v)) => format!("rejected:{}", format_value(vm, v)),
None => "<pending>".to_string(),
}
} else {
format!("{val:?}")
}
} else {
format!("{val:?}")
}
}
/// 对象元素数组跨执行期收集读回:元素经 epoch 晋升 session,churn 制造
/// 死 session 对象供原地 sweep;替换后元素值与串载荷逐一比对。
#[test]
fn inrun_array_element_readback_after_churn() {
let mut vm = vm_with_threshold(4096);
let out = eval_src(
&mut vm,
"var a = []; \
for (var i = 0; i < 120; i++) { a.push({ v: i, s: 'p' + i }); } \
var t; \
for (var i = 0; i < 30000; i++) { t = { c: i, s: 'churn' + i }; a[i % 120] = { v: i % 120, s: 'q' + (i % 120) }; } \
var bad = -1; \
for (var i = 0; i < 120; i++) { \
if (a[i].v !== i || a[i].s !== 'q' + i) { bad = i; break; } \
} \
bad < 0 ? 'ok' : 'bad:' + bad",
);
assert_eq!(out, "ok", "元素读回应全对");
}
/// 挂起生成器跨执行期收集恢复:挂起帧寄存器与状态盒边在晋升/sweep 后
/// 仍指向存活值,恢复迭代序列与数组读回均正确。
#[test]
fn inrun_suspended_generator_resumes_after_collect() {
let mut vm = vm_with_threshold(4096);
let out = eval_src(
&mut vm,
"var a = []; \
for (var i = 0; i < 80; i++) { a.push({ v: i }); } \
function* g() { yield a[0].v; yield a[79].v; } \
var it = g(); \
var first = it.next().value; \
var t; \
for (var i = 0; i < 30000; i++) { t = { c: i }; } \
var second = it.next().value; \
var bad = (first !== 0 || second !== 79) ? 'gen' : ''; \
for (var i = 0; i < 80; i++) { if (a[i].v !== i) { bad = 'arr' + i; break; } } \
bad || 'ok'",
);
assert_eq!(out, "ok", "挂起恢复与数组读回应全对");
}
/// 挂起帧持长串与 BigInt 边跨执行期收集:帧内边并入统一 mark 分发,
/// 恢复后结算值读回(悬垂串/BigInt 即错值)。
#[test]
fn inrun_suspended_frame_string_bigint_edges() {
let mut vm = vm_with_threshold(4096);
let out = eval_src(
&mut vm,
"function* g(x, y) { \
var t; \
for (var i = 0; i < 30000; i++) { t = { c: i, s: 'z' + i }; } \
yield String(x) + y; \
} \
var it = g('a'.repeat(64), 10n); \
it.next().value",
);
assert_eq!(out, format!("{}10", "a".repeat(64)), "挂起帧串/BigInt 边读回");
}
/// 异步函数挂起帧持 BigInt 跨执行期收集恢复:结算值经微任务 drain 读回。
#[test]
fn inrun_async_suspended_bigint_readback() {
let mut vm = vm_with_threshold(4096);
let out = eval_src(
&mut vm,
"async function f(x) { await 0; return x + 1n; } \
var t; \
for (var i = 0; i < 30000; i++) { t = { c: i, s: 'churn' + i }; } \
f(41n).then(function (v) { return String(v); })",
);
assert_eq!(out, "42", "BigInt 结算值读回");
}
/// Set/Map 盒持对象键跨执行期收集:键存活、读回值与 size 正确
/// (盒边在 mark 侧并入统一分发,悬垂即错值/崩溃)。
#[test]
fn inrun_native_box_edges_survive_collect() {
let mut vm = vm_with_threshold(4096);
let out = eval_src(
&mut vm,
"var s = new Set(); var m = new Map(); var keys = []; \
for (var i = 0; i < 60; i++) { var k = { id: i }; keys.push(k); s.add(k); m.set(k, i); } \
var t; \
for (var i = 0; i < 30000; i++) { t = { c: i, s: 'churn' + i }; } \
var bad = -1; \
for (var i = 0; i < 60; i++) { \
var v = m.get(keys[i]); \
if (v !== i || !s.has(keys[i])) { bad = i; break; } \
} \
if (bad < 0 && s.size !== 60) bad = 1000; \
bad < 0 ? 'ok' : 'bad:' + bad",
);
assert_eq!(out, "ok", "盒键读回应全对");
}
/// 大面积 churn 制造死 session 对象(原地 sweep 释放),存活根集
/// (global 子树 + 挂起生成器)读回不腐化。
#[test]
fn inrun_dead_session_sweep_keeps_live_roots() {
let mut vm = vm_with_threshold(4096);
let out = eval_src(
&mut vm,
"globalThis.holder = []; \
for (var i = 0; i < 100; i++) { globalThis.holder.push({ v: i }); } \
function* g() { for (var i = 0; i < 10; i++) { yield i; } } \
var it = g(); it.next(); \
for (var round = 0; round < 6; round++) { \
var next = []; \
for (var i = 0; i < 100; i++) { next.push({ v: i + round }); } \
globalThis.holder = next; \
} \
var bad = -1; \
for (var i = 0; i < 100; i++) { if (globalThis.holder[i].v !== i + 5) { bad = i; break; } } \
bad < 0 ? 'ok:' + it.next().value : 'bad:' + bad",
);
assert_eq!(out, "ok:1", "holder 读回全对且迭代器恢复");
}
/// Set 串值经 values() 迭代器读回跨执行期收集:迭代器包装对象已登记
/// epoch 对象表,晋升后不死;读回串载荷即验证整条边链。
#[test]
fn inrun_set_string_value_survives_collect() {
let mut vm = vm_with_threshold(512);
let out = eval_src(
&mut vm,
"(function(){ \
var s = 'setbox'.repeat(8); \
var st = new Set(); st.add(s); globalThis.st = st; })(); \
for (var i = 0; i < 2000; i++) { var t = 'z'.repeat(48); } \
globalThis.st.values().next().value",
);
assert_eq!(out, "setbox".repeat(8));
}