fix: 字符串默认迭代器缺失时按不可迭代抛错

This commit is contained in:
2026-09-17 19:52:25 +00:00
parent 738cf620c1
commit 12aa250542
5 changed files with 447 additions and 55 deletions
+40
View File
@@ -137,6 +137,46 @@ pub(crate) fn arraylike_get<H: VmHost>(vm: &mut H, ptr: *mut JsObject, i: usize)
}
}
/// 字符串源的 array-like 单元序列;非字符串源返回 `None`。
///
/// String exotic 对象的 `length` 与索引属性在引擎内未物化,Array.from /
/// %TypedArray%.from 的 array-like 回退按 UTF-16 单元直接读:原始串取自身单元,
/// 装箱串取其 `[[StringData]]`(槽 0)。单元长度即 `length`,第 i 个索引为第 i 个码元。
///
/// # 边界
/// 装箱串槽 0 非字符串(异常形态)按非字符串源处理;指针为空返回 `None`。
pub(crate) fn string_arraylike_units<H: VmHost>(vm: &H, value: JsValue) -> Option<Vec<u16>> {
let raw = if value.is_string() {
value
} else if value.is_object() {
let ptr = value.as_js_object_ptr();
if ptr.is_null() {
return None;
}
// SAFETY: ptr 来自对象 JsValue,指向存活的 JsObject。
let obj = unsafe { &*ptr };
if !obj.is_string_obj() {
return None;
}
let data = obj.get_prop_at(0);
if !data.is_string() {
return None;
}
data
} else {
return None;
};
Some(vm.string_units(raw).into_owned())
}
/// 取单个 UTF-16 单元对应的字符串值:ASCII 单元命中共享 perm 串,其余按单元序列创建。
pub(crate) fn unit_string_value<H: VmHost>(vm: &mut H, unit: u16) -> JsValue {
match vm.single_unit(unit) {
Some(v) => v,
None => vm.new_string_units(&[unit]),
}
}
/// 元素读取失败时直接把异常作为 NativeResult::Err 返回。
macro_rules! arraylike_get_or_err {
($vm:expr, $ptr:expr, $i:expr) => {
+31 -17
View File
@@ -9,7 +9,8 @@ use oxide_runtime_api::{NativeResult, VmHost};
use super::common::{
array_length_arg, array_type_error, arraylike_get, arraylike_get_or_err, create_new_array, invoke_native_callback,
is_constructor_value, js_array_index, require_callback, unexpected_tail_call_error,
is_constructor_value, js_array_index, require_callback, string_arraylike_units, unexpected_tail_call_error,
unit_string_value,
};
/// JS `Array()` 构造逻辑:单个数字参数创建指定长度空数组,其余情况把参数作为元素。
@@ -197,23 +198,33 @@ pub fn array_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
} else {
// array-like 路径:ToObject 后读 length,构造结果对象,逐索引取值。
let obj_val = match oxide_runtime_api::to_object(items, vm) {
Ok(o) => o,
Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)),
// 字符串源(原始串/装箱串)的 length 与索引未物化,直接按 UTF-16 单元读。
let string_units = string_arraylike_units(vm, items);
let obj_val = match &string_units {
Some(_) => JsValue::undefined(),
None => match oxide_runtime_api::to_object(items, vm) {
Ok(o) => o,
Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)),
},
};
let obj_ptr = obj_val.as_js_object_ptr();
let obj = unsafe { &*obj_ptr };
let length_key = vm.new_string("length");
let length_si = vm.property_key_si(length_key);
let len_val = match vm.ordinary_get(obj, length_si, obj_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(from_engine_error(vm, &err)),
let len = match &string_units {
Some(units) => units.len().min(MAX_DENSE_PROPS),
None => {
let obj_ptr = obj_val.as_js_object_ptr();
let obj = unsafe { &*obj_ptr };
let length_key = vm.new_string("length");
let length_si = vm.property_key_si(length_key);
let len_val = match vm.ordinary_get(obj, length_si, obj_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(from_engine_error(vm, &err)),
};
let len_u64 = oxide_runtime_api::to_length(len_val);
if len_u64 > u32::MAX as u64 {
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid array length"));
}
len_u64.min(MAX_DENSE_PROPS as u64) as usize
}
};
let len_u64 = oxide_runtime_api::to_length(len_val);
if len_u64 > u32::MAX as u64 {
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid array length"));
}
let len = len_u64.min(MAX_DENSE_PROPS as u64) as usize;
a_ptr = match construct_array_from_result(vm, c, &[JsValue::int(len as i32)]) {
Ok((ptr, is_arr)) => {
@@ -223,7 +234,10 @@ pub fn array_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
Err(err) => return NativeResult::Err(err),
};
for i in 0..len {
let elem = arraylike_get_or_err!(vm, obj_ptr, i);
let elem = match &string_units {
Some(units) => unit_string_value(vm, units[i]),
None => arraylike_get_or_err!(vm, obj_val.as_js_object_ptr(), i),
};
let mapped = match mapping {
Some(cb) => match invoke_native_callback(vm, cb, this_arg, &[elem, js_array_index(i)]) {
NativeResult::Ok(m) => m,