From 317c940f04df2facc60b5fa42e886c03b8523484 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 22 Sep 2026 23:42:17 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20ArrayBuffer=20transfer=20=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E4=B8=8E=20detach=20=E6=BA=90=E6=8B=B7?= =?UTF-8?q?=E8=B4=9D=E6=A0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array_buffer.rs | 153 ++++++++++++++++++ .../src/bindings/bind_array_buffer.rs | 15 ++ .../tests/builtins_arraybuffer_state_tests.rs | 147 +++++++++++++++++ 3 files changed, 315 insertions(+) diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index afe7d08e..5db05c9c 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -444,6 +444,121 @@ pub fn array_buffer_slice(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_array_buffer(vm, data[start..end].to_vec(), 0, proto))) } +/// transfer 族新缓冲保持性:Preserve 源存储态上限原样拷贝,Fixed 定长, +/// Immutable 定长并置 immutable 标志。 +#[derive(Clone, Copy)] +enum TransferKeep { + Preserve, + Fixed, + Immutable, +} + +/// `ArrayBufferCopyAndDetach(O, newLength, keep)` 共享核:品牌守卫 → +/// newLength 求值 → detached/immutable 判定 → 界校验 → 前缀拷贝零填充建新 +/// 缓冲 → 源 detach。 +/// +/// # 步骤 +/// 1. this 品牌校验(非对象/非 ArrayBuffer → TypeError)。 +/// 2. newLength 缺省 → 源当前字节长;在场 → ToIndex 传播式(强转副作用 +/// 先于一切守卫观测)。 +/// 3. 重取源载荷指针(求值可已晋升/detach/改 immutable),重检状态位: +/// detached → TypeError;immutable → TypeError。 +/// 4. 界校验:resizable 源且 newByteLength > 真实上限(重读存储态 − 1)→ +/// RangeError;newByteLength > 引擎分配上界 → RangeError。 +/// 5. 物化拷贝(载荷指针新鲜,字节借出止于本步)。 +/// 6. 新建缓冲(proto 取 %ArrayBuffer.prototype%,保持性按 keep),字节 +/// 序列零填充至 newByteLength。 +/// 7. 再重取源载荷指针 detach(`data → None`):分配可已晋升源对象,新缓冲 +/// 持独立拷贝,源 detach 不影响返回值。 +/// 8. 返回新缓冲。 +fn array_buffer_copy_and_detach( + vm: &mut H, this_reg: u8, new_length: Option, keep: TransferKeep, +) -> NativeResult { + 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 载荷; + // 只读当前字节长,借用即结束,不跨 JS 调用点。 + let cur_len = unsafe { &*payload_ptr }.data.as_ref().map_or(0, |d| d.len()); + let new_len = match new_length { + None => cur_len, + Some(v) => native_try!(to_index(vm, v)), + }; + // 求值可已把本缓冲区晋升进 session、detach 或置 immutable:接收者寄存器 + // 与状态位均须重取重检,不得消费求值前快照。 + let this_val = vm.reg(this_reg); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: 重取的 payload_ptr 指向存活载荷盒;标量拷出后借用即结束。 + let (cur_len, stored_max, immutable, detached) = unsafe { + let p = &*payload_ptr; + (p.data.as_ref().map_or(0, |d| d.len()), p.max_byte_length, p.immutable, p.data.is_none()) + }; + if detached { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + } + if immutable { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer is immutable")); + } + // 存储态上限 +1 编码:真实上限 = 存储态 − 1(上限 0 的缓冲只许同长)。 + if stored_max != 0 && new_len > stored_max - 1 { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); + } + if new_len > MAX_ARRAY_BUFFER_LENGTH { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); + } + // SAFETY: 重取的 payload_ptr 指向存活载荷盒;detached 已排除,data 必为 + // Some。字节借出止于本语句。 + let copy_len = new_len.min(cur_len); + let mut new_data: Vec = unsafe { &*payload_ptr } + .data + .as_deref() + .map_or_else(Vec::new, |d| d[..copy_len].to_vec()); + new_data.resize(new_len, 0); + let (dest_max, dest_immutable) = match keep { + TransferKeep::Preserve => (stored_max, false), + TransferKeep::Fixed => (0, false), + TransferKeep::Immutable => (0, true), + }; + let proto = default_array_buffer_proto(vm); + let dest_ptr = new_array_buffer(vm, new_data, dest_max, proto); + // SAFETY: dest_ptr 为 alloc_object 新建对象,载荷盒由 new_array_buffer 建。 + if dest_immutable { + let Some(p) = array_buffer_payload_ptr(unsafe { &*dest_ptr }) else { + return type_error(vm, "ArrayBuffer internal state invalid"); + }; + unsafe { (*p).immutable = true }; + } + // 分配可已将源晋升:detach 前再重取指针,免悬垂窗。 + let this_val = vm.reg(this_reg); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: 重取的 payload_ptr 指向存活载荷盒。 + unsafe { (*payload_ptr).data = None }; + NativeResult::Ok(JsValue::from_js_object(dest_ptr)) +} + +/// `ArrayBuffer.prototype.transfer([newLength])`:拷贝并 detach,保持性 +/// 原样(定长→定长,resizable 源→resizable 同上限)。 +pub fn array_buffer_transfer(vm: &mut H, args: &[u8]) -> NativeResult { + let this_reg = if args.is_empty() { 0 } else { args[0] }; + let new_length = if args.len() > 1 { Some(vm.reg(args[1])) } else { None }; + array_buffer_copy_and_detach(vm, this_reg, new_length, TransferKeep::Preserve) +} + +/// `ArrayBuffer.prototype.transferToFixedLength([newLength])`:拷贝并 +/// detach,新缓冲恒定长。 +pub fn array_buffer_transfer_to_fixed_length(vm: &mut H, args: &[u8]) -> NativeResult { + let this_reg = if args.is_empty() { 0 } else { args[0] }; + let new_length = if args.len() > 1 { Some(vm.reg(args[1])) } else { None }; + array_buffer_copy_and_detach(vm, this_reg, new_length, TransferKeep::Fixed) +} + +/// `ArrayBuffer.prototype.transferToImmutable([newLength])`:拷贝并 +/// detach,新缓冲恒定长且 immutable。 +pub fn array_buffer_transfer_to_immutable(vm: &mut H, args: &[u8]) -> NativeResult { + let this_reg = if args.is_empty() { 0 } else { args[0] }; + let new_length = if args.len() > 1 { Some(vm.reg(args[1])) } else { None }; + array_buffer_copy_and_detach(vm, this_reg, new_length, TransferKeep::Immutable) +} + /// `ArrayBuffer.isView(value)`:参数是 DataView 或 TypedArray 才返回 true。 pub fn array_buffer_is_view(vm: &mut H, args: &[u8]) -> NativeResult { let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; @@ -601,6 +716,44 @@ mod tests { assert_eq!(vm.lookup_str(e).unwrap(), "TypeError"); } + /// transfer 族新缓冲存储态字段表:Preserve 源上限原样(+1 编码不变)、 + /// Fixed/Immutable 定长,仅 Immutable 置位;源均 detach。 + #[test] + fn transfer_dest_payload_field_table() { + let mut vm = Vm::new(); + let r = eval_ab( + &mut vm, + "var src = new ArrayBuffer(4, {maxByteLength: 8}); \ + var v = new Uint8Array(src); v[0] = 9; \ + var dest = src.transfer(5); \ + var a = new Uint8Array(dest)[0]; \ + var src1 = new ArrayBuffer(4, {maxByteLength: 8}); \ + var d1 = src1.transferToFixedLength(5).maxByteLength; \ + var src2 = new ArrayBuffer(4, {maxByteLength: 8}); \ + var d2 = src2.transferToImmutable(5); \ + var fixed = new ArrayBuffer(3); \ + var d3 = fixed.transfer(3).resizable; \ + [a, src.detached, d1, d2.immutable, d2.resizable, fixed.detached, d3].join(',')", + ) + .unwrap(); + assert_eq!(vm.lookup_str(r).unwrap(), "9,true,5,true,false,true,false"); + // 载荷存储态直读:transfer 产物 max = 8 + 1(+1 编码),ttf/tti 产物 = 0。 + let dest_val = eval_ab( + &mut vm, + "var s = new ArrayBuffer(4, {maxByteLength: 8}); \ + globalThis.__d = s.transfer(5); globalThis.__d", + ) + .unwrap(); + // SAFETY: __d 为已晋升 session 的 ArrayBuffer 对象,借用止于断言、不跨下一次 eval。 + let dest_obj = unsafe { &*dest_val.as_js_object_ptr() }; + let p = array_buffer_payload_ptr(dest_obj).expect("产物应携带载荷盒"); + // SAFETY: p 指向存活载荷盒。 + let payload = unsafe { &*p }; + assert_eq!(payload.max_byte_length, 9); + assert!(!payload.immutable); + assert_eq!(payload.data.as_ref().expect("产物应附着").len(), 5); + } + /// resize 后置 detach 重检钉:强转调用中途 detach 后按 TypeError 抛, /// 不触碰已 detach 载荷(现树 expect 臂的 abort 钉改点)。 #[test] diff --git a/crates/oxide_vm/src/bindings/bind_array_buffer.rs b/crates/oxide_vm/src/bindings/bind_array_buffer.rs index 8a10b013..79f65aee 100644 --- a/crates/oxide_vm/src/bindings/bind_array_buffer.rs +++ b/crates/oxide_vm/src/bindings/bind_array_buffer.rs @@ -63,6 +63,21 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global oxide_builtins::array_buffer::array_buffer_mark_immutable:: as *const (), 0, ), + ( + "transfer", + oxide_builtins::array_buffer::array_buffer_transfer:: as *const (), + 0, + ), + ( + "transferToFixedLength", + oxide_builtins::array_buffer::array_buffer_transfer_to_fixed_length:: as *const (), + 0, + ), + ( + "transferToImmutable", + oxide_builtins::array_buffer::array_buffer_transfer_to_immutable:: as *const (), + 0, + ), ], ); diff --git a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs index 8f8f3061..fc7da44b 100644 --- a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs +++ b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs @@ -671,3 +671,150 @@ fn ab_ta_view_length_detached_zero() { oxide_builtins::typed_array::TaIndexGate::NumericInvalid ); } + +/// transfer 拷贝/增减/零填充/源 detach 钉:grow 补零、same 原样、shrink +/// 截断;transfer(0) 产物附着 0 长(区别于 detach)。 +#[test] +fn ab_transfer_copies_grows_shrinks_and_detaches() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "function fill(ab) { var v = new Uint8Array(ab); v[0]=1; v[1]=2; v[2]=3; v[3]=4; return ab; } \ + var a = new Uint8Array(fill(new ArrayBuffer(4)).transfer(5)); \ + a[0]===1 && a[1]===2 && a[2]===3 && a[3]===4 && a[4]===0 \ + && new Uint8Array(fill(new ArrayBuffer(4)).transfer(4)).length === 4 \ + && new Uint8Array(fill(new ArrayBuffer(4)).transfer(2)).join(',') === '1,2' \ + && fill(new ArrayBuffer(4)).transfer(0).detached === false \ + && fill(new ArrayBuffer(4)).transfer(0).byteLength === 0", + ) + .unwrap(); + assert!(result.as_bool()); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4); ab.transfer(2); ab.detached === true && ab.byteLength === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// transfer 保持性钉:resizable 源 → 产物 resizable 同上限,产物附着。 +#[test] +fn ab_transfer_preserves_resizability() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var dest = new ArrayBuffer(4, {maxByteLength: 8}).transfer(5); \ + dest.resizable === true && dest.maxByteLength === 8 \ + && dest.detached === false && dest.byteLength === 5", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// transferToFixedLength 钉:resizable 源 → 产物恒定长、源 detach。 +#[test] +fn ab_transfer_to_fixed_length_fixed() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4, {maxByteLength: 8}); \ + var dest = ab.transferToFixedLength(6); \ + dest.resizable === false && dest.maxByteLength === 6 && dest.byteLength === 6 \ + && ab.detached === true && ab.byteLength === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// transferToImmutable 钉:产物 immutable + 恒定长、源 detach;显式 longer +/// 零填充。 +#[test] +fn ab_transfer_to_immutable_sets_flag() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4); \ + var dest = ab.transferToImmutable(); \ + dest.immutable === true && dest.resizable === false && ab.detached === true \ + && new Uint8Array(new ArrayBuffer(4).transferToImmutable(9)).length === 9 \ + && new Uint8Array(new ArrayBuffer(4).transferToImmutable(9))[8] === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// newLength 求值序与强转钉:valueOf→toString 序 TypeError、2^53 RangeError、 +/// valueOf 内 detach 后走 detached 判定(求值先于守卫)。 +#[test] +fn ab_transfer_new_length_order_and_coercion() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var log = []; \ + var nl = { toString() { log.push('toString'); return {}; }, \ + valueOf() { log.push('valueOf'); return {}; } }; \ + var t1 = false; \ + try { new ArrayBuffer(0).transfer(nl); } catch (e) { t1 = e instanceof TypeError; } \ + t1 && log.length === 2 && log[0] === 'valueOf' && log[1] === 'toString' \ + && (function () { var t2 = false; \ + try { new ArrayBuffer(0).transfer(2 ** 53); } catch (e) { t2 = e instanceof RangeError; } \ + return t2; })() \ + && (function () { var ab = new ArrayBuffer(8); var t3 = false; \ + try { ab.transfer({ valueOf() { $262.detachArrayBuffer(ab); return 1; } }); } \ + catch (e) { t3 = e instanceof TypeError; } \ + return t3; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// detached 源钉:三方法同形 TypeError。 +#[test] +fn ab_transfer_detached_source_type_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var a = new ArrayBuffer(4); a.transfer(); \ + var t1 = false, t2 = false, t3 = false; \ + try { a.transfer(); } catch (e) { t1 = e instanceof TypeError; } \ + try { a.transferToFixedLength(); } catch (e) { t2 = e instanceof TypeError; } \ + try { a.transferToImmutable(); } catch (e) { t3 = e instanceof TypeError; } \ + t1 && t2 && t3", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// immutable 源钉:三方法同形 TypeError,newLength 读取先于 mutability 守卫。 +#[test] +fn ab_transfer_immutable_source_type_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var iab = new ArrayBuffer(4).transferToImmutable(); \ + var calls = []; \ + var nl = { valueOf() { calls.push('valueOf'); return 1; } }; \ + var t1 = false, t2 = false, t3 = false; \ + try { iab.transfer(nl); } catch (e) { t1 = e instanceof TypeError; } \ + try { iab.transferToFixedLength(); } catch (e) { t2 = e instanceof TypeError; } \ + try { iab.transferToImmutable(); } catch (e) { t3 = e instanceof TypeError; } \ + t1 && t2 && t3 && calls.length === 1 && calls[0] === 'valueOf'", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 界校验钉:resizable 源超真实上限 RangeError、超引擎分配上界 RangeError。 +#[test] +fn ab_transfer_range_and_alloc_limits() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var t1 = false, t2 = false; \ + try { new ArrayBuffer(4, {maxByteLength: 8}).transfer(9); } catch (e) { t1 = e instanceof RangeError; } \ + try { new ArrayBuffer(0).transfer(2 ** 32); } catch (e) { t2 = e instanceof RangeError; } \ + t1 && t2", + ) + .unwrap(); + assert!(result.as_bool()); +}