fix: 顶层 var 读写直连全局对象属性单一真值——GDI 序言 + 声明处同步删除,类原型 session 直分防晋升克隆 identity 分裂,自增减 int 保持

This commit is contained in:
2026-09-12 15:48:32 +00:00
parent 4b807b5b90
commit 44c16cab8a
21 changed files with 447 additions and 74 deletions
+12 -7
View File
@@ -270,11 +270,12 @@ fn closure_cells_freed_by_full_reset_without_double_free() {
fn dead_closure_upvalues_freed_by_sweep_without_double_free() {
let mut vm = vm_with_threshold(4096);
// run1:两个 3-upvalue 闭包逃逸 global(成 session 对象),同 run 内调用
// 存活闭包确认 upvalue 语义完好。
// 存活闭包确认 upvalue 语义完好。捕获变量置于函数作用域:顶层 var 按全局
// 对象属性单一真值读取,不产生 cell 捕获。
let first = compile(
"var a = 1; var b = 2; var c = 3; \
"(function(){var a = 1; var b = 2; var c = 3; \
globalThis.dead = function() { return a + b + c; }; \
globalThis.live = function() { return a + b + c; }; globalThis.live()",
globalThis.live = function() { return a + b + c; }; return globalThis.live();})()",
);
let result = vm.run(&Arc::new(first)).expect("run1");
assert_eq!(format!("{}", result), "6");
@@ -299,8 +300,8 @@ fn dead_closure_upvalues_freed_by_sweep_without_double_free() {
// 重置后引擎健康:重建新 3-upvalue 闭包,同 run 调用语义正确。
let fourth = compile(
"var x = 10; var y = 20; var z = 30; \
globalThis.f = function() { return x + y + z; }; globalThis.f()",
"(function(){var x = 10; var y = 20; var z = 30; \
globalThis.f = function() { return x + y + z; }; return globalThis.f();})()",
);
let result = vm.run(&Arc::new(fourth)).expect("run4");
assert_eq!(format!("{}", result), "60");
@@ -309,7 +310,8 @@ fn dead_closure_upvalues_freed_by_sweep_without_double_free() {
// 各走独立 VM 的「创建 → 撤根 → 完整收集」。两次收集的字节账目差即死
// arrow 的 upvalue 列表 Box 字节数——对象本体、length/name 属性区、函数
// 名 session 串在 A/B 恒等,差分相消(各 1 个死对象 + 1 条死串)。
// 死分支不释放 upvalue 列表(泄漏)时差分为 0,本断言转红。
// 捕获变量置于函数作用域(顶层 var 按全局对象属性读取,不产生 cell
// 捕获)。死分支不释放 upvalue 列表(泄漏)时差分为 0,本断言转红。
let collect_freed = |src: &str| -> u64 {
let mut v = vm_with_threshold(4096);
v.run(&Arc::new(compile(src))).expect("run create");
@@ -320,7 +322,10 @@ fn dead_closure_upvalues_freed_by_sweep_without_double_free() {
assert!(stats.last_collection_objects_dead >= 1, "arrow 闭包应经 sweep 死分支");
stats.last_collection_bytes_freed
};
let freed_with_captures = collect_freed("var a = 1; var b = 2; var c = 3; globalThis.arrow = () => a + b + c; 0");
let freed_with_captures = collect_freed(
"(function(){var a = 1; var b = 2; var c = 3; \
globalThis.arrow = () => a + b + c; return 0;})()",
);
let freed_no_captures = collect_freed("globalThis.arrow = () => 7; 0");
let upvalue_box_min = (std::mem::size_of::<Vec<*mut oxide_types::object::Cell>>()
+ 3 * std::mem::size_of::<*mut oxide_types::object::Cell>()) as u64;
@@ -243,3 +243,83 @@ fn map_box_value_survives_reset() {
"var fill; for (var i = 0; i < 200; i++) { let t = {junk: i, pad: 'x'.repeat(32)}; fill = t; } globalThis.m.get(globalThis.key).v === 2",
));
}
/// 顶层 var 单一真值:裸读、globalThis 别名、GOPD 描述符三者见同一值——
/// 声明后写点落全局对象属性,镜像寄存器不再参与读。
#[test]
fn top_level_var_read_write_single_source() {
eval_truthy("var x = 1; x = 2; globalThis.x === 2");
eval_truthy("var x = 1; x = 2; x === 2 && globalThis.x === 2");
eval_truthy("var x = 1; x = 2; Object.getOwnPropertyDescriptor(globalThis, 'x').value === 2 && x === 2");
// 属性被直写后裸读见新值:读走全局对象属性而非镜像槽。
eval_truthy("var v = 1; globalThis.v = 2; v === 2");
// 复合赋值旧值取属性值:属性被外部更新后 RMW 基址须跟随属性。
eval_truthy("var c = 5; globalThis.c = 100; c += 2; c === 102 && globalThis.c === 102");
}
/// 顶层 var 的 typeof 读全局对象属性:声明语句前 GDI 序言已建属性(值
/// undefined);声明后见真实值。
#[test]
fn top_level_var_typeof_reads_global_property() {
eval_truthy("(function(){ seen = (typeof t === 'undefined'); })(); var t; seen === true");
eval_truthy("var t2 = 's'; typeof t2 === 'string' && globalThis.t2 === 's'");
}
/// 顶层 var 自增/自减、复合/逻辑赋值:新值落全局对象属性。
#[test]
fn update_and_compound_assign_write_global_property() {
eval_truthy("var u = 3; ++u; u === 4 && globalThis.u === 4");
eval_truthy("var p = 3; p++; p === 4 && globalThis.p === 4");
eval_truthy("var c = 5; c += 2; c === 7 && globalThis.c === 7");
eval_truthy("var l = 0; l ||= 9; l === 9 && globalThis.l === 9");
eval_truthy("var n = 1; n &&= 0; n === 0 && globalThis.n === 0");
}
/// 顶层 var 作解构赋值目标:值落全局对象属性。
#[test]
fn destructuring_assign_writes_global_property() {
eval_truthy("var d1; ({d1} = {d1: 5}); d1 === 5 && globalThis.d1 === 5");
eval_truthy("var d2; [d2] = [7]; d2 === 7 && globalThis.d2 === 7");
}
/// 顶层 for-in 头的迭代值落全局对象属性:声明头与赋值头两形。
#[test]
fn for_in_head_writes_global_property() {
eval_truthy("for (var k in {a: 1}) {} k === 'a' && globalThis.k === 'a'");
eval_truthy("var k2; for (k2 in {a: 1, b: 2}) {} k2 === 'b' && globalThis.k2 === 'b'");
}
/// 嵌套函数局部同名遮蔽不穿透顶层绑定:遮蔽侧走局部槽,顶层属性不受影响;
/// 无遮蔽时嵌套裸读/裸写直连全局对象属性(不经 cell)。
#[test]
fn nested_local_shadow_does_not_pierce_tier() {
eval_truthy("var s = 1; (function(){ var s = 2; return s; })() === 2 && s === 1 && globalThis.s === 1");
eval_truthy(
"var s2 = 1; (function(){ var s2 = 9; s2 = 10; return s2; })() === 10 && s2 === 1 && globalThis.s2 === 1",
);
eval_truthy("var n = 42; (function(){ return n; })() === 42");
// 严格嵌套函数 this=undefined:写点不依赖 this,经 session 全局对象落属性。
eval_truthy("\"use strict\"; var w = 1; (function(){ \"use strict\"; w = 5; })(); w === 5 && globalThis.w === 5");
}
/// 直接 eval:var 声明在全局对象建可配置属性;eval 内裸写穿透全局对象
/// 属性,外层(顶层 var 名)读见同一值。
#[test]
fn direct_eval_var_and_write_visible_to_outer() {
eval_truthy("eval('var y = 5'); y === 5 && globalThis.y === 5");
eval_truthy("var q = 1; eval('q = 55'); q === 55 && globalThis.q === 55");
eval_truthy("eval('var ev = 1'); Object.getOwnPropertyDescriptor(globalThis, 'ev').configurable === true");
// 嵌套函数内直接 eval:写点经 session 解析全局对象(不依赖 this)。
eval_truthy("var z = 1; (function(){ \"use strict\"; eval('z = 77'); })(); z === 77 && globalThis.z === 77");
}
/// 既有属性与顶层 var 声明交互:序言 define-if-absent 零动作,声明值写
/// 是描述符感知写——既有不可写属性保值不覆盖。既有属性须先于 GDI 存在,
/// 分两阶段:阶段一建属性(该程序无 var,GDI 不触碰),阶段二声明。
#[test]
fn tier_declaration_respects_existing_property() {
assert!(eval_two_phases(
"Object.defineProperty(globalThis, 'rw', {value: 7, writable: false, configurable: true})",
"var rw = 99; rw === 7 && globalThis.rw === 7",
));
}
+4 -2
View File
@@ -92,13 +92,15 @@ fn test_full_reset_clears_session_state() {
/// NEW_OBJECT 字面量必须登记进 epoch 追踪表:`{}` 写入超内联容量的属性会分配
/// hash_props 堆 Box,reset 时经追踪表统一释放;不登记则每个字面量泄漏一个 Box。
/// 对象变量置于函数作用域:顶层 var 在声明写即逃逸 promote,后续属性写落
/// session 克隆(其堆数据不属 epoch 源对象,reset 不释放)。
#[test]
fn new_object_literal_tracked_and_freed_on_reset() {
let mut vm = Vm::new();
let result = run_source(
&mut vm,
"var o = {}; for (var i = 0; i < 40; i++) { o['k' + i] = i; } \
delete o.k7; o.k8",
"(function(){var o = {}; for (var i = 0; i < 40; i++) { o['k' + i] = i; } \
delete o.k7; return o.k8;})()",
);
assert_eq!(format!("{}", result), "8", "属性写入/删除后读取应正常");
assert!(vm.epoch_object_count() > 0, "NEW_OBJECT 应登记 epoch 追踪表");