fix: 字符串迭代器函数直构并统一字节数组转换

This commit is contained in:
2026-09-17 20:14:36 +00:00
parent e86ec8d7d2
commit f11506a949
4 changed files with 128 additions and 21 deletions
+22 -15
View File
@@ -1190,18 +1190,6 @@ pub fn make_iterator_for_value_without_return<H: VmHost>(vm: &mut H, value: JsVa
}
}
/// 同 [`make_iterator_for_value`],但包装器原型指向调用方指定的原型
/// (`String.prototype[@@iterator]` 用 %StringIteratorPrototype%)。
pub(crate) fn make_iterator_for_value_with_proto<H: VmHost>(
vm: &mut H, value: JsValue, wrapper_proto: *mut JsObject,
) -> Result<JsValue, JsValue> {
match try_make_iterator_inner_proto(vm, value, true, Some(wrapper_proto)) {
Ok(Some(iterator)) => Ok(iterator),
Ok(None) => Err(crate::error::create_type_error(vm, "value is not iterable")),
Err(err) => Err(err),
}
}
/// 尝试创建迭代器包装对象,把"不可迭代"与"真异常"区分返回。
///
/// # 步骤
@@ -1232,8 +1220,27 @@ pub(crate) fn try_make_iterator_inner_proto<H: VmHost>(
Ok(None) => return Ok(None),
Err(err) => return Err(err),
};
// 通用包装器默认挂 %IteratorPrototype%(经原型链获得 @@iterator 返回自身);
// 调用方指定原型时优先(如 String 迭代器的 %StringIteratorPrototype%)。
Ok(Some(build_iterator_wrapper(vm, inner, cached_next, bind_return, wrapper_proto)))
}
/// 以给定内层迭代器构造统一迭代器包装对象。
///
/// # 步骤
/// 1. 选定包装器原型:调用方指定时优先(如 String 迭代器的
/// %StringIteratorPrototype%),否则默认 %IteratorPrototype%(经原型链获得
/// `@@iterator` 返回自身)。
/// 2. 写入 `__inner__`/`__index__` 槽;鸭子回退路径把创建时读到的 `next` 闭包
/// 缓存进 `__next__` 槽,消费期直读槽不重触发 getter。
/// 3. 挂 `next` 方法;`bind_return` 且内层有可调用 `return` 时条件暴露 `return`。
///
/// # 边界
/// - `bind_return` 控制是否暴露 `return` 方法(for-of/解构的 IteratorClose 需要,
/// `yield*` 委托不需要且须避免创建时访问内层 return getter)。
/// - 本函数不解析 `@@iterator`:`get_iterator` 派生内层,或默认迭代器函数直接以
/// 已 ToString 的串为内层构造(其语义只由 `this` 决定,与属性表状态解耦)。
pub(crate) fn build_iterator_wrapper<H: VmHost>(
vm: &mut H, inner: JsValue, cached_next: Option<JsValue>, bind_return: bool, wrapper_proto: Option<*mut JsObject>,
) -> JsValue {
let iterator_proto = match wrapper_proto {
Some(proto) => proto,
None => vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject,
@@ -1271,7 +1278,7 @@ pub(crate) fn try_make_iterator_inner_proto<H: VmHost>(
}
}
Ok(Some(JsValue::from_js_object(wrapper)))
JsValue::from_js_object(wrapper)
}
fn iterator_wrapper_return<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
+4 -6
View File
@@ -849,13 +849,11 @@ pub fn string_symbol_iterator<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
}
};
// String 迭代器挂 %StringIteratorPrototype%(链到 %IteratorPrototype%):
// 包装机制复用通用路径,仅替换原型为中继层。
// 直构 String 迭代器:内层即 ToString 结果,不再回读 @@iterator。本函数语义
// 只由 this 决定,属性表被删除/置 null/被覆盖均不影响已保存引用的直调。
// 迭代器挂 %StringIteratorPrototype%(链到 %IteratorPrototype%);串内层不暴露 return。
let string_iter_proto = vm.session().builtin_world().string_iterator_proto.as_ptr() as *mut JsObject;
match crate::iterator::make_iterator_for_value_with_proto(vm, s_val, string_iter_proto) {
Ok(iterator) => NativeResult::Ok(iterator),
Err(err) => NativeResult::Err(err),
}
NativeResult::Ok(crate::iterator::build_iterator_wrapper(vm, s_val, None, true, Some(string_iter_proto)))
}
/// `matchAll` 迭代器的 `next`:返回 `{value: 匹配数组, done}`,耗尽后 done 为 true。
+10
View File
@@ -457,6 +457,16 @@ fn collect_array_like<H: VmHost>(vm: &mut H, value: JsValue, consult_iterator: b
return Ok(values);
}
// 字符串源(装箱串)的 length 与索引未物化,直接按 UTF-16 单元取值:
// 与 %TypedArray%.from 的 array-like 臂同读法,避免落通用 length 读空。
if let Some(units) = crate::array::string_arraylike_units(vm, value) {
let mut values = Vec::with_capacity(units.len());
for &unit in &units {
values.push(crate::array::unit_string_value(vm, unit));
}
return Ok(values);
}
let length_si = vm.kernel_core().perm_interner().intern("length").0;
let len_val = vm
.ordinary_get(obj, length_si, value)
@@ -296,3 +296,95 @@ fn default_string_iterator_regression() {
})()"#,
);
}
#[test]
fn saved_default_iterator_still_works_after_delete_or_null() {
// 已保存的默认迭代器引用直调:语义只由 this 决定,属性表删除/置 null/置
// undefined 不影响,仍按码元产出(不重新回读 @@iterator)。
assert_truthy(
"saved iterator delete",
r#"(function(){
var f = String.prototype[Symbol.iterator];
delete String.prototype[Symbol.iterator];
var it = f.call("ab");
return it.next().value === "a" && it.next().value === "b";
})()"#,
);
assert_truthy(
"saved iterator null",
r#"(function(){
var f = String.prototype[Symbol.iterator];
String.prototype[Symbol.iterator] = null;
var it = f.call("ab");
return it.next().value === "a" && it.next().value === "b";
})()"#,
);
assert_truthy(
"saved iterator undefined",
r#"(function(){
var f = String.prototype[Symbol.iterator];
String.prototype[Symbol.iterator] = undefined;
var it = f.call("ab");
return it.next().value === "a" && it.next().value === "b";
})()"#,
);
}
#[test]
fn saved_default_iterator_ignores_user_override() {
// 用户把 @@iterator 覆盖成函数后,已保存的默认迭代器引用直调仍构造 String
// 迭代器(规范忽略覆盖),不转发到覆盖函数。
assert_truthy(
"saved iterator override",
r#"(function(){
var f = String.prototype[Symbol.iterator];
var called = false;
String.prototype[Symbol.iterator] = function(){ called = true; return null; };
var it = f.call("ab");
return !called && it.next().value === "a" && it.next().value === "b";
})()"#,
);
}
#[test]
fn saved_default_iterator_handles_surrogate_pair_codepoint() {
// 直构仍走码点口径:合法代理对整体产出单个元素。
assert_truthy(
"saved iterator gclef",
r#"(function(){
var f = String.prototype[Symbol.iterator];
delete String.prototype[Symbol.iterator];
var it = f.call("\uD834\uDD1E");
var first = it.next().value;
return first === "\uD834\uDD1E" && it.next().done === true;
})()"#,
);
}
#[test]
fn typed_array_constructor_boxed_string_uses_units_after_delete() {
// 构造器 array-like 臂与 %TypedArray%.from 对称:装箱串按 UTF-16 单元取值,
// 不读自身可调用 next,也不因 length 未物化读空。
assert_truthy(
"new Uint8Array boxed duck-next delete",
r#"(function(){
var s = new String("ab");
var duckCalled = false;
s.next = function(){ duckCalled = true; return { value: 99, done: false }; };
delete String.prototype[Symbol.iterator];
var u = new Uint8Array(s);
return u.length === 2 && u[0] === 0 && u[1] === 0 && !duckCalled;
})()"#,
);
assert_truthy(
"Uint8Array.from boxed duck-next delete symmetric",
r#"(function(){
var s = new String("ab");
var duckCalled = false;
s.next = function(){ duckCalled = true; return { value: 99, done: false }; };
delete String.prototype[Symbol.iterator];
var u = Uint8Array.from(s);
return u.length === 2 && u[0] === 0 && u[1] === 0 && !duckCalled;
})()"#,
);
}