feat: 添加 session arena 与逃逸晋升
This commit is contained in:
@@ -127,14 +127,13 @@ pub fn array_is_array(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let arr_ptr = array_ptr!(vm, args);
|
||||
let arr = unsafe { &mut *arr_ptr };
|
||||
let mut len = arr.prop_count();
|
||||
let mut len = unsafe { &*arr_ptr }.prop_count();
|
||||
for &arg_reg in args.iter().skip(1) {
|
||||
let val = vm.reg(arg_reg);
|
||||
arr.set_prop_at(len, val);
|
||||
let val = vm.promote_if_needed_for_write_ptr(arr_ptr, vm.reg(arg_reg));
|
||||
unsafe { &mut *arr_ptr }.set_prop_at(len, val);
|
||||
len += 1;
|
||||
}
|
||||
arr.set_prop_count(len);
|
||||
unsafe { &mut *arr_ptr }.set_prop_count(len);
|
||||
NativeResult::Ok(JsValue::int(len as i32))
|
||||
}
|
||||
|
||||
|
||||
@@ -136,24 +136,25 @@ impl Vm {
|
||||
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
if self.is_object_prototype(obj_ptr) && !self.regs[a].is_null() {
|
||||
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
|
||||
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
|
||||
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
|
||||
self.pc += 3;
|
||||
return Ok(());
|
||||
}
|
||||
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
|
||||
self.pc += 3;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let ext0 = self.bytecode[self.pc];
|
||||
let ext1 = self.bytecode[self.pc + 1];
|
||||
let _ext2 = self.bytecode[self.pc + 2];
|
||||
self.pc += 3;
|
||||
let value = self.regs[a];
|
||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
|
||||
let receiver = self.regs[rd];
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
if obj.has_prop_meta() {
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
|
||||
return Ok(());
|
||||
@@ -209,17 +210,20 @@ impl Vm {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "SET_PROP on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
|
||||
if self.is_object_prototype(obj_ptr) && !self.regs[a].is_null() {
|
||||
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
|
||||
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
|
||||
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
|
||||
return Ok(());
|
||||
}
|
||||
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
|
||||
return Ok(());
|
||||
}
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, self.regs[a], self.regs[rd])?;
|
||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -240,17 +244,20 @@ impl Vm {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "SET_PROP_DYNAMIC on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.property_key_si(self.regs[a]);
|
||||
if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
|
||||
if self.is_object_prototype(obj_ptr) && !self.regs[b].is_null() {
|
||||
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
|
||||
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
|
||||
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
|
||||
return Ok(());
|
||||
}
|
||||
obj.set_proto(self.regs[b]).map_err(|e| e.to_string())?;
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
|
||||
return Ok(());
|
||||
}
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, self.regs[b], self.regs[rd])?;
|
||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -265,8 +272,9 @@ impl Vm {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
obj.set_prop_at(idx, self.regs[b]);
|
||||
obj.set_prop_at(idx, value);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod builtins;
|
||||
pub mod coercion;
|
||||
mod dispatch;
|
||||
pub mod native;
|
||||
mod session_arena;
|
||||
pub mod vm;
|
||||
mod vm_dispatch_ctrl;
|
||||
mod vm_dispatch_misc;
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::vm::Vm;
|
||||
|
||||
impl Vm {
|
||||
pub(crate) fn is_session_escape_root_ptr(&self, target_ptr: *mut JsObject) -> bool {
|
||||
if target_ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
|
||||
std::ptr::eq(target_ptr, global_ptr) || unsafe { (&*target_ptr).is_session_epoch() }
|
||||
}
|
||||
|
||||
pub(crate) fn promote_object(&mut self, src: *mut JsObject) -> *mut JsObject {
|
||||
let mut forwarding = HashMap::new();
|
||||
self.promote_object_inner(src, &mut forwarding)
|
||||
}
|
||||
|
||||
pub(crate) fn promote_object_inner(
|
||||
&mut self, src: *mut JsObject, forwarding: &mut HashMap<*mut JsObject, *mut JsObject>,
|
||||
) -> *mut JsObject {
|
||||
if src.is_null() {
|
||||
return src;
|
||||
}
|
||||
let src_ref = unsafe { &*src };
|
||||
if src_ref.is_session_epoch() || !self.epoch.is_epoch_ptr(src.cast::<u8>()) {
|
||||
return src;
|
||||
}
|
||||
if let Some(dst) = forwarding.get(&src).copied() {
|
||||
return dst;
|
||||
}
|
||||
|
||||
let clone = src_ref.clone_for_session_epoch();
|
||||
let dst = self.session_epoch.alloc(clone) as *mut JsObject;
|
||||
forwarding.insert(src, dst);
|
||||
|
||||
let dst_ref = unsafe { &mut *dst };
|
||||
dst_ref.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding));
|
||||
dst
|
||||
}
|
||||
|
||||
pub(crate) fn promote_value_if_epoch_object(
|
||||
&mut self, value: JsValue, forwarding: &mut HashMap<*mut JsObject, *mut JsObject>,
|
||||
) -> JsValue {
|
||||
if !value.is_object() {
|
||||
return value;
|
||||
}
|
||||
let ptr = value.as_js_object_ptr();
|
||||
if ptr.is_null() || !self.epoch.is_epoch_ptr(ptr.cast::<u8>()) {
|
||||
return value;
|
||||
}
|
||||
JsValue::from_js_object(self.promote_object_inner(ptr, forwarding))
|
||||
}
|
||||
|
||||
pub(crate) fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue {
|
||||
if !value.is_object() || !self.is_session_escape_root_ptr(target_ptr) {
|
||||
return value;
|
||||
}
|
||||
let value_ptr = value.as_js_object_ptr();
|
||||
if value_ptr.is_null() || !self.epoch.is_epoch_ptr(value_ptr.cast::<u8>()) {
|
||||
return value;
|
||||
}
|
||||
JsValue::from_js_object(self.promote_object(value_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::object::{PropAttributes, PropMetaEntry};
|
||||
|
||||
fn plain_object(vm: &mut Vm) -> *mut JsObject {
|
||||
let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||
vm.epoch
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)))
|
||||
}
|
||||
|
||||
fn is_epoch_object(vm: &Vm, value: JsValue) -> bool {
|
||||
value.is_object() && vm.epoch.is_epoch_ptr(value.as_js_object_ptr().cast::<u8>())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_arena_promotion_preserves_cycle_identity() {
|
||||
let mut vm = Vm::new();
|
||||
let root = plain_object(&mut vm);
|
||||
unsafe {
|
||||
(*root).set_prop_at(0, JsValue::from_js_object(root));
|
||||
}
|
||||
|
||||
let promoted = vm.promote_object(root);
|
||||
let promoted_obj = unsafe { &*promoted };
|
||||
|
||||
assert!(promoted_obj.is_session_epoch());
|
||||
assert!(std::ptr::eq(promoted_obj.get_prop_at(0).as_js_object_ptr(), promoted));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_arena_promotion_preserves_duplicate_child_reference() {
|
||||
let mut vm = Vm::new();
|
||||
let root = plain_object(&mut vm);
|
||||
let child = plain_object(&mut vm);
|
||||
unsafe {
|
||||
(*root).set_prop_at(0, JsValue::from_js_object(child));
|
||||
(*root).set_prop_at(1, JsValue::from_js_object(child));
|
||||
}
|
||||
|
||||
let promoted = vm.promote_object(root);
|
||||
let promoted_obj = unsafe { &*promoted };
|
||||
let left = promoted_obj.get_prop_at(0).as_js_object_ptr();
|
||||
let right = promoted_obj.get_prop_at(1).as_js_object_ptr();
|
||||
|
||||
assert!(std::ptr::eq(left, right));
|
||||
assert!(!std::ptr::eq(left, child));
|
||||
assert!(unsafe { (&*left).is_session_epoch() });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_arena_captured_and_home_object_links_promote() {
|
||||
let mut vm = Vm::new();
|
||||
let root = plain_object(&mut vm);
|
||||
let proto = plain_object(&mut vm);
|
||||
let captured = plain_object(&mut vm);
|
||||
let home = plain_object(&mut vm);
|
||||
let getter = plain_object(&mut vm);
|
||||
let setter = plain_object(&mut vm);
|
||||
unsafe {
|
||||
(*root).set_proto(JsValue::from_js_object(proto)).expect("proto");
|
||||
(*root).set_captured_this(JsValue::from_js_object(captured));
|
||||
(*root).set_home_object(JsValue::from_js_object(home));
|
||||
(*root).set_prop_at(0, JsValue::undefined());
|
||||
(*root).set_accessor_meta(
|
||||
0,
|
||||
JsValue::from_js_object(getter),
|
||||
JsValue::from_js_object(setter),
|
||||
PropAttributes::DEFAULT_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
let promoted = vm.promote_object(root);
|
||||
let promoted_obj = unsafe { &*promoted };
|
||||
let meta = promoted_obj.prop_meta_at(0).expect("meta");
|
||||
|
||||
assert!(!is_epoch_object(&vm, promoted_obj.proto()));
|
||||
assert!(!is_epoch_object(&vm, promoted_obj.captured_this()));
|
||||
assert!(!is_epoch_object(&vm, promoted_obj.home_object()));
|
||||
assert!(!is_epoch_object(&vm, meta.get));
|
||||
assert!(!is_epoch_object(&vm, meta.set));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_arena_barrier_promotes_global_root_write() {
|
||||
let mut vm = Vm::new();
|
||||
let value = JsValue::from_js_object(plain_object(&mut vm));
|
||||
let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject;
|
||||
|
||||
let promoted = vm.promote_if_needed_for_write_ptr(global_ptr, value);
|
||||
|
||||
assert!(promoted.is_object());
|
||||
assert!(!is_epoch_object(&vm, promoted));
|
||||
assert!(unsafe { (&*promoted.as_js_object_ptr()).is_session_epoch() });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_arena_barrier_promotes_already_session_target_write() {
|
||||
let mut vm = Vm::new();
|
||||
let target_epoch = plain_object(&mut vm);
|
||||
let target = vm.promote_object(target_epoch);
|
||||
let value = JsValue::from_js_object(plain_object(&mut vm));
|
||||
|
||||
let promoted = vm.promote_if_needed_for_write_ptr(target, value);
|
||||
|
||||
assert!(!is_epoch_object(&vm, promoted));
|
||||
assert!(unsafe { (&*promoted.as_js_object_ptr()).is_session_epoch() });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_arena_barrier_leaves_non_escape_target_write_unchanged() {
|
||||
let mut vm = Vm::new();
|
||||
let target = plain_object(&mut vm);
|
||||
let value = JsValue::from_js_object(plain_object(&mut vm));
|
||||
|
||||
let unchanged = vm.promote_if_needed_for_write_ptr(target, value);
|
||||
|
||||
assert_eq!(unchanged, value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_arena_promotion_rewrites_accessor_metadata_values() {
|
||||
let mut vm = Vm::new();
|
||||
let root = plain_object(&mut vm);
|
||||
let getter = plain_object(&mut vm);
|
||||
let setter = plain_object(&mut vm);
|
||||
unsafe {
|
||||
(*root).set_prop_at(0, JsValue::undefined());
|
||||
(*root).set_accessor_meta(
|
||||
0,
|
||||
JsValue::from_js_object(getter),
|
||||
JsValue::from_js_object(setter),
|
||||
PropAttributes::DEFAULT_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
let promoted = vm.promote_object(root);
|
||||
let meta: PropMetaEntry = unsafe { &*promoted }.prop_meta_at(0).expect("meta");
|
||||
|
||||
assert!(!is_epoch_object(&vm, meta.get));
|
||||
assert!(!is_epoch_object(&vm, meta.set));
|
||||
}
|
||||
}
|
||||
@@ -142,6 +142,7 @@ pub struct Vm {
|
||||
pub(crate) session: KernelSession,
|
||||
pub(crate) interned_strings: Vec<u32>,
|
||||
pub epoch: Epoch,
|
||||
pub(crate) session_epoch: bumpalo::Bump,
|
||||
pub object_prototype: P<JsObject>,
|
||||
pub math_rng_state: u64,
|
||||
pub(crate) sub_modules: Vec<CompiledModule>,
|
||||
@@ -835,6 +836,7 @@ impl Vm {
|
||||
if !func_val.is_object() || !home_val.is_object() {
|
||||
throw_err!(self, TypeError, "SET_HOME_OBJECT expects function and object");
|
||||
}
|
||||
let home_val = self.promote_if_needed_for_write_ptr(func_val.as_js_object_ptr(), home_val);
|
||||
let func_obj = unsafe { &mut *func_val.as_js_object_ptr() };
|
||||
if !func_obj.is_function() {
|
||||
throw_err!(self, TypeError, "SET_HOME_OBJECT target is not a function");
|
||||
|
||||
@@ -114,6 +114,7 @@ impl Vm {
|
||||
fn ordinary_set_inner(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, use_frame_push: bool,
|
||||
) -> Result<(), String> {
|
||||
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
|
||||
if let Some(pos) = self.get_own_property_slot(obj, prop_name_si) {
|
||||
if let Some(meta) = obj.prop_meta_at(pos) {
|
||||
if meta.is_accessor {
|
||||
@@ -216,6 +217,7 @@ impl Vm {
|
||||
pub(crate) fn set_member_prop(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
|
||||
) -> Result<(), String> {
|
||||
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
if obj.has_prop_meta() {
|
||||
self.ordinary_set(obj, prop_name_si, val, receiver)?;
|
||||
@@ -230,6 +232,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
|
||||
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
obj.set_prop_at(pos, val);
|
||||
} else {
|
||||
@@ -243,6 +246,7 @@ impl Vm {
|
||||
pub(crate) fn define_data_property(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
|
||||
) -> Result<(), String> {
|
||||
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
|
||||
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
pos
|
||||
} else {
|
||||
@@ -277,6 +281,9 @@ impl Vm {
|
||||
pub(crate) fn define_accessor_property(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, get: JsValue, set: JsValue, attributes: PropAttributes,
|
||||
) -> Result<(), String> {
|
||||
let target_ptr = obj as *mut JsObject;
|
||||
let get = self.promote_if_needed_for_write_ptr(target_ptr, get);
|
||||
let set = self.promote_if_needed_for_write_ptr(target_ptr, set);
|
||||
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
pos
|
||||
} else {
|
||||
|
||||
@@ -30,6 +30,7 @@ impl Vm {
|
||||
session,
|
||||
interned_strings: Vec::new(),
|
||||
epoch: Epoch::new(),
|
||||
session_epoch: bumpalo::Bump::new(),
|
||||
object_prototype: obj_proto,
|
||||
math_rng_state: 0,
|
||||
sub_modules: Vec::new(),
|
||||
@@ -67,6 +68,7 @@ impl Vm {
|
||||
session,
|
||||
interned_strings: Vec::new(),
|
||||
epoch: Epoch::new(),
|
||||
session_epoch: bumpalo::Bump::new(),
|
||||
object_prototype: obj_proto,
|
||||
math_rng_state: 0,
|
||||
sub_modules: Vec::new(),
|
||||
@@ -117,6 +119,7 @@ impl Vm {
|
||||
self.bytecode.clear();
|
||||
self.constants.clear();
|
||||
self.epoch.reset();
|
||||
self.session_epoch.reset();
|
||||
self.interned_strings.clear();
|
||||
self.symbol_counter = 0;
|
||||
self.symbol_descriptions.clear();
|
||||
@@ -404,6 +407,25 @@ mod tests {
|
||||
assert!(!vm.session.is_dirty_since_snapshot());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_epoch_survives_reset() {
|
||||
let mut vm = Vm::new();
|
||||
let session_ptr = vm.session_epoch.alloc(123i32) as *mut i32;
|
||||
|
||||
vm.reset();
|
||||
|
||||
assert!(unsafe { *session_ptr } == 123);
|
||||
}
|
||||
|
||||
#[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.session_epoch.reset()").count(), 1);
|
||||
assert!(production.contains("fn clear_full_reset_state(&mut self)"));
|
||||
assert!(production.contains("self.session_epoch.reset();"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_reset_refreshes_object_prototype_after_object_dirty() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn run_source(vm: &mut Vm, source: &str) -> JsValue {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||
let module = Compiler::new().compile(&program).expect("compile failed");
|
||||
vm.run(&module).expect("vm run failed")
|
||||
}
|
||||
|
||||
fn global_prop(vm: &Vm, name: &str) -> JsValue {
|
||||
let si = vm.kernel_core().string_forge().intern(name).0;
|
||||
let global = vm.session().global_object();
|
||||
let pos = vm
|
||||
.kernel_core()
|
||||
.shape_forge()
|
||||
.lookup_position(global.shape_id(), si)
|
||||
.expect("global property slot");
|
||||
global.get_prop_at(pos)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_object_escape() {
|
||||
let mut vm = Vm::new();
|
||||
run_source(&mut vm, "globalThis.x = {}; globalThis.x.y = 1");
|
||||
vm.reset();
|
||||
|
||||
let result = run_source(&mut vm, "globalThis.x.y");
|
||||
|
||||
assert_eq!(result, JsValue::int(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_escape() {
|
||||
let mut vm = Vm::new();
|
||||
run_source(&mut vm, "globalThis.a = []; globalThis.a.push({ v: 2 })");
|
||||
vm.reset();
|
||||
|
||||
let result = run_source(
|
||||
&mut vm,
|
||||
"var i = 0; while (i < 1000) { var tmp = { v: i }; i = i + 1; } globalThis.a[0].v",
|
||||
);
|
||||
|
||||
assert_eq!(result, JsValue::int(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_closure_captured_this_escape() {
|
||||
let mut vm = Vm::new();
|
||||
run_source(&mut vm, "globalThis.marker = { v: 9 }; globalThis.f = () => this.marker.v");
|
||||
vm.reset();
|
||||
|
||||
let function = run_source(&mut vm, "globalThis.f");
|
||||
let result = run_source(&mut vm, "globalThis.marker.v");
|
||||
let function_obj = unsafe { &*function.as_js_object_ptr() };
|
||||
|
||||
assert!(function.is_object());
|
||||
assert!(function_obj.is_session_epoch());
|
||||
assert_eq!(global_prop(&vm, "f"), function);
|
||||
assert_eq!(result, JsValue::int(9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transitive_escape() {
|
||||
let mut vm = Vm::new();
|
||||
run_source(&mut vm, "globalThis.root = {}");
|
||||
vm.reset();
|
||||
run_source(&mut vm, "globalThis.root.child = { v: 3 }");
|
||||
vm.reset();
|
||||
|
||||
let result = run_source(&mut vm, "globalThis.root.child.v");
|
||||
|
||||
assert_eq!(result, JsValue::int(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_reset_clears_session_state() {
|
||||
let mut vm = Vm::new();
|
||||
run_source(&mut vm, "globalThis.x = {}; globalThis.x.y = 1");
|
||||
vm.reset();
|
||||
assert_eq!(run_source(&mut vm, "globalThis.x.y"), JsValue::int(1));
|
||||
|
||||
vm.full_reset();
|
||||
|
||||
let result = run_source(&mut vm, "globalThis.x");
|
||||
assert!(result.is_undefined());
|
||||
}
|
||||
Reference in New Issue
Block a user