diff --git a/crates/oxide_builtins/src/array/element.rs b/crates/oxide_builtins/src/array/element.rs index 0c14cb13..36cfb0b1 100644 --- a/crates/oxide_builtins/src/array/element.rs +++ b/crates/oxide_builtins/src/array/element.rs @@ -525,6 +525,11 @@ pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { } else { None }; + // 下值取出即钉入返回寄存器:跨上端读与两端 Set 的用户调用窗口期间 + // 保持 GC 根,写后从钉位读回(Rust 局部不属根集)。 + if let Some(v) = lower_val { + vm.set_reg(0, v); + } let upper_val = if vm.has_property(unsafe { &*arr_ptr }, upper_si) { match vm.ordinary_get(unsafe { &*arr_ptr }, upper_si, recv) { Ok(v) => Some(v), @@ -534,10 +539,7 @@ pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { None }; match (lower_val, upper_val) { - (Some(lv), Some(ov)) => { - // 下值跨上端读与两次 Set 共三个用户调用窗口:先钉返回寄存器 - // (Rust 局部不属 GC 根集),写后从钉位读回。 - vm.set_reg(0, lv); + (Some(_lv), Some(ov)) => { if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, lower_si, ov, recv, true) { return NativeResult::Err(from_engine_error(vm, &err)); } @@ -553,8 +555,7 @@ pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Err(err); } } - (Some(lv), None) => { - vm.set_reg(0, lv); + (Some(_lv), None) => { let old_count = unsafe { &*arr_ptr }.prop_count() as usize; if let Err(err) = delete_prop_or_throw(vm, arr_ptr, lower) { return NativeResult::Err(err); @@ -562,10 +563,11 @@ pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, upper_si, vm.reg(0), recv, true) { return NativeResult::Err(from_engine_error(vm, &err)); } - // 读值期 getter 截断元素区后,上端写在区域外越界扩展会把间隙 - // 填成 present-undefined:被删端落入新扩展段时须补置洞。 - if lower >= old_count { - unsafe { (*arr_ptr).mark_hole_at(lower) }; + // 读值期 getter 截断元素区后,上端写在区域外越界扩展会把 + // [old_count, upper) 填成 present-undefined:整段补置洞 + // (lower 落入段内时重复补置幂等)。 + for h in old_count..upper { + unsafe { (*arr_ptr).mark_hole_at(h) }; } } (None, None) => {} diff --git a/crates/oxide_builtins/tests/array_hole_semantics_tests.rs b/crates/oxide_builtins/tests/array_hole_semantics_tests.rs index 394a237e..ed3e7a48 100644 --- a/crates/oxide_builtins/tests/array_hole_semantics_tests.rs +++ b/crates/oxide_builtins/tests/array_hole_semantics_tests.rs @@ -163,6 +163,20 @@ fn test_reverse_getter_truncation_pair() { assert_eq!(out, "false|true|first|2"); } +// reverse 读值期 getter 中间截断:上端写越界扩展的整段间隙 [old_count, upper) +// 补置洞,不只补被删端一洞。 +#[test] +fn test_reverse_getter_mid_truncation_gaps_holes() { + let out = eval_str( + "(() => { const a = new Array(5); a[0] = 1; \ + Object.defineProperty(a, 0, { get: () => { a.length = 3; return 1; } }); \ + a.reverse(); \ + return (1 in a) + '|' + (3 in a) + '|' + a[4] + '|' + a.length; })()", + ) + .unwrap(); + assert_eq!(out, "false|false|1|5"); +} + // 防过度修护栏:fill 对洞位无条件 Set,洞位变 present-0,不改。 #[test] fn test_fill_materializes_hole_unchanged() { diff --git a/crates/oxide_vm/tests/gc_runtime_tests.rs b/crates/oxide_vm/tests/gc_runtime_tests.rs index 8edf4f41..8cb5e141 100644 --- a/crates/oxide_vm/tests/gc_runtime_tests.rs +++ b/crates/oxide_vm/tests/gc_runtime_tests.rs @@ -375,6 +375,29 @@ fn shift_getter_temporary_object_survives_runtime_gc() { assert!(vm.session_gc_stats().total_collections > 0, "执行期应触发收集"); } +/// reverse 双存在臂下值跨上端 Get 窗口:getter 临时对象经结果寄存器钉为 GC 根, +/// 跨低阈值执行期收集存活,落位身份保持。 +#[test] +fn reverse_getter_temporary_object_survives_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); } \ + var box = null; var a = new Array(2); \ + Object.defineProperty(Array.prototype, '0', { \ + get: function() { box = { m: 1 }; return box; }, \ + set: function(v) { Object.defineProperty(this, 0, { value: v, writable: true, enumerable: true, configurable: true }); } }); \ + Object.defineProperty(Array.prototype, '1', { \ + get: function() { var t = 'z'; for (var j = 0; j < 20; j++) { t = t + t; } return 'up'; }, \ + set: function(v) { Object.defineProperty(this, 1, { value: v, writable: true, enumerable: true, configurable: true }); } }); \ + a.reverse(); \ + (a[1] === box) + ':' + a[0] + ':' + a.length + ':' + a[1].m", + ); + let result = vm.run(&Arc::new(module)).expect("run"); + let text = vm.lookup_str(result).expect("结果应为字符串"); + assert_eq!(text, "true:up:2:1"); + assert!(vm.session_gc_stats().total_collections > 0, "执行期应触发收集"); +} + // ── 原生盒内字符串/BigInt 边存活(mark 边收集去对象预过滤) ───────────────── /// 盒持唯一引用 session 串(Promise 结算值):churn 窗口内执行期收集