From 92df76565b8a0ae665358a848180fffaa789d304 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 14 Sep 2026 22:16:43 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BF=AD=E4=BB=A3=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E5=AF=B9=E9=9D=9E=E7=A9=BA=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E8=B0=83=E7=94=A8=20@@iterator=20=E6=8A=9B=E9=94=99=E5=B9=B6?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20IsHTMLDDA=20=E5=AE=BF=E4=B8=BB=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/iterator.rs | 25 ++++-- crates/oxide_runtime_api/src/lib.rs | 24 ++++- crates/oxide_types/src/object.rs | 9 ++ crates/oxide_vm/src/dispatch/misc.rs | 5 +- crates/oxide_vm/src/test262_host.rs | 7 ++ .../oxide_vm/tests/builtins_iterator_tests.rs | 89 +++++++++++++++++-- 6 files changed, 143 insertions(+), 16 deletions(-) diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 2070bf8c..93a54358 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1365,16 +1365,22 @@ pub fn iterator_wrapper_next(vm: &mut H, args: &[u8]) -> NativeResult NativeResult::Err(crate::error::create_type_error(vm, "value is not iterable")) } -/// 判断 value 是否可迭代,只读取 `@@iterator` 方法而不调用它(GetMethod 语义)。 +/// 只读取 value 的 `@@iterator` 方法而不调用,按 GetMethod-可选入口的三态语义 +/// 判定可迭代性(Array.from / TypedArray 构造器 / TypedArray.from 共用)。 /// -/// 与 [`get_iterator`] 的判定一致:统一经原型链解析 `@@iterator`(String 臂与 -/// `get_iterator` 同为无条件快速路径),解析到可调用才视为可迭代;解析为 -/// null/undefined(或非对象值)时返回 false,由调用方落 array-like 索引读臂。 -/// 不可调用时再回退到自身可调用的 `next`。 +/// 统一经原型链解析 `@@iterator`(String 臂与 `get_iterator` 同为无条件快速 +/// 路径):解析到可调用返 true(调用方走迭代臂);解析为 null/undefined 返 +/// false(调用方落 array-like 索引读臂),此时再按引擎扩展回退到自身可调用的 +/// `next`;解析为非空不可调用值时抛 TypeError(GetMethod 步 4),不落入 +/// array-like 臂。 /// /// # 边界 /// 非对象原始值(number/boolean 等)不装箱,直接判不可迭代。 -/// `@@iterator` getter 抛错时透传 `Err`。 +/// `@@iterator` getter 抛错时透传 `Err`(不落入鸭子回退)。 +/// +/// # 注意事项 +/// 三态是 GetMethod-可选入口的规范语义:非空不可调用(含 [[IsHTMLDDA]] +/// 宿主值等不可调用对象)必须抛错而非静默 array-like。 pub(crate) fn peek_iterator_method(vm: &mut H, value: JsValue) -> Result { // String 臂无条件可迭代(与 get_iterator 的 String 臂同一近似)。 if value.is_string() { @@ -1396,7 +1402,12 @@ pub(crate) fn peek_iterator_method(vm: &mut H, value: JsValue) -> Res if is_callable(method) { return Ok(true); } - // 鸭子回退:对象自身有可调用 next。 + // GetMethod 步 4:非空不可调用方法是 TypeError(不落入鸭子回退与 + // array-like 臂);null/undefined 才放行到回退判定。 + if !method.is_null() && !method.is_undefined() { + 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, value) { if is_callable(next) { diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index fe6f3394..f9c89e1c 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -623,7 +623,8 @@ pub fn push_units_to(val: JsValue, buf: &mut Vec) { buf.extend(s.encode_utf16()); } -/// ToBoolean(ECMA-262 §7.1.2):falsy 值仅限 undefined/null/false/±0/NaN/空串,其余为 true。 +/// ToBoolean(ECMA-262 §7.1.2):falsy 值仅限 undefined/null/false/±0/NaN/空串,其余为 true; +/// [[IsHTMLDDA]] 宿主对象按 undefined 处理(B.3.4)。 pub fn to_boolean(val: JsValue) -> bool { match val.js_type() { JsType::Undefined | JsType::Null => false, @@ -636,7 +637,12 @@ pub fn to_boolean(val: JsValue) -> bool { } JsType::String => !unsafe { (*val.as_string_ptr()).is_empty() }, JsType::BigInt => !unsafe { bigint_data(val) }.is_zero(), - JsType::Object | JsType::Symbol => true, + JsType::Object => { + // SAFETY: Object 型 JsValue 的载荷位是存活对象的裸指针,借用即时消费。 + let obj = unsafe { &*val.as_js_object_ptr() }; + !obj.is_html_dda_obj() + } + JsType::Symbol => true, } } @@ -650,6 +656,20 @@ pub fn to_boolean(val: JsValue) -> bool { pub fn abstract_eq(lhs: JsValue, rhs: JsValue, host: &mut H) -> Result { let tl = lhs.js_type(); let tr = rhs.js_type(); + // [[IsHTMLDDA]] 宿主对象:宽松相等一律按 undefined 处理(B.3.4), + // 重入 undefined 口径(undefined/null 互等、与其余类型不等)。 + if tl == JsType::Object { + // SAFETY: Object 型 JsValue 的载荷位是存活对象的裸指针,借用即时消费。 + if unsafe { &*lhs.as_js_object_ptr() }.is_html_dda_obj() { + return abstract_eq(JsValue::undefined(), rhs, host); + } + } + if tr == JsType::Object { + // SAFETY: 同上。 + if unsafe { &*rhs.as_js_object_ptr() }.is_html_dda_obj() { + return abstract_eq(lhs, JsValue::undefined(), host); + } + } // 同类型 → 严格相等。 if tl == tr { return Ok(strict_equality(lhs, rhs)); diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 4042b63d..766d10a8 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -880,6 +880,10 @@ impl JsObject { /// `native_fn` 槽),无 RegExp 可见属性面;`native_fn` 的释放与深拷贝 /// 守卫与 RegExp 对象一致。 pub const OBJ_TYPE_REGEX_STUB: u8 = 28; + /// [[IsHTMLDDA]] 宿主对象(test262 `$262.IsHTMLDDA`):ToBoolean、typeof + /// 与宽松相等按 undefined 处理,Type 与其余强制转换按普通对象处理;无额外 + /// 内部载荷。 + pub const OBJ_TYPE_HTML_DDA: u8 = 29; /// `is_session_epoch` 字段中的 session 标记位。 pub const SESSION_EPOCH_BIT: u8 = 0x01; /// `is_session_epoch` 字段中的 GC 标记位。 @@ -908,6 +912,11 @@ impl JsObject { pub fn holds_compiled_regex(&self) -> bool { matches!(self.type_tag, Self::OBJ_TYPE_REGEXP | Self::OBJ_TYPE_REGEX_STUB) } + /// 是否 [[IsHTMLDDA]] 宿主对象(ToBoolean/typeof/宽松相等按 undefined 处理)。 + #[inline] + pub fn is_html_dda_obj(&self) -> bool { + self.type_tag == Self::OBJ_TYPE_HTML_DDA + } /// 是否装箱 Boolean 对象。 #[inline] pub fn is_boolean_obj(&self) -> bool { diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index b6fd7717..9437a4fc 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -85,10 +85,13 @@ impl Vm { JsType::Symbol => 5, JsType::BigInt => 6, JsType::Object => { - // 函数对象归为 "function"(语言类型是 object,typeof 有专属分支)。 + // 函数对象归为 "function"(语言类型是 object,typeof 有专属分支); + // [[IsHTMLDDA]] 宿主对象按 "undefined" 报告(B.3.4,Type 仍是 object)。 let obj = unsafe { &*val.as_js_object_ptr() }; if obj.is_function() { 7 + } else if obj.is_html_dda_obj() { + 0 } else { 1 } diff --git a/crates/oxide_vm/src/test262_host.rs b/crates/oxide_vm/src/test262_host.rs index d84b9ac7..fb7df564 100644 --- a/crates/oxide_vm/src/test262_host.rs +++ b/crates/oxide_vm/src/test262_host.rs @@ -111,6 +111,13 @@ pub fn bind_test262_host(core: &Arc, session: &KernelSession, global ); let global_this = JsValue::from_js_object(global as *mut JsObject); bind_global_value(core, &mut host, "global", global_this); + // [[IsHTMLDDA]] 宿主值(B.3.4):ToBoolean/typeof/宽松相等按 undefined 处理, + // Type 与其余强制转换按普通对象处理。无额外内部载荷,挂 Object.prototype。 + let mut dda = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)); + dda.type_tag = JsObject::OBJ_TYPE_HTML_DDA; + let dda_ptr = Box::into_raw(Box::new(dda)); + session.builtin_world().track_leaked_object(dda_ptr); + bind_global_value(core, &mut host, "IsHTMLDDA", JsValue::from_js_object(dda_ptr)); let host_ptr = Box::into_raw(Box::new(host)); session.builtin_world().track_leaked_object(host_ptr); bind_global_value(core, global, "$262", JsValue::from_js_object(host_ptr)); diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index b9605cfe..682d3fe3 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -2238,13 +2238,90 @@ fn typed_array_set_reads_array_like_source_by_index() { let r = eval( &mut vm, "(() => { - const src = { 0: 7, 1: 8, length: 2 }; - src[Symbol.iterator] = function* () { yield 99; }; - const t = new Uint8Array(4); - t.set(src); - return t.join(','); - })()", + const src = { 0: 7, 1: 8, length: 2 }; + src[Symbol.iterator] = function* () { yield 99; }; + const t = new Uint8Array(4); + t.set(src); + return t.join(','); + })()", ) .unwrap(); assert_eq!(to_str(&vm, r), "7,8,0,0"); } + +#[test] +fn array_from_throws_when_iterator_not_callable() { + let mut vm = Vm::new(); + // 红形钉:GetMethod-可选入口(Array.from)对"非空不可调用" @@iterator 须抛 + // TypeError(GetMethod 步 4),红形为静默回退 array-like 得元素。自身置 + // 对象/原始值两形皆红;null 仍走 array-like(对照不抛)。 + let r = eval( + &mut vm, + "(() => { + const o = { length: 2, 0: 1, 1: 2 }; + o[Symbol.iterator] = {}; + let objErr; + try { objErr = Array.from(o).join(','); } + catch (e) { objErr = e.constructor.name; } + o[Symbol.iterator] = 5; + let primErr; + try { primErr = Array.from(o).join(','); } + catch (e) { primErr = e.constructor.name; } + o[Symbol.iterator] = null; + let nullFb; + try { nullFb = Array.from(o).join(','); } + catch (e) { nullFb = e.constructor.name; } + return objErr + '|' + primErr + '|' + nullFb; + })()", + ) + .unwrap(); + assert_eq!(to_str(&vm, r), "TypeError|TypeError|1,2"); +} + +#[test] +fn typed_array_ctor_throws_when_iterator_not_callable() { + let mut vm = Vm::new(); + // 红形钉:TA 构造器(GetMethod-可选入口)对"非空不可调用" @@iterator 须抛 + // TypeError,红形为静默回退 array-like(无 length 得 0 元素)。 + let r = eval( + &mut vm, + "(() => { + const o = { length: 2, 0: 1, 1: 2 }; + o[Symbol.iterator] = {}; + let objErr; + try { objErr = new Uint8Array(o).length; } + catch (e) { objErr = e.constructor.name; } + o[Symbol.iterator] = true; + let boolErr; + try { boolErr = new Uint8Array(o).length; } + catch (e) { boolErr = e.constructor.name; } + return objErr + '|' + boolErr; + })()", + ) + .unwrap(); + assert_eq!(to_str(&vm, r), "TypeError|TypeError"); +} + +#[test] +fn typed_array_from_throws_when_iterator_not_callable() { + let mut vm = Vm::new(); + // 红形钉:TypedArray.from(GetMethod-可选入口)对"非空不可调用" @@iterator + // 须抛 TypeError,红形为静默回退 array-like。 + let r = eval( + &mut vm, + "(() => { + const o = { length: 2, 0: 1, 1: 2 }; + o[Symbol.iterator] = {}; + let objErr; + try { objErr = Uint8Array.from(o).length; } + catch (e) { objErr = e.constructor.name; } + o[Symbol.iterator] = 42; + let primErr; + try { primErr = Uint8Array.from(o).length; } + catch (e) { primErr = e.constructor.name; } + return objErr + '|' + primErr; + })()", + ) + .unwrap(); + assert_eq!(to_str(&vm, r), "TypeError|TypeError"); +}