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> {