mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 18:51:13 +08:00
fix: 读路径按规范跳洞对齐并补 HasProperty 存在判定
This commit is contained in:
@@ -177,6 +177,14 @@ pub(crate) fn unit_string_value<H: VmHost>(vm: &mut H, unit: u16) -> JsValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// 索引位置存在性判定(规范 HasProperty:自身与原型链任一层命中即存在,
|
||||
/// 数组元素区 hole 视同缺失,TypedArray 整数索引按视图长度判在界)。
|
||||
pub(crate) fn arraylike_index_present<H: VmHost>(vm: &mut H, ptr: *mut JsObject, i: usize) -> bool {
|
||||
// SAFETY: ptr 来自 this/参数对象,指向存活对象。
|
||||
let key_si = vm.string_key_si(&i.to_string());
|
||||
vm.has_property(unsafe { &*ptr }, key_si)
|
||||
}
|
||||
|
||||
/// 元素读取失败时直接把异常作为 NativeResult::Err 返回。
|
||||
macro_rules! arraylike_get_or_err {
|
||||
($vm:expr, $ptr:expr, $i:expr) => {
|
||||
|
||||
@@ -9,8 +9,9 @@ use crate::builtins_debug;
|
||||
use crate::builtins_error;
|
||||
|
||||
use super::common::{
|
||||
array_ptr, array_type_error, arraylike_get, arraylike_get_or_err, check_array_create_len, clamp_relative,
|
||||
create_new_array, get_this_array_ref, get_this_arraylike, js_array_index, to_integer_or_infinity_bounded,
|
||||
array_ptr, array_type_error, arraylike_get, arraylike_get_or_err, arraylike_index_present, check_array_create_len,
|
||||
clamp_relative, create_new_array, get_this_array_ref, get_this_arraylike, js_array_index,
|
||||
to_integer_or_infinity_bounded,
|
||||
};
|
||||
use super::from::{array_species_create, create_data_property_or_throw, from_engine_error};
|
||||
|
||||
@@ -125,28 +126,38 @@ pub fn array_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Ok(p) => p,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
};
|
||||
// HasProperty + Get + CreateDataPropertyOrThrow 逐元素拷贝。
|
||||
// HasProperty + Get + CreateDataPropertyOrThrow 逐元素拷贝;洞位只在真数组
|
||||
// 目标上留洞(目标预填 present-undefined,须显式置洞)。
|
||||
let mut k = start;
|
||||
let mut out_idx = 0usize;
|
||||
let mut holes: Vec<usize> = Vec::new();
|
||||
while k < end {
|
||||
let key_str = vm.new_string(&k.to_string());
|
||||
let key_si = vm.property_key_si(key_str);
|
||||
if vm.resolve_property(unsafe { &*arr_ptr }, key_si).is_some() {
|
||||
if arraylike_index_present(vm, arr_ptr, k) {
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, k);
|
||||
let out_key = vm.new_string(&out_idx.to_string());
|
||||
let out_key_si = vm.property_key_si(out_key);
|
||||
if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, out_key_si, elem) {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
out_idx += 1;
|
||||
} else {
|
||||
holes.push(out_idx);
|
||||
}
|
||||
out_idx += 1;
|
||||
k += 1;
|
||||
}
|
||||
// 收尾 Set(A, "length", final):普通 Set 语义(可触发继承 setter/异常)。
|
||||
// 收尾 Set(A, "length", final):终长 = 区间长度(end - start),洞位计入;
|
||||
// 普通 Set 语义(可触发继承 setter/异常)。
|
||||
let a_val = JsValue::from_js_object(a_ptr);
|
||||
if unsafe { &*a_ptr }.is_array() {
|
||||
for p in holes {
|
||||
unsafe {
|
||||
(*a_ptr).mark_hole_at(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
let length_key = vm.new_string("length");
|
||||
let length_si = vm.property_key_si(length_key);
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *a_ptr }, length_si, js_array_index(out_idx), a_val, true) {
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *a_ptr }, length_si, js_array_index(count), a_val, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
NativeResult::Ok(JsValue::from_js_object(a_ptr))
|
||||
@@ -225,12 +236,19 @@ pub fn array_concat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
// this 与数组实参的洞位按缺失复制:结果对应位留洞(记录洞下标,收尾置洞)。
|
||||
let mut all: Vec<JsValue> = Vec::new();
|
||||
let mut holes: Vec<usize> = Vec::new();
|
||||
if let Err(err) = check_array_create_len(vm, n) {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
for i in 0..n {
|
||||
all.push(arraylike_get_or_err!(vm, arr_ptr, i));
|
||||
if arraylike_index_present(vm, arr_ptr, i) {
|
||||
all.push(arraylike_get_or_err!(vm, arr_ptr, i));
|
||||
} else {
|
||||
holes.push(all.len());
|
||||
all.push(JsValue::undefined());
|
||||
}
|
||||
}
|
||||
for &arg_reg in args.iter().skip(1) {
|
||||
let val = vm.reg(arg_reg);
|
||||
@@ -239,10 +257,16 @@ pub fn array_concat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if !o_ptr.is_null() {
|
||||
let o = unsafe { &*o_ptr };
|
||||
let on = o.prop_count() as usize;
|
||||
// 真数组时展开元素。
|
||||
// 真数组时展开元素(洞位同样留洞)。
|
||||
if o.is_array() && on > 0 {
|
||||
let dense = o.array_elements_meta_vec().is_none();
|
||||
for i in 0..on {
|
||||
all.push(o.get_prop_at(i));
|
||||
if dense || !o.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
|
||||
all.push(o.get_prop_at(i));
|
||||
} else {
|
||||
holes.push(all.len());
|
||||
all.push(JsValue::undefined());
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -255,6 +279,9 @@ pub fn array_concat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
for (i, val) in all.iter().enumerate() {
|
||||
(*new_arr).set_prop_at(i, *val);
|
||||
}
|
||||
for h in holes {
|
||||
(*new_arr).mark_hole_at(h);
|
||||
}
|
||||
(*new_arr).set_prop_count(all.len());
|
||||
}
|
||||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||||
@@ -329,6 +356,10 @@ pub fn array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
0
|
||||
};
|
||||
for i in from_index..n {
|
||||
// 洞位不参与比较:HasProperty 缺失即跳过(洞不是 undefined 元素)。
|
||||
if !arraylike_index_present(vm, ptr, i) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, ptr, i);
|
||||
if oxide_runtime_api::strict_equality(elem, target) {
|
||||
return NativeResult::Ok(js_array_index(i));
|
||||
@@ -426,8 +457,15 @@ pub fn array_flat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let o = unsafe { &*ptr };
|
||||
if o.is_array() {
|
||||
seen.push(ptr);
|
||||
// 嵌套展开丢弃洞位:flat 结果恒为紧凑数组。
|
||||
let on = o.prop_count() as usize;
|
||||
let sub: Vec<JsValue> = (0..on).map(|i| o.get_prop_at(i)).collect();
|
||||
let dense = o.array_elements_meta_vec().is_none();
|
||||
let mut sub: Vec<JsValue> = Vec::with_capacity(on);
|
||||
for i in 0..on {
|
||||
if dense || !o.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
|
||||
sub.push(o.get_prop_at(i));
|
||||
}
|
||||
}
|
||||
let flat = flatten(&sub, remaining_depth - 1, seen);
|
||||
seen.pop();
|
||||
out.extend(flat);
|
||||
@@ -440,7 +478,14 @@ pub fn array_flat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
out
|
||||
}
|
||||
|
||||
let all: Vec<JsValue> = (0..n).map(|i| arr.get_prop_at(i)).collect();
|
||||
// 顶层同样丢弃洞位(接收者恒为真数组,元数据表判洞免字符串键)。
|
||||
let dense = arr.array_elements_meta_vec().is_none();
|
||||
let mut all: Vec<JsValue> = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
if dense || !arr.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
|
||||
all.push(arr.get_prop_at(i));
|
||||
}
|
||||
}
|
||||
let mut seen = vec![arr_ptr];
|
||||
let flat = flatten(&all, depth, &mut seen);
|
||||
let new_arr = create_new_array(vm, flat.len());
|
||||
@@ -689,6 +734,10 @@ pub fn array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Ok(JsValue::int(-1));
|
||||
}
|
||||
for i in (0..=from_index_isize as usize).rev() {
|
||||
// 洞位不参与比较:HasProperty 缺失即跳过(洞不是 undefined 元素)。
|
||||
if !arraylike_index_present(vm, ptr, i) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, ptr, i);
|
||||
if oxide_runtime_api::strict_equality(elem, search) {
|
||||
return NativeResult::Ok(js_array_index(i));
|
||||
|
||||
@@ -8,8 +8,9 @@ use crate::builtins_debug;
|
||||
use crate::builtins_error;
|
||||
|
||||
use super::common::{
|
||||
array_ptr_len3, array_type_error, arraylike_get, arraylike_get_or_err, check_array_create_len, create_new_array,
|
||||
get_this_arraylike, invoke_native_callback, js_array_index, require_callback, unexpected_tail_call_error,
|
||||
array_ptr_len3, array_type_error, arraylike_get, arraylike_get_or_err, arraylike_index_present,
|
||||
check_array_create_len, create_new_array, get_this_arraylike, invoke_native_callback, js_array_index,
|
||||
require_callback, unexpected_tail_call_error,
|
||||
};
|
||||
|
||||
/// `Array.prototype.forEach(callback, thisArg)`:对每个元素调用 callback,返回 undefined。
|
||||
@@ -26,6 +27,10 @@ pub fn array_for_each<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in 0..n {
|
||||
// 洞位跳过:回调不得在缺失索引上触发。
|
||||
if !arraylike_index_present(vm, arr_ptr, i) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||||
NativeResult::Ok(_) => {}
|
||||
@@ -54,6 +59,13 @@ pub fn array_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
for i in 0..n {
|
||||
// 洞位跳过回调,并在结果对应位留洞(结果数组预填 present-undefined,须显式置洞)。
|
||||
if !arraylike_index_present(vm, arr_ptr, i) {
|
||||
unsafe {
|
||||
(*new_arr).mark_hole_at(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||||
NativeResult::Ok(mapped) => unsafe {
|
||||
@@ -91,6 +103,10 @@ pub fn array_filter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
for i in 0..n {
|
||||
// 洞位跳过:结果保持紧凑,不复制缺失索引。
|
||||
if !arraylike_index_present(vm, arr_ptr, i) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||||
NativeResult::Ok(result_val) => {
|
||||
@@ -118,10 +134,6 @@ pub fn array_reduce<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("Array.prototype.reduce called with {} args", args.len());
|
||||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||||
let o_val = vm.reg(args[0]);
|
||||
if n == 0 && args.len() < 3 {
|
||||
builtins_error!("Array.prototype.reduce: invalid receiver");
|
||||
return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value"));
|
||||
}
|
||||
if args.len() < 2 {
|
||||
builtins_error!("Array.prototype.reduce: invalid receiver");
|
||||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||||
@@ -140,11 +152,30 @@ pub fn array_reduce<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
accumulator = vm.reg(args[2]);
|
||||
start_idx = 0;
|
||||
} else {
|
||||
accumulator = arraylike_get_or_err!(vm, arr_ptr, 0);
|
||||
start_idx = 1;
|
||||
// FindFirstKey:无初值时取首个存在索引作累加器;全洞(含空数组)抛 TypeError。
|
||||
let mut found = None;
|
||||
for i in 0..n {
|
||||
if arraylike_index_present(vm, arr_ptr, i) {
|
||||
found = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let idx = match found {
|
||||
Some(idx) => idx,
|
||||
None => {
|
||||
builtins_error!("Array.prototype.reduce: invalid receiver");
|
||||
return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value"));
|
||||
}
|
||||
};
|
||||
accumulator = arraylike_get_or_err!(vm, arr_ptr, idx);
|
||||
start_idx = idx + 1;
|
||||
}
|
||||
let this_val = JsValue::undefined();
|
||||
for i in start_idx..n {
|
||||
// 洞位跳过:累加器保持上一 present 元素的结果。
|
||||
if !arraylike_index_present(vm, arr_ptr, i) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[accumulator, elem, js_array_index(i), o_val]) {
|
||||
NativeResult::Ok(result) => accumulator = result,
|
||||
@@ -217,6 +248,10 @@ pub fn array_some<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in 0..n {
|
||||
// 洞位跳过:回调不得在缺失索引上触发。
|
||||
if !arraylike_index_present(vm, arr_ptr, i) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||||
NativeResult::Ok(result_val) => {
|
||||
@@ -255,6 +290,10 @@ pub fn array_every<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in 0..n {
|
||||
// 洞位跳过:回调不得在缺失索引上触发。
|
||||
if !arraylike_index_present(vm, arr_ptr, i) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||||
NativeResult::Ok(result_val) => {
|
||||
@@ -297,6 +336,10 @@ pub fn array_flat_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
for i in 0..n {
|
||||
// 洞位跳过:回调不得在缺失索引上触发。
|
||||
if !arraylike_index_present(vm, arr_ptr, i) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||||
NativeResult::Ok(result) => {
|
||||
@@ -305,9 +348,13 @@ pub fn array_flat_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if !r_ptr.is_null() {
|
||||
let r = unsafe { &*r_ptr };
|
||||
if r.is_array() {
|
||||
// 嵌套数组展开丢弃洞位:结果为紧凑数组。
|
||||
let rn = r.prop_count() as usize;
|
||||
let dense = r.array_elements_meta_vec().is_none();
|
||||
for j in 0..rn {
|
||||
flat.push(r.get_prop_at(j));
|
||||
if dense || !r.prop_meta_at(j).is_some_and(|m| m.is_hole()) {
|
||||
flat.push(r.get_prop_at(j));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -394,8 +441,8 @@ pub fn array_find_last<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in (0..n).rev() {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i), o_val]) {
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i as usize);
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i as usize), o_val]) {
|
||||
NativeResult::Ok(r) => {
|
||||
if oxide_runtime_api::to_boolean(r) {
|
||||
return NativeResult::Ok(elem);
|
||||
@@ -433,15 +480,35 @@ pub fn array_reduce_right<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (mut acc, start_idx): (JsValue, i32) = if args.len() > 2 {
|
||||
(vm.reg(args[2]), n as i32 - 1)
|
||||
} else {
|
||||
if n == 0 {
|
||||
builtins_error!("Array.prototype.reduceRight: invalid receiver");
|
||||
return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value"));
|
||||
// FindFirstKey 自尾:无初值时取末尾首个存在索引作累加器;全洞抛 TypeError。
|
||||
let mut found = None;
|
||||
for i in (0..n).rev() {
|
||||
if arraylike_index_present(vm, arr_ptr, i) {
|
||||
found = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
(unsafe { (*arr_ptr).get_prop_at(n - 1) }, n as i32 - 2)
|
||||
let idx = match found {
|
||||
Some(idx) => idx,
|
||||
None => {
|
||||
builtins_error!("Array.prototype.reduceRight: invalid receiver");
|
||||
return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value"));
|
||||
}
|
||||
};
|
||||
(arraylike_get_or_err!(vm, arr_ptr, idx), idx as i32 - 1)
|
||||
};
|
||||
for i in (0..=start_idx).rev() {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i as usize) };
|
||||
match invoke_native_callback(vm, callback_val, JsValue::undefined(), &[acc, elem, JsValue::int(i), o_val]) {
|
||||
// 洞位跳过:累加器保持上一 present 元素的结果。
|
||||
if !arraylike_index_present(vm, arr_ptr, i as usize) {
|
||||
continue;
|
||||
}
|
||||
let elem = arraylike_get_or_err!(vm, arr_ptr, i as usize);
|
||||
match invoke_native_callback(
|
||||
vm,
|
||||
callback_val,
|
||||
JsValue::undefined(),
|
||||
&[acc, elem, js_array_index(i as usize), o_val],
|
||||
) {
|
||||
NativeResult::Ok(r) => acc = r,
|
||||
NativeResult::Err(err) => {
|
||||
builtins_error!("Array.prototype.reduceRight: invalid receiver");
|
||||
|
||||
@@ -73,7 +73,8 @@ pub(crate) fn parse_sort_comparator<H: VmHost>(vm: &mut H, candidate: JsValue) -
|
||||
/// 原地排序数组元素并返回原数组(`Array.prototype.sort`)。
|
||||
///
|
||||
/// 未提供比较回调时按 ToString 结果的字符串字典序排序;底层 `sort_by` 为稳定
|
||||
/// 排序,比较结果相等(含 NaN)的元素保持原有相对次序。
|
||||
/// 排序,比较结果相等(含 NaN)的元素保持原有相对次序。洞位不参与比较:
|
||||
/// present 值稳定排序后自索引 0 紧凑写回,尾部空位转为洞。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 接收者不是数组时抛 TypeError;
|
||||
@@ -82,7 +83,13 @@ pub fn array_sort<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("Array.prototype.sort called with {} args", args.len());
|
||||
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();
|
||||
// 洞位不参与比较:只收集 present 元素(接收者恒为真数组,元数据表判洞免字符串键)。
|
||||
let arr_ref = unsafe { &*arr_ptr };
|
||||
let dense = arr_ref.array_elements_meta_vec().is_none();
|
||||
let mut vals: Vec<JsValue> = (0..len)
|
||||
.filter(|&i| dense || !arr_ref.prop_meta_at(i).is_some_and(|m| m.is_hole()))
|
||||
.map(|i| arr_ref.get_prop_at(i))
|
||||
.collect();
|
||||
let comparator = if args.len() > 1 {
|
||||
match parse_sort_comparator(vm, vm.reg(args[1])) {
|
||||
Ok(c) => c,
|
||||
@@ -98,10 +105,14 @@ pub fn array_sort<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_error!("Array.prototype.sort: invalid receiver");
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
// present 值稳定排序后从索引 0 紧凑写回,尾部空位转为洞。
|
||||
let arr = unsafe { &mut *arr_ptr };
|
||||
for (i, &v) in vals.iter().enumerate() {
|
||||
arr.set_prop_at(i, v);
|
||||
}
|
||||
for i in vals.len()..len {
|
||||
arr.mark_hole_at(i);
|
||||
}
|
||||
NativeResult::Ok(vm.reg(args[0]))
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ pub fn typed_array_element_get<H: VmHost>(vm: &mut H, obj: &JsObject, index: u32
|
||||
|
||||
/// 若对象是 TypedArray 且属性键是整数索引,返回 `(索引, 视图长度)`;否则 `None`。
|
||||
/// 供 VM 在 receiver ≠ TA 时裁决整数索引的写前语义(越界直接返回,界内落到 receiver)。
|
||||
pub fn typed_array_integer_index<H: VmHost>(vm: &mut H, obj: &JsObject, prop_name_si: u32) -> Option<(usize, usize)> {
|
||||
pub fn typed_array_integer_index<H: VmHost>(vm: &H, obj: &JsObject, prop_name_si: u32) -> Option<(usize, usize)> {
|
||||
let ptr = typed_array_data_ptr(obj)?;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
|
||||
@@ -103,6 +103,9 @@ pub trait VmHost {
|
||||
/// 供建键入口(fromEntries/json/rest excluded)与 `property_key_si` 的字符串分支统一口径。
|
||||
fn string_key_si(&mut self, s: &str) -> u32;
|
||||
fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue>;
|
||||
/// 存在性判定(规范 HasProperty):自身与原型链任一层 P 为自有属性即存在;
|
||||
/// 数组元素区 hole 视同缺失,TypedArray 整数索引按视图长度判在界。
|
||||
fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool;
|
||||
fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32>;
|
||||
|
||||
// 属性访问
|
||||
|
||||
@@ -212,6 +212,43 @@ impl Vm {
|
||||
None
|
||||
}
|
||||
|
||||
/// 存在性判定(规范 HasProperty):自身与原型链逐级检查,任一层 P 为自有
|
||||
/// 属性即存在。数组 length 虚拟属性恒存在;数组元素区 hole 视同缺失;
|
||||
/// TypedArray 整数索引按视图长度判在界;其余按 shape 槽判定。
|
||||
///
|
||||
/// 与 `resolve_property` 的差异:原型链上每层都检查数组元素区与 TypedArray
|
||||
/// 元素(`resolve_property` 只在顶层检查元素区,链上仅走 shape 槽),
|
||||
/// 继承自父数组/TypedArray 的索引属性在此判存在。
|
||||
pub(crate) fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool {
|
||||
let length_si = self.length_si;
|
||||
let mut current = Some(obj);
|
||||
let mut depth = 0usize;
|
||||
while let Some(obj) = current {
|
||||
if obj.is_array() && prop_name_si == length_si {
|
||||
return true;
|
||||
}
|
||||
if obj.is_typed_array_obj() {
|
||||
if let Some((index, len)) =
|
||||
oxide_builtins::typed_array::typed_array_integer_index(self, obj, prop_name_si)
|
||||
{
|
||||
if index < len {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.get_own_property_slot(obj, prop_name_si).is_some() {
|
||||
return true;
|
||||
}
|
||||
if depth >= MAX_PROTO_CHAIN_DEPTH {
|
||||
break;
|
||||
}
|
||||
depth += 1;
|
||||
let proto = obj.proto();
|
||||
current = proto.is_object().then(|| unsafe { &*proto.as_js_object_ptr() });
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 查找自身属性槽下标:数组 length 虚拟属性返回 `None`,元素区返回下标,
|
||||
/// shape 槽按数组(元素区之后偏移)与普通对象(槽位即下标)各自定位;
|
||||
/// 未命中的槽返回 `None`。
|
||||
|
||||
@@ -87,6 +87,9 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue> {
|
||||
self.resolve_property(obj, prop_name_si)
|
||||
}
|
||||
fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool {
|
||||
self.has_property(obj, prop_name_si)
|
||||
}
|
||||
fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32> {
|
||||
self.get_own_property_slot(obj, prop_name_si)
|
||||
}
|
||||
|
||||
@@ -263,3 +263,60 @@ fn new_array_holes_pop_shift() {
|
||||
assert_eq!(eval_str("var a=new Array(1); var p=a.pop(); (p===undefined)+':'+a.length"), "true:0");
|
||||
assert_eq!(eval_str("var a=new Array(1); var s=a.shift(); (s===undefined)+':'+a.length"), "true:0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_paths_skip_holes_per_spec() {
|
||||
// iterate 族:洞位不触发回调;map 结果在源洞位留洞、长度不变。
|
||||
assert_eq!(eval_str("var n=0; new Array(2).forEach(function(){n++;}); ''+n"), "0");
|
||||
assert_eq!(
|
||||
eval_str("var n=0; var r=[1,,3].map(function(x){n++;return x;}); n+':'+(1 in r)+':'+r.length"),
|
||||
"2:false:3"
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("var r=new Array(3).map(function(x){return x;}); (0 in r)+':'+r.length"),
|
||||
"false:3"
|
||||
);
|
||||
assert_eq!(eval_str("new Array(2).filter(function(){return true;}).length"), "0");
|
||||
// reduce/reduceRight:无初值取首个/末尾 present 位作累加器,洞位跳过;全洞抛 TypeError。
|
||||
assert_eq!(
|
||||
eval_str("[1,,].reduce(function(a,b){return b;})+':'+(function(){try{new Array(1).reduce(function(){});return 'no-throw';}catch(e){return e.name;}})()"),
|
||||
"1:TypeError"
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("[,1,2].reduceRight(function(a,b){return a+b;})+':'+(function(){try{new Array(1).reduceRight(function(){});return 'no-throw';}catch(e){return e.name;}})()"),
|
||||
"3:TypeError"
|
||||
);
|
||||
// flatMap:洞位不触发回调;嵌套数组展开丢弃洞位(紧凑结果)。
|
||||
assert_eq!(
|
||||
eval_str("new Array(1).flatMap(function(x){return [x];}).length+':'+[1].flatMap(function(){return [1,,3];}).join(',')+':'+[1].flatMap(function(){return [1,,3];}).length"),
|
||||
"0:1,3:2"
|
||||
);
|
||||
// indexOf/lastIndexOf:洞位不参与比较。
|
||||
assert_eq!(eval_str("[1,,3].indexOf(undefined)+':'+[1,,3].lastIndexOf(undefined)"), "-1:-1");
|
||||
// slice:终长 = end - start;源洞位在结果中留洞。
|
||||
assert_eq!(
|
||||
eval_str("new Array(3).slice().length+':'+[1,,3].slice(0,2).length+':'+(1 in [1,,3].slice(0,2))"),
|
||||
"3:2:false"
|
||||
);
|
||||
// concat:this 与数组实参的洞位均在结果中保洞。
|
||||
assert_eq!(
|
||||
eval_str("[1].concat([,2]).length+':'+(1 in [1].concat([,2]))+':'+[1].concat(new Array(1)).length"),
|
||||
"3:false:2"
|
||||
);
|
||||
// flat:顶层与嵌套洞位均丢弃(紧凑结果)。
|
||||
assert_eq!(eval_str("[,1].flat().length+':'+[1,[2,,3],4].flat().join(',')"), "1:1,2,3,4");
|
||||
// sort:洞位不参与比较;present 值紧凑写回,尾部转洞。
|
||||
assert_eq!(
|
||||
eval_str("var a=[,1,3,2].sort(); (0 in a)+':'+(3 in a)+':'+a.join(',')"),
|
||||
"true:false:1,2,3,"
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("var b=[5,,1,3].sort(function(x,y){return x-y;}); b.join(',')+':'+(3 in b)"),
|
||||
"1,3,5,:false"
|
||||
);
|
||||
// 防过度门控:find 族无门控(洞位照常调回调)、includes 无门控(洞 Get 得 undefined)。
|
||||
assert_eq!(
|
||||
eval_str("[,1].findIndex(function(){return true;})+':'+[1,,3].includes(undefined)+':'+[,1].findIndex(function(x){return x===1;})"),
|
||||
"0:true:1"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -392,19 +392,19 @@ fn array_fill_single() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_iteration_hole_reads_undefined() {
|
||||
// 稀疏数组(字面量空洞 / delete)元素读取落到 undefined:引擎对 hole 不跳过
|
||||
// 回调而是读 undefined(pre-existing 语义,直读 fast path 与慢路径一致)。
|
||||
fn array_iteration_holes_skipped_per_spec() {
|
||||
// 稀疏数组(字面量空洞 / delete):洞位不触发回调,map 结果在洞位留洞,
|
||||
// join 把洞位渲染为空串。
|
||||
let (vm, result) = eval("[10,,30].map(x=>x*2).join(',')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "20,NaN,60");
|
||||
assert_eq!(to_str(&vm, result), "20,,60");
|
||||
|
||||
// delete 产生的 hole 经 map 读 undefined,不残留洞前值。
|
||||
// delete 产生的洞位经 map 同样跳过,不残留洞前值。
|
||||
let (vm, result) = eval("var a=[1,2,3]; delete a[1]; a.map(x=>x+1).join(',')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "2,NaN,4");
|
||||
assert_eq!(to_str(&vm, result), "2,,4");
|
||||
|
||||
// reduce 对 hole 累加 undefined。
|
||||
// reduce 跳过洞位:初值直接接到首个 present 元素。
|
||||
let (vm, result) = eval("var a=[1,,3]; a.reduce((s,x)=>s+','+x,'')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), ",1,undefined,3");
|
||||
assert_eq!(to_str(&vm, result), ",1,3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user