diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index e1d2399e..c606f5d7 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -201,6 +201,34 @@ pub(crate) fn buffer_payload_ptr(obj: &JsObject) -> Option<*mut ArrayBufferPaylo shared_array_buffer_payload_ptr(obj) } +/// 缓冲区载荷读入口(两标签双认):校验 receiver 为 ArrayBuffer 或 +/// SharedArrayBuffer 并取载荷指针。AB 臂经 `array_buffer_payload_ptr` 与 +/// `array_buffer_payload` 路径同函数同序(逐位同形),SAB 臂经 +/// `shared_array_buffer_payload_ptr`;非对象 / 双标签外 / 槽位空(防御背板) +/// → TypeError。 +/// +/// # 边界与前提 +/// - SAB 无 detach 生产路径,载荷 `data` 恒在场;detach 分叉由消费方现读处理。 +/// - 调用方不得跨 JS 调用窗口持有返回指针(晋升可改写对象,须重取)。 +pub(crate) fn buffer_payload(vm: &mut H, this_val: JsValue) -> Result<*mut ArrayBufferPayload, JsValue> { + if !this_val.is_object() { + return Err(crate::error::create_type_error(vm, "ArrayBuffer method called on incompatible receiver")); + } + let obj_ptr = this_val.as_js_object_ptr(); + if obj_ptr.is_null() { + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + } + // SAFETY: is_object 保证指针非空且对象本 session 存活。 + let obj = unsafe { &*obj_ptr }; + let Some(payload_ptr) = buffer_payload_ptr(obj) else { + return Err(crate::error::create_type_error(vm, "ArrayBuffer method called on incompatible receiver")); + }; + if payload_ptr.is_null() { + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + } + Ok(payload_ptr) +} + /// SharedArrayBuffer 载荷盒字节数(`native_fn` 槽);非 SAB 或已释放 → 0。 pub fn shared_array_buffer_native_size(obj: &JsObject) -> u64 { let payload_ptr = match shared_array_buffer_payload_ptr(obj) { diff --git a/crates/oxide_builtins/src/data_view.rs b/crates/oxide_builtins/src/data_view.rs index dd8707a9..e6bfb3ed 100644 --- a/crates/oxide_builtins/src/data_view.rs +++ b/crates/oxide_builtins/src/data_view.rs @@ -5,7 +5,7 @@ use oxide_types::value::JsValue; use num_bigint::BigInt; use num_traits::ToPrimitive; -use crate::array_buffer::{array_buffer_payload, ArrayBufferPayload}; +use crate::array_buffer::{buffer_payload, ArrayBufferPayload}; use oxide_runtime_api::{NativeResult, VmHost}; @@ -117,7 +117,7 @@ fn get_data_view_data(vm: &mut H, this_val: JsValue) -> Result( vm: &mut H, payload_ptr: *mut ArrayBufferPayload, view: DataViewData, ) -> Result { - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + // SAFETY: payload_ptr 由调用方读入口(buffer_payload)校验为合法缓冲区载荷。 let Some(data) = unsafe { &*payload_ptr }.data.as_deref() else { return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; @@ -136,8 +136,8 @@ fn dv_view_bounds( /// set 面入口写守卫:immutable 缓冲 → TypeError(先于一切实参强转)。 fn dv_buffer_writable(vm: &mut H, view: DataViewData) -> Result<(), JsValue> { - let payload_ptr = array_buffer_payload(vm, view.buffer)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 if unsafe { &*payload_ptr }.immutable { return Err(crate::error::create_type_error(vm, "ArrayBuffer is immutable")); } @@ -149,8 +149,8 @@ fn dv_buffer_writable(vm: &mut H, view: DataViewData) -> Result<(), J /// 返回(寄存器重读值、live 长、存储上限)。 fn revalidate_buffer(vm: &mut H, buf_reg: u8) -> Result<(JsValue, usize, usize), JsValue> { let buffer = vm.reg(buf_reg); - let payload_ptr = array_buffer_payload(vm, buffer)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = buffer_payload(vm, buffer)?; + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let payload = unsafe { &*payload_ptr }; let Some(data) = payload.data.as_deref() else { return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); @@ -162,8 +162,8 @@ fn revalidate_buffer(vm: &mut H, buf_reg: u8) -> Result<(JsValue, usi /// (detach/OOB → TypeError,live 长口径),后视图相对越界 /// (→ RangeError,判活视图长)。 fn read_bytes_at(vm: &mut H, view: DataViewData, offset: usize) -> Result<[u8; N], JsValue> { - let payload_ptr = array_buffer_payload(vm, view.buffer)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; @@ -182,10 +182,10 @@ fn read_bytes_at(vm: &mut H, view: DataViewData, offs fn write_bytes( vm: &mut H, view: DataViewData, offset: usize, bytes: [u8; N], ) -> Result<(), JsValue> { - let payload_ptr = array_buffer_payload(vm, view.buffer)?; + let payload_ptr = buffer_payload(vm, view.buffer)?; // 界判核共享借用先于可变借用建立,避免活别名。 let view_len = dv_view_bounds(vm, payload_ptr, view)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷; + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认); // 界判核已证 data 在场,下方守卫为结构性兜底。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); @@ -259,7 +259,7 @@ pub fn data_view_constructor(vm: &mut H, args: &[u8]) -> NativeResult } let buffer = vm.reg(args[1]); // 首个载荷取用只做品牌校验,指针即弃,不持借用跨 JS 调用。 - native_try!(array_buffer_payload(vm, buffer)); + native_try!(buffer_payload(vm, buffer)); let byte_offset = if args.len() > 2 { native_try!(to_index(vm, vm.reg(args[2]), "DataView byteOffset out of bounds")) @@ -765,7 +765,7 @@ pub fn data_view_buffer_getter(vm: &mut H, args: &[u8]) -> NativeResu pub fn data_view_byte_offset_getter(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_data_view_data(vm, this_val)); - let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); native_try!(dv_view_bounds(vm, payload_ptr, view)); NativeResult::Ok(JsValue::float(view.byte_offset as f64)) } @@ -776,7 +776,7 @@ pub fn data_view_byte_offset_getter(vm: &mut H, args: &[u8]) -> Nativ pub fn data_view_byte_length_getter(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_data_view_data(vm, this_val)); - let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); let view_len = native_try!(dv_view_bounds(vm, payload_ptr, view)); NativeResult::Ok(JsValue::float(view_len as f64)) } diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 1c8f1f35..7a7f8524 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -6,8 +6,7 @@ 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, array_buffer_payload_ptr, buffer_payload_ptr, default_array_buffer_proto, new_array_buffer, - MAX_ARRAY_BUFFER_LENGTH, + buffer_payload, buffer_payload_ptr, default_array_buffer_proto, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH, }; use oxide_runtime_api::{NativeResult, VmHost}; @@ -314,7 +313,7 @@ pub(crate) fn ta_validate( 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 let Some(payload_ptr) = buffer_payload_ptr(unsafe { &*buffer_ptr }) { if !payload_ptr.is_null() && unsafe { (*payload_ptr).immutable } { return Err(type_error(vm, "ArrayBuffer is immutable")); } @@ -786,8 +785,8 @@ fn collect_array_like(vm: &mut H, value: JsValue, consult_iterator: b } if obj.is_typed_array_obj() { let view = get_typed_array_data(vm, value)?; - let payload_ptr = array_buffer_payload(vm, view.buffer)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { // 源 buffer detach:按规范的源缓冲校验步抛 TypeError;各入口的 // 校验/活长界判先行,此臂为防御背板。 @@ -908,8 +907,8 @@ fn typed_array_new(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> let byte_len = values.len().saturating_mul(bpe); let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len], 0, default_array_buffer_proto(vm))); - let payload_ptr = native_try!(array_buffer_payload(vm, buffer)); - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = native_try!(buffer_payload(vm, buffer)); + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let Some(buffer_ref) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { // 新建 buffer 载荷恒存活(无人可 detach),此臂为防御背板。 return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); @@ -1051,8 +1050,8 @@ pub fn typed_array_fill(vm: &mut H, args: &[u8]) -> NativeResult { native_try!(ta_validate(vm, view, true)); let start = clamp_index_to_len(start_raw, len); let end = clamp_index_to_len(end_raw, len).min(ta_spec_length(view)); - let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { // 转换窗口内 detach:填充写退化为静默 no-op(循环内无重入窗,单次 // 判即封窗),按 [[Set]] 语义正常返回 this。 @@ -1226,7 +1225,7 @@ pub fn typed_array_subarray(vm: &mut H, args: &[u8]) -> NativeResult // 2 参构造(省略 count,子视图随 buffer 伸缩)仅当 end 未给、源视图为 // auto 且 buffer 可缩放;定长视图恒三参定死区间(窗口超 buffer 由构造器 // 抛 RangeError)。 - let resizable = match array_buffer_payload(vm, view.buffer) { + let resizable = match buffer_payload(vm, view.buffer) { Ok(p) => unsafe { &*p }.max_byte_length != 0, Err(_) => false, }; @@ -1290,8 +1289,8 @@ pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { if (source_len as u64) + (offset as u64) > len as u64 { return NativeResult::Err(range_error(vm, "TypedArray.set offset out of bounds")); } - let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let mut buffer = unsafe { &mut *payload_ptr }.data.as_deref_mut(); // 逐元素惰性读源→转换→写:循环中的 detach/收缩使后续写入静默失效 // (目标越界不写,与规范 SetValueInBuffer 边界无操作同语义);载荷缺失 @@ -1448,8 +1447,8 @@ fn set_typed_array_element(vm: &mut H, ta: JsValue, index: usize, val if index >= ta_live_length(view) { return Ok(()); } - let payload_ptr = array_buffer_payload(vm, view.buffer)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { // 载荷缺失即 detach(live 界判后不可达,防御背板):转换副作用已先 // 发生,写按 [[Set]] 静默 no-op。 @@ -1484,8 +1483,8 @@ fn ta_read(vm: &mut H, view: TypedArrayData, index: usize) -> Result< if index >= ta_live_length(view) { return Ok(JsValue::undefined()); } - let payload_ptr = array_buffer_payload(vm, view.buffer)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { // 载荷缺失即 detach(live 界判后不可达,防御背板):按 [[Get]] 读 // undefined。 @@ -1501,8 +1500,8 @@ fn ta_write(vm: &mut H, view: TypedArrayData, index: usize, value: Js if index >= ta_live_length(view) { return Ok(()); } - let payload_ptr = array_buffer_payload(vm, view.buffer)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let payload_ptr = buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 buffer_payload 校验为合法缓冲区载荷(AB/SAB 双认)。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { // 载荷缺失即 detach(live 界判后不可达,防御背板):写静默 no-op。 return Ok(()); @@ -2583,8 +2582,8 @@ pub fn uint8array_to_base64(vm: &mut H, args: &[u8]) -> NativeResult .map_err(|e| crate::iterator::engine_error(vm, &e))); omit_padding = ta_to_boolean(v); } - let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); - // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 来自活动缓冲区对象(AB/SAB 双认),视图范围由构造保证界内。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, // 此臂为防御背板。 @@ -2601,8 +2600,8 @@ pub fn uint8array_to_base64(vm: &mut H, args: &[u8]) -> NativeResult pub fn uint8array_to_hex(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(validate_uint8_array(vm, this_val)); - let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); - // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 来自活动缓冲区对象(AB/SAB 双认),视图范围由构造保证界内。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, // 此臂为防御背板。 @@ -2622,8 +2621,8 @@ pub fn uint8array_set_from_base64(vm: &mut H, args: &[u8]) -> NativeR let view = native_try!(validate_uint8_array(vm, this_val)); let string = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let options = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); - // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 来自活动缓冲区对象(AB/SAB 双认),视图范围由构造保证界内。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, // 此臂为防御背板。 @@ -2649,8 +2648,8 @@ pub fn uint8array_set_from_hex(vm: &mut H, args: &[u8]) -> NativeResu if !string.is_string() { return NativeResult::Err(type_error(vm, "string argument required")); } - let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); - // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let payload_ptr = native_try!(buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 来自活动缓冲区对象(AB/SAB 双认),视图范围由构造保证界内。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, // 此臂为防御背板。 diff --git a/crates/oxide_test262/src/discovery.rs b/crates/oxide_test262/src/discovery.rs index 3b757bbd..0c7bd924 100644 --- a/crates/oxide_test262/src/discovery.rs +++ b/crates/oxide_test262/src/discovery.rs @@ -40,7 +40,6 @@ pub(crate) fn is_skipped(meta: &TestMeta) -> Option { "Proxy", "Intl", "Atomics", - "SharedArrayBuffer", "cross-realm", // await-dictionary(Promise.allKeyed/allSettledKeyed)是 2025 proposal,未实现。 "await-dictionary", diff --git a/crates/oxide_vm/tests/builtins_dataview_live_tests.rs b/crates/oxide_vm/tests/builtins_dataview_live_tests.rs index 27985cf9..dbbe4a16 100644 --- a/crates/oxide_vm/tests/builtins_dataview_live_tests.rs +++ b/crates/oxide_vm/tests/builtins_dataview_live_tests.rs @@ -296,3 +296,158 @@ fn dv_ctor_fixed_default_len_static() { .unwrap(); assert!(result.as_bool()); } + +/// DV-over-SAB 构造基础形:缺省长视图,buffer 指向 SAB 本体。 +#[test] +fn dv_sab_ctor_basic() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var sab = new SharedArrayBuffer(8); \ + var dv = new DataView(sab); \ + dv.byteLength === 8 && dv.byteOffset === 0 && dv.buffer === sab \ + && Object.getPrototypeOf(dv) === DataView.prototype", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// DV-over-SAB 显式 offset/length 形:视图界与 buffer 指向。 +#[test] +fn dv_sab_ctor_offset_length() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var sab = new SharedArrayBuffer(8); \ + var dv = new DataView(sab, 2, 4); \ + dv.byteLength === 4 && dv.byteOffset === 2 && dv.buffer === sab", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// DV-over-SAB 构造越界三形:offset > live / 显式长越界 / 负 offset 均 RangeError。 +#[test] +fn dv_sab_ctor_oob() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + var sab = new SharedArrayBuffer(4); \ + function kind(expr) { try { expr(); return 'none'; } catch (e) { return e.name; } } \ + return kind(function () { new DataView(sab, 5); }) === 'RangeError' \ + && kind(function () { new DataView(sab, 0, 5); }) === 'RangeError' \ + && kind(function () { new DataView(sab, -1); }) === 'RangeError'; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// DV-over-SAB 读写核回卷:各宽 get/set 位模式与同形 AB 视图对照同值。 +#[test] +fn dv_sab_get_set_roundtrip() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + var sab = new SharedArrayBuffer(32); \ + var dv = new DataView(sab); \ + dv.setInt8(0, -7); \ + dv.setUint8(1, 200); \ + dv.setInt32(2, 0x01020304, true); \ + dv.setInt32(6, 0x01020304, false); \ + dv.setFloat64(8, 1.5); \ + dv.setBigInt64(16, 42n); \ + dv.setBigUint64(24, 18446744073709551615n); \ + var ab = new ArrayBuffer(32); \ + var ref = new DataView(ab); \ + ref.setInt8(0, -7); \ + ref.setUint8(1, 200); \ + ref.setInt32(2, 0x01020304, true); \ + ref.setInt32(6, 0x01020304, false); \ + ref.setFloat64(8, 1.5); \ + ref.setBigInt64(16, 42n); \ + ref.setBigUint64(24, 18446744073709551615n); \ + return dv.getInt8(0) === ref.getInt8(0) \ + && dv.getUint8(1) === ref.getUint8(1) \ + && dv.getInt32(2, true) === ref.getInt32(2, true) \ + && dv.getInt32(6, false) === ref.getInt32(6, false) \ + && dv.getFloat64(8, true) === ref.getFloat64(8, true) \ + && dv.getBigInt64(16, true) === ref.getBigInt64(16, true) \ + && dv.getBigUint64(24, false) === ref.getBigUint64(24, false); })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// DV-over-SAB 读越界:跨视图尾 getInt32 → RangeError。 +#[test] +fn dv_sab_get_oob() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + var dv = new DataView(new SharedArrayBuffer(4)); \ + try { dv.getInt32(1); return false; } catch (e) { return e instanceof RangeError; } })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// DV-over-SAB live 读:定长视图静态界 + growable SAB auto 视图随 grow 活读。 +#[test] +fn dv_sab_live_getters() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + var sab = new SharedArrayBuffer(8); \ + var fixed = new DataView(sab, 1, 3); \ + var ok = fixed.byteOffset === 1 && fixed.byteLength === 3; \ + var growable = new SharedArrayBuffer(4, { maxByteLength: 8 }); \ + var auto = new DataView(growable); \ + ok = ok && auto.byteLength === 4 && auto.byteOffset === 0; \ + growable.grow(8); \ + return ok && auto.byteLength === 8 && auto.byteOffset === 0; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// DV-over-SAB 原型链:buffer 属性返回 SAB 本体 + custom-proto 臂 +/// (newTarget 自定义原型经 GpFC 落位)。 +#[test] +fn dv_sab_proto_chain() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + var sab = new SharedArrayBuffer(4); \ + var dv = new DataView(sab); \ + var ok = dv.buffer === sab; \ + var newTarget = function() {}.bind(null); \ + newTarget.prototype = {}; \ + var dv2 = Reflect.construct(DataView, [sab], newTarget); \ + return ok && Object.getPrototypeOf(dv2) === newTarget.prototype; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// DV-over-SAB 品牌负钉:非缓冲区对象(裸对象 / TypedArray / null)构造 +/// 首检均 TypeError。 +#[test] +fn dv_sab_brand_reject() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + function kind(expr) { try { expr(); return 'none'; } catch (e) { return e.name; } } \ + var ta = new Int8Array(4); \ + return kind(function () { new DataView({}); }) === 'TypeError' \ + && kind(function () { new DataView(ta); }) === 'TypeError' \ + && kind(function () { new DataView(null); }) === 'TypeError'; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index 38c9c843..a4ddaf2f 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -2131,3 +2131,146 @@ fn ta_for_in_stable_after_delete_str_prop() { .unwrap(); assert!(result.as_bool()); } + +#[test] +fn ta_sab_fill() { + // TA-over-SAB fill 全量回填并读回,构造/写/读同走双认载荷入口。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int32Array(new SharedArrayBuffer(16)); \ + var ok = ta.length === 4 && ta.buffer instanceof SharedArrayBuffer; \ + ta.fill(7); \ + return ok && ta[0] === 7 && ta[1] === 7 && ta[2] === 7 && ta[3] === 7; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_sab_set_ta_source() { + // TA-over-SAB set(TypedArray) 三形:跨 buffer 拷贝、同 buffer 快照 + // 写前取值、BigInt 宽度源。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + var src = new Int32Array(new SharedArrayBuffer(16)); src.fill(5); \ + var dst = new Int32Array(new SharedArrayBuffer(32)); \ + dst.set(src, 2); \ + var ok = dst[0] === 0 && dst[2] === 5 && dst[5] === 5 && dst[6] === 0; \ + var t2 = new Int32Array(new SharedArrayBuffer(16)); t2.fill(1); t2[0] = 9; \ + t2.set(t2.subarray(1, 3), 0); \ + ok = ok && t2[0] === 1 && t2[1] === 1; \ + var bs = new BigInt64Array(new SharedArrayBuffer(16)); bs.fill(9n); \ + var bd = new BigInt64Array(new SharedArrayBuffer(32)); \ + bd.set(bs, 1); \ + return ok && bd[0] === 0n && bd[1] === 9n && bd[2] === 9n; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_sab_copy_within() { + // TA-over-SAB copyWithin 前/后向重叠两形全位回卷。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { \ + var f = new Int8Array(new SharedArrayBuffer(8)); \ + f.set([1, 2, 3, 4, 5, 6, 7, 8]); \ + f.copyWithin(2, 0, 6); \ + var ok = f.join(',') === '1,2,1,2,3,4,5,6'; \ + var r = new Int8Array(new SharedArrayBuffer(8)); \ + r.set([1, 2, 3, 4, 5, 6, 7, 8]); \ + r.copyWithin(0, 2, 8); \ + return ok && r.join(',') === '3,4,5,6,7,8,7,8'; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_sab_subarray() { + // TA-over-SAB subarray:视图属性指向 SAB 本体,界内子视图共享 buffer。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var sab = new SharedArrayBuffer(16); \ + var ta = new Int32Array(sab); \ + var sub = ta.subarray(1, 3); \ + return sub.length === 2 && sub.byteOffset === 4 \ + && sub.buffer === sab && ta.buffer instanceof SharedArrayBuffer; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_sab_slice() { + // TA-over-SAB slice 返回独立 AB 缓冲:值拷贝 + 缓冲类型断言。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int32Array(new SharedArrayBuffer(16)); \ + ta.fill(3); \ + var out = ta.slice(1, 3); \ + return out.length === 2 && out[0] === 3 && out[1] === 3 \ + && out.buffer instanceof ArrayBuffer \ + && !(out.buffer instanceof SharedArrayBuffer); })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_sab_element_rw() { + // TA-over-SAB 元素读写:写命中 SAB 载荷、读回值 + 邻位不受扰。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int32Array(new SharedArrayBuffer(16)); \ + ta[2] = 42; \ + return ta[2] === 42 && ta[0] === 0 && ta[3] === 0; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_sab_sort_reverse() { + // TA-over-SAB sort/reverse:原地重排回卷,reverse 返回 this。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int32Array(new SharedArrayBuffer(16)); \ + ta.set([3, 1, 4, 2]); \ + ta.sort(); \ + var ok = ta.join(',') === '1,2,3,4'; \ + var rv = ta.reverse(); \ + return ok && rv === ta && ta.join(',') === '4,3,2,1'; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_sab_base64() { + // TA-over-SAB base64 对:编码输出与同形 AB 视图逐位一致(双认入口 + // 零漂移)+ setFromBase64 解码计数与未写尾位清零。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Uint8Array(new SharedArrayBuffer(4)); \ + ta.set([0xfb, 0xff, 0xfe, 0x00]); \ + var ref = new Uint8Array(new ArrayBuffer(4)); \ + ref.set([0xfb, 0xff, 0xfe, 0x00]); \ + var ok = ta.toBase64() === ref.toBase64(); \ + var r = ta.setFromBase64('////'); \ + return ok && r.read === 4 && r.written === 3 \ + && ta[0] === 0xff && ta[1] === 0xff && ta[2] === 0xff && ta[3] === 0; })()", + ) + .unwrap(); + assert!(result.as_bool()); +}