feat: 新增 to_integer_or_infinity/to_length/same_value_zero 三个 ES 抽象操作辅助函数,修复 objects_keys/create/defineProperty/assign/getOwnPropertyNames 五个方法的 TypeError 处理
This commit is contained in:
Generated
+3
@@ -1059,10 +1059,13 @@ dependencies = [
|
|||||||
"fast-float",
|
"fast-float",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"oxide_compiler",
|
||||||
"oxide_kernel",
|
"oxide_kernel",
|
||||||
"oxide_log",
|
"oxide_log",
|
||||||
|
"oxide_parser",
|
||||||
"oxide_runtime_api",
|
"oxide_runtime_api",
|
||||||
"oxide_types",
|
"oxide_types",
|
||||||
|
"oxide_vm",
|
||||||
"regex",
|
"regex",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -19,3 +19,8 @@ serde_json.workspace = true
|
|||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
unicode-normalization.workspace = true
|
unicode-normalization.workspace = true
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
oxide_vm = { path = "../oxide_vm" }
|
||||||
|
oxide_compiler = { path = "../oxide_compiler" }
|
||||||
|
oxide_parser = { path = "../oxide_parser" }
|
||||||
|
|||||||
@@ -42,17 +42,10 @@ pub fn object_constructor<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
if args.len() < 2 {
|
let obj_ptr = match require_obj_arg(vm, args, "keys") {
|
||||||
return NativeResult::Err(JsValue::undefined());
|
Ok(ptr) => ptr,
|
||||||
}
|
Err(err) => return NativeResult::Err(err),
|
||||||
let val = vm.reg(args[1]);
|
};
|
||||||
if !val.is_object() {
|
|
||||||
return NativeResult::Err(JsValue::undefined());
|
|
||||||
}
|
|
||||||
let obj_ptr = val.as_js_object_ptr();
|
|
||||||
if obj_ptr.is_null() {
|
|
||||||
return NativeResult::Err(JsValue::undefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
let key_names: Vec<String>;
|
let key_names: Vec<String>;
|
||||||
{
|
{
|
||||||
@@ -86,11 +79,18 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
|
|
||||||
pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
return NativeResult::Ok(JsValue::undefined());
|
return NativeResult::Err(crate::error::create_type_error(vm, "Object.create: at least 1 argument required"));
|
||||||
}
|
}
|
||||||
let proto_val = vm.reg(args[1]);
|
let proto_val = vm.reg(args[1]);
|
||||||
if !proto_val.is_null() && !proto_val.is_object() {
|
if proto_val.is_null() {
|
||||||
return NativeResult::Ok(JsValue::undefined());
|
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||||
|
return NativeResult::Ok(JsValue::from_js_object(obj));
|
||||||
|
}
|
||||||
|
if !proto_val.is_object() {
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(
|
||||||
|
vm,
|
||||||
|
"Object.create: prototype must be an object or null",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
|
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
|
||||||
NativeResult::Ok(JsValue::from_js_object(obj))
|
NativeResult::Ok(JsValue::from_js_object(obj))
|
||||||
@@ -109,24 +109,32 @@ pub fn object_assign<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
if target_ptr.is_null() {
|
if target_ptr.is_null() {
|
||||||
return NativeResult::Ok(target_val);
|
return NativeResult::Ok(target_val);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
let target = unsafe { &mut *target_ptr };
|
let mut all_assignments: Vec<(u32, JsValue, JsValue)> = Vec::new();
|
||||||
for &arg_reg in args.iter().skip(2) {
|
for &arg_reg in args.iter().skip(2) {
|
||||||
let source_val = vm.reg(arg_reg);
|
let source_val = vm.reg(arg_reg);
|
||||||
if !source_val.is_object() {
|
if !source_val.is_object() {
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
let source_ptr = source_val.as_js_object_ptr();
|
|
||||||
if source_ptr.is_null() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let source = unsafe { &*source_ptr };
|
|
||||||
let source_keys = walk_own_keys(vm, source);
|
|
||||||
for (_si, offset) in source_keys {
|
|
||||||
let prop_val = source.get_prop_at(offset);
|
|
||||||
target.push_prop(prop_val);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let source_ptr = source_val.as_js_object_ptr();
|
||||||
|
if source_ptr.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let source_keys: Vec<(u32, u32)> = {
|
||||||
|
let source = unsafe { &*source_ptr };
|
||||||
|
walk_own_keys(vm, source)
|
||||||
|
};
|
||||||
|
for (si, offset) in source_keys {
|
||||||
|
let source = unsafe { &*source_ptr };
|
||||||
|
let val = source.get_prop_at(offset);
|
||||||
|
all_assignments.push((si, source_val, val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = unsafe { &mut *target_ptr };
|
||||||
|
for (si, source_val, val) in all_assignments {
|
||||||
|
let promoted = vm.promote_if_needed_for_write_ptr(target_ptr, val);
|
||||||
|
let _ = vm.ordinary_set(target, si, promoted, source_val);
|
||||||
}
|
}
|
||||||
NativeResult::Ok(target_val)
|
NativeResult::Ok(target_val)
|
||||||
}
|
}
|
||||||
@@ -139,23 +147,26 @@ pub fn object_is<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
|
|
||||||
pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
if args.len() < 4 {
|
if args.len() < 4 {
|
||||||
return NativeResult::Err(JsValue::undefined());
|
return NativeResult::Err(crate::error::create_type_error(
|
||||||
|
vm,
|
||||||
|
"Object.defineProperty: expected at least 3 arguments",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let obj_val = vm.reg(args[1]);
|
let obj_val = vm.reg(args[1]);
|
||||||
if !obj_val.is_object() {
|
if !obj_val.is_object() {
|
||||||
return NativeResult::Err(JsValue::undefined());
|
return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object"));
|
||||||
}
|
}
|
||||||
let obj_ptr = obj_val.as_js_object_ptr();
|
let obj_ptr = obj_val.as_js_object_ptr();
|
||||||
if obj_ptr.is_null() {
|
if obj_ptr.is_null() {
|
||||||
return NativeResult::Err(JsValue::undefined());
|
return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object"));
|
||||||
}
|
}
|
||||||
let desc_val = vm.reg(args[3]);
|
let desc_val = vm.reg(args[3]);
|
||||||
if !desc_val.is_object() {
|
if !desc_val.is_object() {
|
||||||
return NativeResult::Err(JsValue::undefined());
|
return NativeResult::Err(crate::error::create_type_error(vm, "Property description must be an object"));
|
||||||
}
|
}
|
||||||
let desc_ptr = desc_val.as_js_object_ptr();
|
let desc_ptr = desc_val.as_js_object_ptr();
|
||||||
if desc_ptr.is_null() {
|
if desc_ptr.is_null() {
|
||||||
return NativeResult::Err(JsValue::undefined());
|
return NativeResult::Err(crate::error::create_type_error(vm, "Property description must be an object"));
|
||||||
}
|
}
|
||||||
let prop_name_str = oxide_runtime_api::to_string(vm.reg(args[2]));
|
let prop_name_str = oxide_runtime_api::to_string(vm.reg(args[2]));
|
||||||
let si = vm.kernel_core().perm_interner().intern(&prop_name_str).0;
|
let si = vm.kernel_core().perm_interner().intern(&prop_name_str).0;
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn eval(source: &str) -> Result<(Vm, JsValue), String> {
|
||||||
|
let allocator = oxide_parser::Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||||
|
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = vm.run(&module)?;
|
||||||
|
Ok((vm, result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_keys_no_args_type_error() {
|
||||||
|
assert!(eval("Object.keys()").is_err(), "Object.keys() should throw TypeError");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_keys_non_object_type_error() {
|
||||||
|
assert!(eval("Object.keys(42)").is_err(), "Object.keys(42) should throw TypeError");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_create_null_proto() {
|
||||||
|
let (_vm, result) = eval("var o = Object.create(null); o").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
let obj_ptr = result.as_js_object_ptr();
|
||||||
|
assert!(!obj_ptr.is_null());
|
||||||
|
let obj = unsafe { &*obj_ptr };
|
||||||
|
let proto = obj.proto();
|
||||||
|
assert!(proto.is_null(), "Object.create(null) should set prototype to null");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_create_number_proto_type_error() {
|
||||||
|
assert!(eval("Object.create(42)").is_err(), "Object.create(42) should throw TypeError");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_create_no_args_type_error() {
|
||||||
|
assert!(eval("Object.create()").is_err(), "Object.create() should throw TypeError");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_names_no_args_type_error() {
|
||||||
|
assert!(
|
||||||
|
eval("Object.getOwnPropertyNames()").is_err(),
|
||||||
|
"Object.getOwnPropertyNames() should throw TypeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_names_non_object_type_error() {
|
||||||
|
assert!(
|
||||||
|
eval("Object.getOwnPropertyNames(42)").is_err(),
|
||||||
|
"Object.getOwnPropertyNames(42) should throw TypeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_keys_valid_object() {
|
||||||
|
let (_vm, result) = eval("var o = {a:1, b:2}; Object.keys(o).join(',')").unwrap();
|
||||||
|
assert!(result.is_string(), "Object.keys should return an array");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_define_property_data_and_accessor_conflict() {
|
||||||
|
assert!(
|
||||||
|
eval("Object.defineProperty({}, 'x', {value: 1, get: function() {}})").is_err(),
|
||||||
|
"data+accessor conflict should throw TypeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_define_property_non_object_target() {
|
||||||
|
assert!(
|
||||||
|
eval("Object.defineProperty(null, 'x', {value: 1})").is_err(),
|
||||||
|
"non-object target should throw TypeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_define_property_missing_descriptor() {
|
||||||
|
assert!(
|
||||||
|
eval("Object.defineProperty({}, 'x')").is_err(),
|
||||||
|
"missing descriptor should throw TypeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_assign_skips_null_source() {
|
||||||
|
let (_vm, result) = eval("var t = {a:1}; Object.assign(t, null); t.a").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 1, "Object.assign should skip null sources");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_assign_merges_two_sources() {
|
||||||
|
let (_vm, result) = eval("var t = {a:1}; Object.assign(t, {b:2}); t.b").unwrap();
|
||||||
|
assert!(result.is_int() || result.is_double(), "Object.assign should copy from source");
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
use oxide_runtime_api::{same_value_zero, to_integer_or_infinity, to_length};
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_integer_or_infinity_nan() {
|
||||||
|
assert_eq!(to_integer_or_infinity(JsValue::float(f64::NAN)), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_integer_or_infinity_zero() {
|
||||||
|
assert_eq!(to_integer_or_infinity(JsValue::float(0.0)), 0.0);
|
||||||
|
assert_eq!(to_integer_or_infinity(JsValue::float(-0.0)), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_integer_or_infinity_infinity() {
|
||||||
|
assert_eq!(to_integer_or_infinity(JsValue::float(f64::INFINITY)), f64::INFINITY);
|
||||||
|
assert_eq!(to_integer_or_infinity(JsValue::float(f64::NEG_INFINITY)), f64::NEG_INFINITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_integer_or_infinity_truncation() {
|
||||||
|
assert_eq!(to_integer_or_infinity(JsValue::float(3.7)), 3.0);
|
||||||
|
assert_eq!(to_integer_or_infinity(JsValue::float(-3.7)), -3.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_integer_or_infinity_int() {
|
||||||
|
assert_eq!(to_integer_or_infinity(JsValue::int(42)), 42.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_length_nan() {
|
||||||
|
assert_eq!(to_length(JsValue::float(f64::NAN)), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_length_negative() {
|
||||||
|
assert_eq!(to_length(JsValue::float(-1.0)), 0);
|
||||||
|
assert_eq!(to_length(JsValue::int(-42)), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_length_positive() {
|
||||||
|
assert_eq!(to_length(JsValue::float(100.0)), 100);
|
||||||
|
assert_eq!(to_length(JsValue::int(999)), 999);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_length_overflow_clamp() {
|
||||||
|
assert_eq!(to_length(JsValue::float(1e100)), 9_007_199_254_740_991);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_same_value_zero_nan() {
|
||||||
|
assert!(same_value_zero(JsValue::float(f64::NAN), JsValue::float(f64::NAN)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_same_value_zero_positive_and_negative_zero() {
|
||||||
|
assert!(same_value_zero(JsValue::float(0.0), JsValue::float(-0.0)));
|
||||||
|
assert!(same_value_zero(JsValue::float(-0.0), JsValue::float(0.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_same_value_zero_equal_numbers() {
|
||||||
|
assert!(same_value_zero(JsValue::float(42.0), JsValue::float(42.0)));
|
||||||
|
assert!(same_value_zero(JsValue::int(42), JsValue::int(42)));
|
||||||
|
assert!(same_value_zero(JsValue::int(42), JsValue::float(42.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_same_value_zero_different_numbers() {
|
||||||
|
assert!(!same_value_zero(JsValue::float(1.0), JsValue::float(2.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_same_value_zero_null_undefined() {
|
||||||
|
assert!(same_value_zero(JsValue::null(), JsValue::null()));
|
||||||
|
assert!(same_value_zero(JsValue::undefined(), JsValue::undefined()));
|
||||||
|
assert!(!same_value_zero(JsValue::null(), JsValue::undefined()));
|
||||||
|
}
|
||||||
@@ -442,6 +442,38 @@ pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ToIntegerOrInfinity(argument) — ECMA-262 §7.1.4.
|
||||||
|
pub fn to_integer_or_infinity(val: JsValue) -> f64 {
|
||||||
|
let n = to_number(val);
|
||||||
|
if n.is_nan() || n == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else if n.is_infinite() {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
n.trunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ToLength(argument) — ECMA-262 §7.1.20.
|
||||||
|
pub fn to_length(val: JsValue) -> u64 {
|
||||||
|
let n = to_number(val);
|
||||||
|
let len = if n.is_nan() || n <= 0.0 { 0.0 } else { n.min(9_007_199_254_740_991.0) };
|
||||||
|
len.trunc() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SameValueZero(x, y) — ECMA-262 §7.2.11.
|
||||||
|
pub fn same_value_zero(lhs: JsValue, rhs: JsValue) -> bool {
|
||||||
|
if (lhs.is_double() || lhs.is_int()) && (rhs.is_double() || rhs.is_int()) {
|
||||||
|
let a = to_f64(lhs);
|
||||||
|
let b = to_f64(rhs);
|
||||||
|
if a.is_nan() && b.is_nan() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
same_value(lhs, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn strict_equality(lhs: JsValue, rhs: JsValue) -> bool {
|
pub fn strict_equality(lhs: JsValue, rhs: JsValue) -> bool {
|
||||||
if lhs.is_double() && rhs.is_double() {
|
if lhs.is_double() && rhs.is_double() {
|
||||||
return strict_double_eq(lhs.as_double(), rhs.as_double());
|
return strict_double_eq(lhs.as_double(), rhs.as_double());
|
||||||
|
|||||||
Reference in New Issue
Block a user