From 5dbff7d941fd59f1be6af95e749661b06f7dd3b9 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 23 Sep 2026 12:23:36 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20ArrayBuffer=20sliceToImmutable=20?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=85=A8=E8=AF=AD=E4=B9=89=E8=90=BD=E5=9C=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array_buffer.rs | 95 ++++++++++++ .../src/bindings/bind_array_buffer.rs | 5 + .../tests/builtins_arraybuffer_state_tests.rs | 136 +++++++++++++++++- 3 files changed, 235 insertions(+), 1 deletion(-) diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index 6fc6888c..2338fa55 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -560,6 +560,82 @@ pub fn array_buffer_slice(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(new_val) } +/// `ArrayBuffer.prototype.sliceToImmutable(start, end)`:复制字节区间生成 +/// immutable 新 ArrayBuffer,源缓冲不 detach、不改任何状态位。 +/// +/// # 步骤 +/// 1. 品牌守卫与 detached 源守卫先于参数读(抛错时参数无副作用)。 +/// 2. len 快照 → ResolveBounds(与 slice 共享助手)→ newLen。 +/// 3. 强转窗口后重取源载荷重检:detached → TypeError(先于长度终检); +/// currentLen < final → RangeError(源中途收缩到解析界之下)。 +/// 4. GetPrototypeFromConstructor(%ArrayBuffer%):完整读构造器 "prototype" +/// (访问器抛错传播,非对象回落 %ArrayBuffer.prototype%)。 +/// 5. proto 读窗口后再次重取源载荷(窗口内源可 detach/收缩/晋升), +/// 重检 detached 与在界后拷贝前 newLen 字节(恒在界内),零填充分配, +/// 后置 immutable 标志;源不动。 +/// +/// # 边界与前提 +/// - 源 immutable 是合法输入(只读拷贝);产物恒 immutable、恒定长。 +/// - 无物种面:构造器恒 %ArrayBuffer%。 +pub fn array_buffer_slice_to_immutable(vm: &mut H, args: &[u8]) -> NativeResult { + 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 载荷; + // 标量拷出后借用即结束,不跨 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 { 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); + + // 强转窗口可 detach/晋升源:重取寄存器与载荷后重检(先于长度终检)。 + 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")); + }; + if data.len() < final_ { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); + } + // GetPrototypeFromConstructor(%ArrayBuffer%):prototype 读为 JS 窗口 + // (访问器可被用户重定义),抛错传播;非对象回落默认原型。 + let ctor_val = + JsValue::from_js_object(vm.session().builtin_world().array_buffer_constructor.as_ptr() as *mut JsObject); + let ctor_obj = unsafe { &*ctor_val.as_js_object_ptr() }; + let proto_si = vm.kernel_core().perm_interner().intern("prototype").0; + let proto_val = match vm.ordinary_get(ctor_obj, proto_si, ctor_val) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::iterator::engine_error(vm, &msg)), + }; + let proto = if proto_val.is_object() { proto_val } else { default_array_buffer_proto(vm) }; + // proto 读窗口可 detach/收缩/晋升源:再重取载荷重检 detached 与在界, + // 拷贝恒在界内(currentLen ≥ final ≥ first + newLen)。 + 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")); + }; + if data.len() < final_ { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); + } + let out: Vec = data[first..first + new_len].to_vec(); + let dest_ptr = new_array_buffer(vm, out, 0, proto); + // SAFETY: dest_ptr 为 alloc_object 新建对象,载荷盒由 new_array_buffer 建。 + let Some(dest_payload) = array_buffer_payload_ptr(unsafe { &*dest_ptr }) else { + return type_error(vm, "ArrayBuffer internal state invalid"); + }; + // SAFETY: dest_payload 指向存活载荷盒;immutable 后置位与 transfer-Immutable + // 同形,不扩 new_array_buffer 签名。 + unsafe { (*dest_payload).immutable = true }; + NativeResult::Ok(JsValue::from_js_object(dest_ptr)) +} + /// transfer 族新缓冲保持性:Preserve 源存储态上限原样拷贝,Fixed 定长, /// Immutable 定长并置 immutable 标志。 #[derive(Clone, Copy)] @@ -919,6 +995,25 @@ mod tests { assert!(r.as_bool()); } + /// sliceToImmutable 源不 detach 钉:产物 immutable,源返回后可写/可 + /// resize/可 detach,产物内容独立于源。 + #[test] + fn sti_source_writable_after_return() { + let mut vm = Vm::new(); + let r = eval_ab( + &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 dest = ab.sliceToImmutable(); \ + var ok = dest.immutable === true && dest.resizable === false && ab.detached === false; \ + v[0] = 86; \ + var v2 = new Uint8Array(dest); \ + return ok && v2[0] === 1 && v2[7] === 8 && ab.byteLength === 8; })()", + ) + .unwrap(); + assert!(r.as_bool()); + } + /// slice 构造窗口源 detach 重检钉:species 构造器内 detach 源 → 构造后 /// 重检抛 TypeError(不交付结果)。 #[test] diff --git a/crates/oxide_vm/src/bindings/bind_array_buffer.rs b/crates/oxide_vm/src/bindings/bind_array_buffer.rs index 79f65aee..ec5ab346 100644 --- a/crates/oxide_vm/src/bindings/bind_array_buffer.rs +++ b/crates/oxide_vm/src/bindings/bind_array_buffer.rs @@ -53,6 +53,11 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global 1, ), ("slice", oxide_builtins::array_buffer::array_buffer_slice:: as *const (), 2), + ( + "sliceToImmutable", + oxide_builtins::array_buffer::array_buffer_slice_to_immutable:: as *const (), + 2, + ), ( "toString", oxide_builtins::array_buffer::array_buffer_to_string:: as *const (), diff --git a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs index 087ad939..49f217bf 100644 --- a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs +++ b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs @@ -756,7 +756,7 @@ fn ab_transfer_new_length_order_and_coercion() { 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; \ + && (function () { var t2 = false, t3 = false, e2 = '?'; \ try { new ArrayBuffer(0).transfer(2 ** 53); } catch (e) { t2 = e instanceof RangeError; } \ return t2; })() \ && (function () { var ab = new ArrayBuffer(8); var t3 = false; \ @@ -1005,3 +1005,137 @@ fn ab_slice_source_detach_family() { .unwrap(); assert!(result.as_bool()); } + +/// sliceToImmutable 界与缺省钉:(undefined,undefined)/(6,undefined)/负/越界/ +/// 空窗长度形 + immutable 产物形 + prop-desc 描述符与 name/length。 +#[test] +fn ab_sti_bounds_and_defaults() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(8); \ + var bl = function (s) { return ab.sliceToImmutable(s[0], s[1]).byteLength; }; \ + return bl([undefined, undefined]) === 8 && bl([6, undefined]) === 2 \ + && bl([6]) === 2 && bl([-2]) === 2 && bl([0, -2]) === 6 \ + && bl([20]) === 0 && bl([2, 20]) === 6 && bl([5, 2]) === 0 && bl([3, 3]) === 0 \ + && ab.sliceToImmutable().immutable === true \ + && ab.sliceToImmutable().resizable === false })()", + ) + .unwrap(); + assert!(result.as_bool()); + let result = eval( + &mut vm, + "(function () { var d = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'sliceToImmutable'); \ + return typeof d.value === 'function' && d.value.name === 'sliceToImmutable' \ + && d.value.length === 2 && d.writable === true && d.enumerable === false \ + && d.configurable === true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// sliceToImmutable 源增缩钉:grows 形产物 [4,5,6] 源 12 长;shrinks 第一 +/// block 同形源 8 长;resize 到解析界下 → RangeError。 +#[test] +fn ab_sti_grow_shrink_bounds() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var source = new ArrayBuffer(10, { maxByteLength: 12 }); \ + var view = new Uint8Array(source); \ + for (var i = 0; i < 10; i++) view[i] = i + 1; \ + var start = { valueOf: function () { source.resize(11); return -7; } }; \ + var end = { valueOf: function () { source.resize(12); return -4; } }; \ + var dest = source.sliceToImmutable(start, end); \ + var dv = new Uint8Array(dest); \ + var ok = dv.length === 3 && dv[0] === 4 && dv[1] === 5 && dv[2] === 6 && source.byteLength === 12; \ + var source2 = new ArrayBuffer(10, { maxByteLength: 10 }); \ + var view2 = new Uint8Array(source2); \ + for (var j = 0; j < 10; j++) view2[j] = j + 1; \ + var s2 = { valueOf: function () { source2.resize(9); return -7; } }; \ + var e2 = { valueOf: function () { source2.resize(8); return -4; } }; \ + var d2 = new Uint8Array(source2.sliceToImmutable(s2, e2)); \ + ok = ok && d2.length === 3 && d2[0] === 4 && d2[1] === 5 && d2[2] === 6 && source2.byteLength === 8; \ + source2.resize(10); \ + var e3 = { valueOf: function () { source2.resize(5); return -4; } }; \ + var t3 = false; \ + try { source2.sliceToImmutable(s2, e3); } catch (err) { t3 = err instanceof RangeError; } \ + return ok && t3; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// sliceToImmutable 源 detach 两形钉:源已 detach → TypeError 且参数未读; +/// end.valueOf 内 detach → TypeError,calls 双记。 +#[test] +fn ab_sti_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 e1 = { valueOf: function () { calls1.push('e'); return 1; } }; \ + var t1 = ty(function () { a1.sliceToImmutable(s1, e1); }); \ + var a2 = new ArrayBuffer(8); \ + var calls2 = []; \ + var s2 = { valueOf: function () { calls2.push('start.valueOf'); return 0; } }; \ + var e2 = { valueOf: function () { $262.detachArrayBuffer(a2); calls2.push('end.valueOf'); return 1; } }; \ + var t2 = ty(function () { a2.sliceToImmutable(s2, e2); }); \ + return t1 && calls1.length === 0 && t2 && calls2.join(',') === 'start.valueOf,end.valueOf'; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// sliceToImmutable 源后改无影响钉:源写/resize/detach 后产物内容不变、 +/// 产物恒 8 长 immutable。 +#[test] +fn ab_sti_modify_source() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var source = new ArrayBuffer(8, { maxByteLength: 8 }); \ + var view = new Uint8Array(source); \ + for (var i = 0; i < 8; i++) view[i] = i + 1; \ + var dest = source.sliceToImmutable(); \ + var dv = new Uint8Array(dest); \ + var ok = dv.length === 8 && dv[0] === 1 && dv[7] === 8 && dest.immutable === true; \ + view[0] = 86; \ + ok = ok && new Uint8Array(dest)[0] === 1; \ + source.resize(4); \ + ok = ok && dest.byteLength === 8 && new Uint8Array(dest)[7] === 8; \ + $262.detachArrayBuffer(source); \ + return ok && new Uint8Array(dest)[0] === 1 && source.detached === true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// sliceToImmutable 品牌钉:非对象/普通对象/数组/函数/DataView/TypedArray +/// 六形 TypeError 且参数未读;`new (实例.sliceToImmutable)()` 形态 +/// TypeError(nonconstructor,成员链绑定使 new 目标即方法本身)。 +#[test] +fn ab_sti_brand_family() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var fn = ArrayBuffer.prototype.sliceToImmutable; \ + var calls = []; \ + var s = { valueOf: function () { calls.push('s'); return 0; } }; \ + function ty(v) { calls.length = 0; \ + try { fn.call(v, s); return false; } catch (e) { return e instanceof TypeError; } } \ + var ok = ty(undefined) && ty(null) && ty({}) && ty([]) && ty(function () {}) \ + && ty(new DataView(new ArrayBuffer(8), 0)) && ty(new Int8Array(8)); \ + var calls2 = []; \ + var s2 = { valueOf: function () { calls2.push('s'); return 0; } }; \ + var t2 = false, t3 = false; \ + var nab = new ArrayBuffer(8); \ + try { new nab.sliceToImmutable(); t3 = true; } catch (e) { t2 = e instanceof TypeError; t3 = false; } \ + return ok && calls.length === 0 && t2 && calls2.length === 0 && !t3; })()", + ) + .unwrap(); + assert!(result.as_bool()); +}