fix: 修复全局边界安全与内存稳定性问题

This commit is contained in:
2026-06-22 19:43:00 +08:00
parent 32af37963e
commit 2d166cbfa5
35 changed files with 620 additions and 135 deletions
+40 -23
View File
@@ -1,5 +1,5 @@
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::JsObject;
use oxide_types::object::{JsObject, MAX_DENSE_PROPS};
use oxide_types::value::JsValue;
use crate::coercion;
@@ -40,8 +40,20 @@ fn get_this_array_ref(val: JsValue) -> Result<*mut JsObject, JsValue> {
fn create_new_array(vm: &mut Vm, n: usize) -> *mut JsObject {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
vm.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump()))
vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(proto),
n.min(MAX_DENSE_PROPS),
vm.epoch().bump(),
))
}
fn array_length_arg(vm: &mut Vm, value: JsValue) -> Result<usize, JsValue> {
let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref());
if !n.is_finite() || n < 0.0 || n.fract() != 0.0 || n > MAX_DENSE_PROPS as f64 {
return Err(crate::builtins::error::create_range_error(vm, "Invalid array length"));
}
Ok(n as usize)
}
fn invoke_native_callback(vm: &mut Vm, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue]) -> NativeResult {
@@ -92,26 +104,18 @@ pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() == 2 {
let val = vm.reg(args[1]);
if val.is_int() {
let n = val.as_int().max(0) as usize;
let arr = vm
.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n, vm.epoch().bump()));
return NativeResult::Ok(JsValue::from_js_object(arr));
}
if val.is_double() {
let n = val.as_double() as usize;
let arr = vm
.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n, vm.epoch().bump()));
if val.is_int() || val.is_double() {
let n = match array_length_arg(vm, val) {
Ok(n) => n,
Err(err) => return NativeResult::Err(err),
};
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n, vm.epoch().bump()));
return NativeResult::Ok(JsValue::from_js_object(arr));
}
}
let n_elems = if args.len() > 1 { args.len() - 1 } else { 0 };
let arr = vm
.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n_elems, vm.epoch().bump()));
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n_elems, vm.epoch().bump()));
for i in 0..n_elems {
unsafe {
(*arr).set_prop_at(i, vm.reg(args[1 + i]));
@@ -373,22 +377,34 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult {
let arr = unsafe { &*arr_ptr };
let n = arr.prop_count() as usize;
let depth = if args.len() > 1 {
(coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32).max(1) as usize
let n = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref());
if !n.is_finite() {
vm.kernel_core.config.max_call_depth
} else {
(n as i32).max(1) as usize
}
} else {
1
};
}
.min(vm.kernel_core.config.max_call_depth);
fn flatten(items: &[JsValue], remaining_depth: usize) -> Vec<JsValue> {
fn flatten(items: &[JsValue], remaining_depth: usize, seen: &mut Vec<*mut JsObject>) -> Vec<JsValue> {
let mut out = Vec::new();
for &v in items {
if remaining_depth > 0 && v.is_object() {
let ptr = v.as_js_object_ptr();
if !ptr.is_null() {
if seen.iter().any(|seen_ptr| std::ptr::eq(*seen_ptr, ptr)) {
out.push(v);
continue;
}
let o = unsafe { &*ptr };
if o.is_array() {
seen.push(ptr);
let on = o.prop_count() as usize;
let sub: Vec<JsValue> = (0..on).map(|i| o.get_prop_at(i)).collect();
let flat = flatten(&sub, remaining_depth - 1);
let flat = flatten(&sub, remaining_depth - 1, seen);
seen.pop();
out.extend(flat);
continue;
}
@@ -400,7 +416,8 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult {
}
let all: Vec<JsValue> = (0..n).map(|i| arr.get_prop_at(i)).collect();
let flat = flatten(&all, depth);
let mut seen = vec![arr_ptr];
let flat = flatten(&all, depth, &mut seen);
let new_arr = create_new_array(vm, flat.len());
unsafe {
for (i, val) in flat.iter().enumerate() {
+19 -1
View File
@@ -66,7 +66,25 @@ pub(crate) fn new_array_buffer(vm: &mut Vm, data: Vec<u8>) -> *mut JsObject {
JsValue::int(len as i32),
PropAttributes::new(false, false, false),
);
vm.epoch().alloc(obj)
vm.alloc_object(obj)
}
pub(crate) fn drop_array_buffer_native(obj: &mut JsObject) -> u64 {
if !obj.is_array_buffer_obj() {
return 0;
}
let Some(ptr) = obj.native_fn() else {
return 0;
};
let data_ptr = ptr.as_ptr() as *mut Vec<u8>;
if data_ptr.is_null() {
return 0;
}
// SAFETY: ArrayBuffer stores `Box<Vec<u8>>` in native_fn.
let data = unsafe { Box::from_raw(data_ptr) };
let bytes = std::mem::size_of::<Vec<u8>>() + data.capacity();
obj.set_native_fn(None);
bytes as u64
}
pub(crate) fn array_buffer_data_ptr(vm: &mut Vm, this_val: JsValue) -> Result<*mut Vec<u8>, JsValue> {
+67 -2
View File
@@ -8,7 +8,7 @@ use crate::native::NativeResult;
use crate::vm::Vm;
#[derive(Clone, Copy)]
struct DataViewData {
pub(crate) struct DataViewData {
buffer: JsValue,
byte_offset: usize,
byte_length: usize,
@@ -177,7 +177,72 @@ pub fn data_view_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
JsValue::int(byte_length as i32),
PropAttributes::new(false, false, false),
);
NativeResult::Ok(JsValue::from_js_object(vm.epoch().alloc(obj)))
NativeResult::Ok(JsValue::from_js_object(vm.alloc_object(obj)))
}
fn data_view_data_ptr(obj: &JsObject) -> Option<*mut DataViewData> {
if !obj.is_data_view_obj() {
return None;
}
obj.native_fn().map(|ptr| ptr.as_ptr() as *mut DataViewData)
}
pub(crate) fn data_view_native_edges(obj: &JsObject) -> Vec<JsValue> {
let Some(ptr) = data_view_data_ptr(obj) else {
return Vec::new();
};
if ptr.is_null() {
return Vec::new();
}
let data = unsafe { *ptr };
if data.buffer.is_object() {
vec![data.buffer]
} else {
Vec::new()
}
}
pub(crate) fn clone_data_view_native_with_rewrite<F>(old_obj: &JsObject, new_obj: &mut JsObject, mut rewrite: F)
where
F: FnMut(JsValue) -> JsValue,
{
let Some(ptr) = data_view_data_ptr(old_obj) else {
return;
};
if ptr.is_null() {
return;
}
let mut data = unsafe { *ptr };
data.buffer = rewrite(data.buffer);
let cloned = Box::into_raw(Box::new(data));
new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned as *const ()) }));
}
pub(crate) fn rewrite_data_view_native<F>(obj: &mut JsObject, mut rewrite: F)
where
F: FnMut(JsValue) -> JsValue,
{
let Some(ptr) = data_view_data_ptr(obj) else {
return;
};
if ptr.is_null() {
return;
}
unsafe {
(*ptr).buffer = rewrite((*ptr).buffer);
}
}
pub(crate) fn drop_data_view_native(obj: &mut JsObject) -> u64 {
let Some(ptr) = data_view_data_ptr(obj) else {
return 0;
};
if ptr.is_null() {
return 0;
}
unsafe { drop(Box::from_raw(ptr)) };
obj.set_native_fn(None);
std::mem::size_of::<DataViewData>() as u64
}
pub fn data_view_get_int8(vm: &mut Vm, args: &[u8]) -> NativeResult {
+1 -1
View File
@@ -201,7 +201,7 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
obj.type_tag = JsObject::OBJ_TYPE_DATE;
obj.set_prop_at(0, JsValue::float(timestamp));
let ptr = vm.epoch().alloc(obj);
let ptr = vm.alloc_object(obj);
NativeResult::Ok(JsValue::from_js_object(ptr))
}
+1 -1
View File
@@ -91,7 +91,7 @@ pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult {
}
let bound_this = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let wrapper = vm.epoch().alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
unsafe {
(*wrapper).set_function(true);
// SAFETY: bind_dispatcher is a NativeFn fn-item; valid to store as NativeFnPtr.
+1 -1
View File
@@ -156,7 +156,7 @@ fn make_native_function(vm: &mut Vm, name: &str, native_fn: *const (), arg_count
// SAFETY: native_fn is provided from a NativeFn fn item.
func.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(native_fn) }));
func.set_native_arg_count(arg_count);
let func = vm.epoch().alloc(func);
let func = vm.alloc_object(func);
let name_si = vm.kernel_core().string_forge().intern("name").0;
let value = vm.intern(name);
let func_ref = unsafe { &mut *func };
+2 -2
View File
@@ -48,7 +48,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue {
serde_json::Value::Array(arr) => {
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let n = arr.len();
let array_obj = vm.epoch().alloc(JsObject::new_array(
let array_obj = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
@@ -72,7 +72,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue {
obj.set_shape_id(new_shape);
obj.ensure_hash_props().push(jsv);
}
let obj_ptr = vm.epoch().alloc(obj);
let obj_ptr = vm.alloc_object(obj);
JsValue::from_js_object(obj_ptr)
}
}
+1 -1
View File
@@ -65,7 +65,7 @@ fn alloc_map(vm: &mut Vm) -> *mut JsObject {
obj.set_map(true);
let inner = new_map_inner();
obj.set_native_data(inner as *mut u8);
vm.epoch().alloc(obj)
vm.alloc_object(obj)
}
pub(crate) fn map_native_edges(obj: &JsObject) -> Vec<JsValue> {
+8 -8
View File
@@ -39,7 +39,7 @@ pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u32)> {
}
pub fn object_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult {
let obj = vm.epoch().alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
NativeResult::Ok(JsValue::from_js_object(obj))
}
@@ -68,7 +68,7 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult {
let n = key_names.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch().alloc(JsObject::new_array(
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
@@ -94,7 +94,7 @@ pub fn object_create(vm: &mut Vm, args: &[u8]) -> NativeResult {
if !proto_val.is_null() && !proto_val.is_object() {
return NativeResult::Ok(JsValue::undefined());
}
let obj = vm.epoch().alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
NativeResult::Ok(JsValue::from_js_object(obj))
}
@@ -412,7 +412,7 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult {
let n = key_names.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch().alloc(JsObject::new_array(
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
@@ -450,7 +450,7 @@ pub fn object_from_entries(vm: &mut Vm, args: &[u8]) -> NativeResult {
if !entries.is_object() {
return NativeResult::Ok(JsValue::null());
}
let obj_ptr = vm.epoch().alloc(JsObject::new_empty(
let obj_ptr = vm.alloc_object(JsObject::new_empty(
EMPTY_SHAPE_ID,
JsValue::from_js_object(vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject),
));
@@ -517,7 +517,7 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult {
let keys = walk_own_keys(vm, obj);
let n = keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch().alloc(JsObject::new_array(
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
@@ -527,7 +527,7 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult {
let key_str = vm.kernel_core().string_forge().lookup(*si).unwrap_or_default();
let key_val = vm.intern(&key_str);
let val = obj.get_prop_at(*offset);
let pair = vm.epoch().alloc(JsObject::new_array(
let pair = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
2,
@@ -552,7 +552,7 @@ pub fn object_values(vm: &mut Vm, args: &[u8]) -> NativeResult {
let keys = walk_own_keys(vm, obj);
let n = keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch().alloc(JsObject::new_array(
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
+1 -1
View File
@@ -221,7 +221,7 @@ fn own_field(vm: &Vm, obj: &JsObject, prop_si: u32) -> Option<JsValue> {
fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch().alloc(JsObject::new_array(
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(proto),
parts.len(),
+19 -3
View File
@@ -103,7 +103,7 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
// SAFETY: re_ptr is a Box<regex::Regex> pointer stored by the constructor via
// NativeFnPtr::from_raw(re_ptr as *const ()). RegExp objects repurpose the native_fn
// field to hold the compiled Regex — not a NativeFn pointer. Valid for the object's
// lifetime because the Box is never freed (intentional leak tied to epoch).
// lifetime; VM reset drops the Box through `drop_regexp_native`.
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(re_ptr as *const ()) }));
}
Err(e) => {
@@ -123,10 +123,26 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
set_prop(&mut obj, "unicode", JsValue::bool(false), vm);
obj.type_tag = JsObject::OBJ_TYPE_REGEXP;
let obj_ptr = vm.epoch().alloc(obj);
let obj_ptr = vm.alloc_object(obj);
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
}
pub(crate) fn drop_regexp_native(obj: &mut JsObject) -> u64 {
if !obj.is_regexp_obj() {
return 0;
}
let Some(ptr) = obj.native_fn() else {
return 0;
};
let regex_ptr = ptr.as_ptr() as *mut regex::Regex;
if regex_ptr.is_null() {
return 0;
}
unsafe { drop(Box::from_raw(regex_ptr)) };
obj.set_native_fn(None);
std::mem::size_of::<regex::Regex>() as u64
}
pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult {
let re_ptr = match get_regexp_ptr(vm, args) {
Ok(ptr) => ptr,
@@ -231,7 +247,7 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult {
set_prop_at(re_ptr, 0, JsValue::int(m.end() as i32));
}
let obj_ptr = vm.epoch().alloc(match_obj);
let obj_ptr = vm.alloc_object(match_obj);
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
} else {
if is_global {
+1 -1
View File
@@ -105,7 +105,7 @@ fn alloc_set(vm: &mut Vm) -> *mut JsObject {
obj.set_set(true);
let inner = new_set_inner();
obj.set_native_data(inner as *mut u8);
vm.epoch().alloc(obj)
vm.alloc_object(obj)
}
pub(crate) fn set_native_edges(obj: &JsObject) -> Vec<JsValue> {
+6
View File
@@ -288,6 +288,9 @@ pub fn string_pad_start(vm: &mut Vm, args: &[u8]) -> NativeResult {
} else {
s_len
};
if target > 10000 {
return NativeResult::Err(crate::builtins::error::create_range_error(vm, "Invalid string length"));
}
let pad = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() };
if s_len >= target || pad.is_empty() {
return NativeResult::Ok(vm.intern(&s));
@@ -308,6 +311,9 @@ pub fn string_pad_end(vm: &mut Vm, args: &[u8]) -> NativeResult {
} else {
s_len
};
if target > 10000 {
return NativeResult::Err(crate::builtins::error::create_range_error(vm, "Invalid string length"));
}
let pad = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() };
if s_len >= target || pad.is_empty() {
return NativeResult::Ok(vm.intern(&s));
+66 -1
View File
@@ -117,7 +117,72 @@ fn create_typed_array(
JsValue::int(length as i32),
PropAttributes::new(false, false, false),
);
vm.epoch().alloc(obj)
vm.alloc_object(obj)
}
fn typed_array_data_ptr(obj: &JsObject) -> Option<*mut TypedArrayData> {
if !obj.is_typed_array_obj() {
return None;
}
obj.native_fn().map(|ptr| ptr.as_ptr() as *mut TypedArrayData)
}
pub(crate) fn typed_array_native_edges(obj: &JsObject) -> Vec<JsValue> {
let Some(ptr) = typed_array_data_ptr(obj) else {
return Vec::new();
};
if ptr.is_null() {
return Vec::new();
}
let data = unsafe { *ptr };
if data.buffer.is_object() {
vec![data.buffer]
} else {
Vec::new()
}
}
pub(crate) fn clone_typed_array_native_with_rewrite<F>(old_obj: &JsObject, new_obj: &mut JsObject, mut rewrite: F)
where
F: FnMut(JsValue) -> JsValue,
{
let Some(ptr) = typed_array_data_ptr(old_obj) else {
return;
};
if ptr.is_null() {
return;
}
let mut data = unsafe { *ptr };
data.buffer = rewrite(data.buffer);
let cloned = Box::into_raw(Box::new(data));
new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned as *const ()) }));
}
pub(crate) fn rewrite_typed_array_native<F>(obj: &mut JsObject, mut rewrite: F)
where
F: FnMut(JsValue) -> JsValue,
{
let Some(ptr) = typed_array_data_ptr(obj) else {
return;
};
if ptr.is_null() {
return;
}
unsafe {
(*ptr).buffer = rewrite((*ptr).buffer);
}
}
pub(crate) fn drop_typed_array_native(obj: &mut JsObject) -> u64 {
let Some(ptr) = typed_array_data_ptr(obj) else {
return 0;
};
if ptr.is_null() {
return 0;
}
unsafe { drop(Box::from_raw(ptr)) };
obj.set_native_fn(None);
std::mem::size_of::<TypedArrayData>() as u64
}
pub(crate) fn get_typed_array_data(vm: &mut Vm, this_val: JsValue) -> Result<TypedArrayData, JsValue> {
+1 -1
View File
@@ -312,7 +312,7 @@ pub fn to_object(val: JsValue, vm: &mut Vm) -> Result<JsValue, &'static str> {
(&*vm.object_prototype as *const JsObject as *mut JsObject, JsObject::OBJ_TYPE_PLAIN)
};
let proto_val = JsValue::from_js_object(proto_ptr);
let obj = vm.epoch.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
let obj_val = JsValue::from_js_object(obj);
let obj_ref = unsafe { &mut *obj };
obj_ref.type_tag = type_tag;
+1 -1
View File
@@ -21,7 +21,7 @@ impl Vm {
&mut self, obj: &JsObject, callee: JsValue, this_reg: u8, first_arg_reg: u8, arg_count: usize,
) -> Result<(), String> {
if self.native_call_depth >= self.kernel_core.config.max_call_depth {
return Err("RangeError: Maximum call stack size exceeded".into());
return self.raise_error_kind("RangeError", "Maximum call stack size exceeded");
}
let (args_buf, len) = Self::build_native_args(first_arg_reg, arg_count, this_reg);
let args_slice = &args_buf[..len];
+4 -2
View File
@@ -5,7 +5,7 @@ use oxide_types::object::JsObject;
use oxide_types::private_key::make_private_name_id;
use oxide_types::value::JsValue;
use crate::vm::Vm;
use crate::vm::{Vm, MAX_PROTO_CHAIN_DEPTH};
#[inline(always)]
fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32) -> Option<JsValue> {
@@ -98,7 +98,9 @@ impl Vm {
return Some(obj.get_prop_at(pos));
}
let mut proto = obj.proto();
while proto.is_object() {
let mut depth = 0usize;
while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH {
depth += 1;
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
if let Some(pos) = self.private_slot(proto_obj, private_key) {
return Some(proto_obj.get_prop_at(pos));
+9 -1
View File
@@ -3,7 +3,7 @@ use std::collections::HashMap;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::builtins::{map, set};
use crate::builtins::{data_view, map, set, typed_array};
use crate::vm::Vm;
impl Vm {
@@ -50,6 +50,14 @@ impl Vm {
set::clone_set_native_with_rewrite(src_ref, dst_ref, |value| {
self.promote_value_if_epoch_object(value, forwarding)
});
} else if src_ref.is_typed_array_obj() {
typed_array::clone_typed_array_native_with_rewrite(src_ref, dst_ref, |value| {
self.promote_value_if_epoch_object(value, forwarding)
});
} else if src_ref.is_data_view_obj() {
data_view::clone_data_view_native_with_rewrite(src_ref, dst_ref, |value| {
self.promote_value_if_epoch_object(value, forwarding)
});
}
dst
}
+27 -3
View File
@@ -5,7 +5,7 @@ use std::time::Instant;
use oxide_types::object::{JsObject, PropMetaEntry};
use oxide_types::value::JsValue;
use crate::builtins::{map, set};
use crate::builtins::{array_buffer, data_view, map, regexp, set, typed_array};
use crate::vm::Vm;
pub struct SessionGc {
@@ -87,6 +87,12 @@ impl SessionGc {
if obj.is_set() {
edges.extend(set::set_native_edges(obj));
}
if obj.is_typed_array_obj() {
edges.extend(typed_array::typed_array_native_edges(obj));
}
if obj.is_data_view_obj() {
edges.extend(data_view::data_view_native_edges(obj));
}
edges
}
@@ -141,7 +147,7 @@ impl SessionGc {
}
}
fn drop_session_object_heap_data(obj_ptr: *mut JsObject) -> u64 {
pub(crate) fn drop_object_heap_data(obj_ptr: *mut JsObject, require_session: bool) -> u64 {
if obj_ptr.is_null() {
return 0;
}
@@ -150,7 +156,9 @@ impl SessionGc {
// `JsObject::ensure_hash_props`/`ensure_prop_meta` and then drop them once here.
unsafe {
let obj = &mut *obj_ptr;
debug_assert!(obj.is_session_epoch());
if require_session {
debug_assert!(obj.is_session_epoch());
}
let mut freed_bytes = 0u64;
let hash_ptr = obj.hash_props_raw() as *mut Vec<JsValue>;
@@ -170,11 +178,19 @@ impl SessionGc {
freed_bytes += map::drop_map_native(obj);
freed_bytes += set::drop_set_native(obj);
freed_bytes += array_buffer::drop_array_buffer_native(obj);
freed_bytes += regexp::drop_regexp_native(obj);
freed_bytes += typed_array::drop_typed_array_native(obj);
freed_bytes += data_view::drop_data_view_native(obj);
freed_bytes
}
}
fn drop_session_object_heap_data(obj_ptr: *mut JsObject) -> u64 {
Self::drop_object_heap_data(obj_ptr, true)
}
fn drop_dead_session_object(obj_ptr: *mut JsObject) -> u64 {
Self::drop_session_object_heap_data(obj_ptr) + size_of::<JsObject>() as u64
}
@@ -204,6 +220,10 @@ impl SessionGc {
map::clone_map_native_with_rewrite(old_ref, new_ref, |value| value);
} else if old_ref.is_set() {
set::clone_set_native_with_rewrite(old_ref, new_ref, |value| value);
} else if old_ref.is_typed_array_obj() {
typed_array::clone_typed_array_native_with_rewrite(old_ref, new_ref, |value| value);
} else if old_ref.is_data_view_obj() {
data_view::clone_data_view_native_with_rewrite(old_ref, new_ref, |value| value);
}
forwarding.insert(old_ptr, new_ptr);
freed_bytes += Self::drop_session_object_heap_data(old_ptr);
@@ -229,6 +249,10 @@ impl SessionGc {
map::rewrite_map_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
} else if obj.is_set() {
set::rewrite_set_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
} else if obj.is_typed_array_obj() {
typed_array::rewrite_typed_array_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
} else if obj.is_data_view_obj() {
data_view::rewrite_data_view_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
}
}
+18 -6
View File
@@ -18,6 +18,8 @@ use oxide_types::mem::{Epoch, P};
use oxide_types::object::{JsObject, NativeFnPtr};
use oxide_types::value::JsValue;
pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024;
/// Convert a `NativeFnPtr` to a callable `NativeFn`.
///
/// # Safety
@@ -145,6 +147,7 @@ pub struct Vm {
pub epoch: Epoch,
pub(crate) session_epoch: bumpalo::Bump,
pub(crate) session_gc: SessionGc,
pub(crate) epoch_object_ptrs: Vec<*mut JsObject>,
pub(crate) session_object_ptrs: Vec<*mut JsObject>,
pub(crate) session_bytes_allocated: usize,
pub object_prototype: P<JsObject>,
@@ -180,6 +183,12 @@ impl Vm {
unsafe { (*obj_ptr).is_session_epoch() }
}
pub(crate) fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject {
let ptr = self.epoch.alloc(obj);
self.epoch_object_ptrs.push(ptr);
ptr
}
pub(crate) fn gc_roots(&self) -> Vec<JsValue> {
let mut roots = Vec::new();
roots.extend_from_slice(&self.regs);
@@ -374,7 +383,9 @@ impl Vm {
}
}
let mut proto = obj.proto();
while proto.is_object() {
let mut depth = 0usize;
while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH {
depth += 1;
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
if let Some(pos) = self
.kernel_core
@@ -429,7 +440,8 @@ impl Vm {
if let Some(native_fn) = callee_obj.native_fn() {
if self.native_call_depth >= self.kernel_core.config.max_call_depth {
return Err("RangeError: Maximum call stack size exceeded".into());
self.raise_error_kind("RangeError", "Maximum call stack size exceeded")?;
return Ok(JsValue::undefined());
}
let saved_regs = self.regs;
let saved_this = self.regs[254];
@@ -488,7 +500,7 @@ impl Vm {
));
}
if self.frames.len() >= self.kernel_core.config.max_call_depth {
return Err("RangeError: Maximum call stack size exceeded".into());
return self.raise_error_kind("RangeError", "Maximum call stack size exceeded");
}
let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone();
@@ -940,7 +952,7 @@ impl Vm {
let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
let n = opcode::imm16(instr) as usize;
let bump = self.epoch.bump();
let obj = self.epoch.alloc(JsObject::new_array(
let obj = self.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(proto_ptr),
n,
@@ -1226,13 +1238,13 @@ mod tests {
obj.set_function(true);
// SAFETY: f is a NativeFn fn-item; valid to store as NativeFnPtr.
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(f as *const ()) }));
JsValue::object(vm.epoch.alloc(obj) as *mut u8)
JsValue::object(vm.alloc_object(obj) as *mut u8)
}
fn plain_object(vm: &mut Vm) -> JsValue {
let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
let obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
JsValue::object(vm.epoch.alloc(obj) as *mut u8)
JsValue::object(vm.alloc_object(obj) as *mut u8)
}
fn add_accessor(vm: &mut Vm, obj_val: JsValue, name: &str, get: JsValue, set: JsValue) {
+14 -3
View File
@@ -1,5 +1,5 @@
use crate::native::{NativeFn, NativeResult};
use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm};
use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH};
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::is_private_name_key;
use oxide_types::value::JsValue;
@@ -37,7 +37,7 @@ impl Vm {
let arg_count = (ext & 0xFF) as usize;
let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject;
let new_obj = self.epoch.alloc(JsObject::new_empty(
let new_obj = self.alloc_object(JsObject::new_empty(
oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
JsValue::from_js_object(proto_ptr),
));
@@ -225,11 +225,17 @@ impl Vm {
};
let mut proto = unsafe { &*lhs_val.as_js_object_ptr() }.proto();
let mut depth = 0usize;
loop {
if !proto.is_object() {
self.regs[rd] = JsValue::bool(false);
break;
}
if depth >= MAX_PROTO_CHAIN_DEPTH {
self.regs[rd] = JsValue::bool(false);
break;
}
depth += 1;
let proto_ptr = proto.as_js_object_ptr();
if proto_ptr == ctor_proto_ptr {
self.regs[rd] = JsValue::bool(true);
@@ -262,11 +268,16 @@ impl Vm {
let mut keys_vec = bumpalo::collections::Vec::new_in(self.epoch.bump());
let mut seen = std::collections::HashSet::new();
let mut current = obj_val;
let mut depth = 0usize;
loop {
if !current.is_object() {
break;
}
if depth >= MAX_PROTO_CHAIN_DEPTH {
break;
}
depth += 1;
let cur = unsafe { &*current.as_js_object_ptr() };
let mut cursor = Some(cur.shape_id());
while let Some(id) = cursor {
@@ -425,7 +436,7 @@ impl Vm {
let excluded: std::collections::HashSet<&str> = excluded.split('\0').filter(|s| !s.is_empty()).collect();
let proto_ptr = self.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
let rest_ptr = self.epoch.alloc(JsObject::new_empty(
let rest_ptr = self.alloc_object(JsObject::new_empty(
oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
JsValue::from_js_object(proto_ptr),
));
+14 -1
View File
@@ -1,6 +1,7 @@
#![allow(clippy::arc_with_non_send_sync)]
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use crate::vm::Vm;
use oxide_kernel::kernel::KernelCore;
@@ -91,7 +92,19 @@ impl VmPool {
};
}
inner = self.condvar.wait(inner).unwrap();
let (guard, wait) = self.condvar.wait_timeout(inner, Duration::from_secs(5)).unwrap();
inner = guard;
if wait.timed_out() {
inner.total_count += 1;
drop(inner);
let vm = Self::new_vm(&self.kernel_core);
return VmGuard {
vm: Some(vm),
pool: Arc::clone(self),
dirty: false,
interned_strings: Vec::new(),
};
}
}
}
}
+38 -31
View File
@@ -1,5 +1,5 @@
use crate::coercion;
use crate::vm::{FrameContinuation, Vm};
use crate::vm::{FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH};
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::value::JsValue;
@@ -20,39 +20,44 @@ impl Vm {
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, target_reg: Option<u8>,
) -> Result<JsValue, String> {
let length_si = self.kernel_core.string_forge().intern("length").0;
if obj.is_array() && prop_name_si == length_si {
return Ok(JsValue::int(obj.prop_count() as i32));
}
if obj.is_array() {
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
if index < obj.prop_vec_len() as u32 {
return Ok(obj.get_prop_at(index));
let mut current = Some(obj);
let mut depth = 0usize;
while let Some(obj) = current {
if obj.is_array() && prop_name_si == length_si {
return Ok(JsValue::int(obj.prop_count() as i32));
}
if obj.is_array() {
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
if index < obj.prop_vec_len() as u32 {
return Ok(obj.get_prop_at(index));
}
}
}
}
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 {
return if meta.get.is_undefined() {
Ok(JsValue::undefined())
} else if let Some(tr) = target_reg {
let getter = meta.get;
let pushed = self.push_bytecode_getter_frame(getter, receiver, tr)?;
if pushed {
return Ok(JsValue::undefined());
}
Ok(self.regs[tr as usize])
} else {
self.call_function_sync(meta.get, receiver, &[])
};
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 {
return if meta.get.is_undefined() {
Ok(JsValue::undefined())
} else if let Some(tr) = target_reg {
let getter = meta.get;
let pushed = self.push_bytecode_getter_frame(getter, receiver, tr)?;
if pushed {
return Ok(JsValue::undefined());
}
Ok(self.regs[tr as usize])
} else {
self.call_function_sync(meta.get, receiver, &[])
};
}
}
return Ok(obj.get_prop_at(pos));
}
return Ok(obj.get_prop_at(pos));
}
let proto = obj.proto();
if proto.is_object() {
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
return self.ordinary_get_inner(proto_obj, prop_name_si, receiver, target_reg);
if depth >= MAX_PROTO_CHAIN_DEPTH {
break;
}
depth += 1;
let proto = obj.proto();
current = proto.is_object().then(|| unsafe { &*proto.as_js_object_ptr() });
}
Ok(JsValue::undefined())
}
@@ -89,7 +94,9 @@ impl Vm {
fn inherited_property_meta(&self, obj: &JsObject, prop_name_si: u32) -> Option<PropMetaEntry> {
let mut proto = obj.proto();
while proto.is_object() {
let mut depth = 0usize;
while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH {
depth += 1;
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
if let Some(pos) = self.get_own_property_slot(proto_obj, prop_name_si) {
return proto_obj.prop_meta_at(pos);
+8 -1
View File
@@ -21,8 +21,14 @@ impl Vm {
));
}
if self.frames.len() >= self.kernel_core.config.max_call_depth {
return Err("RangeError: Maximum call stack size exceeded".into());
self.raise_error_kind("RangeError", "Maximum call stack size exceeded")?;
return Ok(JsValue::undefined());
}
if self.native_call_depth >= self.kernel_core.config.max_call_depth {
self.raise_error_kind("RangeError", "Maximum call stack size exceeded")?;
return Ok(JsValue::undefined());
}
self.native_call_depth += 1;
let sub = self.sub_modules[sub_idx].clone();
let converted_constants = self.convert_constants(&sub.constants).map_err(String::from)?;
@@ -67,6 +73,7 @@ impl Vm {
let _ = callee;
let result = self.dispatch();
self.native_call_depth -= 1;
self.regs = *saved.regs;
self.pc = saved.pc;
+19 -1
View File
@@ -32,6 +32,7 @@ impl Vm {
epoch: Epoch::new(),
session_epoch: bumpalo::Bump::new(),
session_gc: crate::session_gc::SessionGc::new(),
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_bytes_allocated: 0,
object_prototype: obj_proto,
@@ -73,6 +74,7 @@ impl Vm {
epoch: Epoch::new(),
session_epoch: bumpalo::Bump::new(),
session_gc: crate::session_gc::SessionGc::new(),
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_bytes_allocated: 0,
object_prototype: obj_proto,
@@ -124,7 +126,9 @@ impl Vm {
self.clear_execution_state();
self.bytecode.clear();
self.constants.clear();
self.free_epoch_object_heap_data();
self.epoch.reset();
self.epoch_object_ptrs.clear();
self.session_epoch.reset();
self.session_object_ptrs.clear();
self.session_bytes_allocated = 0;
@@ -137,6 +141,17 @@ impl Vm {
self.active_reg_limit = 0;
}
fn free_epoch_object_heap_data(&mut self) {
let mut freed = 0u64;
for ptr in self.epoch_object_ptrs.drain(..) {
freed += crate::session_gc::SessionGc::drop_object_heap_data(ptr, false);
}
if freed > 0 {
self.session_gc.total_bytes_freed = self.session_gc.total_bytes_freed.saturating_add(freed);
self.session_gc.last_collection_bytes_freed = freed;
}
}
pub(crate) fn clear_execution_state(&mut self) {
// Reset contract:
// - Clears register file, pc, frame/iterator stacks, saved execution stacks,
@@ -163,7 +178,9 @@ impl Vm {
self.maybe_collect_session_gc();
self.bytecode.clear();
self.constants.clear();
self.free_epoch_object_heap_data();
self.epoch.reset();
self.epoch_object_ptrs.clear();
self.interned_strings.clear();
self.root_reg_limit = 0;
self.active_reg_limit = 0;
@@ -194,7 +211,7 @@ impl Vm {
obj.set_arrow(true);
obj.set_captured_this(self.regs[254]);
}
let obj_ptr = self.epoch.alloc(obj);
let obj_ptr = self.alloc_object(obj);
let func_val = JsValue::object(obj_ptr as *mut u8);
if !is_arrow {
@@ -202,6 +219,7 @@ impl Vm {
let prototype_obj = self
.epoch
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto_ptr)));
self.epoch_object_ptrs.push(prototype_obj);
let prototype_val = JsValue::from_js_object(prototype_obj);
let constructor_si = self.kernel_core.string_forge().intern("constructor").0;