fix: 修复数组回调与原始值属性访问

This commit is contained in:
爱特每羊队
2026-06-17 12:59:11 +08:00
parent 9db28b22d6
commit 5d168316ad
10 changed files with 328 additions and 66 deletions
+5
View File
@@ -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 (),
+112 -39
View File
@@ -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 {
+10
View File
@@ -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,
+13
View File
@@ -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() {
+49 -27
View File
@@ -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();
+14
View File
@@ -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}"
);
}
}