mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 修复数组回调与原始值属性访问
This commit is contained in:
@@ -109,6 +109,7 @@ pub struct ErrorMethods {
|
||||
}
|
||||
|
||||
pub struct StringMethods {
|
||||
pub from_char_code: *const (),
|
||||
pub index_of: *const (),
|
||||
pub includes: *const (),
|
||||
pub char_at: *const (),
|
||||
@@ -938,6 +939,10 @@ impl BuiltinWorld {
|
||||
}
|
||||
|
||||
pub fn bind_string_methods(&self, methods: &StringMethods, string_forge: &StringForge, shape_forge: &ShapeForge) {
|
||||
let ctor_ptr = P::as_ptr(&self.string_constructor) as *mut JsObject;
|
||||
let ctor = unsafe { &mut *ctor_ptr };
|
||||
bind_methods!(self, ctor, string_forge, shape_forge, ("fromCharCode", methods.from_char_code, 1),);
|
||||
|
||||
let proto_ptr = P::as_ptr(&self.string_proto) as *mut JsObject;
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
bind_methods!(
|
||||
|
||||
@@ -7,6 +7,7 @@ use oxide_types::value::JsValue;
|
||||
|
||||
pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||
let string_methods = StringMethods {
|
||||
from_char_code: crate::builtins::string::string_from_char_code as *const (),
|
||||
index_of: crate::builtins::string::string_index_of as *const (),
|
||||
includes: crate::builtins::string::string_includes as *const (),
|
||||
char_at: crate::builtins::string::string_char_at as *const (),
|
||||
|
||||
@@ -3,8 +3,8 @@ use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::coercion;
|
||||
use crate::native::{NativeFn, NativeResult};
|
||||
use crate::vm::{native_fn_ptr_to_fn, Vm};
|
||||
use crate::native::NativeResult;
|
||||
use crate::vm::Vm;
|
||||
|
||||
macro_rules! array_ptr {
|
||||
($vm:expr, $args:expr) => {{
|
||||
@@ -46,31 +46,44 @@ fn create_new_array(vm: &mut Vm, n: usize) -> *mut JsObject {
|
||||
|
||||
fn invoke_native_callback(vm: &mut Vm, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue]) -> NativeResult {
|
||||
if !callback_val.is_object() {
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(vm, "callback is not a function"));
|
||||
}
|
||||
let cb_ptr = callback_val.as_js_object_ptr();
|
||||
if cb_ptr.is_null() {
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(vm, "callback is not a function"));
|
||||
}
|
||||
let cb = unsafe { &*cb_ptr };
|
||||
if !cb.is_function() || cb.native_fn().is_none() {
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
if !cb.is_function() {
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(vm, "callback is not a function"));
|
||||
}
|
||||
// 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(cb.native_fn().unwrap()) };
|
||||
|
||||
let base = 240u8;
|
||||
let n = cb_args.len().min(15);
|
||||
let mut args_buf = [0u8; 17];
|
||||
vm.set_reg(base, this_val);
|
||||
args_buf[0] = base;
|
||||
for i in 0..n {
|
||||
vm.set_reg(base + 1 + i as u8, cb_args[i]);
|
||||
args_buf[i + 1] = base + 1 + i as u8;
|
||||
match vm.call_function_sync(callback_val, this_val, cb_args) {
|
||||
Ok(value) => NativeResult::Ok(value),
|
||||
Err(err) => NativeResult::Err(callback_error_from_text(vm, &err)),
|
||||
}
|
||||
}
|
||||
|
||||
func(vm, &args_buf[..n + 1])
|
||||
fn callback_error_from_text(vm: &mut Vm, err: &str) -> JsValue {
|
||||
let err = err.strip_prefix("uncaught ").unwrap_or(err);
|
||||
if let Some(msg) = err.strip_prefix("TypeError: ") {
|
||||
return crate::builtins::error::create_type_error(vm, msg);
|
||||
}
|
||||
if let Some(msg) = err.strip_prefix("ReferenceError: ") {
|
||||
return crate::builtins::error::create_reference_error(vm, msg);
|
||||
}
|
||||
if let Some(msg) = err.strip_prefix("RangeError: ") {
|
||||
return crate::builtins::error::create_range_error(vm, msg);
|
||||
}
|
||||
if let Some(msg) = err.strip_prefix("SyntaxError: ") {
|
||||
return crate::builtins::error::create_syntax_error(vm, msg);
|
||||
}
|
||||
if let Some(msg) = err.strip_prefix("Error: ") {
|
||||
return crate::builtins::error::create_error(vm, msg);
|
||||
}
|
||||
crate::builtins::error::create_error(vm, err)
|
||||
}
|
||||
|
||||
fn unexpected_tail_call_error(vm: &mut Vm) -> NativeResult {
|
||||
NativeResult::Err(crate::builtins::error::create_type_error(vm, "unexpected tail call in array callback"))
|
||||
}
|
||||
|
||||
pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -410,7 +423,11 @@ pub fn array_for_each(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
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) };
|
||||
let _ = invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) {
|
||||
NativeResult::Ok(_) => {}
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
@@ -432,7 +449,8 @@ pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(mapped) => unsafe {
|
||||
(*new_arr).set_prop_at(i, mapped);
|
||||
},
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
@@ -461,7 +479,8 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
kept.push(elem);
|
||||
}
|
||||
}
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
let new_arr = create_new_array(vm, kept.len());
|
||||
@@ -506,7 +525,8 @@ pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
&[accumulator, elem, JsValue::int(i as i32), vm.reg(args[0])],
|
||||
) {
|
||||
NativeResult::Ok(result) => accumulator = result,
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(accumulator)
|
||||
@@ -531,7 +551,8 @@ pub fn array_find(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Ok(elem);
|
||||
}
|
||||
}
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
@@ -556,7 +577,8 @@ pub fn array_some(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
}
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::bool(false))
|
||||
@@ -581,7 +603,8 @@ pub fn array_every(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
}
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::bool(true))
|
||||
@@ -617,7 +640,8 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
flat.push(result);
|
||||
}
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
let new_arr = create_new_array(vm, flat.len());
|
||||
@@ -765,7 +789,8 @@ pub fn array_find_index(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Ok(JsValue::int(i as i32));
|
||||
}
|
||||
}
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::int(-1))
|
||||
@@ -789,7 +814,8 @@ pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Ok(elem);
|
||||
}
|
||||
}
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
@@ -818,26 +844,73 @@ pub fn array_reduce_right(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
&[acc, elem, JsValue::int(i), vm.reg(args[0])],
|
||||
) {
|
||||
NativeResult::Ok(r) => acc = r,
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(acc)
|
||||
}
|
||||
|
||||
pub fn array_sort(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
let arr_ptr = array_ptr!(vm, _args);
|
||||
let arr = unsafe { &mut *arr_ptr };
|
||||
let len = arr.prop_count() as usize;
|
||||
let mut vals: Vec<JsValue> = (0..len).map(|i| arr.get_prop_at(i)).collect();
|
||||
pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let arr_ptr = array_ptr!(vm, args);
|
||||
let len = unsafe { (*arr_ptr).prop_count() as usize };
|
||||
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 {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut sort_error = None;
|
||||
vals.sort_by(|a, b| {
|
||||
let sa = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *a);
|
||||
let sb = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *b);
|
||||
sa.cmp(&sb)
|
||||
if sort_error.is_some() {
|
||||
return std::cmp::Ordering::Equal;
|
||||
}
|
||||
if let Some(callback) = comparator {
|
||||
match invoke_native_callback(vm, callback, JsValue::undefined(), &[*a, *b]) {
|
||||
NativeResult::Ok(result) => {
|
||||
let n = coercion::to_number(result, vm.kernel_core().string_forge().as_ref());
|
||||
if n.is_nan() || n == 0.0 {
|
||||
std::cmp::Ordering::Equal
|
||||
} else if n < 0.0 {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
}
|
||||
NativeResult::Err(err) => {
|
||||
sort_error = Some(err);
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
NativeResult::TailCall { .. } => {
|
||||
sort_error =
|
||||
Some(crate::builtins::error::create_type_error(vm, "unexpected tail call in array callback"));
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let sa = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *a);
|
||||
let sb = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *b);
|
||||
sa.cmp(&sb)
|
||||
}
|
||||
});
|
||||
if let Some(err) = sort_error {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
let arr = unsafe { &mut *arr_ptr };
|
||||
for (i, &v) in vals.iter().enumerate() {
|
||||
arr.set_prop_at(i, v);
|
||||
}
|
||||
NativeResult::Ok(vm.reg(_args[0]))
|
||||
NativeResult::Ok(vm.reg(args[0]))
|
||||
}
|
||||
|
||||
pub fn array_values(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
@@ -57,6 +57,9 @@ pub fn boolean_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
pub fn boolean_prototype_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if this_val.is_bool() {
|
||||
return NativeResult::Ok(this_val);
|
||||
}
|
||||
if !this_val.is_object() {
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
@@ -76,6 +79,13 @@ pub fn boolean_prototype_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if this_val.is_bool() {
|
||||
return if this_val.as_bool() {
|
||||
NativeResult::Ok(vm.intern("true"))
|
||||
} else {
|
||||
NativeResult::Ok(vm.intern("false"))
|
||||
};
|
||||
}
|
||||
if !this_val.is_object() {
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
|
||||
@@ -11,6 +11,19 @@ fn this_string(vm: &Vm, args: &[u8]) -> String {
|
||||
coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[0]))
|
||||
}
|
||||
|
||||
pub fn string_from_char_code(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let mut out = String::new();
|
||||
for &arg_reg in args.iter().skip(1) {
|
||||
let code = coercion::to_uint32(vm.reg(arg_reg), vm.kernel_core().string_forge().as_ref()) & 0xFFFF;
|
||||
if let Some(ch) = char::from_u32(code) {
|
||||
out.push(ch);
|
||||
} else {
|
||||
out.push('\u{FFFD}');
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(vm.intern(&out))
|
||||
}
|
||||
|
||||
pub fn string_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(args[0]);
|
||||
if this_val.is_string() {
|
||||
|
||||
@@ -59,6 +59,36 @@ impl Vm {
|
||||
make_private_name_id(local_id)
|
||||
}
|
||||
|
||||
fn primitive_property_get(&mut self, val: JsValue, prop_name_si: u32) -> Result<Option<JsValue>, String> {
|
||||
if val.is_string() {
|
||||
let length_si = self.kernel_core.string_forge().intern("length").0;
|
||||
if prop_name_si == length_si {
|
||||
let len = self
|
||||
.kernel_core
|
||||
.string_forge()
|
||||
.lookup(val.as_string_index())
|
||||
.unwrap_or_default()
|
||||
.encode_utf16()
|
||||
.count();
|
||||
return Ok(Some(JsValue::int(len as i32)));
|
||||
}
|
||||
let proto_ptr = self.session.builtin_world().string_proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &*proto_ptr };
|
||||
return self.ordinary_get(proto, prop_name_si, val).map(Some);
|
||||
}
|
||||
if val.is_int() || val.is_double() {
|
||||
let proto_ptr = self.session.builtin_world().number_proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &*proto_ptr };
|
||||
return self.ordinary_get(proto, prop_name_si, val).map(Some);
|
||||
}
|
||||
if val.is_bool() {
|
||||
let proto_ptr = self.session.builtin_world().boolean_proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &*proto_ptr };
|
||||
return self.ordinary_get(proto, prop_name_si, val).map(Some);
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn private_slot(&self, obj: &JsObject, private_key: u32) -> Option<u32> {
|
||||
self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key)
|
||||
}
|
||||
@@ -138,27 +168,11 @@ impl Vm {
|
||||
let val = self.regs[a];
|
||||
let obj_ptr = val.as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
if val.is_string() {
|
||||
let proto_ptr = self.session.builtin_world().string_proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &*proto_ptr };
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
let resolved = self.ordinary_get(proto, prop_name_si, val)?;
|
||||
if !resolved.is_undefined() {
|
||||
self.regs[a] = resolved;
|
||||
self.pc += 3;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if val.is_int() || val.is_double() {
|
||||
let proto_ptr = self.session.builtin_world().number_proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &*proto_ptr };
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
let resolved = self.ordinary_get(proto, prop_name_si, val)?;
|
||||
if !resolved.is_undefined() {
|
||||
self.regs[a] = resolved;
|
||||
self.pc += 3;
|
||||
return Ok(());
|
||||
}
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
if let Some(resolved) = self.primitive_property_get(val, prop_name_si)? {
|
||||
self.regs[a] = resolved;
|
||||
self.pc += 3;
|
||||
return Ok(());
|
||||
}
|
||||
self.raise_type_error("IC_GET_PROP on non-object")?;
|
||||
return Ok(());
|
||||
@@ -222,7 +236,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn dispatch_ic_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "IC_SET_PROP on non-object")? else {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
@@ -286,11 +300,15 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn dispatch_get_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
if let Some(value) = self.primitive_property_get(self.regs[rd], prop_name_si)? {
|
||||
self.regs[a] = value;
|
||||
return Ok(());
|
||||
}
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "GET_PROP on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
let val = self.ordinary_get_with_target(obj, prop_name_si, self.regs[rd], a as u8)?;
|
||||
if self.accessor_frame_target_reg.take().is_none() {
|
||||
self.regs[a] = val;
|
||||
@@ -299,7 +317,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn dispatch_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "SET_PROP on non-object")? else {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
@@ -320,11 +338,15 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn dispatch_get_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let prop_name_si = self.property_key_si(self.regs[a]);
|
||||
if let Some(value) = self.primitive_property_get(self.regs[rd], prop_name_si)? {
|
||||
self.regs[b] = value;
|
||||
return Ok(());
|
||||
}
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "GET_PROP_DYNAMIC on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let prop_name_si = self.property_key_si(self.regs[a]);
|
||||
let val = self.ordinary_get_with_target(obj, prop_name_si, self.regs[rd], b as u8)?;
|
||||
if self.accessor_frame_target_reg.take().is_none() {
|
||||
self.regs[b] = val;
|
||||
@@ -333,7 +355,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn dispatch_set_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "SET_PROP_DYNAMIC on non-object")? else {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let prop_name_si = self.property_key_si(self.regs[a]);
|
||||
@@ -354,7 +376,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn dispatch_set_elem(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "SET_ELEM on non-object")? else {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let idx = if self.regs[a].is_int() {
|
||||
|
||||
@@ -12,6 +12,18 @@ fn eval(source: &str) -> Result<(Vm, JsValue), String> {
|
||||
Ok((vm, result))
|
||||
}
|
||||
|
||||
fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||
vm.kernel_core()
|
||||
.string_forge()
|
||||
.lookup(val.as_string_index())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn assert_num_eq(val: JsValue, expected: f64) {
|
||||
let actual = if val.is_int() { val.as_int() as f64 } else { val.as_double() };
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_push_adds_element() {
|
||||
let (_vm, result) = eval("var a = [1,2]; a.push(3); a").unwrap();
|
||||
@@ -246,6 +258,66 @@ fn array_sort_default() {
|
||||
assert_eq!(obj.prop_count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_map_filter_reduce_accept_bytecode_callbacks() {
|
||||
let (vm, result) = eval("[1,2,3].map(function(x){return x+1}).join(',')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "2,3,4");
|
||||
|
||||
let (vm, result) = eval("[1,2,3].map(x => x * 2).join(',')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "2,4,6");
|
||||
|
||||
let (vm, result) = eval("[1,2,3,4].filter(x => x % 2 === 0).join(',')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "2,4");
|
||||
|
||||
let (_vm, result) = eval("[1,2,3].reduce((a,b)=>a+b,0)").unwrap();
|
||||
assert_num_eq(result, 6.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_iteration_predicates_accept_bytecode_callbacks() {
|
||||
let (_vm, result) = eval("[1,2,3].forEach(function(x){return x+1})").unwrap();
|
||||
assert!(result.is_undefined());
|
||||
|
||||
let (_vm, result) = eval("[1,2,3].find(x => x > 1)").unwrap();
|
||||
assert_num_eq(result, 2.0);
|
||||
|
||||
let (_vm, result) = eval("[1,2,3].some(x => x === 2)").unwrap();
|
||||
assert_eq!(result, JsValue::bool(true));
|
||||
|
||||
let (_vm, result) = eval("[1,2,3].every(function(x){return x > 0})").unwrap();
|
||||
assert_eq!(result, JsValue::bool(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_callback_exceptions_propagate() {
|
||||
let (vm, result) = eval("try{[1].forEach(()=>{throw new TypeError('boom')})}catch(e){e.message}").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "boom");
|
||||
|
||||
let (vm, result) = eval("try{[1].map(()=>{throw new Error('x')})}catch(e){e.message}").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "x");
|
||||
|
||||
let (vm, result) = eval("try{[1].filter(()=>{throw new Error('f')})}catch(e){e.message}").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "f");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_sort_uses_user_comparator() {
|
||||
let (vm, result) = eval("[3,1,2].sort((a,b)=>a-b).join(',')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "1,2,3");
|
||||
|
||||
let (vm, result) = eval("[3,1,2].sort((a,b)=>b-a).join(',')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "3,2,1");
|
||||
|
||||
let (vm, result) = eval("[10,9,1].sort().join(',')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "1,10,9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_sort_propagates_comparator_exception() {
|
||||
let (vm, result) = eval("try{[1,2,3].sort(()=>{throw new Error('cmp')})}catch(e){e.message}").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "cmp");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_copy_within_copies() {
|
||||
let (_vm, result) = eval("[1,2,3,4,5].copyWithin(0,3)").unwrap();
|
||||
|
||||
@@ -106,3 +106,23 @@ fn boolean_new_to_string() {
|
||||
let r = eval(&mut vm, "new Boolean(false).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_primitive_to_string_autoboxes() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(&mut vm, "true.toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true");
|
||||
|
||||
let r = eval(&mut vm, "false.toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_primitive_value_of_autoboxes() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(&mut vm, "false.valueOf()").unwrap();
|
||||
assert_eq!(r, JsValue::bool(false));
|
||||
|
||||
let r = eval(&mut vm, "(true).constructor === Boolean").unwrap();
|
||||
assert_eq!(r, JsValue::bool(true));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn assert_num_eq(val: JsValue, expected: f64) {
|
||||
let actual = if val.is_int() { val.as_int() as f64 } else { val.as_double() };
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_index_of_found() {
|
||||
let mut vm = Vm::new();
|
||||
@@ -23,6 +28,13 @@ fn string_index_of_found() {
|
||||
assert_eq!(result.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_from_char_code_static() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "String.fromCharCode(65,66,67)").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "ABC");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_index_of_not_found() {
|
||||
let mut vm = Vm::new();
|
||||
@@ -86,6 +98,26 @@ fn string_to_upper_case() {
|
||||
assert_eq!(to_str(&vm, s), "HELLO");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_primitive_length_autoboxes() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'abc'.length").unwrap();
|
||||
assert_num_eq(result, 3.0);
|
||||
|
||||
let result = eval(&mut vm, "''.length").unwrap();
|
||||
assert_num_eq(result, 0.0);
|
||||
|
||||
let result = eval(&mut vm, "'abc'.length + 'de'.length").unwrap();
|
||||
assert_num_eq(result, 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_length_does_not_break_methods() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hello'.toUpperCase()").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "HELLO");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_to_lower_case() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
@@ -105,3 +105,17 @@ fn test_to_int32_and_to_uint32() {
|
||||
assert_eq!(coercion::to_int32(JsValue::float(f64::INFINITY), sf), 0);
|
||||
assert_eq!(coercion::to_uint32(JsValue::int(-1), sf), 4_294_967_295);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primitive_property_write_throws_clean_type_error() {
|
||||
for source in ["'abc'.x = 1", "(5).foo = 1", "true.foo = 1"] {
|
||||
let err = eval(source);
|
||||
assert!(err.contains("TypeError"), "expected TypeError for {source}, got {err}");
|
||||
assert!(
|
||||
!err.contains("IC_SET_PROP on non-object")
|
||||
&& !err.contains("SET_PROP on non-object")
|
||||
&& !err.contains("SET_PROP_DYNAMIC on non-object"),
|
||||
"unexpected internal opcode message for {source}: {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user