fix: 修复数组回调与原始值属性访问
This commit is contained in:
@@ -109,6 +109,7 @@ pub struct ErrorMethods {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct StringMethods {
|
pub struct StringMethods {
|
||||||
|
pub from_char_code: *const (),
|
||||||
pub index_of: *const (),
|
pub index_of: *const (),
|
||||||
pub includes: *const (),
|
pub includes: *const (),
|
||||||
pub char_at: *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) {
|
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_ptr = P::as_ptr(&self.string_proto) as *mut JsObject;
|
||||||
let proto = unsafe { &mut *proto_ptr };
|
let proto = unsafe { &mut *proto_ptr };
|
||||||
bind_methods!(
|
bind_methods!(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use oxide_types::value::JsValue;
|
|||||||
|
|
||||||
pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||||
let string_methods = StringMethods {
|
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 (),
|
index_of: crate::builtins::string::string_index_of as *const (),
|
||||||
includes: crate::builtins::string::string_includes as *const (),
|
includes: crate::builtins::string::string_includes as *const (),
|
||||||
char_at: crate::builtins::string::string_char_at 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 oxide_types::value::JsValue;
|
||||||
|
|
||||||
use crate::coercion;
|
use crate::coercion;
|
||||||
use crate::native::{NativeFn, NativeResult};
|
use crate::native::NativeResult;
|
||||||
use crate::vm::{native_fn_ptr_to_fn, Vm};
|
use crate::vm::Vm;
|
||||||
|
|
||||||
macro_rules! array_ptr {
|
macro_rules! array_ptr {
|
||||||
($vm:expr, $args:expr) => {{
|
($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 {
|
fn invoke_native_callback(vm: &mut Vm, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue]) -> NativeResult {
|
||||||
if !callback_val.is_object() {
|
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();
|
let cb_ptr = callback_val.as_js_object_ptr();
|
||||||
if cb_ptr.is_null() {
|
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 };
|
let cb = unsafe { &*cb_ptr };
|
||||||
if !cb.is_function() || cb.native_fn().is_none() {
|
if !cb.is_function() {
|
||||||
return NativeResult::Err(JsValue::undefined());
|
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;
|
match vm.call_function_sync(callback_val, this_val, cb_args) {
|
||||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
Ok(value) => NativeResult::Ok(value),
|
||||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(cb.native_fn().unwrap()) };
|
Err(err) => NativeResult::Err(callback_error_from_text(vm, &err)),
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
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() };
|
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
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())
|
NativeResult::Ok(JsValue::undefined())
|
||||||
}
|
}
|
||||||
@@ -432,7 +449,8 @@ pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|||||||
NativeResult::Ok(mapped) => unsafe {
|
NativeResult::Ok(mapped) => unsafe {
|
||||||
(*new_arr).set_prop_at(i, mapped);
|
(*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 {
|
unsafe {
|
||||||
@@ -461,7 +479,8 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|||||||
kept.push(elem);
|
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());
|
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])],
|
&[accumulator, elem, JsValue::int(i as i32), vm.reg(args[0])],
|
||||||
) {
|
) {
|
||||||
NativeResult::Ok(result) => accumulator = result,
|
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)
|
NativeResult::Ok(accumulator)
|
||||||
@@ -531,7 +551,8 @@ pub fn array_find(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|||||||
return NativeResult::Ok(elem);
|
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())
|
NativeResult::Ok(JsValue::undefined())
|
||||||
@@ -556,7 +577,8 @@ pub fn array_some(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|||||||
return NativeResult::Ok(JsValue::bool(true));
|
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))
|
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));
|
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))
|
NativeResult::Ok(JsValue::bool(true))
|
||||||
@@ -617,7 +640,8 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|||||||
}
|
}
|
||||||
flat.push(result);
|
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());
|
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));
|
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))
|
NativeResult::Ok(JsValue::int(-1))
|
||||||
@@ -789,7 +814,8 @@ pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|||||||
return NativeResult::Ok(elem);
|
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())
|
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])],
|
&[acc, elem, JsValue::int(i), vm.reg(args[0])],
|
||||||
) {
|
) {
|
||||||
NativeResult::Ok(r) => acc = r,
|
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)
|
NativeResult::Ok(acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn array_sort(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
let arr_ptr = array_ptr!(vm, _args);
|
let arr_ptr = array_ptr!(vm, args);
|
||||||
let arr = unsafe { &mut *arr_ptr };
|
let len = unsafe { (*arr_ptr).prop_count() as usize };
|
||||||
let len = arr.prop_count() as usize;
|
let mut vals: Vec<JsValue> = (0..len).map(|i| unsafe { (*arr_ptr).get_prop_at(i) }).collect();
|
||||||
let mut vals: Vec<JsValue> = (0..len).map(|i| arr.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| {
|
vals.sort_by(|a, b| {
|
||||||
|
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 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);
|
let sb = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *b);
|
||||||
sa.cmp(&sb)
|
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() {
|
for (i, &v) in vals.iter().enumerate() {
|
||||||
arr.set_prop_at(i, v);
|
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 {
|
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 {
|
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] });
|
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() {
|
if !this_val.is_object() {
|
||||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||||
vm,
|
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 {
|
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] });
|
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() {
|
if !this_val.is_object() {
|
||||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||||
vm,
|
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]))
|
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 {
|
pub fn string_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
let this_val = vm.reg(args[0]);
|
let this_val = vm.reg(args[0]);
|
||||||
if this_val.is_string() {
|
if this_val.is_string() {
|
||||||
|
|||||||
@@ -59,6 +59,36 @@ impl Vm {
|
|||||||
make_private_name_id(local_id)
|
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> {
|
fn private_slot(&self, obj: &JsObject, private_key: u32) -> Option<u32> {
|
||||||
self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key)
|
self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key)
|
||||||
}
|
}
|
||||||
@@ -138,28 +168,12 @@ impl Vm {
|
|||||||
let val = self.regs[a];
|
let val = self.regs[a];
|
||||||
let obj_ptr = val.as_object_ptr() as *mut JsObject;
|
let obj_ptr = val.as_object_ptr() as *mut JsObject;
|
||||||
if obj_ptr.is_null() {
|
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 prop_name_si = self.property_key_si(self.regs[b]);
|
||||||
let resolved = self.ordinary_get(proto, prop_name_si, val)?;
|
if let Some(resolved) = self.primitive_property_get(val, prop_name_si)? {
|
||||||
if !resolved.is_undefined() {
|
|
||||||
self.regs[a] = resolved;
|
self.regs[a] = resolved;
|
||||||
self.pc += 3;
|
self.pc += 3;
|
||||||
return Ok(());
|
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(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.raise_type_error("IC_GET_PROP on non-object")?;
|
self.raise_type_error("IC_GET_PROP on non-object")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -222,7 +236,7 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_ic_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
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(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,11 +300,15 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_get_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
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 {
|
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "GET_PROP on non-object")? else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let obj = unsafe { &*obj_ptr };
|
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)?;
|
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() {
|
if self.accessor_frame_target_reg.take().is_none() {
|
||||||
self.regs[a] = val;
|
self.regs[a] = val;
|
||||||
@@ -299,7 +317,7 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
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(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
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> {
|
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 {
|
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "GET_PROP_DYNAMIC on non-object")? else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let obj = unsafe { &*obj_ptr };
|
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)?;
|
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() {
|
if self.accessor_frame_target_reg.take().is_none() {
|
||||||
self.regs[b] = val;
|
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> {
|
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(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let prop_name_si = self.property_key_si(self.regs[a]);
|
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> {
|
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(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let idx = if self.regs[a].is_int() {
|
let idx = if self.regs[a].is_int() {
|
||||||
|
|||||||
@@ -12,6 +12,18 @@ fn eval(source: &str) -> Result<(Vm, JsValue), String> {
|
|||||||
Ok((vm, result))
|
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]
|
#[test]
|
||||||
fn array_push_adds_element() {
|
fn array_push_adds_element() {
|
||||||
let (_vm, result) = eval("var a = [1,2]; a.push(3); a").unwrap();
|
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);
|
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]
|
#[test]
|
||||||
fn array_copy_within_copies() {
|
fn array_copy_within_copies() {
|
||||||
let (_vm, result) = eval("[1,2,3,4,5].copyWithin(0,3)").unwrap();
|
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();
|
let r = eval(&mut vm, "new Boolean(false).toString()").unwrap();
|
||||||
assert_eq!(str_val(&vm, r), "false");
|
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()
|
.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]
|
#[test]
|
||||||
fn string_index_of_found() {
|
fn string_index_of_found() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
@@ -23,6 +28,13 @@ fn string_index_of_found() {
|
|||||||
assert_eq!(result.as_int(), 1);
|
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]
|
#[test]
|
||||||
fn string_index_of_not_found() {
|
fn string_index_of_not_found() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
@@ -86,6 +98,26 @@ fn string_to_upper_case() {
|
|||||||
assert_eq!(to_str(&vm, s), "HELLO");
|
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]
|
#[test]
|
||||||
fn string_to_lower_case() {
|
fn string_to_lower_case() {
|
||||||
let mut vm = Vm::new();
|
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_int32(JsValue::float(f64::INFINITY), sf), 0);
|
||||||
assert_eq!(coercion::to_uint32(JsValue::int(-1), sf), 4_294_967_295);
|
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