This repository has been archived on 2026-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OxideJS/crates/oxide_vm/src/vm_support.rs
T

632 lines
26 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#![allow(clippy::arc_with_non_send_sync)]
use std::sync::Arc;
use oxide_bytecode::module::Constant;
use crate::bindings;
use crate::vm::Vm;
use crate::vm_info;
use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState};
use oxide_kernel::kernel::{KernelConfig, KernelCore, KernelSession};
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::mem::{Epoch, P};
use oxide_types::object::{JsObject, JsString, PropAttributes};
use oxide_types::value::JsValue;
impl Vm {
/// 以最小配置创建独立 VM:新建 `KernelCore` + `KernelSession` 并初始化内置对象。
pub fn new() -> Self {
let core = KernelCore::new(KernelConfig::minimal());
let mut session = KernelSession::new(&core);
bindings::init_kernel_builtins(&core, &mut session);
let obj_proto = P::clone(&session.builtin_world().object_proto);
let vm = Self {
regs: [JsValue::undefined(); 256],
pc: 0,
bytecode: Vec::new(),
immutables_cache: Vec::new(),
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
frames: smallvec::SmallVec::new(),
kernel_core: core,
session,
epoch: Epoch::new(),
object_prototype: obj_proto,
math_rng_state: 0,
sub_modules: Arc::new(Vec::new()),
saved_bytecode_stack: Vec::new(),
saved_immutables_stack: Vec::new(),
save_stack: Vec::new(),
spill_stack: Vec::new(),
try_stack: Vec::new(),
exception_value: None,
last_uncaught_value: None,
pending_exception: None,
pending_error_kind: None,
pending_completion: None,
root_reg_limit: 0,
active_reg_limit: 0,
native_call_depth: 0,
inline_args_base: 0,
inline_args_count: 0,
accessor_frame_target_reg: None,
inline_callee: None,
gc_state: GcState {
session_epoch: bumpalo::Bump::new(),
session_gc: crate::session_gc::SessionGc::new(),
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_string_ptrs: Vec::new(),
session_bytes_allocated: 0,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
},
symbols: SymbolState {
symbol_counter: 0,
symbol_descriptions: Vec::new(),
symbol_registry: std::collections::HashMap::new(),
},
iters: IterState {
for_in_iters: Vec::new(),
for_of_iters: Vec::new(),
last_for_of_result: JsValue::undefined(),
},
profiling: ProfilingState {
ic_hits: std::cell::Cell::new(0),
ic_misses: std::cell::Cell::new(0),
instruction_count: 0,
},
string_buf: String::new(),
cell_stack: Vec::new(),
};
vm_info!("Vm created");
vm
}
/// 复用共享 `KernelCore` 创建 VM(VM 池路径),共享 intern/shape/code 缓存。
pub fn with_kernel_core(core: Arc<KernelCore>) -> Self {
let mut session = KernelSession::new(&core);
bindings::init_kernel_builtins(&core, &mut session);
let obj_proto = P::clone(&session.builtin_world().object_proto);
let vm = Self {
regs: [JsValue::undefined(); 256],
pc: 0,
bytecode: Vec::new(),
immutables_cache: Vec::new(),
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
frames: smallvec::SmallVec::new(),
kernel_core: core,
session,
epoch: Epoch::new(),
object_prototype: obj_proto,
math_rng_state: 0,
sub_modules: Arc::new(Vec::new()),
saved_bytecode_stack: Vec::new(),
saved_immutables_stack: Vec::new(),
save_stack: Vec::new(),
spill_stack: Vec::new(),
try_stack: Vec::new(),
exception_value: None,
last_uncaught_value: None,
pending_exception: None,
pending_error_kind: None,
pending_completion: None,
root_reg_limit: 0,
active_reg_limit: 0,
native_call_depth: 0,
inline_args_base: 0,
inline_args_count: 0,
accessor_frame_target_reg: None,
inline_callee: None,
gc_state: GcState {
session_epoch: bumpalo::Bump::new(),
session_gc: crate::session_gc::SessionGc::new(),
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_string_ptrs: Vec::new(),
session_bytes_allocated: 0,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
},
symbols: SymbolState {
symbol_counter: 0,
symbol_descriptions: Vec::new(),
symbol_registry: std::collections::HashMap::new(),
},
iters: IterState {
for_in_iters: Vec::new(),
for_of_iters: Vec::new(),
last_for_of_result: JsValue::undefined(),
},
profiling: ProfilingState {
ic_hits: std::cell::Cell::new(0),
ic_misses: std::cell::Cell::new(0),
instruction_count: 0,
},
string_buf: String::new(),
cell_stack: Vec::new(),
};
vm_info!("Vm created (pool)");
vm
}
/// 全量隔离重置:仅重建被污染的内置对象与 global,并清空所有执行状态与内存。
///
/// 用于在多次 JS 执行之间达到完全隔离:session 内未被污染的 builtin 保留原指针。
pub fn full_reset(&mut self) {
let dirty = self.session.selective_reset(&self.kernel_core);
if dirty.any_builtin_dirty() {
bindings::rebind_dirty_builtins(&self.kernel_core, &mut self.session, Some(&dirty));
}
if dirty.global {
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
let global = unsafe { &mut *global_ptr };
bindings::bind_global_builtin_slots(&self.kernel_core, &self.session, global);
}
self.session.record_snapshot();
self.object_prototype = P::clone(&self.session.builtin_world().object_proto);
self.clear_full_reset_state();
vm_info!("full_reset completed");
}
/// 旧版全量重置:总是丢弃并重建整个 session 与内置对象(benchmark 专用)。
#[doc(hidden)]
pub fn full_reset_legacy_for_bench(&mut self) {
self.session = KernelSession::new(&self.kernel_core);
bindings::init_kernel_builtins(&self.kernel_core, &mut self.session);
self.object_prototype = P::clone(&self.session.builtin_world().object_proto);
self.clear_full_reset_state();
}
fn clear_full_reset_state(&mut self) {
self.clear_execution_state();
self.bytecode.clear();
self.immutables_cache.clear();
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
self.free_epoch_object_heap_data();
self.epoch.reset();
self.gc_state.epoch_object_ptrs.clear();
self.gc_state.session_epoch.reset();
self.gc_state.session_object_ptrs.clear();
self.gc_state.session_bytes_allocated = 0;
self.gc_state.session_gc = crate::session_gc::SessionGc::new();
self.free_session_string_heap_data();
self.symbols.reset();
self.root_reg_limit = 0;
self.active_reg_limit = 0;
}
fn free_epoch_object_heap_data(&mut self) {
let mut freed = 0u64;
for ptr in self.gc_state.epoch_object_ptrs.drain(..) {
freed += crate::session_gc::SessionGc::drop_object_heap_data(ptr, false);
}
if freed > 0 {
self.gc_state.session_gc.total_bytes_freed =
self.gc_state.session_gc.total_bytes_freed.saturating_add(freed);
self.gc_state.session_gc.last_collection_bytes_freed = freed;
}
}
pub(crate) fn clear_execution_state(&mut self) {
// 重置契约:
// - 清空寄存器文件、pc、帧/迭代器栈、保存的执行栈、try 处理器、
// 待处理异常与 native 调用深度。
// - 保留 kernel 共享状态不变。
// - `reset()` 额外清空 bytecode/constants 并重置 epoch 归属。
self.regs = [JsValue::undefined(); 256];
self.pc = 0;
self.frames.clear();
self.iters.reset();
self.saved_bytecode_stack.clear();
self.saved_immutables_stack.clear();
self.save_stack.clear();
self.spill_stack.clear();
self.cell_stack.clear();
self.try_stack.clear();
self.exception_value = None;
self.pending_exception = None;
self.pending_error_kind = None;
self.pending_completion = None;
self.native_call_depth = 0;
}
/// 轻量重置:清空执行状态并回收 epoch 内存,但保留 session 字符串与 builtin。
pub fn reset(&mut self) {
self.clear_execution_state();
self.maybe_collect_session_gc();
self.bytecode.clear();
self.immutables_cache.clear();
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
self.free_epoch_object_heap_data();
self.epoch.reset();
self.gc_state.epoch_object_ptrs.clear();
self.root_reg_limit = 0;
self.active_reg_limit = 0;
}
/// 分配一个可被 session GC 回收的字符串 `JsValue`(session-heap 字符串)。
pub fn new_string(&mut self, s: &str) -> JsValue {
self.new_string_owned(s.to_string())
}
/// 同 `new_string`,但以 move 接收 `String`,避免一次克隆。
pub fn new_string_owned(&mut self, s: String) -> JsValue {
let len = s.len();
let ptr = Box::into_raw(Box::new(JsString::new(s)));
self.gc_state.session_string_ptrs.push(ptr);
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsString>() + len;
JsValue::string(ptr)
}
/// 把字符串 intern 为永久 key id(属性名/方法名),进程生命周期内稳定。
pub fn intern_key(&self, s: &str) -> u32 {
self.kernel_core.perm_interner().intern(s).0
}
/// 把编译期字符串字面量 intern 为永久、进程生命周期内、跨 session 共享的
/// `JsString` 值。源码字面量与 RegExp 的 source/flags 会反复出现(模板化/
/// 重复代码)且不可变,经 `PermInterner` 共享可恢复跨 session 字符串复用——
/// 不 intern 瞬态计算值(它们走 session 堆 `new_string`,保持可回收)。
pub fn perm_string(&self, s: &str) -> JsValue {
let id = self.kernel_core.perm_interner().intern(s).0;
JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(id))
}
/// 释放全部 session 堆 `JsString`。仅在完全隔离重置
/// (`full_reset` / `clear_full_reset_state`)时调用,此时没有存活的 session 对象
/// 会引用它们。较轻量的 `reset()` 刻意保留它们,与 session 对象跨 eval 存活一致。
fn free_session_string_heap_data(&mut self) {
for ptr in self.gc_state.session_string_ptrs.drain(..) {
// SAFETY: 每个指针来自 new_string 中的 Box::into_raw(Box::new(JsString)),
// 且只在这里恰好释放一次。
unsafe {
drop(Box::from_raw(ptr));
}
}
}
/// 为 BytecodeFunc 常量创建函数 JsObject。
/// 当 `is_arrow` 为 true 时,捕获当前 `this`(regs[254]),供调用时词法 this 绑定。
pub(crate) fn create_function_object(
&mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool,
needs_home_object: bool,
) -> JsValue {
let func_proto_ptr = self.session.builtin_world().function_proto.as_ptr() as *mut JsObject;
let proto_val = JsValue::from_js_object(func_proto_ptr);
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto_val);
obj.set_function(true);
obj.set_sub_module_index(sub_idx);
obj.set_class_constructor(is_class_constructor);
obj.set_derived_constructor(is_derived_constructor);
let _ = needs_home_object;
if is_arrow {
obj.set_arrow(true);
obj.set_captured_this(self.regs[254]);
}
let obj_ptr = self.alloc_object(obj);
let func_val = JsValue::object(obj_ptr as *mut u8);
if !is_arrow {
let object_proto_ptr = self.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
let prototype_obj = self
.epoch
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto_ptr)));
self.gc_state.track_epoch_object(prototype_obj);
let prototype_val = JsValue::from_js_object(prototype_obj);
let constructor_si = self.kernel_core.perm_interner().intern("constructor").0;
let constructor_shape = self.kernel_core.shape_forge().make_shape(EMPTY_SHAPE_ID, constructor_si);
let prototype = unsafe { &mut *prototype_obj };
prototype.set_shape_id(constructor_shape);
let constructor_pos = prototype.push_prop(func_val);
prototype.set_data_meta(constructor_pos, PropAttributes::new(true, false, true));
prototype.bump_generation();
let prototype_si = self.kernel_core.perm_interner().intern("prototype").0;
let func = unsafe { &mut *obj_ptr };
let prototype_shape = self.kernel_core.shape_forge().make_shape(func.shape_id(), prototype_si);
func.set_shape_id(prototype_shape);
func.ensure_hash_props().push(prototype_val);
func.bump_generation();
}
func_val
}
pub(crate) fn error_text(&self, val: JsValue) -> String {
if let Some(s) = self.lookup_str(val) {
return s;
}
if val.is_object() {
let obj = unsafe { &*val.as_js_object_ptr() };
let name_si = self.kernel_core.perm_interner().intern("name").0;
let message_si = self.kernel_core.perm_interner().intern("message").0;
let name = self
.resolve_property(obj, name_si)
.and_then(|v| self.lookup_str(v))
.unwrap_or_else(|| "Error".to_string());
let message = self
.resolve_property(obj, message_si)
.and_then(|v| self.lookup_str(v))
.unwrap_or_default();
return crate::vm::format_error_message(&name, &message);
}
format!("{val}")
}
fn convert_constant(&self, constant: &Constant) -> JsValue {
match constant {
Constant::Number(v) => JsValue::float(*v),
Constant::Int(v) => JsValue::int(*v),
Constant::String(s) => self.perm_string(s),
Constant::Boolean(b) => JsValue::bool(*b),
Constant::Null => JsValue::null(),
Constant::Undefined => JsValue::undefined(),
}
}
/// 把模块的不可变常量池转换为 `JsValue`。不可能失败——CreateClosure/CreateRegExp
/// 已把函数与正则移出常量池,池中只剩标量 + 永久 intern 字符串值。`&self`
/// 使其可运行于 `OnceLock::get_or_init` 内部。
pub(crate) fn convert_immutables(&self, constants: &[Constant]) -> Vec<JsValue> {
constants.iter().map(|c| self.convert_constant(c)).collect()
}
}
#[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<JsValue> {
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(&module).expect("vm run failed")
}
#[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();
let result = run_source(&mut vm, "Array.prototype.push.call([1], 2)");
assert_eq!(result, JsValue::int(2));
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);
}
#[test]
fn immutables_cache_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.immutables_cache.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_reset_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.reset()").count(), 1);
assert!(production.contains("fn clear_full_reset_state(&mut self)"));
assert!(production.contains("self.gc_state.session_epoch.reset();"));
}
#[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());
}
}