diff --git a/crates/oxide_builtins/src/array/from.rs b/crates/oxide_builtins/src/array/from.rs index 37d8fad4..5d53e8b5 100644 --- a/crates/oxide_builtins/src/array/from.rs +++ b/crates/oxide_builtins/src/array/from.rs @@ -335,32 +335,32 @@ pub(crate) fn array_species_create( let o_ptr = o_val.as_js_object_ptr(); let o_obj = unsafe { &*o_ptr }; let ctor_key = vm.kernel_core().perm_interner().intern("constructor").0; - let mut c = match vm.ordinary_get(o_obj, ctor_key, o_val) { + let c = match vm.ordinary_get(o_obj, ctor_key, o_val) { Ok(v) => v, Err(msg) => return Err(from_engine_error(vm, &msg)), }; - // C 为 undefined 时不查 species,直接回退内置 ArrayCreate。 + // C 为 undefined 时直接回退内置 ArrayCreate;C 为其余非对象(null/基元)抛 TypeError。 if c.is_undefined() { return Ok(create_new_array(vm, count)); } - if !is_constructor_value(c) { - if !c.is_object() { - return Err(crate::error::create_type_error(vm, "Species constructor not a constructor")); - } - let species_key = make_well_known_symbol_key(10); - let s = match vm.ordinary_get(unsafe { &*c.as_js_object_ptr() }, species_key, c) { - Ok(v) => v, - Err(msg) => return Err(from_engine_error(vm, &msg)), - }; - c = if s.is_null() { JsValue::undefined() } else { s }; - } - if c.is_undefined() { - return Ok(create_new_array(vm, count)); - } - if !is_constructor_value(c) { + if !c.is_object() { return Err(crate::error::create_type_error(vm, "Species constructor not a constructor")); } - match construct_array_from_result(vm, c, &[js_array_index(count)]) { + // 一切对象 C 均须查 @@species(null 归一为 undefined):S 为 undefined 回退 + // ArrayCreate,S 非构造器抛 TypeError,否则以 S 构造。 + let species_key = make_well_known_symbol_key(10); + let s = match vm.ordinary_get(unsafe { &*c.as_js_object_ptr() }, species_key, c) { + Ok(v) => v, + Err(msg) => return Err(from_engine_error(vm, &msg)), + }; + let s = if s.is_null() { JsValue::undefined() } else { s }; + if s.is_undefined() { + return Ok(create_new_array(vm, count)); + } + if !is_constructor_value(s) { + return Err(crate::error::create_type_error(vm, "Species constructor not a constructor")); + } + match construct_array_from_result(vm, s, &[js_array_index(count)]) { Ok((ptr, _)) => Ok(ptr), Err(err) => Err(err), } diff --git a/crates/oxide_builtins/src/array/iterate.rs b/crates/oxide_builtins/src/array/iterate.rs index c854df03..04ca47cf 100644 --- a/crates/oxide_builtins/src/array/iterate.rs +++ b/crates/oxide_builtins/src/array/iterate.rs @@ -9,8 +9,8 @@ use crate::builtins_error; use super::common::{ array_ptr_len3, array_type_error, arraylike_get, arraylike_get_or_err, arraylike_index_present, - check_array_create_len, create_new_array, get_this_arraylike, invoke_native_callback, js_array_index, - require_callback, unexpected_tail_call_error, + check_array_create_len, get_this_arraylike, invoke_native_callback, js_array_index, require_callback, + unexpected_tail_call_error, }; use super::from::{array_species_create, create_data_property_or_throw, from_engine_error}; @@ -43,9 +43,21 @@ pub fn array_for_each(vm: &mut H, args: &[u8]) -> NativeResult { } /// `Array.prototype.map(callback, thisArg)`:对每个元素调用 callback 生成新数组。 +/// +/// # 步骤 +/// 1. `ArraySpeciesCreate(O, n)` 构建结果对象(真数组按 constructor/@@species +/// 构造,非可构造值抛 TypeError)并钉入返回寄存器。 +/// 2. 逐索引 HasProperty 门控 + Get,回调以 (elem, 索引, O) 调用;present 位以 +/// CreateDataPropertyOrThrow 写入(受限目标抛 TypeError),洞位不写属性, +/// 真数组目标显式置洞(预填 present-undefined)。 +/// 3. 无收尾 Set(length):规范 map 不设 length,length 由构造器/元素写入决定。 +/// +/// # 副作用 +/// - 回调为用户代码;Get 可经原型链触发 getter;species 构造与目标属性写入 +/// 可按规范抛 TypeError。 pub fn array_map(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.map called with {} args", args.len()); - let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args); + let (arr_ptr, n, is_array) = array_ptr_len3!(vm, args); let o_val = vm.reg(args[0]); if args.len() < 2 { return NativeResult::Err(array_type_error(vm, "callback is not a function")); @@ -55,37 +67,57 @@ pub fn array_map(vm: &mut H, args: &[u8]) -> NativeResult { Err(err) => return NativeResult::Err(err), }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - let new_arr = create_new_array(vm, n); if let Err(err) = check_array_create_len(vm, n) { return NativeResult::Err(err); } + // ArraySpeciesCreate(O, n):真数组按 constructor/@@species 构造目标。 + let a_ptr = match array_species_create(vm, o_val, is_array, n) { + Ok(p) => p, + Err(err) => return NativeResult::Err(err), + }; + // 结果钉入返回寄存器:其后的回调各带用户窗口,结果指针须保 GC 根。 + vm.set_reg(0, JsValue::from_js_object(a_ptr)); for i in 0..n { - // 洞位跳过回调,并在结果对应位留洞(结果数组预填 present-undefined,须显式置洞)。 + // 洞位跳过回调且不写属性;真数组目标预填 present-undefined,须显式置洞。 if !arraylike_index_present(vm, arr_ptr, i) { - unsafe { - (*new_arr).mark_hole_at(i); + if unsafe { &*a_ptr }.is_array() { + unsafe { + (*a_ptr).mark_hole_at(i); + } } continue; } let elem = arraylike_get_or_err!(vm, arr_ptr, i); - match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) { - NativeResult::Ok(mapped) => unsafe { - (*new_arr).set_prop_at(i, mapped); - }, + let mapped = match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) { + NativeResult::Ok(mapped) => mapped, NativeResult::Err(err) => return NativeResult::Err(err), NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + }; + let key_si = vm.string_key_si(&i.to_string()); + if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, key_si, mapped) { + return NativeResult::Err(err); } } - unsafe { - (*new_arr).set_prop_count(n); - } - NativeResult::Ok(JsValue::from_js_object(new_arr)) + NativeResult::Ok(JsValue::from_js_object(a_ptr)) } /// `Array.prototype.filter(callback, thisArg)`:保留 callback 返回真值的元素形成新数组。 +/// +/// # 步骤 +/// 1. `ArraySpeciesCreate(O, 0)` 构建结果对象(真数组按 constructor/@@species +/// 构造,非可构造值抛 TypeError)并钉入返回寄存器;真数组结果元素数随写入 +/// 逐位增长。 +/// 2. 逐索引 HasProperty 门控 + Get,回调以 (elem, 索引, O) 调用;真值结果以 +/// CreateDataPropertyOrThrow 写入递增目标位(受限目标抛 TypeError),洞位 +/// 不复制(结果保持紧凑)。 +/// 3. 无收尾 Set(length):规范 filter 不设 length,length 由构造器/元素写入决定。 +/// +/// # 副作用 +/// - 回调为用户代码;Get 可经原型链触发 getter;species 构造与目标属性写入 +/// 可按规范抛 TypeError。 pub fn array_filter(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.filter called with {} args", args.len()); - let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args); + let (arr_ptr, n, is_array) = array_ptr_len3!(vm, args); let o_val = vm.reg(args[0]); if args.len() < 2 { builtins_error!("Array.prototype.filter: invalid receiver"); @@ -99,34 +131,37 @@ pub fn array_filter(vm: &mut H, args: &[u8]) -> NativeResult { } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - let mut kept: Vec = Vec::new(); if let Err(err) = check_array_create_len(vm, n) { return NativeResult::Err(err); } + // ArraySpeciesCreate(O, 0):真数组按 constructor/@@species 构造目标。 + let a_ptr = match array_species_create(vm, o_val, is_array, 0) { + Ok(p) => p, + Err(err) => return NativeResult::Err(err), + }; + // 结果钉入返回寄存器:其后的回调各带用户窗口,结果指针须保 GC 根。 + vm.set_reg(0, JsValue::from_js_object(a_ptr)); + let mut t = 0usize; for i in 0..n { // 洞位跳过:结果保持紧凑,不复制缺失索引。 if !arraylike_index_present(vm, arr_ptr, i) { continue; } let elem = arraylike_get_or_err!(vm, arr_ptr, i); - match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) { - NativeResult::Ok(result_val) => { - if oxide_runtime_api::to_boolean(result_val) { - kept.push(elem); - } - } + let keep = match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) { + NativeResult::Ok(result_val) => oxide_runtime_api::to_boolean(result_val), NativeResult::Err(err) => return NativeResult::Err(err), NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + }; + if keep { + let key_si = vm.string_key_si(&t.to_string()); + if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, key_si, elem) { + return NativeResult::Err(err); + } + t += 1; } } - let new_arr = create_new_array(vm, kept.len()); - unsafe { - for (i, val) in kept.iter().enumerate() { - (*new_arr).set_prop_at(i, *val); - } - (*new_arr).set_prop_count(kept.len()); - } - NativeResult::Ok(JsValue::from_js_object(new_arr)) + NativeResult::Ok(JsValue::from_js_object(a_ptr)) } /// `Array.prototype.reduce(callback, initialValue)`:从左到右累计归约; diff --git a/crates/oxide_builtins/tests/array_hole_semantics_tests.rs b/crates/oxide_builtins/tests/array_hole_semantics_tests.rs index bcc3e583..37a380f1 100644 --- a/crates/oxide_builtins/tests/array_hole_semantics_tests.rs +++ b/crates/oxide_builtins/tests/array_hole_semantics_tests.rs @@ -940,3 +940,310 @@ fn test_reverse_arraylike_length_not_written_back() { .unwrap(); assert_eq!(out, "2.7|y|x"); } + +// map species 实例型 Ctor:构造恰一次且收单参 n=5,返回体直接作结果, +// 回调值经 CreateDataPropertyOrThrow 逐位写入。 +#[test] +fn test_map_species_instance_ctor() { + let out = eval_str( + "(() => { let cc = 0, seen = null; \ + function C1(n) { cc++; seen = n; return { tag: 'inst' }; } \ + const a = [1,2,3,4,5]; a.constructor = {}; a.constructor[Symbol.species] = C1; \ + const r = a.map(x => x * 2); \ + return cc + '|' + seen + '|' + r.tag + '|' + r[0] + '|' + r[4]; })()", + ) + .unwrap(); + assert_eq!(out, "1|5|inst|2|10"); +} + +// map species 普通 Ctor 无返回体:结果非数组、原型为 Ctor.prototype,且无收尾 +// Set(length)(length 为 undefined,仅元素位 present)。 +#[test] +fn test_map_species_plain_ctor_no_length() { + let out = eval_str( + "(() => { let cc = 0; \ + function C2(n) { cc++; } \ + const a = [1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = C2; \ + const r = a.map(x => x + 10); \ + return cc + '|' + Array.isArray(r) + '|' + (Object.getPrototypeOf(r) === C2.prototype) + '|' + \ + r.length + '|' + r[0] + '|' + r[2]; })()", + ) + .unwrap(); + assert_eq!(out, "1|false|true|undefined|11|13"); +} + +// map constructor 访问器抛错:Get 异常原样传播。 +#[test] +fn test_map_ctor_accessor_throws() { + let out = eval_str( + "(() => { const a = [1]; \ + Object.defineProperty(a, 'constructor', { get() { throw new Error('ctor-poison'); } }); \ + try { a.map(x => x); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()", + ) + .unwrap(); + assert_eq!(out, "Error:ctor-poison"); +} + +// map constructor 为非对象非 undefined(null/数字/字符串/布尔四值)均抛 TypeError。 +#[test] +fn test_map_ctor_non_object_throws() { + let out = eval_str( + "(() => { let ok = 0; \ + for (const cv of [null, 1, 'string', true]) { \ + const a = [1,2]; a.constructor = cv; \ + try { a.map(x => x); } catch (e) { if (e instanceof TypeError) ok++; } } \ + return String(ok); })()", + ) + .unwrap(); + assert_eq!(out, "4"); +} + +// map @@species 访问器抛错原样透传。 +#[test] +fn test_map_species_accessor_throws() { + let out = eval_str( + "(() => { const a = [1]; a.constructor = { get [Symbol.species]() { throw new Error('species-poison'); } }; \ + try { a.map(x => x); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()", + ) + .unwrap(); + assert_eq!(out, "Error:species-poison"); +} + +// map species 为非构造器对象(箭头函数)抛 TypeError。 +#[test] +fn test_map_species_non_ctor_throws() { + let out = eval_str( + "(() => { const a = [1]; a.constructor = {}; a.constructor[Symbol.species] = () => {}; \ + try { a.map(x => x); return 'no-throw'; } catch (e) { return e.name; } })()", + ) + .unwrap(); + assert_eq!(out, "TypeError"); +} + +// map 目标不可扩展:CreateDataPropertyOrThrow 抛 TypeError。 +#[test] +fn test_map_species_non_extensible_throws() { + let out = eval_str( + "(() => { function C3() { return Object.preventExtensions({}); } \ + const a = [1]; a.constructor = {}; a.constructor[Symbol.species] = C3; \ + try { a.map(x => x); return 'no-throw'; } catch (e) { return e.name; } })()", + ) + .unwrap(); + assert_eq!(out, "TypeError"); +} + +// map 洞位输入:洞不写属性(结果该位缺失),length 与 present 位值正确。 +#[test] +fn test_map_hole_skipped_not_written() { + let out = eval_str( + "(() => { const r = [1,,3].map(x => x * 10); \ + return (1 in r) + '|' + r.length + '|' + r[0] + '|' + r[2]; })()", + ) + .unwrap(); + assert_eq!(out, "false|3|10|30"); +} + +// map species 为 null / undefined:回退真数组(原型 Array.prototype),值正确。 +#[test] +fn test_map_species_null_undef_plain_array() { + let out = eval_str( + "(() => { const a = [1,2]; a.constructor = {}; a.constructor[Symbol.species] = null; \ + const r1 = a.map(x => x); \ + const b = [1,2]; b.constructor = {}; b.constructor[Symbol.species] = undefined; \ + const r2 = b.map(x => x); \ + return Array.isArray(r1) + '|' + (Object.getPrototypeOf(r1) === Array.prototype) + '|' + r1.length + '|' + \ + Array.isArray(r2) + '|' + r2.length; })()", + ) + .unwrap(); + assert_eq!(out, "true|true|2|true|2"); +} + +// filter species 实例型 Ctor:构造恰一次且收单参 count=0,返回体直接作结果, +// 保留元素逐位写入。 +#[test] +fn test_filter_species_instance_ctor() { + let out = eval_str( + "(() => { let cc = 0, seen = null; \ + function F1(n) { cc++; seen = n; return { tag: 'inst' }; } \ + const a = [1,2,3,4,5,6]; a.constructor = {}; a.constructor[Symbol.species] = F1; \ + const r = a.filter(x => x % 2 === 0); \ + return cc + '|' + seen + '|' + r.tag + '|' + r[0] + '|' + r[1]; })()", + ) + .unwrap(); + assert_eq!(out, "1|0|inst|2|4"); +} + +// filter species Ctor 预设 length=99:无收尾 Set(length),预设值原样保留, +// 保留元素逐位写入。 +#[test] +fn test_filter_species_pre_length_kept() { + let out = eval_str( + "(() => { let cc = 0; \ + function F2(n) { cc++; this.length = 99; } \ + const a = [1,2,3,4]; a.constructor = {}; a.constructor[Symbol.species] = F2; \ + const r = a.filter(x => x > 1); \ + return cc + '|' + Object.prototype.hasOwnProperty.call(r, 'length') + '|' + r.length + '|' + \ + r[0] + '|' + r[1] + '|' + Object.keys(r).join(','); })()", + ) + .unwrap(); + assert_eq!(out, "1|true|99|2|3|0,1,2,length"); +} + +// filter constructor 访问器抛错:Get 异常原样传播。 +#[test] +fn test_filter_ctor_accessor_throws() { + let out = eval_str( + "(() => { const a = [1]; \ + Object.defineProperty(a, 'constructor', { get() { throw new Error('ctor-poison'); } }); \ + try { a.filter(x => true); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()", + ) + .unwrap(); + assert_eq!(out, "Error:ctor-poison"); +} + +// filter constructor 为非对象非 undefined(null/数字/字符串/布尔四值)均抛 TypeError。 +#[test] +fn test_filter_ctor_non_object_throws() { + let out = eval_str( + "(() => { let ok = 0; \ + for (const cv of [null, 1, 'string', true]) { \ + const a = [1]; a.constructor = cv; \ + try { a.filter(x => true); } catch (e) { if (e instanceof TypeError) ok++; } } \ + return String(ok); })()", + ) + .unwrap(); + assert_eq!(out, "4"); +} + +// filter @@species 访问器抛错原样透传。 +#[test] +fn test_filter_species_accessor_throws() { + let out = eval_str( + "(() => { const a = [1]; a.constructor = { get [Symbol.species]() { throw new Error('species-poison'); } }; \ + try { a.filter(x => true); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()", + ) + .unwrap(); + assert_eq!(out, "Error:species-poison"); +} + +// filter species 为非构造器对象(箭头函数)抛 TypeError。 +#[test] +fn test_filter_species_non_ctor_throws() { + let out = eval_str( + "(() => { const a = [1]; a.constructor = {}; a.constructor[Symbol.species] = () => {}; \ + try { a.filter(x => true); return 'no-throw'; } catch (e) { return e.name; } })()", + ) + .unwrap(); + assert_eq!(out, "TypeError"); +} + +// filter 目标不可扩展:CreateDataPropertyOrThrow 抛 TypeError。 +#[test] +fn test_filter_species_non_extensible_throws() { + let out = eval_str( + "(() => { function C3() { return Object.preventExtensions({}); } \ + const a = [1,2,3,4]; a.constructor = {}; a.constructor[Symbol.species] = C3; \ + try { a.filter(x => x > 0); return 'no-throw'; } catch (e) { return e.name; } })()", + ) + .unwrap(); + assert_eq!(out, "TypeError"); +} + +// filter 非数组接收者(arraylike):结果恒为真数组,值正确。 +#[test] +fn test_filter_arraylike_receiver_plain() { + let out = eval_str( + "(() => { const r = Array.prototype.filter.call({ 0: 1, 1: 2, length: 2 }, x => x > 0); \ + return Array.isArray(r) + '|' + r.length + '|' + r.join(','); })()", + ) + .unwrap(); + assert_eq!(out, "true|2|1,2"); +} + +// filter 普通数组无 species 覆写:结果真数组、值与长度正确(防过度修)。 +#[test] +fn test_filter_plain_array_plain_result() { + let out = eval_str( + "(() => { const r = [1,2,3,4].filter(x => x % 2 === 0); \ + return Array.isArray(r) + '|' + r.length + '|' + r.join(','); })()", + ) + .unwrap(); + assert_eq!(out, "true|2|2,4"); +} + +// 可构造 C 覆写 @@species:结果经 species 构造(Other 实例),而非 C 本身。 +#[test] +fn test_slice_species_ctor_override_returns_other() { + let out = eval_str( + "(() => { function Base(n) { this.tag = 'base'; } \ + function Other(n) { this.tag = 'other'; } \ + Base[Symbol.species] = Other; \ + const a = [1,2,3]; a.constructor = Base; \ + const r = a.slice(0); \ + return r.tag + '|' + Array.isArray(r); })()", + ) + .unwrap(); + assert_eq!(out, "other|false"); +} + +// 可构造 C 无 @@species:Get 查得 undefined,回退真数组(原型 Array.prototype)。 +#[test] +fn test_slice_ctor_plain_no_species_plain_array() { + let out = eval_str( + "(() => { function C(n) { this.tag = 'C'; } \ + const a = [1,2,3]; a.constructor = C; \ + const r = a.slice(0); \ + return Array.isArray(r) + '|' + (Object.getPrototypeOf(r) === Array.prototype); })()", + ) + .unwrap(); + assert_eq!(out, "true|true"); +} + +// 可构造 C 的 @@species 为非构造器对象抛 TypeError。 +#[test] +fn test_slice_species_non_ctor_throws() { + let out = eval_str( + "(() => { function C3(n) { this.tag = 'C3'; } C3[Symbol.species] = () => {}; \ + const a = [1,2,3]; a.constructor = C3; \ + try { a.slice(0); return 'no-throw'; } catch (e) { return e.name; } })()", + ) + .unwrap(); + assert_eq!(out, "TypeError"); +} + +// 可构造 C 的 @@species 访问器抛错原样透传。 +#[test] +fn test_slice_species_accessor_throws() { + let out = eval_str( + "(() => { function C4(n) { this.tag = 'C4'; } \ + Object.defineProperty(C4, Symbol.species, { get() { throw new Error('species-poison'); } }); \ + const a = [1,2,3]; a.constructor = C4; \ + try { a.slice(0); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()", + ) + .unwrap(); + assert_eq!(out, "Error:species-poison"); +} + +// 守卫:c={} 不可构造形态经 @@species 构造(136 系列保真,防 228 修点漂移)。 +#[test] +fn test_slice_ctor_braces_species_plain() { + let out = eval_str( + "(() => { function G(n) { this.tag = 'G'; } \ + const a = [1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = G; \ + const r = a.slice(0); \ + return r.tag; })()", + ) + .unwrap(); + assert_eq!(out, "G"); +} + +// 守卫:普通数组 slice 无 species 覆写,结果真数组、值正确。 +#[test] +fn test_slice_plain_array_guard() { + let out = eval_str( + "(() => { const r = [1,2,3].slice(0); \ + return Array.isArray(r) + '|' + r.join(','); })()", + ) + .unwrap(); + assert_eq!(out, "true|1,2,3"); +}