mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
243 lines
9.0 KiB
Rust
243 lines
9.0 KiB
Rust
//! 逃逸写屏障 identity 分裂判别钉(写屏障退直通后:原件与逃逸目标恒同一对象)。
|
|
//!
|
|
//! 覆盖形态:IIFE 纯 JS 逃逸写、flat/flatMap/splice/for-of/嵌套元素窗内访问器
|
|
//! 逃逸写、setter 深度、晋升压载下双引用一致性;对照钉钉住 native 盒双态一致
|
|
//! 与跨 reset 边界克隆收敛。期望值与 node 行为一致。
|
|
//!
|
|
//! 关键不变式:逃逸写(对象值写向全局/session 目标)之后,原件引用与逃逸目标
|
|
//! 引用指向同一对象,经任一路径的读写互见;执行期晋升与边界修复不引入第二份。
|
|
|
|
use std::sync::Arc;
|
|
|
|
use oxide_compiler::compiler::Compiler;
|
|
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
|
use oxide_parser::Allocator;
|
|
use oxide_vm::vm::Vm;
|
|
|
|
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 vm_default() -> Vm {
|
|
Vm::new()
|
|
}
|
|
|
|
/// 低 session GC 阈值 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))
|
|
}
|
|
|
|
/// 执行源码并取字符串完成值。
|
|
fn eval_string(vm: &mut Vm, source: &str) -> String {
|
|
let result = vm.run(&Arc::new(compile(source))).expect("run");
|
|
vm.lookup_str(result).expect("完成值应为字符串").to_string()
|
|
}
|
|
|
|
/// P1 最基础形态:IIFE 内 `g = inner; g[1] = 99`(无 builtin 无 getter),
|
|
/// 逃逸写后原件、逃逸目标值与 identity 三读一致。
|
|
#[test]
|
|
fn escape_write_iife_global_no_accessor() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var inner = [1, 2]; \
|
|
g = inner; \
|
|
g[1] = 99; \
|
|
return inner[1] + '|' + g[1] + '|' + (inner === g); \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "99|99|true", "逃逸写后原件与逃逸目标应同一对象且值互见");
|
|
}
|
|
|
|
/// P2 flat 主形态:getter 内逃逸写后,flat 续读应见写入值而非原件旧值。
|
|
#[test]
|
|
fn escape_write_flat_accessor_readback() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var inner = [1, 2]; \
|
|
Object.defineProperty(inner, 0, { configurable: true, get: function(){ g = inner; g[1] = 99; return 1; } }); \
|
|
return JSON.stringify(inner.flat()); \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "[1,99]", "flat 经原件续读应见 getter 内逃逸写值");
|
|
}
|
|
|
|
/// P3 splice 移位 SET 形态:getter 逃逸写后,移位写经 setter 落原件,
|
|
/// 读回链(removed 值、移位后空洞、setter 捕获值)三者与 node 一致。
|
|
#[test]
|
|
fn escape_write_splice_shifted_readback() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var inner = [1, 2]; \
|
|
var store = 1; \
|
|
Object.defineProperty(inner, 0, { \
|
|
configurable: true, \
|
|
get: function(){ g = inner; g[1] = 99; return store; }, \
|
|
set: function(v){ store = v; } \
|
|
}); \
|
|
var r = inner.splice(0, 1); \
|
|
return JSON.stringify(r) + '|' + inner[1] + '|' + store; \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "[1]|undefined|99", "移位 SET 应经原件 setter 落值");
|
|
}
|
|
|
|
/// P4 setter 深度:`g[1] = 99` 经 setter 写 this 命名属性,原件与逃逸目标
|
|
/// 均应见 setter 写入。
|
|
#[test]
|
|
fn escape_write_setter_depth_original_visible() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var inner = [1, 2]; \
|
|
Object.defineProperty(inner, 0, { configurable: true, get: function(){ g = inner; g[1] = 99; return 1; } }); \
|
|
Object.defineProperty(inner, 1, { configurable: true, set: function(v){ this._t = v; } }); \
|
|
inner.flat(); \
|
|
return (inner._t === 99) + '|' + (g._t === 99); \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "true|true", "setter 写入应落在原件上且双引用互见");
|
|
}
|
|
|
|
/// P5 identity 面:逃逸写后大量分配触发执行期晋升,双引用经晋升档
|
|
/// 改写后仍同一对象(晋升不引入克隆分裂)。
|
|
#[test]
|
|
fn escape_write_identity_after_in_run_promotion() {
|
|
let mut vm = vm_with_threshold(4096);
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"var inner = [1, 2]; \
|
|
g = inner; \
|
|
var big = []; \
|
|
for (var i = 0; i < 50000; i++) big.push({ v: i }); \
|
|
(inner === g) + '|' + inner[1] + '|' + g[1]",
|
|
);
|
|
assert_eq!(out, "true|2|2", "执行期晋升后双引用应仍同一对象且值一致");
|
|
assert!(vm.session_gc_stats().total_collections > 0, "应触发执行期收集");
|
|
}
|
|
|
|
/// P5 值面对照钉:晋升压载下双引用读回值恒一致(压载只作收集触发器)。
|
|
#[test]
|
|
fn escape_write_value_consistent_under_in_run_pressure() {
|
|
let mut vm = vm_with_threshold(4096);
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"var inner = [1, 2]; \
|
|
g = inner; \
|
|
var big = []; \
|
|
for (var i = 0; i < 50000; i++) big.push({ v: i }); \
|
|
inner[1] + '|' + g[1]",
|
|
);
|
|
assert_eq!(out, "2|2", "压载收集后双引用读回值应一致");
|
|
assert!(vm.session_gc_stats().total_collections > 0, "应触发执行期收集");
|
|
}
|
|
|
|
/// P6 for-of 形态:被迭代数组的访问器逃逸写后,迭代续读与迭代后读回一致。
|
|
#[test]
|
|
fn escape_write_for_of_iterated_readback() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var inner = [1, 2]; \
|
|
Object.defineProperty(inner, 0, { configurable: true, get: function(){ g = inner; g[1] = 99; return 1; } }); \
|
|
var s = 0; \
|
|
for (var x of inner) s += x; \
|
|
return s + '|' + inner[1]; \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "100|99", "迭代续读应见逃逸写值");
|
|
}
|
|
|
|
/// P7 对照钉:Map native 盒直插(键值不经过写屏障),盒内值与原件双态
|
|
/// 恒一致——撤屏障克隆后该面不变。
|
|
#[test]
|
|
fn map_native_box_dual_state_consistent() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var inner = [1, 2]; \
|
|
var m = new Map(); \
|
|
m.set('k', inner); \
|
|
var h = m.get('k'); \
|
|
h[1] = 99; \
|
|
return inner[1] + '|' + h[1] + '|' + (inner === h); \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "99|99|true", "native 盒读回与原件应双态一致");
|
|
}
|
|
|
|
/// P8 对照钉:跨 run 边界(reset 路径)session 对象的 epoch 子引用经边界
|
|
/// 修复克隆收敛——双引用跨边界恒同一对象(REPL 两轮语义的 test 形态)。
|
|
#[test]
|
|
fn cross_reset_boundary_clone_converges_identity() {
|
|
let mut vm = vm_default();
|
|
vm.run(&Arc::new(compile("globalThis.inner = [1, 2]; globalThis.g = globalThis.inner; 0")))
|
|
.expect("run1");
|
|
vm.reset();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"globalThis.g[1] = 99; (globalThis.inner[1] === 99) + '|' + (globalThis.inner === globalThis.g)",
|
|
);
|
|
assert_eq!(out, "true|true", "跨 reset 边界后双引用应同一对象且值互见");
|
|
}
|
|
|
|
/// P9 嵌套元素窗:getter 逃逸写嵌套数组后,flat 递归展开经双路读回一致。
|
|
#[test]
|
|
fn escape_write_nested_element_window() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var nested = [7, 8]; \
|
|
var inner = [nested]; \
|
|
Object.defineProperty(inner, 0, { configurable: true, get: function(){ g = nested; g[1] = 99; return g; } }); \
|
|
var r = inner.flat(); \
|
|
return JSON.stringify(r) + '|' + nested[1]; \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "[7,99]|99", "嵌套元素窗读回应见逃逸写值");
|
|
}
|
|
|
|
/// P10 flatMap 形态:回调结果展开 + 嵌套元素 Get 双窗,逃逸写后经原件读回。
|
|
#[test]
|
|
fn escape_write_flat_map_accessor_readback() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var inner = [1, 2]; \
|
|
Object.defineProperty(inner, 0, { configurable: true, get: function(){ g = inner; g[1] = 99; return 1; } }); \
|
|
return JSON.stringify(inner.flatMap(function(v){ return [v]; })); \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "[1,99]", "flatMap 续读应见 getter 内逃逸写值");
|
|
}
|
|
|
|
/// P11 对照钉:concat arraylike 无访问器纯赋值窗(length/元素 Get 面),
|
|
/// 无逃逸写时结果与 node 逐字一致。
|
|
#[test]
|
|
fn concat_arraylike_no_accessor_control() {
|
|
let mut vm = vm_default();
|
|
let out = eval_string(
|
|
&mut vm,
|
|
"(function(){ \
|
|
var inner = [1, 2]; \
|
|
var o = { length: 2, 0: 1, 1: 2 }; \
|
|
return JSON.stringify(inner.concat(o)); \
|
|
})()",
|
|
);
|
|
assert_eq!(out, "[1,2,{\"0\":1,\"1\":2,\"length\":2}]", "无访问器窗行为应对照 node");
|
|
}
|