mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 修复同步调用参数限制与内建语义错误
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user