mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: splice removed 数组按 species 构造并严格化 length 写
This commit is contained in:
@@ -197,13 +197,18 @@ pub fn array_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 折算 `start` / `deleteCount`(负数自尾部、夹到界内)。
|
||||
/// 2. 先建 removed 数组:逐被删下标 HasProperty 门控 + Get,命中
|
||||
/// CreateDataPropertyOrThrow、缺失保洞;结果对象钉入返回寄存器(其后的
|
||||
/// 搬移/插入各带用户调用窗口,Rust 局部指针不属 GC 根集)。
|
||||
/// 3. 双向搬移:源存在则 Get + 严格 Set 到目标,源缺失则目标 DeletePropertyOrThrow
|
||||
/// 2. 先按 ArraySpeciesCreate 建 removed 数组(收集与搬移之前,用户构造器
|
||||
/// 读到未改动的 O),结果对象钉入返回寄存器(其后的收集/搬移/插入各带
|
||||
/// 用户调用窗口,Rust 局部指针不属 GC 根集)。
|
||||
/// 3. 逐被删下标 HasProperty 门控 + Get,命中 CreateDataPropertyOrThrow;
|
||||
/// 缺失位仅真数组 A 保洞(A 预填 present-undefined 须显式置洞),非数组
|
||||
/// A 按规范不建属性。
|
||||
/// 4. `Set(A, "length", deleteCount, true)` 在搬移前收尾。
|
||||
/// 5. 双向搬移:源存在则 Get + 严格 Set 到目标,源缺失则目标 DeletePropertyOrThrow
|
||||
/// (不可配置抛 TypeError);扩容方向下超出当前元素数的目标先记录,length
|
||||
/// 写入后补置洞。
|
||||
/// 4. 插入项逐个严格 Set,length 快写收尾。
|
||||
/// 6. 插入项逐个严格 Set,`Set(this, "length", newLen, true)` 收尾(length
|
||||
/// 不可写抛 TypeError)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 元素被删/插/移;Get/Set 可经原型链触发用户代码。
|
||||
@@ -234,9 +239,13 @@ pub fn array_splice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
let insert_count = if args.len() > 3 { args.len() - 3 } else { 0 };
|
||||
|
||||
// removed 先行构建并钉入返回寄存器:搬移/插入窗口的用户 setter 可触发
|
||||
// 回收,结果对象指针须保 GC 根,返回时从钉位读回。
|
||||
let removed_arr = create_new_array(vm, delete_count);
|
||||
// removed 先行按 ArraySpeciesCreate 构建(收集/搬移之前)并钉入返回寄存器:
|
||||
// 搬移/插入窗口的用户 setter 可触发回收,结果对象指针须保 GC 根,返回时
|
||||
// 从钉位读回;species 读与构造器抛错即 splice 抛错。
|
||||
let removed_arr = match array_species_create(vm, recv, true, delete_count) {
|
||||
Ok(p) => p,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
};
|
||||
vm.set_reg(0, JsValue::from_js_object(removed_arr));
|
||||
for k in 0..delete_count {
|
||||
match gated_get(vm, arr_ptr, start + k, recv) {
|
||||
@@ -246,12 +255,27 @@ pub fn array_splice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Err(err);
|
||||
}
|
||||
}
|
||||
// 源缺失位保洞(removed 预填 present-undefined,须显式置洞)。
|
||||
Ok(None) => unsafe { (*removed_arr).mark_hole_at(k) },
|
||||
// 源缺失位仅真数组 A 保洞(A 预填 present-undefined,须显式置洞);
|
||||
// 非数组 A 不建属性。
|
||||
Ok(None) => {
|
||||
if unsafe { &*removed_arr }.is_array() {
|
||||
unsafe { (*removed_arr).mark_hole_at(k) }
|
||||
}
|
||||
}
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
// Set(A, "length", actualDeleteCount, true) 先于搬移:真数组 A 构造器已置
|
||||
// 长度时为空写,构造器忽略单参时扩长。
|
||||
let a_val = JsValue::from_js_object(removed_arr);
|
||||
let length_si = vm.string_key_si("length");
|
||||
if let Err(err) =
|
||||
vm.ordinary_set(unsafe { &mut *removed_arr }, length_si, js_array_index(delete_count), a_val, true)
|
||||
{
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
|
||||
// 目标超出当前元素数的洞位无法即时置(越界 no-op),length 写入后补。
|
||||
let mut pending_holes: Vec<usize> = Vec::new();
|
||||
|
||||
@@ -305,8 +329,12 @@ pub fn array_splice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
}
|
||||
|
||||
// Set(this, "length", newLen, true) 同样走 Set:length 不可写在此抛
|
||||
// TypeError(与新旧长度是否相同无关)。
|
||||
let new_len = n + insert_count - delete_count;
|
||||
unsafe { (*arr_ptr).set_prop_count_fast(new_len) };
|
||||
if let Err(err) = vm.ordinary_set(unsafe { &mut *arr_ptr }, length_si, js_array_index(new_len), recv, true) {
|
||||
return NativeResult::Err(from_engine_error(vm, &err));
|
||||
}
|
||||
for h in pending_holes {
|
||||
unsafe { (*arr_ptr).mark_hole_at(h) };
|
||||
}
|
||||
|
||||
@@ -194,3 +194,194 @@ fn test_at_out_of_bounds_undefined() {
|
||||
let (_vm, result) = eval_value("[1, 2].at(9)").unwrap();
|
||||
assert!(result.is_undefined());
|
||||
}
|
||||
|
||||
// splice species:Ctor 返回体直接作结果,构造器恰收单参 actualDeleteCount,O 已搬移。
|
||||
#[test]
|
||||
fn test_splice_species_ctor_returned_instance() {
|
||||
let out = eval_str(
|
||||
"(() => { let seen = null; const inst = { mark: 1 }; \
|
||||
function Ctor(n) { seen = n; inst.len = n; return inst; } \
|
||||
const a = [0,1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
|
||||
const r = a.splice(2); \
|
||||
return seen + '|' + (r === inst) + '|' + r.len + '|' + a.join(','); })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "2|true|2|0,1");
|
||||
}
|
||||
|
||||
// splice species 普通 Ctor 无返回体:结果非数组、原型为 Ctor.prototype,
|
||||
// Set(A,"length") 扩长至删除数且值逐位填入。
|
||||
#[test]
|
||||
fn test_splice_species_plain_ctor_extends_length() {
|
||||
let out = eval_str(
|
||||
"(() => { function Ctor() {} \
|
||||
const a = [0,1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
|
||||
const r = a.splice(0); \
|
||||
return Array.isArray(r) + '|' + (Object.getPrototypeOf(r) === Ctor.prototype) + '|' + \
|
||||
r.length + '|' + r[0] + ',' + r[1] + ',' + r[2] + ',' + r[3]; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "false|true|4|0,1,2,3");
|
||||
}
|
||||
|
||||
// splice species 为 null / 缺省:回退真数组(原型为 Array.prototype),值与长度正确。
|
||||
#[test]
|
||||
fn test_splice_species_null_undef_plain_array() {
|
||||
let out = eval_str(
|
||||
"(() => { const a = [0,1,2]; a.constructor = {}; a.constructor[Symbol.species] = null; \
|
||||
const r1 = a.splice(1); \
|
||||
const b = [0,1,2]; b.constructor = {}; \
|
||||
const r2 = b.splice(1); \
|
||||
return Array.isArray(r1) + '|' + (Object.getPrototypeOf(r1) === Array.prototype) + '|' + \
|
||||
r1.length + '|' + r1[0] + '|' + Array.isArray(r2) + '|' + \
|
||||
(Object.getPrototypeOf(r2) === Array.prototype) + '|' + r2.length; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "true|true|2|1|true|true|2");
|
||||
}
|
||||
|
||||
// splice species 构造器收 -0 折算的删除数时收到 +0(非 -0)。
|
||||
#[test]
|
||||
fn test_splice_species_neg_zero() {
|
||||
let out = eval_str(
|
||||
"(() => { let isNeg = null; \
|
||||
function Ctor(n) { isNeg = Object.is(n, -0); } \
|
||||
const a = []; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
|
||||
const r = a.splice(0, -0); \
|
||||
return 'neg=' + isNeg + '|len=' + r.length; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "neg=false|len=0");
|
||||
}
|
||||
|
||||
// splice species 结果 "0" 不可写:CreateDataPropertyOrThrow 覆盖为 present,
|
||||
// 描述符恢复可写可配置。
|
||||
#[test]
|
||||
fn test_splice_species_writable_false_overwritten() {
|
||||
let out = eval_str(
|
||||
"(() => { function Ctor(n) { const o = {}; o.length = n; \
|
||||
Object.defineProperty(o, '0', { writable: false, configurable: true, enumerable: true, value: 99 }); \
|
||||
return o; } \
|
||||
const a = [0,1,2]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
|
||||
const r = a.splice(1); \
|
||||
const d = Object.getOwnPropertyDescriptor(r, '0'); \
|
||||
return r[0] + '|' + d.writable + '|' + d.configurable + '|' + r.length; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "1|true|true|2");
|
||||
}
|
||||
|
||||
// splice species 结果 "0" 不可配置:CreateDataPropertyOrThrow 抛 TypeError。
|
||||
#[test]
|
||||
fn test_splice_species_non_configurable_throws() {
|
||||
let out = eval_str(
|
||||
"(() => { function Ctor(n) { const o = {}; o.length = n; \
|
||||
Object.defineProperty(o, '0', { writable: true, configurable: false, enumerable: true, value: 99 }); \
|
||||
return o; } \
|
||||
const a = [0,1,2]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
|
||||
try { a.splice(1); return 'no-throw'; } catch (e) { return e.constructor.name; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "TypeError");
|
||||
}
|
||||
|
||||
// splice species 结果不可扩展:CreateDataPropertyOrThrow 抛 TypeError。
|
||||
#[test]
|
||||
fn test_splice_species_non_extensible_throws() {
|
||||
let out = eval_str(
|
||||
"(() => { function Ctor(n) { const o = {}; o.length = n; Object.preventExtensions(o); return o; } \
|
||||
const a = [0,1,2]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
|
||||
try { a.splice(1); return 'no-throw'; } catch (e) { return e.constructor.name; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "TypeError");
|
||||
}
|
||||
|
||||
// splice constructor 为非对象非 undefined(null/数字/字符串/布尔四值)均抛 TypeError。
|
||||
#[test]
|
||||
fn test_splice_ctor_non_object_throws() {
|
||||
let out = eval_str(
|
||||
"(() => { let ok = 0; \
|
||||
for (const cv of [null, 1, 'string', true]) { \
|
||||
const a = [0,1,2]; a.constructor = cv; \
|
||||
try { a.splice(1); } catch (e) { if (e instanceof TypeError) ok++; } } \
|
||||
return String(ok); })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "4");
|
||||
}
|
||||
|
||||
// 防过度修:无 species 时结果仍为真数组、原型为 Array.prototype,自定义 Ctor 不被调用。
|
||||
#[test]
|
||||
fn test_splice_no_species_plain_array() {
|
||||
let out = eval_str(
|
||||
"(() => { let calls = 0; function Ctor() { calls++; } \
|
||||
const a = [0,1,2]; \
|
||||
const r = a.splice(1); \
|
||||
return Array.isArray(r) + '|' + (Object.getPrototypeOf(r) === Array.prototype) + '|' + r.length + '|' + calls; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "true|true|2|0");
|
||||
}
|
||||
|
||||
// 步骤序:构造器在收集与搬移之前执行,读到未改动的 O。
|
||||
#[test]
|
||||
fn test_splice_ctor_reads_o_before_mutation() {
|
||||
let out = eval_str(
|
||||
"(() => { let seenLen = null, seen0 = null; \
|
||||
function Ctor(n) { seenLen = a.length; seen0 = a[0]; } \
|
||||
const a = [0,1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
|
||||
a.splice(0, 2, 9); \
|
||||
return seenLen + '|' + seen0 + '|' + a.join(','); })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "4|0|9,2,3");
|
||||
}
|
||||
|
||||
// @@species getter 抛错原样透传(非构造器 constructor 上的 species 读取异常上抛)。
|
||||
#[test]
|
||||
fn test_splice_species_poisoned_propagates() {
|
||||
let out = eval_str(
|
||||
"(() => { const a = [0,1,2]; a.constructor = {}; \
|
||||
Object.defineProperty(a.constructor, Symbol.species, { get() { throw new RangeError('species-get'); } }); \
|
||||
try { a.splice(1); return 'no-throw'; } catch (e) { return e.constructor.name + ':' + e.message; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "RangeError:species-get");
|
||||
}
|
||||
|
||||
// length 不可写(语料同形 defineProperty 仅收窄 writable 位):新旧长度相同的
|
||||
// 空 splice 与收缩/插入 splice 均抛 TypeError。
|
||||
#[test]
|
||||
fn test_splice_length_nonwritable_throws() {
|
||||
let out = eval_str(
|
||||
"(() => { const a = [0,1,2]; Object.defineProperty(a, 'length', { writable: false }); \
|
||||
let k1; try { a.splice(1, 2, 4); return 'shrink-no-throw'; } catch (e) { k1 = e.constructor.name; } \
|
||||
const b = [0,1,2]; Object.defineProperty(b, 'length', { writable: false }); \
|
||||
try { b.splice(0, 0); return 'noop-no-throw'; } catch (e) { return k1 + '|' + e.constructor.name; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "TypeError|TypeError");
|
||||
}
|
||||
|
||||
// 冻结数组空删除也走 length 写:抛 TypeError(防空 splice 被特例放行)。
|
||||
#[test]
|
||||
fn test_splice_frozen_empty_delete_throws_length() {
|
||||
let out = eval_str(
|
||||
"(() => { try { Object.freeze([0,1,2]).splice(0, 0); return 'no-throw'; } \
|
||||
catch (e) { return e.constructor.name; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "TypeError");
|
||||
}
|
||||
|
||||
// 冻结数组删除命中元素:元素严格 Set 面抛 TypeError(回归护栏)。
|
||||
#[test]
|
||||
fn test_splice_frozen_element_set_throws() {
|
||||
let out = eval_str(
|
||||
"(() => { try { Object.freeze([0,1,2]).splice(1, 1); return 'no-throw'; } \
|
||||
catch (e) { return e.constructor.name; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(out, "TypeError");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user