From ff5ec90fb55dad41e3c62eac451192dcc993289f Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 23 Sep 2026 12:06:29 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20ArrayBuffer=20slice=20=E7=89=A9?= =?UTF-8?q?=E7=A7=8D=E5=8C=96=E6=9E=84=E9=80=A0=E4=B8=8E=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E6=AD=A5=E5=BA=8F=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array_buffer.rs | 190 ++++++++++++++++-- .../tests/builtins_arraybuffer_state_tests.rs | 187 +++++++++++++++++ 2 files changed, 360 insertions(+), 17 deletions(-) diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index 26221194..6fc6888c 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -1,5 +1,6 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr}; +use oxide_types::private_key::{make_well_known_symbol_key, WELL_KNOWN_SYMBOL_SPECIES}; use oxide_types::value::JsValue; use oxide_runtime_api::{NativeResult, VmHost}; @@ -412,29 +413,151 @@ pub fn array_buffer_resize(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } +/// ResolveBounds(len, start, end) 共享核:start 为 undefined(或缺省)归 0、 +/// end 为 undefined(或缺省)归 len,在场臂 ToIntegerOrInfinity 上下夹取 +/// (NaN → 0、±Infinity 饱和 0/len、负值按 len 偏移)。slice 与 +/// sliceToImmutable 同源共用。 +fn ab_resolve_bounds( + vm: &mut H, len: usize, start: Option, end: Option, +) -> Result<(usize, usize), JsValue> { + let start_val = start.unwrap_or(JsValue::undefined()); + let first = if start_val.is_undefined() { 0 } else { normalize_index(vm, start_val, len)? }; + let end_val = end.unwrap_or(JsValue::undefined()); + let final_ = if end_val.is_undefined() { len } else { normalize_index(vm, end_val, len)? }; + Ok((first, final_)) +} + +/// SpeciesConstructor(O, %ArrayBuffer%):完整 Get O.constructor(访问器/异常 +/// 传播);undefined 回落 %ArrayBuffer%,其余非对象抛 TypeError;完整 Get +/// C[Symbol.species](null 归一为 undefined);undefined 回落 %ArrayBuffer%, +/// 非构造器抛 TypeError。 +fn ab_species_constructor(vm: &mut H, o_val: JsValue) -> Result { + let o_obj = unsafe { &*o_val.as_js_object_ptr() }; + let ctor_key = vm.kernel_core().perm_interner().intern("constructor").0; + let c = match vm.ordinary_get(o_obj, ctor_key, o_val) { + Ok(v) => v, + Err(msg) => return Err(crate::iterator::engine_error(vm, &msg)), + }; + let default_ctor = + JsValue::from_js_object(vm.session().builtin_world().array_buffer_constructor.as_ptr() as *mut JsObject); + if c.is_undefined() { + return Ok(default_ctor); + } + if !c.is_object() { + return Err(crate::error::create_type_error(vm, "ArrayBuffer constructor is not an object")); + } + let species_key = make_well_known_symbol_key(WELL_KNOWN_SYMBOL_SPECIES); + let s = match vm.ordinary_get(unsafe { &*c.as_js_object_ptr() }, species_key, c) { + Ok(v) => v, + Err(msg) => return Err(crate::iterator::engine_error(vm, &msg)), + }; + let s = if s.is_null() { JsValue::undefined() } else { s }; + if s.is_undefined() { + Ok(default_ctor) + } else if !crate::array::is_constructor_value(s) { + Err(crate::error::create_type_error(vm, "ArrayBuffer species is not a constructor")) + } else { + Ok(s) + } +} + /// `ArrayBuffer.prototype.slice(start, end)`:复制字节区间生成新 ArrayBuffer。 +/// +/// # 步骤 +/// 1. 品牌守卫与 detached 源守卫先于参数读(抛错时参数无副作用)。 +/// 2. len 快照 → ResolveBounds(start undefined/缺省 → 0、end undefined/缺省 +/// → len,不调强转)→ newLen = max(final - first, 0)。 +/// 3. SpeciesConstructor(O, %ArrayBuffer%)(构造器/@@species 完整 Get,异常 +/// 传播;undefined 臂回落 %ArrayBuffer%;非对象/非构造器 TypeError)。 +/// 4. Construct(ctor, «newLen») 经 vm.construct_ctor(native 值传递 / bytecode +/// 压构造帧,含 derived 构造器 super() 语义;构造器抛出值原样上抛)。 +/// 5. 结果五检:AB 载荷槽 → detached → immutable → SameValue(new, O) → +/// new.byteLength < newLen,均 TypeError。 +/// 6. 构造窗口后重取源载荷(窗口内源可被 detach/晋升):detached → +/// TypeError;按活 currentLen 夹拷贝(count = min(newLen, currentLen - +/// first),first < currentLen 守卫),余位零填充。 +/// +/// # 边界与前提 +/// - 源 immutable 是合法输入(只读拷贝);对 new 的 immutable 检只挡物种 +/// 构造器返回的 immutable 产物。 +/// - 源对象经寄存器根保活;每次 JS 调用窗口后重新读接收者寄存器与载荷指针。 pub fn array_buffer_slice(vm: &mut H, args: &[u8]) -> NativeResult { - let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let this_reg = if args.is_empty() { 0 } else { args[0] }; + let this_val = vm.reg(this_reg); let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷; + // 标量拷出后借用即结束,不跨 JS 调用窗口。 let Some(data) = unsafe { &*payload_ptr }.data.as_ref() else { return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; let len = data.len(); - let start = if args.len() > 1 { - native_try!(normalize_index(vm, vm.reg(args[1]), len)) - } else { - 0 + let start = if args.len() > 1 { Some(vm.reg(args[1])) } else { None }; + let end = if args.len() > 2 { Some(vm.reg(args[2])) } else { None }; + let (first, final_) = native_try!(ab_resolve_bounds(vm, len, start, end)); + let new_len = final_.saturating_sub(first); + + // 物种读与构造各是 JS 调用窗口:getter/构造器可 detach、resize、晋升源。 + let ctor = native_try!(ab_species_constructor(vm, this_val)); + let new_val = match vm.construct_ctor(ctor, &[JsValue::int(new_len as i32)]) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), }; - let end = if args.len() > 2 { - native_try!(normalize_index(vm, vm.reg(args[2]), len)) - } else { - len + + // 构造窗口后重取接收者与载荷(源对象可被晋升进 session,旧指针悬垂)。 + let this_val = vm.reg(this_reg); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: 重取的 payload_ptr 指向存活载荷盒;标量拷出后借用即结束。 + let Some(data) = unsafe { &*payload_ptr }.data.as_ref() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; - let end = end.max(start); - // 切片产物恒定长,原型取默认 %ArrayBuffer.prototype%。 - let proto = default_array_buffer_proto(vm); - NativeResult::Ok(JsValue::from_js_object(new_array_buffer(vm, data[start..end].to_vec(), 0, proto))) + // 结果五检:AB 载荷槽 → detached → immutable → SameValue → 长度。 + let new_ptr = new_val.as_js_object_ptr(); + if new_ptr.is_null() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "ArrayBuffer slice result is not an ArrayBuffer", + )); + } + // SAFETY: new_val 为对象且指针非空,对象本 session 存活。 + let Some(new_payload) = array_buffer_payload_ptr(unsafe { &*new_ptr }) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "ArrayBuffer slice result is not an ArrayBuffer", + )); + }; + if new_payload.is_null() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "ArrayBuffer slice result is not an ArrayBuffer", + )); + } + // SAFETY: new_payload 经 array_buffer_payload_ptr 校验为 ArrayBuffer 载荷盒。 + let new_state = unsafe { &*new_payload }; + if new_state.data.is_none() { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer slice result is detached")); + } + if new_state.immutable { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer slice result is immutable")); + } + if new_val == this_val { + return NativeResult::Err(crate::error::create_type_error( + vm, + "ArrayBuffer slice result must not be the source buffer", + )); + } + if new_state.data.as_ref().map_or(0, |d| d.len()) < new_len { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer slice result is too small")); + } + // 活长度夹拷贝写入结果缓冲前区(构造器已零填充):源中途收缩只拷现存 + // 字节,first 越界零拷。 + let current_len = data.len(); + let count = if first < current_len { new_len.min(current_len - first) } else { 0 }; + // SAFETY: new_payload 经 array_buffer_payload_ptr 校验;detached 臂已先行 + // 排除,data 必为 Some,可变借用止于本语句。 + if let Some(dest) = unsafe { (*new_payload).data.as_mut() } { + dest[..count].copy_from_slice(&data[first..first + count]); + } + NativeResult::Ok(new_val) } /// transfer 族新缓冲保持性:Preserve 源存储态上限原样拷贝,Fixed 定长, @@ -772,11 +895,44 @@ mod tests { let r = eval_ab( &mut vm, "var calls = 0; \ - var ab = new ArrayBuffer(4).transferToImmutable(); \ - try { ab.resize({ valueOf() { calls++; return 0; } }); 'no-throw' } \ - catch (err) { (err.name === 'TypeError' ? 'T' : 'X') + calls }", + var ab = new ArrayBuffer(4).transferToImmutable(); \ + try { ab.resize({ valueOf() { calls++; return 0; } }); 'no-throw' } \ + catch (err) { (err.name === 'TypeError' ? 'T' : 'X') + calls }", ) .unwrap(); assert_eq!(vm.lookup_str(r).unwrap(), "T0"); } + + /// slice end 缺省臂钉:end 为 undefined 归 len 不经强转(6, undefined) → 2; + /// start/end 全缺省 → 全长。 + #[test] + fn slice_end_undefined_defaults_len() { + let mut vm = Vm::new(); + let r = eval_ab( + &mut vm, + "new ArrayBuffer(8).slice(6, undefined).byteLength === 2 \ + && new ArrayBuffer(8).slice(undefined).byteLength === 8 \ + && new ArrayBuffer(8).slice(6).byteLength === 2 \ + && new ArrayBuffer(8).slice(undefined, undefined).byteLength === 8", + ) + .unwrap(); + assert!(r.as_bool()); + } + + /// slice 构造窗口源 detach 重检钉:species 构造器内 detach 源 → 构造后 + /// 重检抛 TypeError(不交付结果)。 + #[test] + fn slice_source_detach_in_construct_throws() { + let mut vm = Vm::new(); + let r = eval_ab( + &mut vm, + "(function () { var ab = new ArrayBuffer(8); \ + var c = {}; c[Symbol.species] = function (len) { \ + $262.detachArrayBuffer(ab); return new ArrayBuffer(len); }; \ + ab.constructor = c; \ + try { ab.slice(); return 'no-throw'; } catch (err) { return err.name; } })()", + ) + .unwrap(); + assert_eq!(vm.lookup_str(r).unwrap(), "TypeError"); + } } diff --git a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs index fc7da44b..087ad939 100644 --- a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs +++ b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs @@ -818,3 +818,190 @@ fn ab_transfer_range_and_alloc_limits() { .unwrap(); assert!(result.as_bool()); } + +/// slice 窗口钉:全窗/前缀/后缀/负 start/负 end/start 越界/end 越界/ +/// start 超 end/空窗九形长度,源不 detach。 +#[test] +fn ab_slice_window_pins() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(8); \ + var bl = function (s) { return ab.slice(s[0], s[1]).byteLength; }; \ + return bl([undefined, undefined]) === 8 && bl([0, 3]) === 3 && bl([6, undefined]) === 2 \ + && bl([-2]) === 2 && bl([0, -2]) === 6 && bl([20]) === 0 \ + && bl([2, 20]) === 6 && bl([5, 2]) === 0 && bl([3, 3]) === 0 \ + && ab.detached === false })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// slice 空窗钉:产物附着 0 长(区别于 detach 空缓冲)。 +#[test] +fn ab_slice_empty_window_not_detached() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var r = new ArrayBuffer(4).slice(3, 3); \ + return r.byteLength === 0 && r.detached === false && r.immutable === false })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// slice 内容钉:[1..8] 源 slice(2,6) → [3,4,5,6],产物独立于源。 +#[test] +fn ab_slice_content_copy() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(8); var v = new Uint8Array(ab); \ + for (var i = 0; i < 8; i++) v[i] = i + 1; \ + var d = new Uint8Array(ab.slice(2, 6)); \ + return d.length === 4 && d[0] === 3 && d[1] === 4 && d[2] === 5 && d[3] === 6 \ + && (v[0] = 90, new Uint8Array(ab.slice(2, 6))[0] === 3) })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// slice 构造窗内源收缩钉:species 构造器内 resize(1) 后返回 AB(8) → +/// 不抛、结果 8 长、前 1 字节为源现存、余零填充(活 currentLen 夹拷贝)。 +#[test] +fn ab_slice_source_shrink_in_construct() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(8, {maxByteLength: 16}); \ + var v = new Uint8Array(ab); v[0] = 9; \ + var c = {}; c[Symbol.species] = function (len) { \ + ab.resize(1); return new ArrayBuffer(8); }; \ + ab.constructor = c; \ + var r = ab.slice(); \ + var d = new Uint8Array(r); \ + return r.byteLength === 8 && ab.byteLength === 1 \ + && d[0] === 9 && d[1] === 0 && d[6] === 0 && d[7] === 0 })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// slice 物种回落三形钉:constructor undefined / species undefined / +/// species null → 默认原型 + 内容正确(防回归钉,语料现巧合绿)。 +#[test] +fn ab_slice_species_default_fallbacks() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + var ab1 = new ArrayBuffer(8); ab1.constructor = undefined; \ + var r1 = ab1.slice(); \ + var c2 = {}; var ab2 = new ArrayBuffer(8); ab2.constructor = c2; \ + var r2 = ab2.slice(); \ + var c3 = {}; c3[Symbol.species] = null; var ab3 = new ArrayBuffer(8); ab3.constructor = c3; \ + var r3 = ab3.slice(); \ + var v = new Uint8Array(ab1); v[0] = 7; \ + var d = new Uint8Array(ab1.slice()); \ + return Object.getPrototypeOf(r1) === ArrayBuffer.prototype && r1.byteLength === 8 \ + && Object.getPrototypeOf(r2) === ArrayBuffer.prototype \ + && Object.getPrototypeOf(r3) === ArrayBuffer.prototype \ + && d[0] === 7 })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// slice 物种交付钉:构造器实参 «8» 逐次捕获、返回自建 AB → 交付原值且 +/// 内容拷贝入产物。 +#[test] +fn ab_slice_species_custom_returns() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var calls = []; var c = {}; \ + c[Symbol.species] = function (len) { calls.push(len); return new ArrayBuffer(8); }; \ + var ab = new ArrayBuffer(8); var v = new Uint8Array(ab); \ + for (var i = 0; i < 8; i++) v[i] = i + 1; \ + ab.constructor = c; \ + var r = ab.slice(); \ + var d = new Uint8Array(r); \ + return r.byteLength === 8 && calls.join(',') === '8' \ + && d[0] === 1 && d[7] === 8 && ab.detached === false })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// slice 物种 TypeError 五形钉:constructor null/true/\"\"、species +/// 对象/Function.prototype 均 TypeError。 +#[test] +fn ab_slice_species_type_error_family() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { function ty(fn) { try { fn(); return false; } catch (e) { return e instanceof TypeError; } } \ + var ab = new ArrayBuffer(8); \ + ab.constructor = null; var t1 = ty(function () { ab.slice(); }); \ + ab.constructor = true; var t2 = ty(function () { ab.slice(); }); \ + ab.constructor = ''; var t3 = ty(function () { ab.slice(); }); \ + var c = {}; ab.constructor = c; \ + c[Symbol.species] = {}; var t4 = ty(function () { ab.slice(); }); \ + c[Symbol.species] = Function.prototype; var t5 = ty(function () { ab.slice(); }); \ + return t1 && t2 && t3 && t4 && t5 })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// slice 结果五检前四形钉:物种返回非 AB / 返回 O 自身 / 返回小缓冲 → +/// TypeError;返回大缓冲 → 交付(内容只填前缀)。 +#[test] +fn ab_slice_species_result_checks() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { function ty(fn) { try { fn(); return false; } catch (e) { return e instanceof TypeError; } } \ + var a1 = new ArrayBuffer(8); var c1 = {}; \ + c1[Symbol.species] = function () { return {}; }; a1.constructor = c1; \ + var t1 = ty(function () { a1.slice(); }); \ + var a2 = new ArrayBuffer(8); var c2 = {}; \ + c2[Symbol.species] = function () { return a2; }; a2.constructor = c2; \ + var t2 = ty(function () { a2.slice(); }); \ + var a3 = new ArrayBuffer(8); var c3 = {}; \ + c3[Symbol.species] = function () { return new ArrayBuffer(4); }; a3.constructor = c3; \ + var t3 = ty(function () { a3.slice(); }); \ + var a4 = new ArrayBuffer(8); var v = new Uint8Array(a4); v[0] = 5; \ + var c4 = {}; c4[Symbol.species] = function () { return new ArrayBuffer(10); }; a4.constructor = c4; \ + var r4 = a4.slice(); var d4 = new Uint8Array(r4); \ + return t1 && t2 && t3 && r4.byteLength === 10 && d4[0] === 5 && d4[8] === 0 })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// slice 源 detach 四形钉:源已 detach(参数未读)、start/end valueOf 内 +/// detach、species 构造内 detach(构造后重检步)均 TypeError。 +#[test] +fn ab_slice_source_detach_family() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { function ty(fn) { try { fn(); return false; } catch (e) { return e instanceof TypeError; } } \ + var a1 = new ArrayBuffer(8); $262.detachArrayBuffer(a1); \ + var calls1 = []; var s1 = { valueOf: function () { calls1.push('s'); return 0; } }; \ + var t1 = ty(function () { a1.slice(s1); }); \ + var a2 = new ArrayBuffer(8); \ + var s2 = { valueOf: function () { $262.detachArrayBuffer(a2); return 0; } }; \ + var t2 = ty(function () { a2.slice(s2); }); \ + var a3 = new ArrayBuffer(8); \ + var e3 = { valueOf: function () { $262.detachArrayBuffer(a3); return 2; } }; \ + var t3 = ty(function () { a3.slice(1, e3); }); \ + var a4 = new ArrayBuffer(8); var c4 = {}; \ + c4[Symbol.species] = function (len) { $262.detachArrayBuffer(a4); return new ArrayBuffer(len); }; \ + a4.constructor = c4; var t4 = ty(function () { a4.slice(); }); \ + return t1 && calls1.length === 0 && t2 && t3 && t4 })()", + ) + .unwrap(); + assert!(result.as_bool()); +}