fix: 修复同步调用参数限制与内建语义错误

This commit is contained in:
2026-06-23 14:38:58 +08:00
parent 2d166cbfa5
commit 69500513b3
13 changed files with 675 additions and 189 deletions
+83 -61
View File
@@ -8,7 +8,7 @@ use crate::vm::Vm;
macro_rules! array_ptr {
($vm:expr, $args:expr) => {{
match get_this_array_ref($vm.reg($args[0])) {
match get_this_array_ref($vm, $vm.reg($args[0])) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
}
@@ -23,21 +23,36 @@ macro_rules! array_ptr_len {
}};
}
fn get_this_array_ref(val: JsValue) -> Result<*mut JsObject, JsValue> {
fn array_type_error(vm: &mut Vm, msg: &str) -> JsValue {
crate::builtins::error::create_type_error(vm, msg)
}
fn get_this_array_ref(vm: &mut Vm, val: JsValue) -> Result<*mut JsObject, JsValue> {
if !val.is_object() {
return Err(JsValue::undefined());
return Err(array_type_error(vm, "Array method called on incompatible receiver"));
}
let ptr = val.as_js_object_ptr();
if ptr.is_null() {
return Err(JsValue::undefined());
return Err(array_type_error(vm, "Array method called on incompatible receiver"));
}
let obj = unsafe { &*ptr };
if !obj.is_array() {
return Err(JsValue::undefined());
return Err(array_type_error(vm, "Array method called on incompatible receiver"));
}
Ok(ptr)
}
fn require_callback(vm: &mut Vm, callback_val: JsValue) -> Result<JsValue, JsValue> {
if !callback_val.is_object() {
return Err(array_type_error(vm, "callback is not a function"));
}
let ptr = callback_val.as_js_object_ptr();
if ptr.is_null() || !unsafe { &*ptr }.is_function() {
return Err(array_type_error(vm, "callback is not a function"));
}
Ok(callback_val)
}
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.alloc_object(JsObject::new_array(
@@ -431,12 +446,12 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_for_each(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Err(JsValue::undefined());
}
let callback_val = vm.reg(args[1]);
if !callback_val.is_object() {
return NativeResult::Err(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
for i in 0..n {
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
@@ -452,12 +467,12 @@ pub fn array_for_each(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Err(JsValue::undefined());
}
let callback_val = vm.reg(args[1]);
if !callback_val.is_object() {
return NativeResult::Err(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let new_arr = create_new_array(vm, n);
for i in 0..n {
@@ -479,12 +494,12 @@ pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Err(JsValue::undefined());
}
let callback_val = vm.reg(args[1]);
if !callback_val.is_object() {
return NativeResult::Err(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let mut kept: Vec<JsValue> = Vec::new();
for i in 0..n {
@@ -513,15 +528,15 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if n == 0 && args.len() < 3 {
return NativeResult::Err(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value"));
}
if args.len() < 2 {
return NativeResult::Err(JsValue::undefined());
}
let callback_val = vm.reg(args[1]);
if !callback_val.is_object() {
return NativeResult::Err(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let has_initial = args.len() > 2;
let mut accumulator;
let start_idx;
@@ -552,12 +567,12 @@ pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_find(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Ok(JsValue::undefined());
}
let callback_val = vm.reg(args[1]);
if !callback_val.is_object() {
return NativeResult::Ok(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
for i in 0..n {
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
@@ -578,12 +593,12 @@ pub fn array_find(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_some(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let callback_val = vm.reg(args[1]);
if !callback_val.is_object() {
return NativeResult::Ok(JsValue::bool(false));
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
for i in 0..n {
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
@@ -604,12 +619,12 @@ pub fn array_some(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_every(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let callback_val = vm.reg(args[1]);
if !callback_val.is_object() {
return NativeResult::Ok(JsValue::bool(false));
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
for i in 0..n {
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
@@ -630,12 +645,12 @@ pub fn array_every(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Err(JsValue::undefined());
}
let callback_val = vm.reg(args[1]);
if !callback_val.is_object() {
return NativeResult::Err(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let mut flat: Vec<JsValue> = Vec::new();
for i in 0..n {
@@ -794,9 +809,12 @@ pub fn array_last_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_find_index(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Ok(JsValue::int(-1));
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = vm.reg(args[1]);
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
for i in 0..n {
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
@@ -819,9 +837,12 @@ pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult {
(arr_ptr, len as i32)
};
if args.len() < 2 {
return NativeResult::Ok(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = vm.reg(args[1]);
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
for i in (0..n).rev() {
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
@@ -841,14 +862,17 @@ pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_reduce_right(vm: &mut Vm, args: &[u8]) -> NativeResult {
let (arr_ptr, n) = array_ptr_len!(vm, args);
if args.len() < 2 {
return NativeResult::Err(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
}
let callback_val = vm.reg(args[1]);
let callback_val = match require_callback(vm, vm.reg(args[1])) {
Ok(callback) => callback,
Err(err) => return NativeResult::Err(err),
};
let (mut acc, start_idx): (JsValue, i32) = if args.len() > 2 {
(vm.reg(args[2]), n as i32 - 1)
} else {
if n == 0 {
return NativeResult::Err(JsValue::undefined());
return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value"));
}
(unsafe { (*arr_ptr).get_prop_at(n - 1) }, n as i32 - 2)
};
@@ -874,15 +898,13 @@ pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult {
let mut vals: Vec<JsValue> = (0..len).map(|i| unsafe { (*arr_ptr).get_prop_at(i) }).collect();
let comparator = if args.len() > 1 {
let candidate = vm.reg(args[1]);
if candidate.is_object() {
let ptr = candidate.as_js_object_ptr();
if !ptr.is_null() && unsafe { &*ptr }.is_function() {
Some(candidate)
} else {
None
}
} else {
if candidate.is_undefined() {
None
} else {
match require_callback(vm, candidate) {
Ok(callback) => Some(callback),
Err(err) => return NativeResult::Err(err),
}
}
} else {
None
+19 -4
View File
@@ -69,14 +69,29 @@ pub(crate) fn new_array_buffer(vm: &mut Vm, data: Vec<u8>) -> *mut JsObject {
vm.alloc_object(obj)
}
pub(crate) fn drop_array_buffer_native(obj: &mut JsObject) -> u64 {
fn array_buffer_vec_ptr(obj: &JsObject) -> Option<*mut Vec<u8>> {
if !obj.is_array_buffer_obj() {
return 0;
return None;
}
let Some(ptr) = obj.native_fn() else {
obj.native_fn().map(|ptr| ptr.as_ptr() as *mut Vec<u8>)
}
pub(crate) fn clone_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObject) {
let Some(ptr) = array_buffer_vec_ptr(old_obj) else {
return;
};
if ptr.is_null() {
return;
}
let cloned = unsafe { (&*ptr).clone() };
let cloned_ptr = Box::into_raw(Box::new(cloned));
new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) }));
}
pub(crate) fn drop_array_buffer_native(obj: &mut JsObject) -> u64 {
let Some(data_ptr) = array_buffer_vec_ptr(obj) else {
return 0;
};
let data_ptr = ptr.as_ptr() as *mut Vec<u8>;
if data_ptr.is_null() {
return 0;
}
+44 -1
View File
@@ -10,6 +10,37 @@ pub fn number_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
} else {
0.0
};
let number_proto = vm.session().builtin_world().number_proto.as_ptr() as *mut oxide_types::object::JsObject;
let is_ctor = if let Some(this_reg) = args.first().copied() {
let this_val = vm.reg(this_reg);
if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
if ptr.is_null() {
false
} else {
let proto_ptr = unsafe { (*ptr).proto().as_js_object_ptr() };
!proto_ptr.is_null() && std::ptr::eq(proto_ptr, number_proto)
}
} else {
false
}
} else {
false
};
if is_ctor {
let this_val = vm.reg(args[0]);
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
obj.type_tag = oxide_types::object::JsObject::OBJ_TYPE_NUMBER_OBJ;
let boxed = if n.fract() == 0.0 && n.is_finite() && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
JsValue::int(n as i32)
} else {
JsValue::float(n)
};
obj.set_prop_at(0, boxed);
return NativeResult::Ok(this_val);
}
if n.fract() == 0.0 && n.is_finite() && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
NativeResult::Ok(JsValue::int(n as i32))
} else {
@@ -222,5 +253,17 @@ pub fn number_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
if this_val.is_int() || this_val.is_double() {
return NativeResult::Ok(this_val);
}
NativeResult::Ok(JsValue::float(f64::NAN))
if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
if !ptr.is_null() {
let obj = unsafe { &*ptr };
if obj.is_number_obj() {
return NativeResult::Ok(obj.get_prop_at(0));
}
}
}
NativeResult::Err(crate::builtins::error::create_type_error(
vm,
"Number.prototype.valueOf called on incompatible receiver",
))
}
+4 -3
View File
@@ -4,9 +4,9 @@ use oxide_types::value::JsValue;
impl Vm {
#[inline(always)]
pub(crate) fn dispatch_add(&mut self, rd: usize, a: usize, b: usize) {
let lhs = self.regs[a];
let rhs = self.regs[b];
pub(crate) fn dispatch_add(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
let lhs = self.coerce_primitive_bounded(self.regs[a], false)?;
let rhs = self.coerce_primitive_bounded(self.regs[b], false)?;
if lhs.is_string() || rhs.is_string() {
let ls = coercion::to_string(self.kernel_core.string_forge().as_ref(), lhs);
let rs = coercion::to_string(self.kernel_core.string_forge().as_ref(), rhs);
@@ -17,5 +17,6 @@ impl Vm {
let rn = coercion::to_number(rhs, self.kernel_core.string_forge().as_ref());
self.regs[rd] = JsValue::float(ln + rn);
}
Ok(())
}
}
+51 -46
View File
@@ -1,4 +1,3 @@
use crate::coercion;
use crate::vm::Vm;
use oxide_types::value::JsValue;
@@ -13,92 +12,98 @@ impl Vm {
}
#[inline(always)]
pub(crate) fn dispatch_bit_and(&mut self, rd: usize, a: usize, b: usize) {
let sf = self.kernel_core.string_forge().as_ref();
self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[a], sf) & coercion::to_int32(self.regs[b], sf));
pub(crate) fn dispatch_bit_and(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
self.regs[rd] =
JsValue::int(self.coerce_int32_bounded(self.regs[a])? & self.coerce_int32_bounded(self.regs[b])?);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_bit_or(&mut self, rd: usize, a: usize, b: usize) {
let sf = self.kernel_core.string_forge().as_ref();
self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[a], sf) | coercion::to_int32(self.regs[b], sf));
pub(crate) fn dispatch_bit_or(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
self.regs[rd] =
JsValue::int(self.coerce_int32_bounded(self.regs[a])? | self.coerce_int32_bounded(self.regs[b])?);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_bit_xor(&mut self, rd: usize, a: usize, b: usize) {
let sf = self.kernel_core.string_forge().as_ref();
self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[a], sf) ^ coercion::to_int32(self.regs[b], sf));
pub(crate) fn dispatch_bit_xor(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
self.regs[rd] =
JsValue::int(self.coerce_int32_bounded(self.regs[a])? ^ self.coerce_int32_bounded(self.regs[b])?);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_shl(&mut self, rd: usize, a: usize, b: usize) {
let sf = self.kernel_core.string_forge().as_ref();
let lhs = coercion::to_int32(self.regs[a], sf);
let shift = coercion::to_uint32(self.regs[b], sf) & 0x1F;
pub(crate) fn dispatch_shl(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
let lhs = self.coerce_int32_bounded(self.regs[a])?;
let shift = self.coerce_uint32_bounded(self.regs[b])? & 0x1F;
self.regs[rd] = JsValue::int(lhs.wrapping_shl(shift));
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_shr(&mut self, rd: usize, a: usize, b: usize) {
let sf = self.kernel_core.string_forge().as_ref();
let lhs = coercion::to_int32(self.regs[a], sf);
let shift = coercion::to_uint32(self.regs[b], sf) & 0x1F;
pub(crate) fn dispatch_shr(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
let lhs = self.coerce_int32_bounded(self.regs[a])?;
let shift = self.coerce_uint32_bounded(self.regs[b])? & 0x1F;
self.regs[rd] = JsValue::int(lhs >> shift);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_ushr(&mut self, rd: usize, a: usize, b: usize) {
let sf = self.kernel_core.string_forge().as_ref();
let lhs = coercion::to_uint32(self.regs[a], sf);
let shift = coercion::to_uint32(self.regs[b], sf) & 0x1F;
pub(crate) fn dispatch_ushr(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
let lhs = self.coerce_uint32_bounded(self.regs[a])?;
let shift = self.coerce_uint32_bounded(self.regs[b])? & 0x1F;
self.write_ushr_result(rd, lhs >> shift);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_bit_not(&mut self, rd: usize, a: usize) {
let sf = self.kernel_core.string_forge().as_ref();
self.regs[rd] = JsValue::int(!coercion::to_int32(self.regs[a], sf));
pub(crate) fn dispatch_bit_not(&mut self, rd: usize, a: usize) -> Result<(), String> {
self.regs[rd] = JsValue::int(!self.coerce_int32_bounded(self.regs[a])?);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_compound_bit_and(&mut self, rd: usize, a: usize) {
let sf = self.kernel_core.string_forge().as_ref();
self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[rd], sf) & coercion::to_int32(self.regs[a], sf));
pub(crate) fn dispatch_compound_bit_and(&mut self, rd: usize, a: usize) -> Result<(), String> {
self.regs[rd] =
JsValue::int(self.coerce_int32_bounded(self.regs[rd])? & self.coerce_int32_bounded(self.regs[a])?);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_compound_bit_or(&mut self, rd: usize, a: usize) {
let sf = self.kernel_core.string_forge().as_ref();
self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[rd], sf) | coercion::to_int32(self.regs[a], sf));
pub(crate) fn dispatch_compound_bit_or(&mut self, rd: usize, a: usize) -> Result<(), String> {
self.regs[rd] =
JsValue::int(self.coerce_int32_bounded(self.regs[rd])? | self.coerce_int32_bounded(self.regs[a])?);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_compound_bit_xor(&mut self, rd: usize, a: usize) {
let sf = self.kernel_core.string_forge().as_ref();
self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[rd], sf) ^ coercion::to_int32(self.regs[a], sf));
pub(crate) fn dispatch_compound_bit_xor(&mut self, rd: usize, a: usize) -> Result<(), String> {
self.regs[rd] =
JsValue::int(self.coerce_int32_bounded(self.regs[rd])? ^ self.coerce_int32_bounded(self.regs[a])?);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_compound_shl(&mut self, rd: usize, a: usize) {
let sf = self.kernel_core.string_forge().as_ref();
let lhs = coercion::to_int32(self.regs[rd], sf);
let shift = coercion::to_uint32(self.regs[a], sf) & 0x1F;
pub(crate) fn dispatch_compound_shl(&mut self, rd: usize, a: usize) -> Result<(), String> {
let lhs = self.coerce_int32_bounded(self.regs[rd])?;
let shift = self.coerce_uint32_bounded(self.regs[a])? & 0x1F;
self.regs[rd] = JsValue::int(lhs.wrapping_shl(shift));
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_compound_shr(&mut self, rd: usize, a: usize) {
let sf = self.kernel_core.string_forge().as_ref();
let lhs = coercion::to_int32(self.regs[rd], sf);
let shift = coercion::to_uint32(self.regs[a], sf) & 0x1F;
pub(crate) fn dispatch_compound_shr(&mut self, rd: usize, a: usize) -> Result<(), String> {
let lhs = self.coerce_int32_bounded(self.regs[rd])?;
let shift = self.coerce_uint32_bounded(self.regs[a])? & 0x1F;
self.regs[rd] = JsValue::int(lhs >> shift);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_compound_ushr(&mut self, rd: usize, a: usize) {
let sf = self.kernel_core.string_forge().as_ref();
let lhs = coercion::to_uint32(self.regs[rd], sf);
let shift = coercion::to_uint32(self.regs[a], sf) & 0x1F;
pub(crate) fn dispatch_compound_ushr(&mut self, rd: usize, a: usize) -> Result<(), String> {
let lhs = self.coerce_uint32_bounded(self.regs[rd])?;
let shift = self.coerce_uint32_bounded(self.regs[a])? & 0x1F;
self.write_ushr_result(rd, lhs >> shift);
Ok(())
}
}
+18 -11
View File
@@ -47,7 +47,7 @@ impl Vm {
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let n = coercion::to_number(prop_val, self.kernel_core.string_forge().as_ref());
let n = self.coerce_number_bounded(prop_val)?;
let new_val = JsValue::float(n + 1.0);
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.regs[a] = new_val;
@@ -61,7 +61,7 @@ impl Vm {
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let n = coercion::to_number(prop_val, self.kernel_core.string_forge().as_ref());
let n = self.coerce_number_bounded(prop_val)?;
let new_val = JsValue::float(n - 1.0);
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.regs[a] = new_val;
@@ -76,7 +76,7 @@ impl Vm {
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[a]);
let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?;
let n = coercion::to_number(prop_val, self.kernel_core.string_forge().as_ref());
let n = self.coerce_number_bounded(prop_val)?;
let new_val = JsValue::float(n + 1.0);
self.ordinary_set(obj, prop_name_si, new_val, receiver)?;
self.regs[b] = new_val;
@@ -91,7 +91,7 @@ impl Vm {
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[a]);
let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?;
let n = coercion::to_number(prop_val, self.kernel_core.string_forge().as_ref());
let n = self.coerce_number_bounded(prop_val)?;
let new_val = JsValue::float(n - 1.0);
self.ordinary_set(obj, prop_name_si, new_val, receiver)?;
self.regs[b] = new_val;
@@ -103,9 +103,12 @@ impl Vm {
return Ok(());
};
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let rhs = self.regs[a];
let prop_raw = {
let obj = unsafe { &mut *obj_ptr };
self.read_member_prop(obj, prop_name_si, receiver)?
};
let prop_val = self.coerce_primitive_bounded(prop_raw, false)?;
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?;
let new_val = if prop_val.is_string() || rhs.is_string() {
let ls = coercion::to_string(self.kernel_core.string_forge().as_ref(), prop_val);
let rs = coercion::to_string(self.kernel_core.string_forge().as_ref(), rhs);
@@ -115,6 +118,7 @@ impl Vm {
let rn = coercion::to_number(rhs, self.kernel_core.string_forge().as_ref());
JsValue::float(ln + rn)
};
let obj = unsafe { &mut *obj_ptr };
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.regs[a] = new_val;
Ok(())
@@ -130,12 +134,15 @@ impl Vm {
return Ok(());
};
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let ln = coercion::to_number(prop_val, self.kernel_core.string_forge().as_ref());
let rn = coercion::to_number(self.regs[a], self.kernel_core.string_forge().as_ref());
let ln = {
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
self.coerce_number_bounded(prop_val)?
};
let rn = self.coerce_number_bounded(self.regs[a])?;
let new_val = JsValue::float(op(ln, rn));
self.regs[a] = new_val;
let obj = unsafe { &mut *obj_ptr };
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
Ok(())
}
+96 -1
View File
@@ -220,6 +220,8 @@ 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_array_buffer_obj() {
array_buffer::clone_array_buffer_native(old_ref, new_ref);
} 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() {
@@ -399,7 +401,7 @@ mod tests {
use oxide_types::object::JsObject;
use super::*;
use crate::builtins::{map, set};
use crate::builtins::{array_buffer, data_view, map, set, typed_array};
use crate::native::NativeResult;
use crate::vm::{CallFrame, FrameContinuation};
@@ -655,4 +657,97 @@ mod tests {
assert!(vm.is_session_ptr(edges[0].as_js_object_ptr()));
assert_eq!(vm.session_object_ptrs.len(), 2);
}
#[test]
fn session_gc_keeps_shared_array_buffer_alive_through_view_native_edges() {
let mut vm = vm_with_low_threshold();
let root = plain_object(&mut vm);
vm.regs[1] = JsValue::int(8);
let buffer = native_ok(array_buffer::array_buffer_constructor(&mut vm, &[0, 1]));
vm.regs[1] = buffer;
let typed = native_ok(typed_array::int32array_constructor(&mut vm, &[0, 1]));
vm.regs[1] = buffer;
let view = native_ok(data_view::data_view_constructor(&mut vm, &[0, 1]));
vm.regs[0] = view;
vm.regs[1] = JsValue::int(0);
vm.regs[2] = JsValue::int(42);
vm.regs[3] = JsValue::bool(true);
native_ok(data_view::data_view_set_int32(&mut vm, &[0, 1, 2, 3]));
unsafe {
(*root).set_prop_at(0, typed);
(*root).set_prop_at(1, view);
}
let root_session = vm.promote_object(root);
vm.regs.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(root_session);
let mut gc = std::mem::replace(&mut vm.session_gc, SessionGc::new());
gc.collect(&mut vm);
vm.session_gc = gc;
let live_root = unsafe { &*vm.regs[0].as_js_object_ptr() };
let live_typed = live_root.get_prop_at(0);
let live_view = live_root.get_prop_at(1);
let typed_obj = unsafe { &*live_typed.as_js_object_ptr() };
let view_obj = unsafe { &*live_view.as_js_object_ptr() };
let typed_edges = typed_array::typed_array_native_edges(typed_obj);
let view_edges = data_view::data_view_native_edges(view_obj);
assert_eq!(typed_edges.len(), 1);
assert_eq!(view_edges.len(), 1);
assert!(vm.is_session_ptr(typed_edges[0].as_js_object_ptr()));
assert!(vm.is_session_ptr(view_edges[0].as_js_object_ptr()));
assert!(std::ptr::eq(typed_edges[0].as_js_object_ptr(), view_edges[0].as_js_object_ptr()));
vm.regs[0] = live_typed;
vm.regs[1] = JsValue::int(0);
assert_eq!(native_ok(typed_array::typed_array_at(&mut vm, &[0, 1])), JsValue::int(42));
vm.regs[0] = live_view;
vm.regs[1] = JsValue::int(4);
vm.regs[2] = JsValue::int(7);
vm.regs[3] = JsValue::bool(true);
native_ok(data_view::data_view_set_int32(&mut vm, &[0, 1, 2, 3]));
vm.regs[0] = live_typed;
vm.regs[1] = JsValue::int(1);
assert_eq!(native_ok(typed_array::typed_array_at(&mut vm, &[0, 1])), JsValue::int(7));
}
#[test]
fn session_gc_rewrites_buffer_retained_only_by_data_view_native_edge() {
let mut vm = vm_with_low_threshold();
vm.regs[1] = JsValue::int(8);
let buffer = native_ok(array_buffer::array_buffer_constructor(&mut vm, &[0, 1]));
vm.regs[1] = buffer;
let view = native_ok(data_view::data_view_constructor(&mut vm, &[0, 1]));
vm.regs[0] = view;
vm.regs[1] = JsValue::int(0);
vm.regs[2] = JsValue::int(9);
native_ok(data_view::data_view_set_int32(&mut vm, &[0, 1, 2]));
let view_session = vm.promote_object(view.as_js_object_ptr());
vm.regs.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(view_session);
let mut gc = std::mem::replace(&mut vm.session_gc, SessionGc::new());
gc.collect(&mut vm);
vm.session_gc = gc;
let live_view = vm.regs[0];
let live_view_obj = unsafe { &*live_view.as_js_object_ptr() };
let edges = data_view::data_view_native_edges(live_view_obj);
assert_eq!(edges.len(), 1);
assert!(vm.is_session_ptr(edges[0].as_js_object_ptr()));
assert_eq!(vm.session_object_ptrs.len(), 2);
vm.regs[0] = live_view;
vm.regs[1] = JsValue::int(0);
assert_eq!(native_ok(data_view::data_view_get_int32(&mut vm, &[0, 1])), JsValue::int(9));
}
}
+176 -43
View File
@@ -66,16 +66,16 @@ macro_rules! throw_err {
macro_rules! binary_arith {
($self:ident, $a:expr, $b:expr, $rd:expr, $op:tt) => {{
let l = coercion::to_number($self.regs[$a], $self.kernel_core.string_forge().as_ref());
let r = coercion::to_number($self.regs[$b], $self.kernel_core.string_forge().as_ref());
let l = $self.coerce_number_bounded($self.regs[$a])?;
let r = $self.coerce_number_bounded($self.regs[$b])?;
$self.regs[$rd] = JsValue::float(l $op r);
}}
}
macro_rules! compound_arith {
($self:ident, $rd:expr, $a:expr, $op:tt) => {{
let l = coercion::to_number($self.regs[$rd], $self.kernel_core.string_forge().as_ref());
let r = coercion::to_number($self.regs[$a], $self.kernel_core.string_forge().as_ref());
let l = $self.coerce_number_bounded($self.regs[$rd])?;
let r = $self.coerce_number_bounded($self.regs[$a])?;
$self.regs[$rd] = JsValue::float(l $op r);
}}
}
@@ -175,6 +175,96 @@ pub struct Vm {
}
impl Vm {
const SYNC_NATIVE_ARG_BASE: usize = 0;
const SYNC_NATIVE_ARG_LIMIT: usize = 253;
pub(crate) fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result<JsValue, String> {
if !value.is_object() {
return Ok(value);
}
let obj_ptr = value.as_js_object_ptr();
if obj_ptr.is_null() {
return Ok(value);
}
let method_names = if prefer_string { ["toString", "valueOf"] } else { ["valueOf", "toString"] };
for method_name in method_names {
let method_si = self.kernel_core.string_forge().intern(method_name).0;
let method = {
let obj = unsafe { &*obj_ptr };
self.ordinary_get(obj, method_si, value)?
};
if method.is_undefined() || method.is_null() {
continue;
}
if !method.is_object() {
return Err(format!("TypeError: {method_name} is not callable"));
}
let method_ptr = method.as_js_object_ptr();
if method_ptr.is_null() || !unsafe { &*method_ptr }.is_function() {
return Err(format!("TypeError: {method_name} is not callable"));
}
let result = self.call_function_sync(method, value, &[])?;
if !result.is_object() {
return Ok(result);
}
}
Err("TypeError: Cannot convert object to primitive value".into())
}
pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String> {
let primitive = self.coerce_primitive_bounded(value, false)?;
Ok(coercion::to_number(primitive, self.kernel_core.string_forge().as_ref()))
}
pub(crate) fn coerce_int32_bounded(&mut self, value: JsValue) -> Result<i32, String> {
let n = self.coerce_number_bounded(value)?;
if n == 0.0 || !n.is_finite() {
return Ok(0);
}
let int = n.trunc().rem_euclid(4_294_967_296.0) as u32;
if int > i32::MAX as u32 {
Ok((int as i64 - 4_294_967_296i64) as i32)
} else {
Ok(int as i32)
}
}
pub(crate) fn coerce_uint32_bounded(&mut self, value: JsValue) -> Result<u32, String> {
let n = self.coerce_number_bounded(value)?;
if n == 0.0 || !n.is_finite() {
return Ok(0);
}
Ok(n.trunc().rem_euclid(4_294_967_296.0) as u32)
}
fn pack_sync_native_call_args(
&mut self, receiver: JsValue, callee: JsValue, args: &[JsValue],
) -> Result<Vec<u8>, String> {
if args.len() > Self::SYNC_NATIVE_ARG_LIMIT {
return Err(format!(
"RangeError: synchronous native call supports at most {} arguments",
Self::SYNC_NATIVE_ARG_LIMIT
));
}
self.regs[253] = receiver;
self.regs[254] = callee;
let mut arg_regs = Vec::with_capacity(args.len() + 1);
arg_regs.push(253);
for (idx, arg) in args.iter().enumerate() {
let reg = (Self::SYNC_NATIVE_ARG_BASE + idx) as u8;
self.regs[reg as usize] = *arg;
arg_regs.push(reg);
}
Ok(arg_regs)
}
pub(crate) fn is_session_ptr(&self, obj_ptr: *mut JsObject) -> bool {
if obj_ptr.is_null() {
return false;
@@ -444,27 +534,14 @@ impl Vm {
return Ok(JsValue::undefined());
}
let saved_regs = self.regs;
let saved_this = self.regs[254];
let saved_callee = self.regs[253];
self.regs[253] = receiver;
self.regs[254] = callee;
for (idx, arg) in args.iter().enumerate() {
self.regs[240 + idx] = *arg;
}
let mut arg_regs = [0u8; 17];
arg_regs[0] = 253;
for idx in 0..args.len().min(16) {
arg_regs[idx + 1] = 240 + idx as u8;
}
let arg_regs = self.pack_sync_native_call_args(receiver, callee, args)?;
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
let func: NativeFn = unsafe { native_fn_ptr_to_fn(native_fn) };
self.native_call_depth += 1;
let result = func(self, &arg_regs[..args.len().min(16) + 1]);
let result = func(self, &arg_regs);
self.native_call_depth -= 1;
self.regs = saved_regs;
self.regs[254] = saved_this;
self.regs[253] = saved_callee;
return match result {
NativeResult::Ok(val) => Ok(val),
NativeResult::Err(err) => Err(self.error_text(err)),
@@ -589,7 +666,7 @@ impl Vm {
}
OpCode::ADD => {
self.dispatch_add(rd, a, b);
self.dispatch_add(rd, a, b)?;
}
OpCode::SUB => {
@@ -609,36 +686,36 @@ impl Vm {
}
OpCode::NEG => {
let v = coercion::to_number(self.regs[a], self.kernel_core.string_forge().as_ref());
let v = self.coerce_number_bounded(self.regs[a])?;
self.regs[rd] = JsValue::float(-v);
}
OpCode::BIT_AND => {
self.dispatch_bit_and(rd, a, b);
self.dispatch_bit_and(rd, a, b)?;
}
OpCode::BIT_OR => {
self.dispatch_bit_or(rd, a, b);
self.dispatch_bit_or(rd, a, b)?;
}
OpCode::BIT_XOR => {
self.dispatch_bit_xor(rd, a, b);
self.dispatch_bit_xor(rd, a, b)?;
}
OpCode::SHL => {
self.dispatch_shl(rd, a, b);
self.dispatch_shl(rd, a, b)?;
}
OpCode::SHR => {
self.dispatch_shr(rd, a, b);
self.dispatch_shr(rd, a, b)?;
}
OpCode::USHR => {
self.dispatch_ushr(rd, a, b);
self.dispatch_ushr(rd, a, b)?;
}
OpCode::BIT_NOT => {
self.dispatch_bit_not(rd, a);
self.dispatch_bit_not(rd, a)?;
}
OpCode::EQ => {
@@ -674,7 +751,7 @@ impl Vm {
}
OpCode::UNARY_PLUS => {
let v = coercion::to_number(self.regs[a], self.kernel_core.string_forge().as_ref());
let v = self.coerce_number_bounded(self.regs[a])?;
self.regs[rd] = JsValue::float(v);
}
@@ -962,8 +1039,8 @@ impl Vm {
}
OpCode::COMPOUND_ADD => {
let lhs = self.regs[rd];
let rhs = self.regs[a];
let lhs = self.coerce_primitive_bounded(self.regs[rd], false)?;
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?;
if lhs.is_string() || rhs.is_string() {
let ls = coercion::to_string(self.kernel_core.string_forge().as_ref(), lhs);
let rs = coercion::to_string(self.kernel_core.string_forge().as_ref(), rhs);
@@ -993,33 +1070,33 @@ impl Vm {
}
OpCode::COMPOUND_EXP => {
let l = coercion::to_number(self.regs[rd], self.kernel_core.string_forge().as_ref());
let r = coercion::to_number(self.regs[a], self.kernel_core.string_forge().as_ref());
let l = self.coerce_number_bounded(self.regs[rd])?;
let r = self.coerce_number_bounded(self.regs[a])?;
self.regs[rd] = JsValue::float(l.powf(r));
}
OpCode::COMPOUND_AND => {
self.dispatch_compound_bit_and(rd, a);
self.dispatch_compound_bit_and(rd, a)?;
}
OpCode::COMPOUND_OR => {
self.dispatch_compound_bit_or(rd, a);
self.dispatch_compound_bit_or(rd, a)?;
}
OpCode::COMPOUND_XOR => {
self.dispatch_compound_bit_xor(rd, a);
self.dispatch_compound_bit_xor(rd, a)?;
}
OpCode::COMPOUND_SHL => {
self.dispatch_compound_shl(rd, a);
self.dispatch_compound_shl(rd, a)?;
}
OpCode::COMPOUND_SHR => {
self.dispatch_compound_shr(rd, a);
self.dispatch_compound_shr(rd, a)?;
}
OpCode::COMPOUND_USHR => {
self.dispatch_compound_ushr(rd, a);
self.dispatch_compound_ushr(rd, a)?;
}
OpCode::TYPEOF => {
@@ -1086,27 +1163,27 @@ impl Vm {
}
OpCode::INC_PRE => {
let n = coercion::to_number(self.regs[rd], self.kernel_core.string_forge().as_ref());
let n = self.coerce_number_bounded(self.regs[rd])?;
let result = JsValue::float(n + 1.0);
self.regs[rd] = result;
self.regs[a] = result;
}
OpCode::INC_POST => {
let n = coercion::to_number(self.regs[rd], self.kernel_core.string_forge().as_ref());
let n = self.coerce_number_bounded(self.regs[rd])?;
self.regs[a] = JsValue::float(n);
self.regs[rd] = JsValue::float(n + 1.0);
}
OpCode::DEC_PRE => {
let n = coercion::to_number(self.regs[rd], self.kernel_core.string_forge().as_ref());
let n = self.coerce_number_bounded(self.regs[rd])?;
let result = JsValue::float(n - 1.0);
self.regs[rd] = result;
self.regs[a] = result;
}
OpCode::DEC_POST => {
let n = coercion::to_number(self.regs[rd], self.kernel_core.string_forge().as_ref());
let n = self.coerce_number_bounded(self.regs[rd])?;
self.regs[a] = JsValue::float(n);
self.regs[rd] = JsValue::float(n - 1.0);
}
@@ -1232,6 +1309,15 @@ mod tests {
NativeResult::Ok(JsValue::undefined())
}
fn native_return_last_arg(vm: &mut Vm, args: &[u8]) -> NativeResult {
let reg = *args.last().expect("receiver + args");
NativeResult::Ok(vm.reg(reg))
}
fn native_return_arg_count(_vm: &mut Vm, args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::int(args.len().saturating_sub(1) as i32))
}
fn native_function(vm: &mut Vm, f: crate::native::NativeFn) -> JsValue {
let proto = vm.session.builtin_world().function_proto.as_ptr() as *mut JsObject;
let mut obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
@@ -1514,4 +1600,51 @@ mod tests {
vm.ordinary_set(obj, x_si, JsValue::int(2), obj_val).expect("set");
assert_eq!(vm.ordinary_get(obj, x_si, obj_val).expect("get"), JsValue::int(2));
}
#[test]
fn call_function_sync_passes_high_arity_native_args_without_truncation() {
let mut vm = Vm::new();
let callee = native_function(&mut vm, native_return_last_arg);
let args: Vec<JsValue> = (0..20).map(JsValue::int).collect();
let result = vm
.call_function_sync(callee, JsValue::undefined(), &args)
.expect("high-arity sync call should succeed");
assert_eq!(result, JsValue::int(19));
}
#[test]
fn call_function_sync_reports_actual_native_arg_count() {
let mut vm = Vm::new();
let callee = native_function(&mut vm, native_return_arg_count);
let args: Vec<JsValue> = (0..32).map(JsValue::int).collect();
let result = vm
.call_function_sync(callee, JsValue::undefined(), &args)
.expect("arg count should be preserved");
assert_eq!(result, JsValue::int(32));
}
#[test]
fn call_function_sync_rejects_unrepresentable_native_arity_without_register_corruption() {
let mut vm = Vm::new();
let callee = native_function(&mut vm, native_return_arg_count);
vm.set_reg(1, JsValue::int(7));
vm.set_reg(253, JsValue::int(11));
vm.set_reg(254, JsValue::int(12));
vm.set_reg(255, JsValue::int(13));
let args = vec![JsValue::undefined(); Vm::SYNC_NATIVE_ARG_LIMIT + 1];
let err = vm
.call_function_sync(callee, JsValue::undefined(), &args)
.expect_err("too many args should fail cleanly");
assert!(err.contains("at most 253 arguments"), "unexpected error: {err}");
assert_eq!(vm.reg(1), JsValue::int(7));
assert_eq!(vm.reg(253), JsValue::int(11));
assert_eq!(vm.reg(254), JsValue::int(12));
assert_eq!(vm.reg(255), JsValue::int(13));
}
}