mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
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",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"oxide_compiler",
|
||||
"oxide_kernel",
|
||||
"oxide_log",
|
||||
"oxide_parser",
|
||||
"oxide_runtime_api",
|
||||
"oxide_types",
|
||||
"oxide_vm",
|
||||
"regex",
|
||||
"ryu",
|
||||
"serde_json",
|
||||
|
||||
@@ -19,3 +19,8 @@ serde_json.workspace = true
|
||||
chrono.workspace = true
|
||||
unicode-normalization.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 {
|
||||
if args.len() < 2 {
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
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 obj_ptr = match require_obj_arg(vm, args, "keys") {
|
||||
Ok(ptr) => ptr,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
};
|
||||
|
||||
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 {
|
||||
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]);
|
||||
if !proto_val.is_null() && !proto_val.is_object() {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
if proto_val.is_null() {
|
||||
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));
|
||||
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() {
|
||||
return NativeResult::Ok(target_val);
|
||||
}
|
||||
{
|
||||
let target = unsafe { &mut *target_ptr };
|
||||
for &arg_reg in args.iter().skip(2) {
|
||||
let source_val = vm.reg(arg_reg);
|
||||
if !source_val.is_object() {
|
||||
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 mut all_assignments: Vec<(u32, JsValue, JsValue)> = Vec::new();
|
||||
for &arg_reg in args.iter().skip(2) {
|
||||
let source_val = vm.reg(arg_reg);
|
||||
if !source_val.is_object() {
|
||||
continue;
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -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 {
|
||||
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]);
|
||||
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();
|
||||
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]);
|
||||
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();
|
||||
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 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
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
if lhs.is_double() && rhs.is_double() {
|
||||
return strict_double_eq(lhs.as_double(), rhs.as_double());
|
||||
|
||||
Reference in New Issue
Block a user