mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 字符串默认迭代器缺失时按不可迭代抛错
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1376,23 +1376,48 @@ pub fn iterator_wrapper_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
/// 只读取 value 的 `@@iterator` 方法而不调用,按 GetMethod-可选入口的三态语义
|
||||
/// 判定可迭代性(Array.from / TypedArray 构造器 / TypedArray.from 共用)。
|
||||
///
|
||||
/// 统一经原型链解析 `@@iterator`(String 臂与 `get_iterator` 同为无条件快速
|
||||
/// 路径):解析到可调用返 true(调用方走迭代臂);解析为 null/undefined 返
|
||||
/// false(调用方落 array-like 索引读臂),此时再按引擎扩展回退到自身可调用的
|
||||
/// `next`;解析为非空不可调用值时抛 TypeError(GetMethod 步 4),不落入
|
||||
/// array-like 臂。
|
||||
/// 统一经原型链解析 `@@iterator`:解析到可调用返 true(调用方走迭代臂);
|
||||
/// 解析为 null/undefined 返 false(调用方落 array-like 索引读臂),此时再按引擎
|
||||
/// 扩展回退到自身可调用的 `next`;解析为非空不可调用值时抛 TypeError
|
||||
/// (GetMethod 步 4),不落入 array-like 臂。
|
||||
///
|
||||
/// # 边界
|
||||
/// 非对象原始值(number/boolean 等)不装箱,直接判不可迭代。
|
||||
/// `@@iterator` getter 抛错时透传 `Err`(不落入鸭子回退)。
|
||||
/// 字符串原始值按自身 wrapper 的原型链读 `@@iterator`(读起点 String.prototype、
|
||||
/// receiver = 原始值),不套用 duck-next 回退(原始串无自身 `next`);非对象原始值
|
||||
/// (number/boolean 等)装箱后判定。`@@iterator` getter 抛错时透传 `Err`
|
||||
/// (不落入鸭子回退)。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// 三态是 GetMethod-可选入口的规范语义:非空不可调用(含 [[IsHTMLDDA]]
|
||||
/// 宿主值等不可调用对象)必须抛错而非静默 array-like。
|
||||
pub(crate) fn peek_iterator_method<H: VmHost>(vm: &mut H, value: JsValue) -> Result<bool, JsValue> {
|
||||
// String 臂无条件可迭代(与 get_iterator 的 String 臂同一近似)。
|
||||
// 字符串原始值:读 String.prototype 上的 @@iterator,receiver = 原始值
|
||||
// (与 get_iterator 的 String 臂同口径)。默认迭代器被删除/置 null 时须落
|
||||
// array-like,而非按"String 恒可迭代"放行迭代路径。
|
||||
if value.is_string() {
|
||||
return Ok(true);
|
||||
let proto_ptr = vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject;
|
||||
// SAFETY: string_proto 是 BuiltinWorld 长驻原型对象,进程内有效且不被 GC 搬移。
|
||||
let proto_obj = unsafe { &*proto_ptr };
|
||||
let sym_iter_si = make_well_known_symbol_key(0);
|
||||
let method = match vm.ordinary_get(proto_obj, sym_iter_si, value) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
// GetMethod 取 @@iterator 时 getter 抛出:透传原值,不落入 array-like。
|
||||
let exc = vm
|
||||
.take_uncaught_value()
|
||||
.unwrap_or_else(|| crate::error::create_type_error(vm, &err));
|
||||
return Err(exc);
|
||||
}
|
||||
};
|
||||
if is_callable(method) {
|
||||
return Ok(true);
|
||||
}
|
||||
// GetMethod 步 4:非空不可调用方法是 TypeError,不落入 array-like 臂。
|
||||
if !method.is_null() && !method.is_undefined() {
|
||||
return Err(crate::error::create_type_error(vm, "value is not iterable"));
|
||||
}
|
||||
// null/undefined:原始串的临时 wrapper 无自身可调用 next,落 array-like。
|
||||
return Ok(false);
|
||||
}
|
||||
// 原始值(number/bigint/boolean/symbol)先装箱再按对象读 @@iterator
|
||||
// (如 `Array.from(5)` 经 Number.prototype[Symbol.iterator]);
|
||||
@@ -1427,10 +1452,14 @@ pub(crate) fn peek_iterator_method<H: VmHost>(vm: &mut H, value: JsValue) -> Res
|
||||
return Err(crate::error::create_type_error(vm, "value is not iterable"));
|
||||
}
|
||||
// 鸭子回退:@@iterator 解析为 null/undefined 且对象自身有可调用 next。
|
||||
let next_si = vm.kernel_core().perm_interner().intern("next").0;
|
||||
if let Ok(next) = vm.ordinary_get(obj, next_si, recv) {
|
||||
if is_callable(next) {
|
||||
return Ok(true);
|
||||
// 装箱串不套用:get_iterator 的 String 臂无 duck-next 回退,规范也只看
|
||||
// @@iterator(Array.from/%TypedArray%.from 应落 array-like)。
|
||||
if !obj.is_string_obj() {
|
||||
let next_si = vm.kernel_core().perm_interner().intern("next").0;
|
||||
if let Ok(next) = vm.ordinary_get(obj, next_si, recv) {
|
||||
if is_callable(next) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1555,8 +1584,10 @@ fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue
|
||||
// GetMethod 步 4:非空不可调用方法 → TypeError。
|
||||
return Err(crate::error::create_type_error(vm, "value is not iterable"));
|
||||
}
|
||||
// null/undefined:String 恒可迭代 → 码元快速路径。
|
||||
return Ok(Some((inner, None)));
|
||||
// @@iterator 解析为 null/undefined:String 不再恒可迭代。返回不可迭代由
|
||||
// make_iterator_for_value 统一折成 TypeError;Array.from/%TypedArray%.from
|
||||
// 已由 peek 门控提前落 array-like,不会到达本臂。
|
||||
return Ok(None);
|
||||
}
|
||||
// 内建集合标记:仅当 @@iterator 经原型链读到的仍是内建默认迭代器函数
|
||||
// 时才走快速路径;用户覆盖(自身属性或原型链改写/删除)走通用协议
|
||||
|
||||
@@ -1565,31 +1565,40 @@ pub fn typed_array_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
}
|
||||
Ok(false) => {
|
||||
let obj_val = match oxide_runtime_api::to_object(source, vm) {
|
||||
Ok(o) => o,
|
||||
Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)),
|
||||
};
|
||||
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
||||
let length_si = vm.kernel_core().perm_interner().intern("length").0;
|
||||
let len_val = match vm.ordinary_get(obj, length_si, obj_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||||
};
|
||||
let n = match oxide_runtime_api::to_number_full(len_val, vm) {
|
||||
Ok(n) => n,
|
||||
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||||
};
|
||||
let len = to_collect_len(n);
|
||||
let mut values = Vec::with_capacity(len);
|
||||
for i in 0..len {
|
||||
let key = vm.new_string(&i.to_string());
|
||||
let key_si = vm.property_key_si(key);
|
||||
match vm.ordinary_get(obj, key_si, obj_val) {
|
||||
Ok(v) => values.push(v),
|
||||
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||||
// 字符串源(原始串/装箱串)的 length 与索引未物化,直接按 UTF-16 单元取值。
|
||||
if let Some(units) = crate::array::string_arraylike_units(vm, source) {
|
||||
let mut values = Vec::with_capacity(units.len());
|
||||
for &unit in &units {
|
||||
values.push(crate::array::unit_string_value(vm, unit));
|
||||
}
|
||||
values
|
||||
} else {
|
||||
let obj_val = match oxide_runtime_api::to_object(source, vm) {
|
||||
Ok(o) => o,
|
||||
Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)),
|
||||
};
|
||||
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
||||
let length_si = vm.kernel_core().perm_interner().intern("length").0;
|
||||
let len_val = match vm.ordinary_get(obj, length_si, obj_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||||
};
|
||||
let n = match oxide_runtime_api::to_number_full(len_val, vm) {
|
||||
Ok(n) => n,
|
||||
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||||
};
|
||||
let len = to_collect_len(n);
|
||||
let mut values = Vec::with_capacity(len);
|
||||
for i in 0..len {
|
||||
let key = vm.new_string(&i.to_string());
|
||||
let key_si = vm.property_key_si(key);
|
||||
match vm.ordinary_get(obj, key_si, obj_val) {
|
||||
Ok(v) => values.push(v),
|
||||
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||||
}
|
||||
}
|
||||
values
|
||||
}
|
||||
values
|
||||
}
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
//! String 臂默认迭代器删除/null/undefined 形钉:`String.prototype[Symbol.iterator]`
|
||||
//! 缺失时 for-of/spread/解构/`Iterator.from`/`new Set` 按不可迭代抛 TypeError,
|
||||
//! `Array.from`/`%TypedArray%.from` 落 array-like 按 UTF-16 单元产出;默认迭代器
|
||||
//! 在位的正常路径不回归。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn run(source: &str) -> Result<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();
|
||||
vm.run(&Arc::new(module))
|
||||
}
|
||||
|
||||
/// 断言 IIFE 求值为 `true`(把多断言收敛为布尔,规避 JsValue Display 只暴露
|
||||
/// number/bool 的限制)。
|
||||
fn assert_truthy(name: &str, source: &str) {
|
||||
let result = run(source).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
assert!(result.as_bool(), "{name}: expected truthy, got {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_string_throws_after_delete() {
|
||||
// 原始串:删除默认迭代器后不再码元步进,按不可迭代抛 TypeError。
|
||||
assert_truthy(
|
||||
"for-of primitive delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
try { for (var c of "ab") {} return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
// 装箱串同形。
|
||||
assert_truthy(
|
||||
"for-of boxed delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
try { for (var c of new String("ab")) {} return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spread_and_destructure_string_throw_after_delete() {
|
||||
assert_truthy(
|
||||
"spread primitive delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
try { [..."ab"]; return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
assert_truthy(
|
||||
"destructure primitive delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
try { var [a, b] = "ab"; return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_from_string_throws_after_delete() {
|
||||
// GetIteratorFlattenable:原始串与装箱串均按不可迭代抛 TypeError。
|
||||
assert_truthy(
|
||||
"Iterator.from primitive delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
try { Iterator.from("ab"); return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
assert_truthy(
|
||||
"Iterator.from boxed delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
try { Iterator.from(new String("ab")); return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_constructor_string_throws_after_delete() {
|
||||
assert_truthy(
|
||||
"new Set primitive delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
try { new Set("ab"); return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
assert_truthy(
|
||||
"new Set boxed delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
try { new Set(new String("ab")); return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_and_undefined_forms_throw() {
|
||||
// null 与 undefined 两形与删除同判:GetMethod 解析为空 → 不可迭代。
|
||||
assert_truthy(
|
||||
"for-of null",
|
||||
r#"(function(){
|
||||
String.prototype[Symbol.iterator] = null;
|
||||
try { for (var c of "ab") {} return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
assert_truthy(
|
||||
"for-of undefined",
|
||||
r#"(function(){
|
||||
String.prototype[Symbol.iterator] = undefined;
|
||||
try { for (var c of "ab") {} return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_string_falls_back_to_array_like_after_delete() {
|
||||
// 删除默认迭代器后 Array.from 走 array-like,按 UTF-16 码元逐个产出,不抛。
|
||||
assert_truthy(
|
||||
"Array.from primitive delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
var r = Array.from("ab");
|
||||
return r.length === 2 && r[0] === "a" && r[1] === "b";
|
||||
})()"#,
|
||||
);
|
||||
assert_truthy(
|
||||
"Array.from boxed delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
var r = Array.from(new String("ab"));
|
||||
return r.length === 2 && r[0] === "a" && r[1] === "b";
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_string_null_and_undefined_fall_back() {
|
||||
assert_truthy(
|
||||
"Array.from null",
|
||||
r#"(function(){
|
||||
String.prototype[Symbol.iterator] = null;
|
||||
var r = Array.from("ab");
|
||||
return r.length === 2 && r[0] === "a" && r[1] === "b";
|
||||
})()"#,
|
||||
);
|
||||
assert_truthy(
|
||||
"Array.from undefined",
|
||||
r#"(function(){
|
||||
String.prototype[Symbol.iterator] = undefined;
|
||||
var r = Array.from("ab");
|
||||
return r.length === 2 && r[0] === "a" && r[1] === "b";
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_string_falls_back_to_code_units_after_delete() {
|
||||
// array-like 口径是 UTF-16 码元:代理对拆成两个孤立 surrogate,不合并成码点。
|
||||
assert_truthy(
|
||||
"Array.from gclef delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
var r = Array.from("\uD834\uDD1E");
|
||||
return r.length === 2 && r[0] === "\uD834" && r[1] === "\uDD1E";
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_string_mapfn_on_array_like_path() {
|
||||
// array-like 回退仍逐元素应用 mapFn,索引按码元位置。
|
||||
assert_truthy(
|
||||
"Array.from mapFn delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
var r = Array.from("ab", function(c, i){ return c + i; });
|
||||
return r.length === 2 && r[0] === "a0" && r[1] === "b1";
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_generic_array_like_unaffected() {
|
||||
// 非字符串 array-like 源不受字符串分支影响。
|
||||
assert_truthy(
|
||||
"Array.from object",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
var r = Array.from({ length: 2, 0: "a", 1: "b" });
|
||||
return r.length === 2 && r[0] === "a" && r[1] === "b";
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_from_string_falls_back_after_delete() {
|
||||
// %TypedArray%.from 同前置门:删除后落 array-like,按单元取值再 ToNumber。
|
||||
assert_truthy(
|
||||
"Uint8Array.from delete",
|
||||
r#"(function(){
|
||||
delete String.prototype[Symbol.iterator];
|
||||
var u = Uint8Array.from("ab");
|
||||
return u.length === 2 && u[0] === 0 && u[1] === 0;
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_callable_iterator_method_throws_for_optional_entry() {
|
||||
// 非空不可调用方法是 GetMethod 步 4 的 TypeError,不得落 array-like。
|
||||
assert_truthy(
|
||||
"42 for-of",
|
||||
r#"(function(){
|
||||
String.prototype[Symbol.iterator] = 42;
|
||||
try { for (var c of "ab") {} return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
assert_truthy(
|
||||
"42 Array.from",
|
||||
r#"(function(){
|
||||
String.prototype[Symbol.iterator] = 42;
|
||||
try { Array.from("ab"); return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peek_getter_sees_primitive_receiver() {
|
||||
// Array.from 的可迭代性探测经 String.prototype 读 @@iterator,receiver 为原始值。
|
||||
assert_truthy(
|
||||
"Array.from getter receiver",
|
||||
r#"(function(){
|
||||
var orig = String.prototype[Symbol.iterator];
|
||||
var obs;
|
||||
Object.defineProperty(String.prototype, Symbol.iterator, {
|
||||
get: function(){ "use strict"; obs = typeof this; return orig; }
|
||||
});
|
||||
var r = Array.from("ab");
|
||||
return obs === "string" && r.length === 2 && r[0] === "a";
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_string_object_ignores_duck_next_after_delete() {
|
||||
// 装箱串 @@iterator 缺失时按 array-like(规范只查 @@iterator):即便对象自身有
|
||||
// 可调用 next,也不走鸭子迭代路径、不因 get_iterator 无 duck 回退而误抛。
|
||||
assert_truthy(
|
||||
"Array.from boxed duck-next delete",
|
||||
r#"(function(){
|
||||
var s = new String("zz");
|
||||
s.next = function(){ return { value: "DUCK", done: false }; };
|
||||
delete String.prototype[Symbol.iterator];
|
||||
var r = Array.from(s);
|
||||
return r.length === 2 && r[0] === "z" && r[1] === "z";
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_string_iterator_regression() {
|
||||
// 默认迭代器在位:码点快速路径不变(抽象字符单个元素),各消费者逐字符。
|
||||
assert_truthy(
|
||||
"default string iterator",
|
||||
r#"(function(){
|
||||
var chars = "";
|
||||
for (var c of "ab") chars += c;
|
||||
var spread = [..."ab"].join("");
|
||||
var destructured = (function(){ var [a, b] = "ab"; return a + b; })();
|
||||
var from = Array.from("ab").join("");
|
||||
var fromIterator = Array.from(Iterator.from("ab")).join("");
|
||||
var gclef = Array.from("\uD834\uDD1E");
|
||||
var direct = String.prototype[Symbol.iterator].call("ab");
|
||||
return chars === "ab" && spread === "ab" && destructured === "ab"
|
||||
&& from === "ab" && fromIterator === "ab"
|
||||
&& gclef.length === 1 && gclef[0] === "\uD834\uDD1E"
|
||||
&& direct.next().value === "a" && direct.next().value === "b";
|
||||
})()"#,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user