fix: 读路径按规范跳洞对齐并补 HasProperty 存在判定

This commit is contained in:
2026-09-19 11:52:53 +00:00
parent 6610384d90
commit f5330da5f0
10 changed files with 277 additions and 42 deletions
@@ -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) => {
+63 -14
View File
@@ -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));
+84 -17
View File
@@ -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]))
}
+1 -1
View File
@@ -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;