mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 迭代可选入口对非空不可调用 @@iterator 抛错并实现 IsHTMLDDA 宿主值
This commit is contained in:
@@ -1365,16 +1365,22 @@ pub fn iterator_wrapper_next<H: VmHost>(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<H: VmHost>(vm: &mut H, value: JsValue) -> Result<bool, JsValue> {
|
||||
// String 臂无条件可迭代(与 get_iterator 的 String 臂同一近似)。
|
||||
if value.is_string() {
|
||||
@@ -1396,7 +1402,12 @@ pub(crate) fn peek_iterator_method<H: VmHost>(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) {
|
||||
|
||||
@@ -623,7 +623,8 @@ pub fn push_units_to(val: JsValue, buf: &mut Vec<u16>) {
|
||||
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<H: VmHost>(lhs: JsValue, rhs: JsValue, host: &mut H) -> Result<bool, String> {
|
||||
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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -111,6 +111,13 @@ pub fn bind_test262_host(core: &Arc<KernelCore>, 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));
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user