feat: ArrayBuffer sliceToImmutable 方法全语义落地

This commit is contained in:
2026-09-23 12:23:36 +00:00
parent ff5ec90fb5
commit 5dbff7d941
3 changed files with 235 additions and 1 deletions
+95
View File
@@ -560,6 +560,82 @@ pub fn array_buffer_slice<H: VmHost>(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<H: VmHost>(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<u8> = 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]
@@ -53,6 +53,11 @@ pub fn bind_array_buffer(core: &Arc<KernelCore>, session: &KernelSession, global
1,
),
("slice", oxide_builtins::array_buffer::array_buffer_slice::<crate::vm::Vm> as *const (), 2),
(
"sliceToImmutable",
oxide_builtins::array_buffer::array_buffer_slice_to_immutable::<crate::vm::Vm> as *const (),
2,
),
(
"toString",
oxide_builtins::array_buffer::array_buffer_to_string::<crate::vm::Vm> as *const (),
@@ -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());
}