From 9458cfd6ee91e284254bba620cada30bf6e3cc1f Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 16 Sep 2026 17:28:29 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20vm=5Fsupport=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=9D=97=E7=A7=BB=E5=85=A5=E7=8B=AC=E7=AB=8B=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/vm_support.rs | 900 +----------------------- crates/oxide_vm/src/vm_support/tests.rs | 897 +++++++++++++++++++++++ 2 files changed, 898 insertions(+), 899 deletions(-) create mode 100644 crates/oxide_vm/src/vm_support/tests.rs diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index ccc1ca19..79eab773 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -881,902 +881,4 @@ impl Vm { } #[cfg(test)] -mod tests { - use super::*; - - fn global_prop(vm: &Vm, name: &str) -> JsValue { - global_prop_opt(vm, name).expect("global slot should exist") - } - - fn global_prop_opt(vm: &Vm, name: &str) -> Option { - let global = vm.session.global_object(); - let si = vm.kernel_core.perm_interner().intern(name).0; - vm.kernel_core - .shape_forge() - .lookup_position(global.shape_id(), si) - .map(|pos| global.get_prop_at(pos)) - } - - fn run_source(vm: &mut Vm, source: &str) -> JsValue { - let allocator = oxide_parser::Allocator::default(); - let program = oxide_parser::parse(&allocator, source).expect("parse failed"); - let module = oxide_compiler::compiler::Compiler::new() - .compile(&program) - .expect("compile failed"); - vm.run(&Arc::new(module)).expect("vm run failed") - } - - /// 计数往返:两条构造路径(独立核/共享核)的登记与 Drop 注销恰好配对, - /// 全部 drop 后计数归零,kernel 可干净 drop(Drop 断言无残留)。 - #[test] - fn active_vms_count_roundtrip() { - let vm = Vm::new(); - assert_eq!(vm.kernel_core.active_vms(), 1); - drop(vm); - - let core = KernelCore::new(KernelConfig::minimal()); - assert_eq!(core.active_vms(), 0); - let v1 = Vm::with_kernel_core(Arc::clone(&core)); - let v2 = Vm::with_kernel_core(Arc::clone(&core)); - let v3 = Vm::with_kernel_core(Arc::clone(&core)); - assert_eq!(core.active_vms(), 3); - drop(v3); - assert_eq!(core.active_vms(), 2); - drop(v1); - drop(v2); - assert_eq!(core.active_vms(), 0); - drop(core); - } - - /// 守卫判别:持活 VM 调 sweep 触发 debug_assert。VM 声明在 kernel 之后, - /// panic unwind 时先注销计数再 drop kernel,drop 断言不受干扰。 - #[test] - #[cfg_attr(debug_assertions, should_panic(expected = "no live VMs"))] - fn sweep_runner_forges_rejects_live_vm() { - let core = KernelCore::new(KernelConfig::minimal()); - let _vm = Vm::with_kernel_core(Arc::clone(&core)); - core.sweep_runner_forges(); - } - - #[test] - fn full_reset_with_session_objects_forces_global_rebuild() { - let mut vm = Vm::new(); - // 池路径场景:`globalThis.Array = {}` 覆盖既有 global 槽(不递增 generation), - // 新值 `{}` 经 promote 进入 session——global 保留时该指针将悬垂。 - let _ = run_source(&mut vm, "globalThis.Array = {}; 0"); - assert!(!vm.gc_state.session_object_ptrs.is_empty(), "覆盖写应触发 promote 进入 session"); - let old_global = vm.session.global_object.as_ptr(); - - vm.full_reset(); - - // global 必须重建:旧 global 与其 session 对象随 epoch 释放,Array 恢复内置构造器。 - assert!(!std::ptr::eq(old_global, vm.session.global_object.as_ptr())); - assert!(std::ptr::eq( - global_prop(&vm, "Array").as_js_object_ptr(), - vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject - )); - assert!(!vm.session.is_dirty_since_snapshot()); - } - - /// P 原型上方法槽的 wrapper 对象原始指针(rebuild 跨轮复用验证用)。 - fn method_wrapper_ptr(vm: &Vm, proto_ptr: *const JsObject, name: &str) -> *const JsObject { - let si = vm.kernel_core.perm_interner().intern(name).0; - // SAFETY: proto_ptr 是本 session 的 P 原型,安全点内无并发读者。 - let proto = unsafe { &*proto_ptr }; - let pos = vm - .kernel_core - .shape_forge() - .lookup_position(proto.shape_id(), si) - .unwrap_or_else(|| panic!("{name} 槽位应存在")); - let val = proto.get_prop_at(pos); - assert!(val.is_object(), "{name} 槽位应为 wrapper 对象: {val:?}"); - val.as_js_object_ptr() - } - - /// global 属性槽数(槽位原位更新跨轮不追加验证用)。 - fn global_slot_count(vm: &Vm) -> usize { - let g = vm.session.global_object.as_ptr() as *mut JsObject; - // SAFETY: global 是本 session 对象,安全点内无并发读者。 - unsafe { (*g).prop_vec_len() } - } - - /// 选择性重建 wrapper 复用与 global 槽原位更新:每轮「原型脏写 + full_reset」 - /// 后存活方法 wrapper 应为同一对象(同 raw 指针、同 shape),释放表计数与 - /// global 属性槽数跨轮不增长,重建后方法行为正确。 - #[test] - fn full_reset_rebuild_reuses_method_wrappers_across_rounds() { - let mut vm = Vm::new(); - let _ = run_source(&mut vm, "0"); - let push_before = method_wrapper_ptr(&vm, vm.session.builtin_world().array_proto.as_ptr(), "push"); - let push_shape_before = unsafe { &*push_before }.shape_id(); - let registry_before = vm.session.builtin_world().leaked_object_count(); - let slots_before = global_slot_count(&vm); - - for i in 0..3u32 { - // 新键写才 bump 原型世代;四家族逐轮全脏,function 家族重建同时 - // 覆盖 wrapper proto 槽重指路径。 - let source = format!( - "Object.prototype['w{i}'] = 1; Array.prototype['w{i}'] = 2; String.prototype['w{i}'] = 3; \ - Function.prototype['w{i}'] = 4; 0" - ); - let _ = run_source(&mut vm, &source); - vm.full_reset(); - } - - let push_after = method_wrapper_ptr(&vm, vm.session.builtin_world().array_proto.as_ptr(), "push"); - assert!(std::ptr::eq(push_before, push_after), "存活方法 wrapper 应复用(同 raw 指针)"); - assert_eq!(push_shape_before, unsafe { &*push_after }.shape_id(), "wrapper shape 应稳定"); - let registry_after = vm.session.builtin_world().leaked_object_count(); - assert!( - registry_after <= registry_before, - "释放表计数跨轮不应增长: {registry_before} -> {registry_after}" - ); - let slots_after = global_slot_count(&vm); - assert!(slots_after <= slots_before, "global 槽数跨轮不应增长: {slots_before} -> {slots_after}"); - assert_eq!(run_source(&mut vm, "[1, 2].push(3)"), JsValue::int(3)); - assert_eq!(run_source(&mut vm, "String.prototype.charCodeAt.call('A', 0)"), JsValue::int(65)); - } - - /// global 槽位原位更新锚点:错误/资源栈家族脏重建(子类型构造器经 Box 自建 - /// 路径)多轮后 global 属性槽数不增长、Error 槽指向新构造器。 - #[test] - fn full_reset_rebuild_keeps_global_slot_count_flat() { - let mut vm = Vm::new(); - let _ = run_source(&mut vm, "0"); - let slots_before = global_slot_count(&vm); - - for i in 0..3u32 { - // 新键写脏错误家族(子类型原型重建触发构造器 Box 路径)与对象家族。 - let source = format!( - "Error.prototype['e{i}'] = 1; TypeError.prototype['e{i}'] = 2; Object.prototype['e{i}'] = 3; 0" - ); - let _ = run_source(&mut vm, &source); - vm.full_reset(); - } - - let slots_after = global_slot_count(&vm); - assert!(slots_after <= slots_before, "global 槽数跨轮不应增长: {slots_before} -> {slots_after}"); - // Error 槽应指向本轮重建的构造器(非滞留旧指针)。 - assert!(std::ptr::eq( - global_prop(&vm, "Error").as_js_object_ptr(), - vm.session.builtin_world().error_constructor.as_ptr() as *mut JsObject - )); - assert!(global_prop(&vm, "TypeError").is_object()); - assert_eq!(run_source(&mut vm, "new TypeError('x') instanceof TypeError"), JsValue::bool(true)); - } - - fn vm_with_low_threshold() -> Vm { - let mut cfg = KernelConfig::minimal(); - cfg.set_session_gc_threshold(1); - Vm::with_kernel_core(KernelCore::new(cfg)) - } - - /// 单 run 分配上限拦截失控分配:死循环持续 push 的 run 须在触达步数上限前 - /// 以 memory limit 错误终止。 - #[test] - fn run_alloc_cap_stops_runaway_allocation() { - let mut cfg = KernelConfig::minimal(); - cfg.max_alloc_bytes = Some(256 * 1024); - let mut vm = Vm::with_kernel_core(KernelCore::new(cfg)); - let allocator = oxide_parser::Allocator::default(); - let program = oxide_parser::parse(&allocator, "var a = []; while (true) { a.push(1); }").expect("parse failed"); - let module = oxide_compiler::compiler::Compiler::new() - .compile(&program) - .expect("compile failed"); - let err = vm - .run(&Arc::new(module)) - .expect_err("runaway allocation must hit the alloc cap"); - assert!(err.contains("memory limit"), "unexpected error: {err}"); - assert!(!err.contains("step limit"), "cap 应先于步数上限生效: {err}"); - } - - /// 上限不误伤正常规模分配:同配置下 1 万元素数组构造正常完成。 - #[test] - fn run_alloc_cap_allows_normal_allocation() { - let mut cfg = KernelConfig::minimal(); - cfg.max_alloc_bytes = Some(4 * 1024 * 1024); - let mut vm = Vm::with_kernel_core(KernelCore::new(cfg)); - let result = run_source(&mut vm, "var a = []; for (var i = 0; i < 10000; i++) { a.push(i); } a.length"); - assert!(result.is_int(), "expected int length, got {result:?}"); - assert_eq!(result.as_int(), 10000); - } - - /// native 终端循环泵送小 JS 重入:每次重入 dispatch 远短于循环内 64 指令采样点, - /// 顶层 steps 不推进——重入边界每 64 hop 强制采样兜底,失控分配以 memory limit - /// 终止而非无限循环。 - #[test] - fn reentry_pump_hits_alloc_cap() { - let mut cfg = KernelConfig::minimal(); - cfg.max_alloc_bytes = Some(256 * 1024); - let mut vm = Vm::with_kernel_core(KernelCore::new(cfg)); - // 永不 done 的生成器经鸭子对象交给 toArray:native 循环每步泵送两个短重入 - // (闭包调用 + 生成器恢复)并新产一个结果对象,分配无界增长。 - let source = "var g = (function* () { for (var i = 0; ; ++i) { yield i; } })(); \ - var obj = { next: function () { return g.next(); } }; \ - Iterator.from(obj).toArray();"; - let allocator = oxide_parser::Allocator::default(); - let program = oxide_parser::parse(&allocator, source).expect("parse failed"); - let module = oxide_compiler::compiler::Compiler::new() - .compile(&program) - .expect("compile failed"); - let err = vm.run(&Arc::new(module)).expect_err("reentry pump must hit the alloc cap"); - assert!(err.contains("memory limit"), "unexpected error: {err}"); - } - - /// 直接恢复生成器一步:等价 `it.next()`——回归聚焦 GC 搬移后的状态盒 - /// 有效性(同 run 内恢复,不经 run 边界换表)。 - fn resume_one_step(vm: &mut Vm, gen: JsValue) -> JsValue { - match vm.resume_generator(gen, crate::generator::GeneratorResumeMode::Next(JsValue::undefined())) { - Ok(crate::generator::GeneratorStep::Suspended { value }) => value, - Ok(crate::generator::GeneratorStep::Completed { value }) => value, - Ok(other) => panic!( - "unexpected step: {:?}", - match other { - crate::generator::GeneratorStep::Thrown { value } => format!("Thrown({value})"), - crate::generator::GeneratorStep::SuspendedRaw { value } => format!("SuspendedRaw({value})"), - _ => String::new(), - } - ), - Err(e) => panic!("resume failed: {e}"), - } - } - - #[test] - fn generator_survives_object_sweep_and_resumes() { - let mut vm = vm_with_low_threshold(); - let _ = run_source(&mut vm, "function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0"); - - // 直接触发完整收集(保留执行上下文):存活生成器克隆进新 arena, - // 状态盒深拷贝为新 Box(走收集入口而非 run 边界:聚焦 GC 搬移本身)。 - vm.maybe_collect_session_gc(); - assert!(vm.session_gc_stats().total_collections > 0, "应触发对象收集"); - - // 从 global 取 sweep 重写后的生成器(同 run,模块表未换发,可恢复)。 - let it = global_prop(&vm, "it"); - assert_eq!(resume_one_step(&mut vm, it), JsValue::int(2), "sweep 后应恢复第二次 yield"); - } - - #[test] - fn generator_captured_upvalue_survives_sweep() { - let mut vm = vm_with_low_threshold(); - // x 函数作用域局部:被生成器 g 经 cell 捕获(顶层 var 直连全局属性不走 cell)。 - let _ = run_source( - &mut vm, - "function outer() { var x = 0; function* g(){ x++; yield x; x++; yield x; } globalThis.it = g(); globalThis.it.next(); return 0; } outer(); 0", - ); - - vm.maybe_collect_session_gc(); - assert!(vm.session_gc_stats().total_collections > 0, "应触发对象收集"); - - // 挂起帧 cell_stack 与闭包 upvalues 中的 cell 独立堆分配(地址稳定), - // 恢复后继续读写捕获变量。 - let it = global_prop(&vm, "it"); - assert_eq!(resume_one_step(&mut vm, it), JsValue::int(2), "sweep 后应恢复捕获变量读写"); - } - - #[test] - fn generator_promoted_clone_owns_independent_state_box() { - let mut vm = Vm::new(); - // `(function(){ var it = g(); it.next(); return it; })()`:it 为函数局部(非顶层 - // var,不经全局属性逃逸),保持 epoch 生成器对象(未 promote)。 - let it = run_source( - &mut vm, - "function* g(){ yield 1; yield 2; } (function(){ var it = g(); it.next(); return it; })()", - ); - assert!(it.is_object()); - let epoch_ptr = it.as_js_object_ptr(); - let epoch_box = unsafe { (*epoch_ptr).native_data() }; - - // 手动 promote:克隆应深拷贝状态盒(新 Box),与源盒互不共享。 - let promoted = vm.promote_object(epoch_ptr); - assert!(!std::ptr::eq(promoted, epoch_ptr)); - let promoted_box = unsafe { (*promoted).native_data() }; - assert!(!std::ptr::eq(epoch_box, promoted_box), "promote 应深拷贝生成器状态盒"); - - // 模拟 full_reset 的 epoch 侧释放:源对象与其状态盒随 epoch 回收, - // 并从追踪表移除登记(克隆的后续回收仍由 VM 统一处理)。 - let _ = crate::session_gc::SessionGc::drop_object_heap_data(epoch_ptr, false); - vm.gc_state.epoch_object_ptrs.retain(|&p| !std::ptr::eq(p, epoch_ptr)); - - // 克隆直接恢复执行:读新盒中的挂起状态,不得悬垂。 - assert_eq!(resume_one_step(&mut vm, JsValue::from_js_object(promoted)), JsValue::int(2)); - } - - #[test] - fn full_reset_clean_keeps_session_objects() { - let mut vm = Vm::new(); - let world_ptr = Arc::as_ptr(&vm.session.builtin_world); - let global_ptr = vm.session.global_object.as_ptr(); - let object_proto_ptr = vm.session.builtin_world().object_proto.as_ptr(); - - vm.full_reset(); - - assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.session.builtin_world))); - assert!(std::ptr::eq(global_ptr, vm.session.global_object.as_ptr())); - assert!(std::ptr::eq(object_proto_ptr, vm.session.builtin_world().object_proto.as_ptr())); - assert!(!vm.session.is_dirty_since_snapshot()); - } - - #[test] - fn full_reset_global_dirty_rebuilds_global_and_restores_slots() { - let mut vm = Vm::new(); - let world_ptr = Arc::as_ptr(&vm.session.builtin_world); - let global_ptr = vm.session.global_object.as_ptr(); - let global = unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }; - bindings::bind_global_value(&vm.kernel_core, global, "userGlobal", JsValue::int(99)); - unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }.bump_generation(); - - vm.full_reset(); - - assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.session.builtin_world))); - assert!(!std::ptr::eq(global_ptr, vm.session.global_object.as_ptr())); - assert!(global_prop_opt(&vm, "userGlobal").is_none()); - assert!(std::ptr::eq( - global_prop(&vm, "Array").as_js_object_ptr(), - vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject - )); - assert!(std::ptr::eq( - global_prop(&vm, "globalThis").as_js_object_ptr(), - vm.session.global_object.as_ptr() as *mut JsObject - )); - assert!(!vm.session.is_dirty_since_snapshot()); - } - - #[test] - fn full_reset_dirty_builtin_rebinds_global_slot() { - let mut vm = Vm::new(); - let old_object_proto = vm.session.builtin_world().object_proto.as_ptr(); - let old_array_proto = vm.session.builtin_world().array_proto.as_ptr(); - unsafe { &mut *(old_array_proto as *mut JsObject) }.bump_generation(); - - vm.full_reset(); - - assert!(std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr())); - assert!(!std::ptr::eq(old_array_proto, vm.session.builtin_world().array_proto.as_ptr())); - assert!(std::ptr::eq( - global_prop(&vm, "Array").as_js_object_ptr(), - vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject - )); - let constructor_si = vm.kernel_core.perm_interner().intern("constructor").0; - let array_proto = &*vm.session.builtin_world().array_proto; - let constructor = vm - .resolve_property(array_proto, constructor_si) - .expect("Array.prototype.constructor"); - assert!(std::ptr::eq( - constructor.as_js_object_ptr(), - vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject - )); - assert!(!vm.session.is_dirty_since_snapshot()); - } - - #[test] - fn full_reset_dirty_function_keeps_call_working() { - let mut vm = Vm::new(); - let function_proto = vm.session.builtin_world().function_proto.as_ptr(); - unsafe { &mut *(function_proto as *mut JsObject) }.bump_generation(); - - vm.full_reset(); - - // Function 原型家族重建后,未重建家族(array/string/object/map 等)的方法 - // wrapper 是跨重置存活对象,其原型链必须仍能解析出 call/apply/bind: - // 任何一条路径失效都说明 wrapper 原型指向了已释放的旧 Function 原型。 - assert_eq!(run_source(&mut vm, "Array.prototype.push.call([1], 2)"), JsValue::int(2)); - assert_eq!(run_source(&mut vm, "Array.prototype.push.apply([], [1, 2, 3])"), JsValue::int(3)); - let replaced = run_source(&mut vm, "String.prototype.replace.call('a', 'a', 'b')"); - assert_eq!(vm.lookup_str(replaced).as_deref(), Some("b")); - let has = run_source(&mut vm, "Object.prototype.hasOwnProperty.call({x: 1}, 'x')"); - assert_eq!(has, JsValue::bool(true)); - let fixed = run_source(&mut vm, "Number.prototype.toFixed.call(1.5, 1)"); - assert_eq!(vm.lookup_str(fixed).as_deref(), Some("1.5")); - let in_map = run_source(&mut vm, "Map.prototype.has.call(new Map([[1, 2]]), 1)"); - assert_eq!(in_map, JsValue::bool(true)); - let mapped = run_source(&mut vm, "Array.prototype.map.call([1, 2], function(x){ return x + 1; }).join(',')"); - assert_eq!(vm.lookup_str(mapped).as_deref(), Some("2,3")); - assert!(!vm.session.is_dirty_since_snapshot()); - } - - /// Function 家族脏重建:未重建家族的方法 wrapper 是跨重置存活对象,其 proto - /// 槽(绑定时固化为旧 fn_proto)必须被重指到新 fn_proto,旧原型链不可再读。 - #[test] - fn full_reset_dirty_function_repoints_retained_wrapper_proto() { - let mut vm = Vm::new(); - let old_fn_proto = vm.session.builtin_world().function_proto.as_ptr() as *mut JsObject; - // 保留方法 wrapper:array 家族不重建,wrapper 对象与其 proto 槽跨重置存活。 - let array_proto = vm.session.builtin_world().array_proto.as_ptr() as *mut JsObject; - let push_si = vm.kernel_core.perm_interner().intern("push").0; - let push = vm - .resolve_property(unsafe { &*array_proto }, push_si) - .expect("Array.prototype.push"); - let push_ptr = push.as_js_object_ptr(); - assert!(std::ptr::eq(unsafe { (*push_ptr).proto().as_js_object_ptr() }, old_fn_proto)); - - unsafe { &mut *old_fn_proto }.bump_generation(); - - vm.full_reset(); - - let new_fn_proto = vm.session.builtin_world().function_proto.as_ptr() as *mut JsObject; - assert!(!std::ptr::eq(new_fn_proto, old_fn_proto)); - // 保留 wrapper proto 槽已重指新 fn_proto,call 链走新原型。 - assert!(std::ptr::eq(unsafe { (*push_ptr).proto().as_js_object_ptr() }, new_fn_proto)); - assert_eq!(run_source(&mut vm, "Array.prototype.push.call([1], 2)"), JsValue::int(2)); - assert!(!vm.session.is_dirty_since_snapshot()); - } - - /// 新键写脏 object/array/string/function 四家族(S2 脏源形态):full_reset 后 - /// 跨家族读语义保持——保留 wrapper 原型链经重指后取 call、同家族 - /// constructor/prototype 对自洽、保留原型链指向新 Object.prototype。 - #[test] - fn full_reset_dirty_four_families_cross_family_reads() { - let mut vm = Vm::new(); - run_source( - &mut vm, - "Object.prototype['d'] = 1; Array.prototype['d'] = 2; String.prototype['d'] = 3; Function.prototype['d'] = 4;", - ); - assert!(vm.session.is_dirty_since_snapshot()); - - vm.full_reset(); - - assert_eq!(run_source(&mut vm, "Array.prototype.push.call([1], 2)"), JsValue::int(2)); - let replaced = run_source(&mut vm, "String.prototype.replace.call('a', 'a', 'b')"); - assert_eq!(vm.lookup_str(replaced).as_deref(), Some("b")); - let has = run_source(&mut vm, "Object.prototype.hasOwnProperty.call({x: 1}, 'x')"); - assert_eq!(has, JsValue::bool(true)); - assert_eq!( - run_source(&mut vm, "Object.getPrototypeOf(Array.prototype) === Object.prototype"), - JsValue::bool(true) - ); - assert_eq!(run_source(&mut vm, "Array.prototype.constructor === Array"), JsValue::bool(true)); - assert!(!vm.session.is_dirty_since_snapshot()); - } - - #[test] - fn session_epoch_survives_reset() { - let mut vm = Vm::new(); - let session_ptr = vm.gc_state.session_epoch.alloc(123i32) as *mut i32; - - vm.reset(); - - assert!(unsafe { *session_ptr } == 123); - } - - /// 未捕获异常侧通道是执行期状态:原生错误后可能残留原值,reset/full_reset - /// 边界须随执行状态一并清空——否则其持有的 epoch 对象指针在池回收后悬垂 - /// (后续原生错误经 raise_call_error 消费残留值即 UAF)。 - #[test] - fn reset_drops_stale_uncaught_value() { - let mut vm = Vm::new(); - let _ = run_source(&mut vm, "0"); - vm.last_uncaught_value = Some(JsValue::float(42.0)); - vm.reset(); - assert!(vm.last_uncaught_value.is_none(), "reset 应清空未捕获异常侧通道"); - - vm.last_uncaught_value = Some(JsValue::float(42.0)); - vm.full_reset(); - assert!(vm.last_uncaught_value.is_none(), "full_reset 应清空未捕获异常侧通道"); - } - - /// run 边界换新 Bump 后双 arena 保留锚恰 0:重源 run 冲高水位, - /// full_reset 后 epoch/session 两 arena 均空(容量不跨 reset 保留)。 - #[test] - fn full_reset_zeroes_arena_retained() { - let mut vm = Vm::new(); - run_source( - &mut vm, - "var t = 0; for (var i = 0; i < 20000; i++) { var o = { s: 'ab' + i, a: [i] }; t += o.s.length + o.a.length; } t", - ); - assert!(vm.epoch.bump().allocated_bytes() > 0, "重源 run 应冲高 epoch arena 水位"); - - vm.full_reset(); - - assert_eq!(vm.epoch.bump().allocated_bytes(), 0); - assert_eq!(vm.gc_state.session_epoch.allocated_bytes(), 0); - } - - /// 直 session 分配站(函数对象 + prototype 子对象)计入 session 堆账目: - /// 分配点计数 = 对象头(属性区容量扩张属 promote 同口径的既有盲区,不钉)。 - /// 另含 CREATE_CLOSURE 站将推断函数名物化为 session 串(name 数据属性, - /// 既有行为),钉值一并计入。 - #[test] - fn direct_session_alloc_counted_in_session_bytes() { - let mut vm = Vm::new(); - let base = vm.session_bytes_allocated(); - run_source(&mut vm, "var f = function(){}; 0"); - let delta = vm.session_bytes_allocated() - base; - let name_len = "f".len(); - assert_eq!(delta, 2 * std::mem::size_of::() + std::mem::size_of::() + name_len); - } - - /// 标签模板 cooked/raw 数组直 session 分配计入 session 堆账目: - /// 含模板的 run 账目增量须超出同等函数对象口径(两数组各含对象头 + 元素区)。 - /// 单 run 内完成(标签函数定义 + 模板调用):账目口径与函数对象钉同 run 对齐。 - #[test] - fn tagged_template_object_counted_in_session_bytes() { - let mut vm = Vm::new(); - let base = vm.session_bytes_allocated(); - // 对照 run:仅函数对象(本体 + prototype 子对象)。 - run_source(&mut vm, "var tag = function(){ return 0; }; 0"); - let fn_cost = vm.session_bytes_allocated() - base; - // 实验 run:同函数 + 标签模板(cookeD/raw 两数组)。 - let before = vm.session_bytes_allocated(); - run_source(&mut vm, "var tag2 = function(){ return 0; }; tag2`a${1}b`; 0"); - let both_cost = vm.session_bytes_allocated() - before; - assert!( - both_cost > fn_cost + 2 * std::mem::size_of::(), - "模板数组未计入账目: both={both_cost} fn={fn_cost}" - ); - } - - #[test] - fn immutables_filled_once_per_module() { - let mut vm = Vm::new(); - // `f` 递归(同一子模块进入 4 次),其不可变常量经 OnceLock 只转换一次。 - let result = run_source(&mut vm, "function f(n){ if(n<=0){ return 'done'; } return f(n-1); } f(3)"); - assert!(result.is_string()); - assert_eq!(vm.lookup_str(result).as_deref(), Some("done")); - // 缓存 = 顶层模块 + 1 个子模块(f);子模块槽由这些调用初始化。 - assert_eq!(vm.current_table().immutables.len(), 2); - // 子模块常量改走 temp_immutables(避免缓存下标冲突) - } - - #[test] - fn dynamic_function_basic_arity() { - let mut vm = Vm::new(); - // 与等价静态函数返回值逐位一致(引擎函数调用统一产 Double 数值)。 - let expected = run_source(&mut vm, "function f(a,b){return a+b} f(3,4)"); - let result = run_source(&mut vm, "new Function('a','b','return a+b')(3,4)"); - assert_eq!(result, expected); - } - - #[test] - fn dynamic_function_called_without_new() { - let mut vm = Vm::new(); - let expected = run_source(&mut vm, "function f(a,b){return a*b} f(6,7)"); - let result = run_source(&mut vm, "Function('a','b','return a*b')(6,7)"); - assert_eq!(result, expected); - } - - #[test] - fn dynamic_function_empty_body_returns_undefined() { - let mut vm = Vm::new(); - assert!(run_source(&mut vm, "Function()()").is_undefined()); - } - - #[test] - fn dynamic_function_syntax_error_throws_syntax_error() { - let mut vm = Vm::new(); - let result = run_source(&mut vm, "try{new Function('return {{')}catch(e){e.name}"); - assert!(result.is_string()); - assert_eq!(vm.lookup_str(result).as_deref(), Some("SyntaxError")); - } - - #[test] - fn dynamic_function_nested_closure_renumbering() { - let mut vm = Vm::new(); - // 匿名函数体声明嵌套函数 g,返回值是引用 g 的闭包:验证子树 flat_id 重编号 - // 与 CREATE_CLOSURE imm16 重写后嵌套调用仍指向正确的子模块。 - let expected = run_source( - &mut vm, - "function outer(){var g=function(n){return n*2}; return function(){return g(21)}} outer()()", - ); - let result = run_source( - &mut vm, - "new Function('var g=function(n){return n*2}; return function(){return g(21)}')()()", - ); - assert_eq!(result, expected); - } - - #[test] - fn dynamic_function_multiple_in_one_run() { - let mut vm = Vm::new(); - // 同一 run 内连续创建多个动态函数:验证 base 偏移累计正确。 - let expected = run_source( - &mut vm, - "function f1(){return 1} function f2(){return 2} function f3(a){return a*3} f1()+f2()+f3(4)", - ); - let result = run_source( - &mut vm, - "new Function('return 1')() + new Function('return 2')() + new Function('a','return a*3')(4)", - ); - assert_eq!(result, expected); - } - - #[test] - fn dynamic_function_name_and_length() { - let mut vm = Vm::new(); - let name = run_source(&mut vm, "var f=new Function('a','b','return a'); f.name"); - assert!(name.is_string()); - assert_eq!(vm.lookup_str(name).as_deref(), Some("anonymous")); - let len = run_source(&mut vm, "var f=new Function('a','b','return a'); f.length"); - assert_eq!(len, JsValue::int(2)); - } - - #[test] - fn dynamic_function_comma_split_params_count() { - let mut vm = Vm::new(); - // 单个实参 "a,b,c" 拼接解析为 3 个形参,length 应为解析后的形参数。 - let result = run_source(&mut vm, "new Function('a,b,c','null').length"); - assert_eq!(result, JsValue::int(3)); - } - - #[test] - fn dynamic_function_name_and_length_attributes() { - let mut vm = Vm::new(); - // length/name 为不可写、不可枚举、可配置的数据属性。 - let attrs = run_source( - &mut vm, - "var d=Object.getOwnPropertyDescriptor(new Function('a','return a'),'length'); String(d.value)+d.writable+d.enumerable+d.configurable", - ); - assert_eq!(vm.lookup_str(attrs).as_deref(), Some("1falsefalsetrue")); - let name_attrs = run_source( - &mut vm, - "var d=Object.getOwnPropertyDescriptor(Function(),'name'); String(d.writable)+d.enumerable+d.configurable", - ); - assert_eq!(vm.lookup_str(name_attrs).as_deref(), Some("falsefalsetrue")); - } - - #[test] - fn dynamic_function_rethrows_to_string_exception() { - let mut vm = Vm::new(); - // 形参 ToString 回调抛出的原始值须原样传播,而非包成 TypeError。 - let result = run_source(&mut vm, "try{new Function({toString:function(){throw 7}})}catch(e){e}"); - assert_eq!(result, JsValue::int(7)); - } - - #[test] - fn session_epoch_replacement_is_only_in_full_reset_state_clear() { - let src = include_str!("vm_support.rs"); - let production = src.split("#[cfg(test)]").next().expect("production source"); - assert_eq!(production.matches("self.gc_state.session_epoch = bumpalo::Bump::new()").count(), 1); - assert!(production.contains("fn clear_full_reset_state(&mut self)")); - assert!(production.contains("self.gc_state.session_epoch = bumpalo::Bump::new();")); - } - - #[test] - fn full_reset_refreshes_object_prototype_after_object_dirty() { - let mut vm = Vm::new(); - let old_object_proto = vm.session.builtin_world().object_proto.as_ptr(); - unsafe { &mut *(old_object_proto as *mut JsObject) }.bump_generation(); - - vm.full_reset(); - - assert!(!std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr())); - assert!(std::ptr::eq( - vm.object_prototype.as_ptr(), - vm.session.builtin_world().object_proto.as_ptr() - )); - assert!(!vm.session.is_dirty_since_snapshot()); - } - - #[test] - fn full_reset_object_dirty_rebinds_iterator_family() { - let mut vm = Vm::new(); - let old_object_proto = vm.session.builtin_world().object_proto.as_ptr(); - let old_iterator_proto = vm.session.builtin_world().iterator_proto.as_ptr(); - // 用户修改 Object.prototype:object 家族世代递增,global 未动。 - unsafe { &mut *(old_object_proto as *mut JsObject) }.bump_generation(); - - vm.full_reset(); - - // object 家族与迭代器原型全部重建(新原型链到新 Object.prototype)。 - assert!(!std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr())); - assert!(!std::ptr::eq(old_iterator_proto, vm.session.builtin_world().iterator_proto.as_ptr())); - // global 保留(dirty.global=false):其 Iterator 函数对象的 prototype - // 属性须对齐到重建后的 %IteratorPrototype%。 - let iter_val = global_prop(&vm, "Iterator"); - let si_prototype = vm.kernel_core.perm_interner().intern("prototype").0; - let iter_obj = unsafe { &*iter_val.as_js_object_ptr() }; - let proto_pos = vm - .kernel_core - .shape_forge() - .lookup_position(iter_obj.shape_id(), si_prototype) - .expect("Iterator should have prototype slot"); - assert!(std::ptr::eq( - iter_obj.get_prop_at(proto_pos).as_js_object_ptr(), - vm.session.builtin_world().iterator_proto.as_ptr() as *mut JsObject - )); - // full_reset 后 session 干净;此后 run_source 执行才重新累积世代变化。 - assert!(!vm.session.is_dirty_since_snapshot()); - // 迭代器家族功能完整:原型 next 就位,for-of/spread/Array.from/Iterator.from/ - // Map/Set/String/yield* 全部可用,原型链与 Iterator.prototype 一致。 - let r = run_source(&mut vm, "[...[1,2,3]].join(',')"); - assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); - let r = run_source(&mut vm, "Array.from([1,2]).join(',')"); - assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2")); - let r = run_source(&mut vm, "Iterator.from({next:function(){return {value:42,done:false}}}).next().value"); - assert_eq!(r, JsValue::int(42)); - let r = run_source(&mut vm, "[...new Map([[1,2],[3,4]])].map(function(x){return x.join(':')}).join(';')"); - assert_eq!(vm.lookup_str(r).as_deref(), Some("1:2;3:4")); - let r = run_source(&mut vm, "[...new Set([1,2,3])].join(',')"); - assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); - let r = run_source(&mut vm, "[...'ab'].join(',')"); - assert_eq!(vm.lookup_str(r).as_deref(), Some("a,b")); - let r = run_source(&mut vm, "function* g(){yield* [1,2]} [...g()].join(',')"); - assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2")); - let r = run_source( - &mut vm, - "Object.getPrototypeOf(Object.getPrototypeOf([].values())) === Iterator.prototype", - ); - assert_eq!(r, JsValue::bool(true)); - let r = run_source(&mut vm, "var it=[].values(); it[Symbol.iterator]()===it"); - assert_eq!(r, JsValue::bool(true)); - } - - #[test] - fn full_reset_global_dirty_keeps_iterator_proto_slots_stable() { - let mut vm = Vm::new(); - let arr_iter_proto = vm.session.builtin_world().array_iterator_proto.as_ptr() as *mut JsObject; - let slots_before = unsafe { &*arr_iter_proto }.hash_props_vec().map_or(0, |v| v.len()); - // global 世代递增(用户写 global),builtin 家族未动。 - unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }.bump_generation(); - - vm.full_reset(); - - // builtin 未脏 → 迭代器原型保留原对象且属性槽不膨胀(重复 full_reset 不再追加)。 - let arr_iter_proto_after = vm.session.builtin_world().array_iterator_proto.as_ptr() as *mut JsObject; - assert!(std::ptr::eq(arr_iter_proto, arr_iter_proto_after)); - let slots_after = unsafe { &*arr_iter_proto_after }.hash_props_vec().map_or(0, |v| v.len()); - assert_eq!(slots_before, slots_after); - assert!(!vm.session.is_dirty_since_snapshot()); - // 迭代器功能经保留原型仍完整(run_source 起再次累积世代变化)。 - let r = run_source(&mut vm, "[...[1,2,3]].join(',')"); - assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); - let r = run_source(&mut vm, "var it=new Set([1]).values(); it[Symbol.iterator]()===it"); - assert_eq!(r, JsValue::bool(true)); - } - - #[test] - fn bigint_literal_arithmetic_and_comparison() { - let mut vm = Vm::new(); - assert_eq!(run_source(&mut vm, "100n + 23n"), run_source(&mut vm, "123n")); - assert_eq!(run_source(&mut vm, "100n - 30n"), run_source(&mut vm, "70n")); - assert_eq!(run_source(&mut vm, "7n * 6n"), run_source(&mut vm, "42n")); - assert_eq!(run_source(&mut vm, "10n / 4n"), run_source(&mut vm, "2n")); - assert_eq!(run_source(&mut vm, "10n % 3n"), run_source(&mut vm, "1n")); - assert_eq!(run_source(&mut vm, "-7n"), run_source(&mut vm, "0n - 7n")); - assert_eq!(run_source(&mut vm, "123n == 123n"), JsValue::bool(true)); - assert_eq!(run_source(&mut vm, "123n === 123n"), JsValue::bool(true)); - assert_eq!(run_source(&mut vm, "5n < 3n"), JsValue::bool(false)); - assert_eq!(run_source(&mut vm, "5n > 3n"), JsValue::bool(true)); - assert_eq!(run_source(&mut vm, "1n === 1"), JsValue::bool(false)); - let typeof_result = run_source(&mut vm, "typeof 123n"); - assert!(typeof_result.is_string()); - assert_eq!(vm.lookup_str(typeof_result).as_deref(), Some("bigint")); - } - - #[test] - fn bigint_constructor_and_string() { - let mut vm = Vm::new(); - assert_eq!(run_source(&mut vm, "BigInt(42)"), run_source(&mut vm, "42n")); - assert_eq!(run_source(&mut vm, "BigInt('123')"), run_source(&mut vm, "123n")); - assert_eq!(run_source(&mut vm, "BigInt('0x10')"), run_source(&mut vm, "16n")); - let s = run_source(&mut vm, "String(123n)"); - assert!(s.is_string()); - assert_eq!(vm.lookup_str(s).as_deref(), Some("123")); - let ts = run_source(&mut vm, "(123n).toString()"); - assert!(ts.is_string()); - assert_eq!(vm.lookup_str(ts).as_deref(), Some("123")); - assert_eq!(run_source(&mut vm, "Number(5n)"), JsValue::int(5)); - } - - #[test] - fn bigint_mixed_type_throws() { - let mut vm = Vm::new(); - let te = run_source(&mut vm, "try { 1n + 1 } catch(e) { e.name }"); - assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError")); - let re = run_source(&mut vm, "try { 1n / 0n } catch(e) { e.name }"); - assert_eq!(vm.lookup_str(re).as_deref(), Some("RangeError")); - let ne = run_source(&mut vm, "try { new BigInt(1) } catch(e) { e.name }"); - assert_eq!(vm.lookup_str(ne).as_deref(), Some("TypeError")); - } - - #[test] - fn bigint_survives_reset_and_gc() { - let mut vm = Vm::new(); - let result = run_source(&mut vm, "100n + 23n"); - assert!(result.is_bigint()); - assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123)); - vm.reset(); - // reset 保留 session 字符串/bigint box:值仍可读。 - assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123)); - } - - #[test] - fn bigint_wrapped_and_number_comparison() { - let mut vm = Vm::new(); - // 包装对象 coerce 后双 BigInt 运算。 - assert_eq!(run_source(&mut vm, "Object(2n) / 2n"), run_source(&mut vm, "1n")); - assert_eq!(run_source(&mut vm, "Object(2n) * 3n"), run_source(&mut vm, "6n")); - assert_eq!(run_source(&mut vm, "2n + Object(3n)"), run_source(&mut vm, "5n")); - // BigInt 与 Number 精确关系比较(超出 f64 精度仍精确)。 - assert_eq!(run_source(&mut vm, "9007199254740993n > 9007199254740992"), JsValue::bool(true)); - assert_eq!(run_source(&mut vm, "9007199254740993n < 9007199254740994"), JsValue::bool(true)); - assert_eq!(run_source(&mut vm, "2n < 3"), JsValue::bool(true)); - assert_eq!(run_source(&mut vm, "3n >= 3"), JsValue::bool(true)); - // NaN 关系比较为 false。 - assert_eq!(run_source(&mut vm, "0n < NaN"), JsValue::bool(false)); - // 混合算术抛 TypeError。 - let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }"); - assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError")); - } - - #[test] - fn reset_clears_runtime_state_like_rerun() { - let mut vm = Vm::new(); - vm.regs[1] = JsValue::int(7); - vm.pc = 3; - vm.frames.push(crate::vm::CallFrame { - return_addr: 1, - function_name: 0, - caller_reg_limit: 2, - caller_active_reg_limit: 2, - saved_reg_offset: 0, - spill_offset: 0, - arguments_base: 0, - arguments_count: 0, - saved_this: JsValue::undefined(), - saved_new_target: JsValue::undefined(), - callee: JsValue::undefined(), - construct_result_reg: None, - strict: false, - constructed_this: None, - is_derived_constructor: false, - super_called: false, - continuation: crate::vm::FrameContinuation::None, - }); - vm.save_stack.push(JsValue::undefined()); - vm.iters - .for_in_iters - .push(std::ptr::dangling_mut::>()); - vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry { - iterator: JsValue::undefined(), - last_result: JsValue::undefined(), - is_async: false, - }); - vm.saved_bytecode_stack.push(Arc::from(vec![oxide_bytecode::opcode::encode( - oxide_bytecode::opcode::OpCode::HALT, - 0, - 0, - 0, - )])); - vm.saved_immutables_stack - .push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0)); - vm.try_stack.push(crate::vm::TryHandler { - catch_pc: Some(1), - finally_pc: None, - finally_active: false, - frame_depth: 0, - for_of_depth: 0, - }); - vm.exception_value = Some(JsValue::int(2)); - vm.pending_exception = Some(JsValue::int(3)); - vm.pending_error_kind = Some("TypeError"); - - vm.reset(); - - assert_eq!(vm.pc, 0); - assert!(vm.frames.is_empty()); - assert!(vm.save_stack.is_empty()); - assert!(vm.iters.for_in_iters.is_empty()); - assert!(vm.iters.for_of_iters.is_empty()); - assert!(vm.saved_bytecode_stack.is_empty()); - assert!(vm.saved_immutables_stack.is_empty()); - assert!(vm.try_stack.is_empty()); - assert!(vm.exception_value.is_none()); - assert!(vm.pending_exception.is_none()); - assert!(vm.pending_error_kind.is_none()); - assert!(vm.bytecode.is_empty()); - assert!(vm.immutables().is_empty()); - } - - #[test] - fn full_reset_clears_symbol_state() { - let mut vm = Vm::new(); - vm.symbols.intern(Some("shared".to_string())); - - vm.full_reset(); - - assert_eq!(vm.symbols.symbol_counter, 0); - assert!(vm.symbols.symbol_descriptions.is_empty()); - assert!(vm.symbols.symbol_registry.is_empty()); - } -} +mod tests; diff --git a/crates/oxide_vm/src/vm_support/tests.rs b/crates/oxide_vm/src/vm_support/tests.rs new file mode 100644 index 00000000..b4ecdf87 --- /dev/null +++ b/crates/oxide_vm/src/vm_support/tests.rs @@ -0,0 +1,897 @@ +//! vm_support 内联测试:全量重置状态清除与全局重建、分配上限、生成器与 BigInt 存活、动态编译及源文本断言行为。 +use super::*; + +fn global_prop(vm: &Vm, name: &str) -> JsValue { + global_prop_opt(vm, name).expect("global slot should exist") +} + +fn global_prop_opt(vm: &Vm, name: &str) -> Option { + let global = vm.session.global_object(); + let si = vm.kernel_core.perm_interner().intern(name).0; + vm.kernel_core + .shape_forge() + .lookup_position(global.shape_id(), si) + .map(|pos| global.get_prop_at(pos)) +} + +fn run_source(vm: &mut Vm, source: &str) -> JsValue { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse failed"); + let module = oxide_compiler::compiler::Compiler::new() + .compile(&program) + .expect("compile failed"); + vm.run(&Arc::new(module)).expect("vm run failed") +} + +/// 计数往返:两条构造路径(独立核/共享核)的登记与 Drop 注销恰好配对, +/// 全部 drop 后计数归零,kernel 可干净 drop(Drop 断言无残留)。 +#[test] +fn active_vms_count_roundtrip() { + let vm = Vm::new(); + assert_eq!(vm.kernel_core.active_vms(), 1); + drop(vm); + + let core = KernelCore::new(KernelConfig::minimal()); + assert_eq!(core.active_vms(), 0); + let v1 = Vm::with_kernel_core(Arc::clone(&core)); + let v2 = Vm::with_kernel_core(Arc::clone(&core)); + let v3 = Vm::with_kernel_core(Arc::clone(&core)); + assert_eq!(core.active_vms(), 3); + drop(v3); + assert_eq!(core.active_vms(), 2); + drop(v1); + drop(v2); + assert_eq!(core.active_vms(), 0); + drop(core); +} + +/// 守卫判别:持活 VM 调 sweep 触发 debug_assert。VM 声明在 kernel 之后, +/// panic unwind 时先注销计数再 drop kernel,drop 断言不受干扰。 +#[test] +#[cfg_attr(debug_assertions, should_panic(expected = "no live VMs"))] +fn sweep_runner_forges_rejects_live_vm() { + let core = KernelCore::new(KernelConfig::minimal()); + let _vm = Vm::with_kernel_core(Arc::clone(&core)); + core.sweep_runner_forges(); +} + +#[test] +fn full_reset_with_session_objects_forces_global_rebuild() { + let mut vm = Vm::new(); + // 池路径场景:`globalThis.Array = {}` 覆盖既有 global 槽(不递增 generation), + // 新值 `{}` 经 promote 进入 session——global 保留时该指针将悬垂。 + let _ = run_source(&mut vm, "globalThis.Array = {}; 0"); + assert!(!vm.gc_state.session_object_ptrs.is_empty(), "覆盖写应触发 promote 进入 session"); + let old_global = vm.session.global_object.as_ptr(); + + vm.full_reset(); + + // global 必须重建:旧 global 与其 session 对象随 epoch 释放,Array 恢复内置构造器。 + assert!(!std::ptr::eq(old_global, vm.session.global_object.as_ptr())); + assert!(std::ptr::eq( + global_prop(&vm, "Array").as_js_object_ptr(), + vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject + )); + assert!(!vm.session.is_dirty_since_snapshot()); +} + +/// P 原型上方法槽的 wrapper 对象原始指针(rebuild 跨轮复用验证用)。 +fn method_wrapper_ptr(vm: &Vm, proto_ptr: *const JsObject, name: &str) -> *const JsObject { + let si = vm.kernel_core.perm_interner().intern(name).0; + // SAFETY: proto_ptr 是本 session 的 P 原型,安全点内无并发读者。 + let proto = unsafe { &*proto_ptr }; + let pos = vm + .kernel_core + .shape_forge() + .lookup_position(proto.shape_id(), si) + .unwrap_or_else(|| panic!("{name} 槽位应存在")); + let val = proto.get_prop_at(pos); + assert!(val.is_object(), "{name} 槽位应为 wrapper 对象: {val:?}"); + val.as_js_object_ptr() +} + +/// global 属性槽数(槽位原位更新跨轮不追加验证用)。 +fn global_slot_count(vm: &Vm) -> usize { + let g = vm.session.global_object.as_ptr() as *mut JsObject; + // SAFETY: global 是本 session 对象,安全点内无并发读者。 + unsafe { (*g).prop_vec_len() } +} + +/// 选择性重建 wrapper 复用与 global 槽原位更新:每轮「原型脏写 + full_reset」 +/// 后存活方法 wrapper 应为同一对象(同 raw 指针、同 shape),释放表计数与 +/// global 属性槽数跨轮不增长,重建后方法行为正确。 +#[test] +fn full_reset_rebuild_reuses_method_wrappers_across_rounds() { + let mut vm = Vm::new(); + let _ = run_source(&mut vm, "0"); + let push_before = method_wrapper_ptr(&vm, vm.session.builtin_world().array_proto.as_ptr(), "push"); + let push_shape_before = unsafe { &*push_before }.shape_id(); + let registry_before = vm.session.builtin_world().leaked_object_count(); + let slots_before = global_slot_count(&vm); + + for i in 0..3u32 { + // 新键写才 bump 原型世代;四家族逐轮全脏,function 家族重建同时 + // 覆盖 wrapper proto 槽重指路径。 + let source = format!( + "Object.prototype['w{i}'] = 1; Array.prototype['w{i}'] = 2; String.prototype['w{i}'] = 3; \ + Function.prototype['w{i}'] = 4; 0" + ); + let _ = run_source(&mut vm, &source); + vm.full_reset(); + } + + let push_after = method_wrapper_ptr(&vm, vm.session.builtin_world().array_proto.as_ptr(), "push"); + assert!(std::ptr::eq(push_before, push_after), "存活方法 wrapper 应复用(同 raw 指针)"); + assert_eq!(push_shape_before, unsafe { &*push_after }.shape_id(), "wrapper shape 应稳定"); + let registry_after = vm.session.builtin_world().leaked_object_count(); + assert!( + registry_after <= registry_before, + "释放表计数跨轮不应增长: {registry_before} -> {registry_after}" + ); + let slots_after = global_slot_count(&vm); + assert!(slots_after <= slots_before, "global 槽数跨轮不应增长: {slots_before} -> {slots_after}"); + assert_eq!(run_source(&mut vm, "[1, 2].push(3)"), JsValue::int(3)); + assert_eq!(run_source(&mut vm, "String.prototype.charCodeAt.call('A', 0)"), JsValue::int(65)); +} + +/// global 槽位原位更新锚点:错误/资源栈家族脏重建(子类型构造器经 Box 自建 +/// 路径)多轮后 global 属性槽数不增长、Error 槽指向新构造器。 +#[test] +fn full_reset_rebuild_keeps_global_slot_count_flat() { + let mut vm = Vm::new(); + let _ = run_source(&mut vm, "0"); + let slots_before = global_slot_count(&vm); + + for i in 0..3u32 { + // 新键写脏错误家族(子类型原型重建触发构造器 Box 路径)与对象家族。 + let source = + format!("Error.prototype['e{i}'] = 1; TypeError.prototype['e{i}'] = 2; Object.prototype['e{i}'] = 3; 0"); + let _ = run_source(&mut vm, &source); + vm.full_reset(); + } + + let slots_after = global_slot_count(&vm); + assert!(slots_after <= slots_before, "global 槽数跨轮不应增长: {slots_before} -> {slots_after}"); + // Error 槽应指向本轮重建的构造器(非滞留旧指针)。 + assert!(std::ptr::eq( + global_prop(&vm, "Error").as_js_object_ptr(), + vm.session.builtin_world().error_constructor.as_ptr() as *mut JsObject + )); + assert!(global_prop(&vm, "TypeError").is_object()); + assert_eq!(run_source(&mut vm, "new TypeError('x') instanceof TypeError"), JsValue::bool(true)); +} + +fn vm_with_low_threshold() -> Vm { + let mut cfg = KernelConfig::minimal(); + cfg.set_session_gc_threshold(1); + Vm::with_kernel_core(KernelCore::new(cfg)) +} + +/// 单 run 分配上限拦截失控分配:死循环持续 push 的 run 须在触达步数上限前 +/// 以 memory limit 错误终止。 +#[test] +fn run_alloc_cap_stops_runaway_allocation() { + let mut cfg = KernelConfig::minimal(); + cfg.max_alloc_bytes = Some(256 * 1024); + let mut vm = Vm::with_kernel_core(KernelCore::new(cfg)); + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, "var a = []; while (true) { a.push(1); }").expect("parse failed"); + let module = oxide_compiler::compiler::Compiler::new() + .compile(&program) + .expect("compile failed"); + let err = vm + .run(&Arc::new(module)) + .expect_err("runaway allocation must hit the alloc cap"); + assert!(err.contains("memory limit"), "unexpected error: {err}"); + assert!(!err.contains("step limit"), "cap 应先于步数上限生效: {err}"); +} + +/// 上限不误伤正常规模分配:同配置下 1 万元素数组构造正常完成。 +#[test] +fn run_alloc_cap_allows_normal_allocation() { + let mut cfg = KernelConfig::minimal(); + cfg.max_alloc_bytes = Some(4 * 1024 * 1024); + let mut vm = Vm::with_kernel_core(KernelCore::new(cfg)); + let result = run_source(&mut vm, "var a = []; for (var i = 0; i < 10000; i++) { a.push(i); } a.length"); + assert!(result.is_int(), "expected int length, got {result:?}"); + assert_eq!(result.as_int(), 10000); +} + +/// native 终端循环泵送小 JS 重入:每次重入 dispatch 远短于循环内 64 指令采样点, +/// 顶层 steps 不推进——重入边界每 64 hop 强制采样兜底,失控分配以 memory limit +/// 终止而非无限循环。 +#[test] +fn reentry_pump_hits_alloc_cap() { + let mut cfg = KernelConfig::minimal(); + cfg.max_alloc_bytes = Some(256 * 1024); + let mut vm = Vm::with_kernel_core(KernelCore::new(cfg)); + // 永不 done 的生成器经鸭子对象交给 toArray:native 循环每步泵送两个短重入 + // (闭包调用 + 生成器恢复)并新产一个结果对象,分配无界增长。 + let source = "var g = (function* () { for (var i = 0; ; ++i) { yield i; } })(); \ + var obj = { next: function () { return g.next(); } }; \ + Iterator.from(obj).toArray();"; + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse failed"); + let module = oxide_compiler::compiler::Compiler::new() + .compile(&program) + .expect("compile failed"); + let err = vm.run(&Arc::new(module)).expect_err("reentry pump must hit the alloc cap"); + assert!(err.contains("memory limit"), "unexpected error: {err}"); +} + +/// 直接恢复生成器一步:等价 `it.next()`——回归聚焦 GC 搬移后的状态盒 +/// 有效性(同 run 内恢复,不经 run 边界换表)。 +fn resume_one_step(vm: &mut Vm, gen: JsValue) -> JsValue { + match vm.resume_generator(gen, crate::generator::GeneratorResumeMode::Next(JsValue::undefined())) { + Ok(crate::generator::GeneratorStep::Suspended { value }) => value, + Ok(crate::generator::GeneratorStep::Completed { value }) => value, + Ok(other) => panic!( + "unexpected step: {:?}", + match other { + crate::generator::GeneratorStep::Thrown { value } => format!("Thrown({value})"), + crate::generator::GeneratorStep::SuspendedRaw { value } => format!("SuspendedRaw({value})"), + _ => String::new(), + } + ), + Err(e) => panic!("resume failed: {e}"), + } +} + +#[test] +fn generator_survives_object_sweep_and_resumes() { + let mut vm = vm_with_low_threshold(); + let _ = run_source(&mut vm, "function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0"); + + // 直接触发完整收集(保留执行上下文):存活生成器克隆进新 arena, + // 状态盒深拷贝为新 Box(走收集入口而非 run 边界:聚焦 GC 搬移本身)。 + vm.maybe_collect_session_gc(); + assert!(vm.session_gc_stats().total_collections > 0, "应触发对象收集"); + + // 从 global 取 sweep 重写后的生成器(同 run,模块表未换发,可恢复)。 + let it = global_prop(&vm, "it"); + assert_eq!(resume_one_step(&mut vm, it), JsValue::int(2), "sweep 后应恢复第二次 yield"); +} + +#[test] +fn generator_captured_upvalue_survives_sweep() { + let mut vm = vm_with_low_threshold(); + // x 函数作用域局部:被生成器 g 经 cell 捕获(顶层 var 直连全局属性不走 cell)。 + let _ = run_source( + &mut vm, + "function outer() { var x = 0; function* g(){ x++; yield x; x++; yield x; } globalThis.it = g(); globalThis.it.next(); return 0; } outer(); 0", + ); + + vm.maybe_collect_session_gc(); + assert!(vm.session_gc_stats().total_collections > 0, "应触发对象收集"); + + // 挂起帧 cell_stack 与闭包 upvalues 中的 cell 独立堆分配(地址稳定), + // 恢复后继续读写捕获变量。 + let it = global_prop(&vm, "it"); + assert_eq!(resume_one_step(&mut vm, it), JsValue::int(2), "sweep 后应恢复捕获变量读写"); +} + +#[test] +fn generator_promoted_clone_owns_independent_state_box() { + let mut vm = Vm::new(); + // `(function(){ var it = g(); it.next(); return it; })()`:it 为函数局部(非顶层 + // var,不经全局属性逃逸),保持 epoch 生成器对象(未 promote)。 + let it = run_source( + &mut vm, + "function* g(){ yield 1; yield 2; } (function(){ var it = g(); it.next(); return it; })()", + ); + assert!(it.is_object()); + let epoch_ptr = it.as_js_object_ptr(); + let epoch_box = unsafe { (*epoch_ptr).native_data() }; + + // 手动 promote:克隆应深拷贝状态盒(新 Box),与源盒互不共享。 + let promoted = vm.promote_object(epoch_ptr); + assert!(!std::ptr::eq(promoted, epoch_ptr)); + let promoted_box = unsafe { (*promoted).native_data() }; + assert!(!std::ptr::eq(epoch_box, promoted_box), "promote 应深拷贝生成器状态盒"); + + // 模拟 full_reset 的 epoch 侧释放:源对象与其状态盒随 epoch 回收, + // 并从追踪表移除登记(克隆的后续回收仍由 VM 统一处理)。 + let _ = crate::session_gc::SessionGc::drop_object_heap_data(epoch_ptr, false); + vm.gc_state.epoch_object_ptrs.retain(|&p| !std::ptr::eq(p, epoch_ptr)); + + // 克隆直接恢复执行:读新盒中的挂起状态,不得悬垂。 + assert_eq!(resume_one_step(&mut vm, JsValue::from_js_object(promoted)), JsValue::int(2)); +} + +#[test] +fn full_reset_clean_keeps_session_objects() { + let mut vm = Vm::new(); + let world_ptr = Arc::as_ptr(&vm.session.builtin_world); + let global_ptr = vm.session.global_object.as_ptr(); + let object_proto_ptr = vm.session.builtin_world().object_proto.as_ptr(); + + vm.full_reset(); + + assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.session.builtin_world))); + assert!(std::ptr::eq(global_ptr, vm.session.global_object.as_ptr())); + assert!(std::ptr::eq(object_proto_ptr, vm.session.builtin_world().object_proto.as_ptr())); + assert!(!vm.session.is_dirty_since_snapshot()); +} + +#[test] +fn full_reset_global_dirty_rebuilds_global_and_restores_slots() { + let mut vm = Vm::new(); + let world_ptr = Arc::as_ptr(&vm.session.builtin_world); + let global_ptr = vm.session.global_object.as_ptr(); + let global = unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }; + bindings::bind_global_value(&vm.kernel_core, global, "userGlobal", JsValue::int(99)); + unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.session.builtin_world))); + assert!(!std::ptr::eq(global_ptr, vm.session.global_object.as_ptr())); + assert!(global_prop_opt(&vm, "userGlobal").is_none()); + assert!(std::ptr::eq( + global_prop(&vm, "Array").as_js_object_ptr(), + vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject + )); + assert!(std::ptr::eq( + global_prop(&vm, "globalThis").as_js_object_ptr(), + vm.session.global_object.as_ptr() as *mut JsObject + )); + assert!(!vm.session.is_dirty_since_snapshot()); +} + +#[test] +fn full_reset_dirty_builtin_rebinds_global_slot() { + let mut vm = Vm::new(); + let old_object_proto = vm.session.builtin_world().object_proto.as_ptr(); + let old_array_proto = vm.session.builtin_world().array_proto.as_ptr(); + unsafe { &mut *(old_array_proto as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + assert!(std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr())); + assert!(!std::ptr::eq(old_array_proto, vm.session.builtin_world().array_proto.as_ptr())); + assert!(std::ptr::eq( + global_prop(&vm, "Array").as_js_object_ptr(), + vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject + )); + let constructor_si = vm.kernel_core.perm_interner().intern("constructor").0; + let array_proto = &*vm.session.builtin_world().array_proto; + let constructor = vm + .resolve_property(array_proto, constructor_si) + .expect("Array.prototype.constructor"); + assert!(std::ptr::eq( + constructor.as_js_object_ptr(), + vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject + )); + assert!(!vm.session.is_dirty_since_snapshot()); +} + +#[test] +fn full_reset_dirty_function_keeps_call_working() { + let mut vm = Vm::new(); + let function_proto = vm.session.builtin_world().function_proto.as_ptr(); + unsafe { &mut *(function_proto as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + // Function 原型家族重建后,未重建家族(array/string/object/map 等)的方法 + // wrapper 是跨重置存活对象,其原型链必须仍能解析出 call/apply/bind: + // 任何一条路径失效都说明 wrapper 原型指向了已释放的旧 Function 原型。 + assert_eq!(run_source(&mut vm, "Array.prototype.push.call([1], 2)"), JsValue::int(2)); + assert_eq!(run_source(&mut vm, "Array.prototype.push.apply([], [1, 2, 3])"), JsValue::int(3)); + let replaced = run_source(&mut vm, "String.prototype.replace.call('a', 'a', 'b')"); + assert_eq!(vm.lookup_str(replaced).as_deref(), Some("b")); + let has = run_source(&mut vm, "Object.prototype.hasOwnProperty.call({x: 1}, 'x')"); + assert_eq!(has, JsValue::bool(true)); + let fixed = run_source(&mut vm, "Number.prototype.toFixed.call(1.5, 1)"); + assert_eq!(vm.lookup_str(fixed).as_deref(), Some("1.5")); + let in_map = run_source(&mut vm, "Map.prototype.has.call(new Map([[1, 2]]), 1)"); + assert_eq!(in_map, JsValue::bool(true)); + let mapped = run_source(&mut vm, "Array.prototype.map.call([1, 2], function(x){ return x + 1; }).join(',')"); + assert_eq!(vm.lookup_str(mapped).as_deref(), Some("2,3")); + assert!(!vm.session.is_dirty_since_snapshot()); +} + +/// Function 家族脏重建:未重建家族的方法 wrapper 是跨重置存活对象,其 proto +/// 槽(绑定时固化为旧 fn_proto)必须被重指到新 fn_proto,旧原型链不可再读。 +#[test] +fn full_reset_dirty_function_repoints_retained_wrapper_proto() { + let mut vm = Vm::new(); + let old_fn_proto = vm.session.builtin_world().function_proto.as_ptr() as *mut JsObject; + // 保留方法 wrapper:array 家族不重建,wrapper 对象与其 proto 槽跨重置存活。 + let array_proto = vm.session.builtin_world().array_proto.as_ptr() as *mut JsObject; + let push_si = vm.kernel_core.perm_interner().intern("push").0; + let push = vm + .resolve_property(unsafe { &*array_proto }, push_si) + .expect("Array.prototype.push"); + let push_ptr = push.as_js_object_ptr(); + assert!(std::ptr::eq(unsafe { (*push_ptr).proto().as_js_object_ptr() }, old_fn_proto)); + + unsafe { &mut *old_fn_proto }.bump_generation(); + + vm.full_reset(); + + let new_fn_proto = vm.session.builtin_world().function_proto.as_ptr() as *mut JsObject; + assert!(!std::ptr::eq(new_fn_proto, old_fn_proto)); + // 保留 wrapper proto 槽已重指新 fn_proto,call 链走新原型。 + assert!(std::ptr::eq(unsafe { (*push_ptr).proto().as_js_object_ptr() }, new_fn_proto)); + assert_eq!(run_source(&mut vm, "Array.prototype.push.call([1], 2)"), JsValue::int(2)); + assert!(!vm.session.is_dirty_since_snapshot()); +} + +/// 新键写脏 object/array/string/function 四家族(S2 脏源形态):full_reset 后 +/// 跨家族读语义保持——保留 wrapper 原型链经重指后取 call、同家族 +/// constructor/prototype 对自洽、保留原型链指向新 Object.prototype。 +#[test] +fn full_reset_dirty_four_families_cross_family_reads() { + let mut vm = Vm::new(); + run_source( + &mut vm, + "Object.prototype['d'] = 1; Array.prototype['d'] = 2; String.prototype['d'] = 3; Function.prototype['d'] = 4;", + ); + assert!(vm.session.is_dirty_since_snapshot()); + + vm.full_reset(); + + assert_eq!(run_source(&mut vm, "Array.prototype.push.call([1], 2)"), JsValue::int(2)); + let replaced = run_source(&mut vm, "String.prototype.replace.call('a', 'a', 'b')"); + assert_eq!(vm.lookup_str(replaced).as_deref(), Some("b")); + let has = run_source(&mut vm, "Object.prototype.hasOwnProperty.call({x: 1}, 'x')"); + assert_eq!(has, JsValue::bool(true)); + assert_eq!( + run_source(&mut vm, "Object.getPrototypeOf(Array.prototype) === Object.prototype"), + JsValue::bool(true) + ); + assert_eq!(run_source(&mut vm, "Array.prototype.constructor === Array"), JsValue::bool(true)); + assert!(!vm.session.is_dirty_since_snapshot()); +} + +#[test] +fn session_epoch_survives_reset() { + let mut vm = Vm::new(); + let session_ptr = vm.gc_state.session_epoch.alloc(123i32) as *mut i32; + + vm.reset(); + + assert!(unsafe { *session_ptr } == 123); +} + +/// 未捕获异常侧通道是执行期状态:原生错误后可能残留原值,reset/full_reset +/// 边界须随执行状态一并清空——否则其持有的 epoch 对象指针在池回收后悬垂 +/// (后续原生错误经 raise_call_error 消费残留值即 UAF)。 +#[test] +fn reset_drops_stale_uncaught_value() { + let mut vm = Vm::new(); + let _ = run_source(&mut vm, "0"); + vm.last_uncaught_value = Some(JsValue::float(42.0)); + vm.reset(); + assert!(vm.last_uncaught_value.is_none(), "reset 应清空未捕获异常侧通道"); + + vm.last_uncaught_value = Some(JsValue::float(42.0)); + vm.full_reset(); + assert!(vm.last_uncaught_value.is_none(), "full_reset 应清空未捕获异常侧通道"); +} + +/// run 边界换新 Bump 后双 arena 保留锚恰 0:重源 run 冲高水位, +/// full_reset 后 epoch/session 两 arena 均空(容量不跨 reset 保留)。 +#[test] +fn full_reset_zeroes_arena_retained() { + let mut vm = Vm::new(); + run_source( + &mut vm, + "var t = 0; for (var i = 0; i < 20000; i++) { var o = { s: 'ab' + i, a: [i] }; t += o.s.length + o.a.length; } t", + ); + assert!(vm.epoch.bump().allocated_bytes() > 0, "重源 run 应冲高 epoch arena 水位"); + + vm.full_reset(); + + assert_eq!(vm.epoch.bump().allocated_bytes(), 0); + assert_eq!(vm.gc_state.session_epoch.allocated_bytes(), 0); +} + +/// 直 session 分配站(函数对象 + prototype 子对象)计入 session 堆账目: +/// 分配点计数 = 对象头(属性区容量扩张属 promote 同口径的既有盲区,不钉)。 +/// 另含 CREATE_CLOSURE 站将推断函数名物化为 session 串(name 数据属性, +/// 既有行为),钉值一并计入。 +#[test] +fn direct_session_alloc_counted_in_session_bytes() { + let mut vm = Vm::new(); + let base = vm.session_bytes_allocated(); + run_source(&mut vm, "var f = function(){}; 0"); + let delta = vm.session_bytes_allocated() - base; + let name_len = "f".len(); + assert_eq!(delta, 2 * std::mem::size_of::() + std::mem::size_of::() + name_len); +} + +/// 标签模板 cooked/raw 数组直 session 分配计入 session 堆账目: +/// 含模板的 run 账目增量须超出同等函数对象口径(两数组各含对象头 + 元素区)。 +/// 单 run 内完成(标签函数定义 + 模板调用):账目口径与函数对象钉同 run 对齐。 +#[test] +fn tagged_template_object_counted_in_session_bytes() { + let mut vm = Vm::new(); + let base = vm.session_bytes_allocated(); + // 对照 run:仅函数对象(本体 + prototype 子对象)。 + run_source(&mut vm, "var tag = function(){ return 0; }; 0"); + let fn_cost = vm.session_bytes_allocated() - base; + // 实验 run:同函数 + 标签模板(cookeD/raw 两数组)。 + let before = vm.session_bytes_allocated(); + run_source(&mut vm, "var tag2 = function(){ return 0; }; tag2`a${1}b`; 0"); + let both_cost = vm.session_bytes_allocated() - before; + assert!( + both_cost > fn_cost + 2 * std::mem::size_of::(), + "模板数组未计入账目: both={both_cost} fn={fn_cost}" + ); +} + +#[test] +fn immutables_filled_once_per_module() { + let mut vm = Vm::new(); + // `f` 递归(同一子模块进入 4 次),其不可变常量经 OnceLock 只转换一次。 + let result = run_source(&mut vm, "function f(n){ if(n<=0){ return 'done'; } return f(n-1); } f(3)"); + assert!(result.is_string()); + assert_eq!(vm.lookup_str(result).as_deref(), Some("done")); + // 缓存 = 顶层模块 + 1 个子模块(f);子模块槽由这些调用初始化。 + assert_eq!(vm.current_table().immutables.len(), 2); + // 子模块常量改走 temp_immutables(避免缓存下标冲突) +} + +#[test] +fn dynamic_function_basic_arity() { + let mut vm = Vm::new(); + // 与等价静态函数返回值逐位一致(引擎函数调用统一产 Double 数值)。 + let expected = run_source(&mut vm, "function f(a,b){return a+b} f(3,4)"); + let result = run_source(&mut vm, "new Function('a','b','return a+b')(3,4)"); + assert_eq!(result, expected); +} + +#[test] +fn dynamic_function_called_without_new() { + let mut vm = Vm::new(); + let expected = run_source(&mut vm, "function f(a,b){return a*b} f(6,7)"); + let result = run_source(&mut vm, "Function('a','b','return a*b')(6,7)"); + assert_eq!(result, expected); +} + +#[test] +fn dynamic_function_empty_body_returns_undefined() { + let mut vm = Vm::new(); + assert!(run_source(&mut vm, "Function()()").is_undefined()); +} + +#[test] +fn dynamic_function_syntax_error_throws_syntax_error() { + let mut vm = Vm::new(); + let result = run_source(&mut vm, "try{new Function('return {{')}catch(e){e.name}"); + assert!(result.is_string()); + assert_eq!(vm.lookup_str(result).as_deref(), Some("SyntaxError")); +} + +#[test] +fn dynamic_function_nested_closure_renumbering() { + let mut vm = Vm::new(); + // 匿名函数体声明嵌套函数 g,返回值是引用 g 的闭包:验证子树 flat_id 重编号 + // 与 CREATE_CLOSURE imm16 重写后嵌套调用仍指向正确的子模块。 + let expected = run_source( + &mut vm, + "function outer(){var g=function(n){return n*2}; return function(){return g(21)}} outer()()", + ); + let result = run_source( + &mut vm, + "new Function('var g=function(n){return n*2}; return function(){return g(21)}')()()", + ); + assert_eq!(result, expected); +} + +#[test] +fn dynamic_function_multiple_in_one_run() { + let mut vm = Vm::new(); + // 同一 run 内连续创建多个动态函数:验证 base 偏移累计正确。 + let expected = run_source( + &mut vm, + "function f1(){return 1} function f2(){return 2} function f3(a){return a*3} f1()+f2()+f3(4)", + ); + let result = run_source( + &mut vm, + "new Function('return 1')() + new Function('return 2')() + new Function('a','return a*3')(4)", + ); + assert_eq!(result, expected); +} + +#[test] +fn dynamic_function_name_and_length() { + let mut vm = Vm::new(); + let name = run_source(&mut vm, "var f=new Function('a','b','return a'); f.name"); + assert!(name.is_string()); + assert_eq!(vm.lookup_str(name).as_deref(), Some("anonymous")); + let len = run_source(&mut vm, "var f=new Function('a','b','return a'); f.length"); + assert_eq!(len, JsValue::int(2)); +} + +#[test] +fn dynamic_function_comma_split_params_count() { + let mut vm = Vm::new(); + // 单个实参 "a,b,c" 拼接解析为 3 个形参,length 应为解析后的形参数。 + let result = run_source(&mut vm, "new Function('a,b,c','null').length"); + assert_eq!(result, JsValue::int(3)); +} + +#[test] +fn dynamic_function_name_and_length_attributes() { + let mut vm = Vm::new(); + // length/name 为不可写、不可枚举、可配置的数据属性。 + let attrs = run_source( + &mut vm, + "var d=Object.getOwnPropertyDescriptor(new Function('a','return a'),'length'); String(d.value)+d.writable+d.enumerable+d.configurable", + ); + assert_eq!(vm.lookup_str(attrs).as_deref(), Some("1falsefalsetrue")); + let name_attrs = run_source( + &mut vm, + "var d=Object.getOwnPropertyDescriptor(Function(),'name'); String(d.writable)+d.enumerable+d.configurable", + ); + assert_eq!(vm.lookup_str(name_attrs).as_deref(), Some("falsefalsetrue")); +} + +#[test] +fn dynamic_function_rethrows_to_string_exception() { + let mut vm = Vm::new(); + // 形参 ToString 回调抛出的原始值须原样传播,而非包成 TypeError。 + let result = run_source(&mut vm, "try{new Function({toString:function(){throw 7}})}catch(e){e}"); + assert_eq!(result, JsValue::int(7)); +} + +#[test] +fn session_epoch_replacement_is_only_in_full_reset_state_clear() { + let src = include_str!("../vm_support.rs"); + let production = src.split("#[cfg(test)]").next().expect("production source"); + assert_eq!(production.matches("self.gc_state.session_epoch = bumpalo::Bump::new()").count(), 1); + assert!(production.contains("fn clear_full_reset_state(&mut self)")); + assert!(production.contains("self.gc_state.session_epoch = bumpalo::Bump::new();")); +} + +#[test] +fn full_reset_refreshes_object_prototype_after_object_dirty() { + let mut vm = Vm::new(); + let old_object_proto = vm.session.builtin_world().object_proto.as_ptr(); + unsafe { &mut *(old_object_proto as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + assert!(!std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr())); + assert!(std::ptr::eq( + vm.object_prototype.as_ptr(), + vm.session.builtin_world().object_proto.as_ptr() + )); + assert!(!vm.session.is_dirty_since_snapshot()); +} + +#[test] +fn full_reset_object_dirty_rebinds_iterator_family() { + let mut vm = Vm::new(); + let old_object_proto = vm.session.builtin_world().object_proto.as_ptr(); + let old_iterator_proto = vm.session.builtin_world().iterator_proto.as_ptr(); + // 用户修改 Object.prototype:object 家族世代递增,global 未动。 + unsafe { &mut *(old_object_proto as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + // object 家族与迭代器原型全部重建(新原型链到新 Object.prototype)。 + assert!(!std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr())); + assert!(!std::ptr::eq(old_iterator_proto, vm.session.builtin_world().iterator_proto.as_ptr())); + // global 保留(dirty.global=false):其 Iterator 函数对象的 prototype + // 属性须对齐到重建后的 %IteratorPrototype%。 + let iter_val = global_prop(&vm, "Iterator"); + let si_prototype = vm.kernel_core.perm_interner().intern("prototype").0; + let iter_obj = unsafe { &*iter_val.as_js_object_ptr() }; + let proto_pos = vm + .kernel_core + .shape_forge() + .lookup_position(iter_obj.shape_id(), si_prototype) + .expect("Iterator should have prototype slot"); + assert!(std::ptr::eq( + iter_obj.get_prop_at(proto_pos).as_js_object_ptr(), + vm.session.builtin_world().iterator_proto.as_ptr() as *mut JsObject + )); + // full_reset 后 session 干净;此后 run_source 执行才重新累积世代变化。 + assert!(!vm.session.is_dirty_since_snapshot()); + // 迭代器家族功能完整:原型 next 就位,for-of/spread/Array.from/Iterator.from/ + // Map/Set/String/yield* 全部可用,原型链与 Iterator.prototype 一致。 + let r = run_source(&mut vm, "[...[1,2,3]].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); + let r = run_source(&mut vm, "Array.from([1,2]).join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2")); + let r = run_source(&mut vm, "Iterator.from({next:function(){return {value:42,done:false}}}).next().value"); + assert_eq!(r, JsValue::int(42)); + let r = run_source(&mut vm, "[...new Map([[1,2],[3,4]])].map(function(x){return x.join(':')}).join(';')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1:2;3:4")); + let r = run_source(&mut vm, "[...new Set([1,2,3])].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); + let r = run_source(&mut vm, "[...'ab'].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("a,b")); + let r = run_source(&mut vm, "function* g(){yield* [1,2]} [...g()].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2")); + let r = run_source( + &mut vm, + "Object.getPrototypeOf(Object.getPrototypeOf([].values())) === Iterator.prototype", + ); + assert_eq!(r, JsValue::bool(true)); + let r = run_source(&mut vm, "var it=[].values(); it[Symbol.iterator]()===it"); + assert_eq!(r, JsValue::bool(true)); +} + +#[test] +fn full_reset_global_dirty_keeps_iterator_proto_slots_stable() { + let mut vm = Vm::new(); + let arr_iter_proto = vm.session.builtin_world().array_iterator_proto.as_ptr() as *mut JsObject; + let slots_before = unsafe { &*arr_iter_proto }.hash_props_vec().map_or(0, |v| v.len()); + // global 世代递增(用户写 global),builtin 家族未动。 + unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + // builtin 未脏 → 迭代器原型保留原对象且属性槽不膨胀(重复 full_reset 不再追加)。 + let arr_iter_proto_after = vm.session.builtin_world().array_iterator_proto.as_ptr() as *mut JsObject; + assert!(std::ptr::eq(arr_iter_proto, arr_iter_proto_after)); + let slots_after = unsafe { &*arr_iter_proto_after }.hash_props_vec().map_or(0, |v| v.len()); + assert_eq!(slots_before, slots_after); + assert!(!vm.session.is_dirty_since_snapshot()); + // 迭代器功能经保留原型仍完整(run_source 起再次累积世代变化)。 + let r = run_source(&mut vm, "[...[1,2,3]].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); + let r = run_source(&mut vm, "var it=new Set([1]).values(); it[Symbol.iterator]()===it"); + assert_eq!(r, JsValue::bool(true)); +} + +#[test] +fn bigint_literal_arithmetic_and_comparison() { + let mut vm = Vm::new(); + assert_eq!(run_source(&mut vm, "100n + 23n"), run_source(&mut vm, "123n")); + assert_eq!(run_source(&mut vm, "100n - 30n"), run_source(&mut vm, "70n")); + assert_eq!(run_source(&mut vm, "7n * 6n"), run_source(&mut vm, "42n")); + assert_eq!(run_source(&mut vm, "10n / 4n"), run_source(&mut vm, "2n")); + assert_eq!(run_source(&mut vm, "10n % 3n"), run_source(&mut vm, "1n")); + assert_eq!(run_source(&mut vm, "-7n"), run_source(&mut vm, "0n - 7n")); + assert_eq!(run_source(&mut vm, "123n == 123n"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "123n === 123n"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "5n < 3n"), JsValue::bool(false)); + assert_eq!(run_source(&mut vm, "5n > 3n"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "1n === 1"), JsValue::bool(false)); + let typeof_result = run_source(&mut vm, "typeof 123n"); + assert!(typeof_result.is_string()); + assert_eq!(vm.lookup_str(typeof_result).as_deref(), Some("bigint")); +} + +#[test] +fn bigint_constructor_and_string() { + let mut vm = Vm::new(); + assert_eq!(run_source(&mut vm, "BigInt(42)"), run_source(&mut vm, "42n")); + assert_eq!(run_source(&mut vm, "BigInt('123')"), run_source(&mut vm, "123n")); + assert_eq!(run_source(&mut vm, "BigInt('0x10')"), run_source(&mut vm, "16n")); + let s = run_source(&mut vm, "String(123n)"); + assert!(s.is_string()); + assert_eq!(vm.lookup_str(s).as_deref(), Some("123")); + let ts = run_source(&mut vm, "(123n).toString()"); + assert!(ts.is_string()); + assert_eq!(vm.lookup_str(ts).as_deref(), Some("123")); + assert_eq!(run_source(&mut vm, "Number(5n)"), JsValue::int(5)); +} + +#[test] +fn bigint_mixed_type_throws() { + let mut vm = Vm::new(); + let te = run_source(&mut vm, "try { 1n + 1 } catch(e) { e.name }"); + assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError")); + let re = run_source(&mut vm, "try { 1n / 0n } catch(e) { e.name }"); + assert_eq!(vm.lookup_str(re).as_deref(), Some("RangeError")); + let ne = run_source(&mut vm, "try { new BigInt(1) } catch(e) { e.name }"); + assert_eq!(vm.lookup_str(ne).as_deref(), Some("TypeError")); +} + +#[test] +fn bigint_survives_reset_and_gc() { + let mut vm = Vm::new(); + let result = run_source(&mut vm, "100n + 23n"); + assert!(result.is_bigint()); + assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123)); + vm.reset(); + // reset 保留 session 字符串/bigint box:值仍可读。 + assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123)); +} + +#[test] +fn bigint_wrapped_and_number_comparison() { + let mut vm = Vm::new(); + // 包装对象 coerce 后双 BigInt 运算。 + assert_eq!(run_source(&mut vm, "Object(2n) / 2n"), run_source(&mut vm, "1n")); + assert_eq!(run_source(&mut vm, "Object(2n) * 3n"), run_source(&mut vm, "6n")); + assert_eq!(run_source(&mut vm, "2n + Object(3n)"), run_source(&mut vm, "5n")); + // BigInt 与 Number 精确关系比较(超出 f64 精度仍精确)。 + assert_eq!(run_source(&mut vm, "9007199254740993n > 9007199254740992"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "9007199254740993n < 9007199254740994"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "2n < 3"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "3n >= 3"), JsValue::bool(true)); + // NaN 关系比较为 false。 + assert_eq!(run_source(&mut vm, "0n < NaN"), JsValue::bool(false)); + // 混合算术抛 TypeError。 + let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }"); + assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError")); +} + +#[test] +fn reset_clears_runtime_state_like_rerun() { + let mut vm = Vm::new(); + vm.regs[1] = JsValue::int(7); + vm.pc = 3; + vm.frames.push(crate::vm::CallFrame { + return_addr: 1, + function_name: 0, + caller_reg_limit: 2, + caller_active_reg_limit: 2, + saved_reg_offset: 0, + spill_offset: 0, + arguments_base: 0, + arguments_count: 0, + saved_this: JsValue::undefined(), + saved_new_target: JsValue::undefined(), + callee: JsValue::undefined(), + construct_result_reg: None, + strict: false, + constructed_this: None, + is_derived_constructor: false, + super_called: false, + continuation: crate::vm::FrameContinuation::None, + }); + vm.save_stack.push(JsValue::undefined()); + vm.iters + .for_in_iters + .push(std::ptr::dangling_mut::>()); + vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry { + iterator: JsValue::undefined(), + last_result: JsValue::undefined(), + is_async: false, + }); + vm.saved_bytecode_stack.push(Arc::from(vec![oxide_bytecode::opcode::encode( + oxide_bytecode::opcode::OpCode::HALT, + 0, + 0, + 0, + )])); + vm.saved_immutables_stack + .push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0)); + vm.try_stack.push(crate::vm::TryHandler { + catch_pc: Some(1), + finally_pc: None, + finally_active: false, + frame_depth: 0, + for_of_depth: 0, + }); + vm.exception_value = Some(JsValue::int(2)); + vm.pending_exception = Some(JsValue::int(3)); + vm.pending_error_kind = Some("TypeError"); + + vm.reset(); + + assert_eq!(vm.pc, 0); + assert!(vm.frames.is_empty()); + assert!(vm.save_stack.is_empty()); + assert!(vm.iters.for_in_iters.is_empty()); + assert!(vm.iters.for_of_iters.is_empty()); + assert!(vm.saved_bytecode_stack.is_empty()); + assert!(vm.saved_immutables_stack.is_empty()); + assert!(vm.try_stack.is_empty()); + assert!(vm.exception_value.is_none()); + assert!(vm.pending_exception.is_none()); + assert!(vm.pending_error_kind.is_none()); + assert!(vm.bytecode.is_empty()); + assert!(vm.immutables().is_empty()); +} + +#[test] +fn full_reset_clears_symbol_state() { + let mut vm = Vm::new(); + vm.symbols.intern(Some("shared".to_string())); + + vm.full_reset(); + + assert_eq!(vm.symbols.symbol_counter, 0); + assert!(vm.symbols.symbol_descriptions.is_empty()); + assert!(vm.symbols.symbol_registry.is_empty()); +}