From 7d7a1950141aab56e8212676d51d9da2e2c13c06 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 22 Sep 2026 23:30:18 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20ArrayBuffer=20=E4=B8=89=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E5=99=A8=E4=B8=8E=20markImmutable=20=E5=8F=8A=20detac?= =?UTF-8?q?h=20=E5=AE=BF=E4=B8=BB=E7=9C=9F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array_buffer.rs | 148 +++++++++++-- crates/oxide_builtins/src/typed_array.rs | 16 +- crates/oxide_test262/src/harness.rs | 2 - .../src/bindings/bind_array_buffer.rs | 33 +++ crates/oxide_vm/src/test262_host.rs | 8 +- .../tests/builtins_arraybuffer_state_tests.rs | 196 ++++++++++++++++++ crates/oxide_vm/tests/test262_host_tests.rs | 14 +- 7 files changed, 392 insertions(+), 25 deletions(-) diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index 62d97a1e..afe7d08e 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -107,7 +107,7 @@ pub(crate) fn new_array_buffer( vm.alloc_object(obj) } -fn array_buffer_payload_ptr(obj: &JsObject) -> Option<*mut ArrayBufferPayload> { +pub(crate) fn array_buffer_payload_ptr(obj: &JsObject) -> Option<*mut ArrayBufferPayload> { if !obj.is_array_buffer_obj() { return None; } @@ -276,15 +276,14 @@ pub fn array_buffer_constructor(vm: &mut H, args: &[u8]) -> NativeRes NativeResult::Ok(JsValue::from_js_object(obj_ptr)) } -/// `ArrayBuffer.prototype.byteLength` getter:返回缓冲区字节数。 +/// `ArrayBuffer.prototype.byteLength` getter:返回缓冲区字节数; +/// detached 缓冲返回 0。 pub fn array_buffer_byte_length(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 - let Some(data) = unsafe { &*payload_ptr }.data.as_ref() else { - return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); - }; - NativeResult::Ok(JsValue::int(data.len() as i32)) + let len = unsafe { &*payload_ptr }.data.as_ref().map_or(0, |d| d.len()); + NativeResult::Ok(JsValue::int(len as i32)) } /// `ArrayBuffer.prototype.resizable` getter:返回缓冲区是否可 resize @@ -297,6 +296,73 @@ pub fn array_buffer_resizable(vm: &mut H, args: &[u8]) -> NativeResul NativeResult::Ok(JsValue::bool(resizable)) } +/// `ArrayBuffer.prototype.maxByteLength` getter:detached → +0;定长 → +/// 当前字节数;resizable → 真实上限(存储态 − 1 解码)。 +pub fn array_buffer_max_byte_length(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload = unsafe { &*payload_ptr }; + if payload.data.is_none() { + return NativeResult::Ok(JsValue::int(0)); + } + let value = if payload.max_byte_length == 0 { + payload.data.as_ref().map_or(0, |d| d.len()) + } else { + payload.max_byte_length - 1 + }; + NativeResult::Ok(JsValue::int(value as i32)) +} + +/// `ArrayBuffer.prototype.immutable` getter:返回载荷 immutable 标志。 +pub fn array_buffer_immutable(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + NativeResult::Ok(JsValue::bool(unsafe { &*payload_ptr }.immutable)) +} + +/// `ArrayBuffer.prototype.detached` getter:detached 判据即载荷 `data` 为 +/// `None`。 +pub fn array_buffer_detached(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + NativeResult::Ok(JsValue::bool(unsafe { &*payload_ptr }.data.is_none())) +} + +/// `ArrayBuffer.prototype.markImmutable()`:定长附着缓冲区置 immutable 标志 +/// 并返回接收者;已置位再调为幂等 no-op。 +/// +/// # 步骤 +/// 1. this 品牌校验(非对象/非 ArrayBuffer → TypeError)。 +/// 2. detached → TypeError。 +/// 3. resizable(存储态上限非 0)→ TypeError。 +/// 4. 置 immutable 标志并返回 O。 +pub fn array_buffer_mark_immutable(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload = unsafe { &mut *payload_ptr }; + if payload.data.is_none() { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + } + if payload.max_byte_length != 0 { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer is not resizable")); + } + payload.immutable = true; + NativeResult::Ok(this_val) +} + +/// ArrayBuffer detach 生产路径:品牌守卫后载荷 `data → None`(字节缓冲随 +/// `Option` 置空释放,载荷盒本体存活至对象 drop)。返回 undefined。 +pub fn detach_array_buffer_native(vm: &mut H, this_val: JsValue) -> NativeResult { + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + unsafe { (*payload_ptr).data = None }; + NativeResult::Ok(JsValue::undefined()) +} + /// `ArrayBuffer.prototype.resize(newLength)`:可 resize 缓冲区原地调整长度, /// 前缀保留、增补零填充、缩小截断。 /// @@ -304,15 +370,16 @@ pub fn array_buffer_resizable(vm: &mut H, args: &[u8]) -> NativeResul /// 1. this 品牌校验(非对象/非 ArrayBuffer → TypeError)。 /// 2. 载荷三状态位(detached / immutable / max)先于任何 JS 调用拷入局部 /// 标量。 -/// 3. detached → TypeError(detach 路径由 transfer 族激活)。 +/// 3. detached → TypeError。 /// 4. immutable → TypeError。 /// 5. newByteLength = ToIntegerOrInfinity(newLength) 传播式(NaN → 0, /// ±Infinity 保留)。 /// 6. 定长(存储态上限 = 0)→ TypeError,先于界判。 /// 7. newByteLength < 0 或 > 真实上限(存储态 − 1)→ RangeError。 /// 8. 重取载荷指针:步 5 的 JS 调用可触发 epoch 晋升,旧指针悬垂。 -/// 9. `Vec::resize` 一次调用收口目标长度。 -/// 10. 返回 undefined。 +/// 9. 载荷重检:强转调用可已 detach 本缓冲,detached 与步 3 同文本 TypeError。 +/// 10. `Vec::resize` 一次调用收口目标长度。 +/// 11. 返回 undefined。 pub fn array_buffer_resize(vm: &mut H, args: &[u8]) -> NativeResult { let this_reg = if args.is_empty() { 0 } else { args[0] }; let this_val = vm.reg(this_reg); @@ -343,10 +410,12 @@ pub fn array_buffer_resize(vm: &mut H, args: &[u8]) -> NativeResult { // 重写,须重新读取后再取载荷指针(旧 this 值可能指向已释放 epoch 对象)。 let this_val = vm.reg(this_reg); let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); - // SAFETY: 重取的 payload_ptr 指向存活载荷盒;detached 已在步 3 排除, - // data 必为 Some。 - let payload = unsafe { &mut *payload_ptr }; - payload.data.as_mut().expect("detached 已排除").resize(new_length as usize, 0); + // SAFETY: 重取的 payload_ptr 指向存活载荷盒。 + let Some(data) = (unsafe { &mut *payload_ptr }).data.as_mut() else { + // 步 5 的强转调用可在中途 detach 本缓冲:重检不通过按步 3 同文本抛。 + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; + data.resize(new_length as usize, 0); NativeResult::Ok(JsValue::undefined()) } @@ -401,6 +470,7 @@ pub fn array_buffer_to_string(vm: &mut H, args: &[u8]) -> NativeResul #[cfg(test)] mod tests { use super::*; + use oxide_vm::vm::Vm; /// 手工构造带载荷盒的 ArrayBuffer 对象(不经 Vm,只验 GC 三自由函数)。 fn ab_object_with_payload(payload: ArrayBufferPayload) -> JsObject { @@ -495,4 +565,56 @@ mod tests { // SAFETY: 测试构造的盒,恰好释放一次。 unsafe { drop(Box::from_raw(payload_ptr)) }; } + + /// JS 求值辅助:单脚本编译执行,返回完成值。 + fn eval_ab(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("parse: {:?}", e[0].message))?; + let module = oxide_compiler::compiler::Compiler::new() + .compile(&program) + .map_err(|e| format!("compile: {e}"))?; + vm.run(&std::sync::Arc::new(module)) + } + + /// markImmutable 标志面钉:置位返回 O、二调幂等、resizable/detached 两 + /// TypeError 形。 + #[test] + fn mark_immutable_flag_and_guard() { + let mut vm = Vm::new(); + let r = eval_ab( + &mut vm, + "var ab = new ArrayBuffer(4); \ + var ret = ab.markImmutable(); \ + ret === ab && ab.immutable === true && ab.markImmutable() === ab \ + && ab.immutable === true && ab.byteLength === 4", + ) + .unwrap(); + assert!(r.as_bool()); + let e = eval_ab(&mut vm, "new ArrayBuffer(4, {maxByteLength: 8}).markImmutable()").unwrap_err(); + assert!(e.contains("TypeError"), "resizable 应抛 TypeError: {e}"); + let e = eval_ab( + &mut vm, + "var ab = new ArrayBuffer(4); $262.detachArrayBuffer(ab); \ + try { ab.markImmutable(); 'no-throw' } catch (err) { err.name }", + ) + .unwrap(); + assert_eq!(vm.lookup_str(e).unwrap(), "TypeError"); + } + + /// resize 后置 detach 重检钉:强转调用中途 detach 后按 TypeError 抛, + /// 不触碰已 detach 载荷(现树 expect 臂的 abort 钉改点)。 + #[test] + fn resize_recheck_after_coercion_detach() { + let mut vm = Vm::new(); + let e = eval_ab( + &mut vm, + "var ab = new ArrayBuffer(64, {maxByteLength: 1024}); \ + try { ab.resize({ valueOf() { $262.detachArrayBuffer(ab); return 0; } }); \ + 'no-throw' } catch (err) { err.name }", + ) + .unwrap(); + assert_eq!(vm.lookup_str(e).unwrap(), "TypeError"); + let r = eval_ab(&mut vm, "ab.detached === true && ab.byteLength === 0").unwrap(); + assert!(r.as_bool()); + } } diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 016a851a..6b319a9e 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -6,7 +6,8 @@ use oxide_types::private_key::{int_key_value, is_int_key, make_well_known_symbol use oxide_types::value::JsValue; use crate::array_buffer::{ - array_buffer_payload, default_array_buffer_proto, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH, + array_buffer_payload, array_buffer_payload_ptr, default_array_buffer_proto, new_array_buffer, + MAX_ARRAY_BUFFER_LENGTH, }; use oxide_runtime_api::{NativeResult, VmHost}; @@ -336,7 +337,8 @@ pub fn ta_index_gate(vm: &H, obj: &JsObject, key_si: u32) -> TaIndexG } /// 取 TypedArray 视图长度(供只需 length 不需门的枚举面消费方); -/// 非 TypedArray 或内部状态无效返回 0。 +/// 非 TypedArray 或内部状态无效返回 0。底层缓冲已 detach(载荷 `data` +/// 为 `None`)时视图长度统一为 0,界内键经门归数字无效。 pub(crate) fn ta_view_length(_vm: &H, obj: &JsObject) -> usize { let Some(ptr) = typed_array_data_ptr(obj) else { return 0; @@ -347,6 +349,16 @@ pub(crate) fn ta_view_length(_vm: &H, obj: &JsObject) -> usize { // SAFETY: ptr 非空,为 TypedArray 对象 native_fn 槽内的 Box, // 与对象同生命周期,此处只读拷贝视图字段。 let view = unsafe { *ptr }; + // 底层缓冲已 detach:视图长度统一 0。 + let buffer_ptr = view.buffer.as_js_object_ptr(); + if !buffer_ptr.is_null() { + // SAFETY: buffer 对象与视图同生命周期,此处只读载荷存活位。 + if let Some(payload_ptr) = array_buffer_payload_ptr(unsafe { &*buffer_ptr }) { + if !payload_ptr.is_null() && unsafe { (*payload_ptr).data.is_none() } { + return 0; + } + } + } view.length } diff --git a/crates/oxide_test262/src/harness.rs b/crates/oxide_test262/src/harness.rs index 859b6776..a1ab1008 100644 --- a/crates/oxide_test262/src/harness.rs +++ b/crates/oxide_test262/src/harness.rs @@ -70,8 +70,6 @@ fn is_blacklisted_harness(name: &str) -> bool { | "atomicsHelper.js" | "proxyTrapsHelper.js" | "tcoHelper.js" - | "detachArrayBuffer.js" - | "resizableArrayBufferUtils.js" | "byteConversionValues.js" | "compareIterator.js" | "iteratorZipUtils.js" diff --git a/crates/oxide_vm/src/bindings/bind_array_buffer.rs b/crates/oxide_vm/src/bindings/bind_array_buffer.rs index 72dec513..8a10b013 100644 --- a/crates/oxide_vm/src/bindings/bind_array_buffer.rs +++ b/crates/oxide_vm/src/bindings/bind_array_buffer.rs @@ -58,6 +58,11 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global oxide_builtins::array_buffer::array_buffer_to_string:: as *const (), 0, ), + ( + "markImmutable", + oxide_builtins::array_buffer::array_buffer_mark_immutable:: as *const (), + 0, + ), ], ); @@ -79,6 +84,34 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global oxide_builtins::array_buffer::array_buffer_resizable:: as *const (), ); + // maxByteLength 原型访问器(set 恒 undefined):detached 回 +0,定长回 + // 当前字节数,resizable 回存储态上限 − 1。 + bind_accessor_getter( + core, + session, + proto, + "maxByteLength", + oxide_builtins::array_buffer::array_buffer_max_byte_length:: as *const (), + ); + + // immutable 原型访问器(set 恒 undefined):读载荷 immutable 标志。 + bind_accessor_getter( + core, + session, + proto, + "immutable", + oxide_builtins::array_buffer::array_buffer_immutable:: as *const (), + ); + + // detached 原型访问器(set 恒 undefined):读载荷 data 存活位。 + bind_accessor_getter( + core, + session, + proto, + "detached", + oxide_builtins::array_buffer::array_buffer_detached:: as *const (), + ); + bind_constructor!( core, global, diff --git a/crates/oxide_vm/src/test262_host.rs b/crates/oxide_vm/src/test262_host.rs index fb7df564..376628d1 100644 --- a/crates/oxide_vm/src/test262_host.rs +++ b/crates/oxide_vm/src/test262_host.rs @@ -57,9 +57,11 @@ pub fn eval_script(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -/// `$262.detachArrayBuffer(buf)`:ArrayBuffer detach 未实现,抛能力缺失错误。 -pub fn detach_array_buffer(vm: &mut Vm, _args: &[u8]) -> NativeResult { - not_supported(vm, "detachArrayBuffer") +/// `$262.detachArrayBuffer(buf)`:真 detach——品牌守卫后载荷 `data → None`, +/// 返回 undefined。 +pub fn detach_array_buffer(vm: &mut Vm, args: &[u8]) -> NativeResult { + let this_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + oxide_builtins::array_buffer::detach_array_buffer_native::(vm, this_val) } /// `$262.createRealm()`:跨 realm 未支持,抛能力缺失错误。 diff --git a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs index f8226496..8f8f3061 100644 --- a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs +++ b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs @@ -475,3 +475,199 @@ fn ab_length_range_error_not_regressed() { .unwrap(); assert!(result.as_bool()); } + +/// maxByteLength 访问器钉:描述符 {get 函数, set undefined, e:0, c:1}、 +/// name/length、定长 42→42 / 0→0、resizable(4,{max 8})→8、detached→0、 +/// this 四形 TypeError。 +#[test] +fn ab_maxbytelength_accessor_pins() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'maxByteLength'); \ + var ok = d.set === undefined && typeof d.get === 'function' \ + && d.enumerable === false && d.configurable === true \ + && d.get.name === 'get maxByteLength' && d.get.length === 0; \ + var t1 = false, t2 = false, t3 = false, t4 = false; \ + try { d.get.call(ArrayBuffer.prototype); } catch (e) { t1 = e instanceof TypeError; } \ + try { d.get.call(undefined); } catch (e) { t2 = e instanceof TypeError; } \ + try { d.get.call({}); } catch (e) { t3 = e instanceof TypeError; } \ + try { d.get.call([]); } catch (e) { t4 = e instanceof TypeError; } \ + ok && t1 && t2 && t3 && t4 \ + && new ArrayBuffer(42).maxByteLength === 42 \ + && new ArrayBuffer(0).maxByteLength === 0 \ + && new ArrayBuffer(4, {maxByteLength: 8}).maxByteLength === 8", + ) + .unwrap(); + assert!(result.as_bool()); + let result = + eval(&mut vm, "var ab = new ArrayBuffer(1); $262.detachArrayBuffer(ab); ab.maxByteLength === 0").unwrap(); + assert!(result.as_bool()); +} + +/// immutable 访问器钉:描述符形、markImmutable 前后 false→true、this 两形。 +#[test] +fn ab_immutable_accessor_pins() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'immutable'); \ + var ok = d.set === undefined && typeof d.get === 'function' \ + && d.enumerable === false && d.configurable === true \ + && d.get.name === 'get immutable' && d.get.length === 0; \ + var ab = new ArrayBuffer(2); \ + var before = ab.immutable; \ + ab.markImmutable(); \ + var t1 = false, t2 = false; \ + try { d.get.call({}); } catch (e) { t1 = e instanceof TypeError; } \ + try { d.get.call(undefined); } catch (e) { t2 = e instanceof TypeError; } \ + ok && before === false && ab.immutable === true && t1 && t2", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// detached 访问器钉:描述符形、附着 false → $262 detach → true、this 两形。 +#[test] +fn ab_detached_accessor_pins() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'detached'); \ + var ok = d.set === undefined && typeof d.get === 'function' \ + && d.enumerable === false && d.configurable === true \ + && d.get.name === 'get detached' && d.get.length === 0; \ + var ab = new ArrayBuffer(1); \ + var before = ab.detached; \ + $262.detachArrayBuffer(ab); \ + var t1 = false, t2 = false; \ + try { d.get.call({}); } catch (e) { t1 = e instanceof TypeError; } \ + try { d.get.call(undefined); } catch (e) { t2 = e instanceof TypeError; } \ + ok && before === false && ab.detached === true && t1 && t2", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// byteLength detached 臂改点钉:detach 后读 0(原 TypeError 臂)。 +#[test] +fn ab_bytelength_detached_zero() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var ab = new ArrayBuffer(1); $262.detachArrayBuffer(ab); ab.byteLength === 0").unwrap(); + assert!(result.as_bool()); +} + +/// resizable detached 不受影响钉:resizable(1,{max 1}) detach 后仍 true。 +#[test] +fn ab_resizable_detached_unchanged() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(1, {maxByteLength: 1}); $262.detachArrayBuffer(ab); ab.resizable === true", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// own maxByteLength 数据属性与原型访问器并存读同值钉(resizable 专属)。 +#[test] +fn ab_maxbytelength_own_still_resizable_only() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4, {maxByteLength: 8}); \ + Object.getOwnPropertyDescriptor(ab, 'maxByteLength').value === 8 && ab.maxByteLength === 8", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// markImmutable 返回接收者并置位钉:返回 === ab、immutable true、二调幂等、 +/// byteLength 不变。 +#[test] +fn ab_markimmutable_returns_this_and_sets_flag() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4); \ + var ret = ab.markImmutable(); \ + ret === ab && ab.immutable === true && ab.markImmutable() === ab \ + && ab.immutable === true && ab.byteLength === 4", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// markImmutable resizable 源钉:TypeError。 +#[test] +fn ab_markimmutable_resizable_type_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var t = false; \ + try { new ArrayBuffer(4, {maxByteLength: 8}).markImmutable(); } catch (e) { t = e instanceof TypeError; } \ + t", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// markImmutable detached 源钉:$262 detach 后 TypeError。 +#[test] +fn ab_markimmutable_detached_type_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4); $262.detachArrayBuffer(ab); \ + var t = false; \ + try { ab.markImmutable(); } catch (e) { t = e instanceof TypeError; } \ + t", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// markImmutable this 品牌校验钉:原型/undefined/{}/[] 四形 TypeError。 +#[test] +fn ab_markimmutable_this_checks() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var t1 = false, t2 = false, t3 = false, t4 = false; \ + try { ArrayBuffer.prototype.markImmutable(); } catch (e) { t1 = e instanceof TypeError; } \ + try { ArrayBuffer.prototype.markImmutable.call(undefined); } catch (e) { t2 = e instanceof TypeError; } \ + try { ArrayBuffer.prototype.markImmutable.call({}); } catch (e) { t3 = e instanceof TypeError; } \ + try { ArrayBuffer.prototype.markImmutable.call([]); } catch (e) { t4 = e instanceof TypeError; } \ + t1 && t2 && t3 && t4", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// P2-1 门钉:TA 界内整数键 detached 前 NumericValid(0)、detached 后 +/// NumericInvalid(232.19.2 Get 臂 detached 界内键用例的前置钉)。 +#[test] +fn ab_ta_view_length_detached_zero() { + let mut vm = Vm::new(); + eval( + &mut vm, + "var ab = new ArrayBuffer(8); \ + globalThis.__ab = ab; globalThis.__ta = new Uint8Array(ab); true", + ) + .unwrap(); + let si = vm.kernel_core().perm_interner().intern("0").0; + let ta_val = eval(&mut vm, "globalThis.__ta").unwrap(); + // SAFETY: __ta 为已晋升 session 的 TypedArray 对象,借用止于断言、不跨下一次 eval。 + let ta_obj = unsafe { &*ta_val.as_js_object_ptr() }; + assert_eq!( + oxide_builtins::typed_array::ta_index_gate(&vm, ta_obj, si), + oxide_builtins::typed_array::TaIndexGate::NumericValid(0) + ); + eval(&mut vm, "$262.detachArrayBuffer(globalThis.__ab); true").unwrap(); + let ta_val = eval(&mut vm, "globalThis.__ta").unwrap(); + let ta_obj = unsafe { &*ta_val.as_js_object_ptr() }; + assert_eq!( + oxide_builtins::typed_array::ta_index_gate(&vm, ta_obj, si), + oxide_builtins::typed_array::TaIndexGate::NumericInvalid + ); +} diff --git a/crates/oxide_vm/tests/test262_host_tests.rs b/crates/oxide_vm/tests/test262_host_tests.rs index 604b7f99..5e882a0f 100644 --- a/crates/oxide_vm/tests/test262_host_tests.rs +++ b/crates/oxide_vm/tests/test262_host_tests.rs @@ -63,11 +63,6 @@ fn eval_script_syntax_error_is_catchable() { /// 未实现方法抛能力缺失错误(消息带 `not supported`),不抛 ReferenceError。 #[test] fn unsupported_methods_throw_not_supported() { - let r = - eval("try { $262.detachArrayBuffer(new ArrayBuffer(8)); 'no-throw' } catch (e) { e.name + ':' + e.message }"); - assert!(r.contains("TypeError"), "期望 TypeError,实际 {r}"); - assert!(r.contains("not supported"), "期望能力缺失消息,实际 {r}"); - let r = eval("try { $262.createRealm(); 'no-throw' } catch (e) { e.name + ':' + e.message }"); assert!(r.contains("TypeError"), "期望 TypeError,实际 {r}"); assert!(r.contains("not supported"), "期望能力缺失消息,实际 {r}"); @@ -76,6 +71,15 @@ fn unsupported_methods_throw_not_supported() { assert!(r.contains("TypeError"), "期望 TypeError,实际 {r}"); } +/// `detachArrayBuffer` 真实现钉:detach 后 `detached` 访问器读 true、 +/// 非 ArrayBuffer 实参抛 TypeError。 +#[test] +fn detach_array_buffer_detaches() { + assert_eq!(eval("var ab = new ArrayBuffer(8); $262.detachArrayBuffer(ab); ab.detached"), "true"); + let r = eval("try { $262.detachArrayBuffer(1); 'no-throw' } catch (e) { e.name }"); + assert_eq!(r, "TypeError"); +} + /// `$262.gc()` 触发一次 session 回收且不抛错。 #[test] fn gc_runs_without_error() {