From 12aa25054202452b837d42fddeb9ab22f669fdc2 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 17 Sep 2026 19:52:25 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AD=97=E7=AC=A6=E4=B8=B2=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E8=BF=AD=E4=BB=A3=E5=99=A8=E7=BC=BA=E5=A4=B1=E6=97=B6?= =?UTF-8?q?=E6=8C=89=E4=B8=8D=E5=8F=AF=E8=BF=AD=E4=BB=A3=E6=8A=9B=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array/common.rs | 40 +++ crates/oxide_builtins/src/array/from.rs | 48 ++- crates/oxide_builtins/src/iterator.rs | 61 +++- crates/oxide_builtins/src/typed_array.rs | 55 ++-- .../tests/string_iterator_removed_tests.rs | 298 ++++++++++++++++++ 5 files changed, 447 insertions(+), 55 deletions(-) create mode 100644 crates/oxide_vm/tests/string_iterator_removed_tests.rs diff --git a/crates/oxide_builtins/src/array/common.rs b/crates/oxide_builtins/src/array/common.rs index 96eeda4c..9142ebbd 100644 --- a/crates/oxide_builtins/src/array/common.rs +++ b/crates/oxide_builtins/src/array/common.rs @@ -137,6 +137,46 @@ pub(crate) fn arraylike_get(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(vm: &H, value: JsValue) -> Option> { + 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(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) => { diff --git a/crates/oxide_builtins/src/array/from.rs b/crates/oxide_builtins/src/array/from.rs index ec345e81..06a64d2d 100644 --- a/crates/oxide_builtins/src/array/from.rs +++ b/crates/oxide_builtins/src/array/from.rs @@ -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(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(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, diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 4dce6067..9de24ebe 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1376,23 +1376,48 @@ pub fn iterator_wrapper_next(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(vm: &mut H, value: JsValue) -> Result { - // 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(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(vm: &mut H, value: JsValue) -> Result(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), }; diff --git a/crates/oxide_vm/tests/string_iterator_removed_tests.rs b/crates/oxide_vm/tests/string_iterator_removed_tests.rs new file mode 100644 index 00000000..4d2774c2 --- /dev/null +++ b/crates/oxide_vm/tests/string_iterator_removed_tests.rs @@ -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 { + 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"; + })()"#, + ); +}