mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
192 lines
7.6 KiB
Rust
192 lines
7.6 KiB
Rust
//! 并行负载下内存面腐化的稳定判别探针(执行期两档收集全路径覆盖)。
|
||
//!
|
||
//! 每个测试独享 `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));
|
||
}
|