From ede3db8ad8ca9f34dd674c8b3cbbc0bc572dad00 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 23 Sep 2026 13:54:18 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20DataView=20=E6=B4=BB=E7=95=8C=E5=88=A4?= =?UTF-8?q?=E4=B8=8E=E6=9E=84=E9=80=A0=E5=99=A8=20proto=20=E8=AF=BB?= =?UTF-8?q?=E5=90=8E=E7=BB=88=E6=A0=A1=E9=AA=8C=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/data_view.rs | 192 +++++++---- .../tests/builtins_dataview_live_tests.rs | 298 ++++++++++++++++++ 2 files changed, 432 insertions(+), 58 deletions(-) create mode 100644 crates/oxide_vm/tests/builtins_dataview_live_tests.rs diff --git a/crates/oxide_builtins/src/data_view.rs b/crates/oxide_builtins/src/data_view.rs index a9b44882..dd8707a9 100644 --- a/crates/oxide_builtins/src/data_view.rs +++ b/crates/oxide_builtins/src/data_view.rs @@ -5,15 +5,19 @@ use oxide_types::value::JsValue; use num_bigint::BigInt; use num_traits::ToPrimitive; -use crate::array_buffer::array_buffer_payload; +use crate::array_buffer::{array_buffer_payload, ArrayBufferPayload}; use oxide_runtime_api::{NativeResult, VmHost}; +/// DataView 视图状态盒。`byte_length` 语义随 `length_is_auto` 分流: +/// 定长视图存静态长;auto 视图(构造时 byteLength 缺省且构造期缓冲可 resize) +/// 存构造期 live − offset 初值,活读一律现算 live − offset。 #[derive(Clone, Copy)] pub(crate) struct DataViewData { buffer: JsValue, byte_offset: usize, byte_length: usize, + length_is_auto: bool, } macro_rules! native_try { @@ -106,50 +110,91 @@ fn get_data_view_data(vm: &mut H, this_val: JsValue) -> Result( - vm: &mut H, view: DataViewData, offset: usize, width: usize, +/// 视图界判核(规范 IsViewOutOfBounds 与 GetViewByteLength 合一):缓冲已 +/// detach(载荷 `data` 为 `None`)、auto 视图 offset 超 live、定长视图 +/// offset + 静态长超 live(含溢出)→ TypeError;通过时返回视图长 +/// (auto 视图现算 live − offset,定长视图返静态长)。 +fn dv_view_bounds( + vm: &mut H, payload_ptr: *mut ArrayBufferPayload, view: DataViewData, ) -> Result { - let Some(end) = offset.checked_add(width) else { - return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(data) = unsafe { &*payload_ptr }.data.as_deref() else { + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; - if end > view.byte_length { - return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); + let live = data.len(); + if view.length_is_auto { + if view.byte_offset > live { + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + } + return Ok(live - view.byte_offset); + } + match view.byte_offset.checked_add(view.byte_length) { + Some(end) if end <= live => Ok(view.byte_length), + _ => Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")), } - Ok(view.byte_offset + offset) } -/// GetViewValue 后半:按已校验的视图与偏移读 N 字节(越界抛 RangeError)。 -/// detach 守卫先于范围校验(规范序:IsDetachedBuffer 检查先于越界判定, -/// detach 后越界偏移也抛 TypeError 而非 RangeError)。 +/// 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 载荷。 + if unsafe { &*payload_ptr }.immutable { + return Err(crate::error::create_type_error(vm, "ArrayBuffer is immutable")); + } + Ok(()) +} + +/// JS 调用点后的缓冲重取纪律:重读寄存器再重取载荷(epoch 晋升改写 +/// 寄存器,旧 JsValue 滞留 epoch 克隆旧盒);detached → TypeError。 +/// 返回(寄存器重读值、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 = unsafe { &*payload_ptr }; + let Some(data) = payload.data.as_deref() else { + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; + Ok((buffer, data.len(), payload.max_byte_length)) +} + +/// GetViewValue 后半:按已校验的视图与偏移读 N 字节。界判核先 +/// (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 Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; - let abs = checked_absolute_offset(vm, view, offset, N)?; - if abs + N > buffer.len() { - return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); + let view_len = dv_view_bounds(vm, payload_ptr, view)?; + match offset.checked_add(N) { + Some(end) if end <= view_len => {} + _ => return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")), } + let abs = view.byte_offset + offset; let mut out = [0u8; N]; out.copy_from_slice(&buffer[abs..abs + N]); Ok(out) } -/// SetViewValue 后半:按已校验的视图与偏移写 N 字节(越界抛 RangeError)。 -/// detach 守卫先于范围校验(规范序同读路径)。 +/// SetViewValue 后半:按已校验的视图与偏移写 N 字节(读路径同序)。 fn write_bytes( vm: &mut H, view: DataViewData, offset: usize, bytes: [u8; N], ) -> Result<(), JsValue> { let payload_ptr = array_buffer_payload(vm, view.buffer)?; - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + // 界判核共享借用先于可变借用建立,避免活别名。 + let view_len = dv_view_bounds(vm, payload_ptr, view)?; + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷; + // 界判核已证 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")); }; - let abs = checked_absolute_offset(vm, view, offset, N)?; - if abs + N > buffer.len() { - return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); + match offset.checked_add(N) { + Some(end) if end <= view_len => {} + _ => return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")), } + let abs = view.byte_offset + offset; buffer[abs..abs + N].copy_from_slice(&bytes); Ok(()) } @@ -183,18 +228,26 @@ fn has_data_view_proto(vm: &mut H, this_val: JsValue) -> bool { /// # 步骤 /// 1. receiver 原型链不含 %DataView.prototype% → TypeError(普通调用面: /// this = globalThis 不满足;构造路径恒传 DataView 原型链上的 receiver)。 -/// 2. buffer 品牌校验(ArrayBufferData)。 -/// 3. offset = ToIndex(byteOffset) 传播式;detached 缓冲 → TypeError(在 -/// 偏移强转之后);offset > bufferLen → RangeError。 -/// 4. byteLength 缺省/undefined → bufferLen - offset,否则 ToIndex 传播式; -/// offset + byteLength > bufferLen → RangeError。 +/// 2. buffer 品牌校验(ArrayBufferData);首个载荷取用后即弃指针, +/// 不持借用跨 JS 调用。 +/// 3. offset = ToIndex(byteOffset) 传播式;其后重取(重读寄存器 + 载荷重取): +/// detached 缓冲 → TypeError(在偏移强转之后);offset > live → RangeError。 +/// 4. byteLength 缺省/undefined → 缺省长 live − offset,auto 位按构造期缓冲 +/// resizable 定;否则 ToIndex 传播式,其后重取按新 live 复验 +/// offset + byteLength → RangeError。 /// 5. GetPrototypeFromConstructor:经 `ordinary_get` 读 NewTarget(regs[255]) /// 的 `prototype`(触发访问器,读期异常原值传播);非对象回落 %DataView.prototype%。 -/// 6. 分配 OBJ_TYPE_DATA_VIEW 对象并挂 DataViewData 状态盒。 +/// 6. 终校验(proto 读后再重取一次):detached → TypeError;offset > live' → +/// RangeError;显式长 offset + byteLength > live' → RangeError;auto 视图 +/// 存储长按 live' − offset 重算。 +/// 7. 分配 OBJ_TYPE_DATA_VIEW 对象并挂 DataViewData 状态盒(终校验先于 +/// 分配,raise 路径无半成品对象)。 /// /// # 边界与前提 -/// - 原型读取在全部校验之后:越界 RangeError 先于抛错的 prototype getter。 +/// - 原型读取在全部初校验之后:越界 RangeError 先于抛错的 prototype getter。 /// - NewTarget 非对象(直调/缺省面)时直接回落默认原型,不抛错。 +/// - 每个 JS 调用点后重取缓冲(重读寄存器 + 载荷重取):晋升改写寄存器, +/// 旧值滞留 epoch 克隆旧盒。 pub fn data_view_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if !has_data_view_proto(vm, this_val) { @@ -205,37 +258,34 @@ pub fn data_view_constructor(vm: &mut H, args: &[u8]) -> NativeResult return NativeResult::Err(crate::error::create_type_error(vm, "DataView requires an ArrayBuffer")); } let buffer = vm.reg(args[1]); - let payload_ptr = native_try!(array_buffer_payload(vm, buffer)); - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 - let buf = unsafe { &*payload_ptr }.data.as_deref(); - let buffer_len = buf.map_or(0, |d| d.len()); + // 首个载荷取用只做品牌校验,指针即弃,不持借用跨 JS 调用。 + native_try!(array_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")) } else { 0 }; - // detach 守卫在偏移强转之后、越界判定之前(规范序:detached 缓冲构造 - // 抛 TypeError,且偏移的 ToNumber 已恰好发生一次)。 - if buf.is_none() { - return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); - } - if byte_offset > buffer_len { + + // 偏移强转后的重取:detached 缓冲构造抛 TypeError,且偏移的 ToNumber + // 已恰好发生一次(规范序)。 + let (_, live, max_byte_length) = native_try!(revalidate_buffer(vm, args[1])); + if byte_offset > live { return NativeResult::Err(crate::error::create_range_error(vm, "DataView byteOffset out of bounds")); } - let default_len = buffer_len - byte_offset; - // byteLength 显式 undefined 视同缺省(ToIndex(undefined) = 0 会吞掉视图长度)。 - let byte_length = if args.len() > 3 { - let bl_val = vm.reg(args[3]); - if bl_val.is_undefined() { - default_len - } else { - native_try!(to_index(vm, bl_val, "DataView byteLength out of bounds")) + + // byteLength 显式 undefined 视同缺省(ToIndex(undefined) = 0 会吞掉视图长度); + // 缺省臂 auto 位按构造期缓冲 resizable 判定。 + let explicit = args.len() > 3 && !vm.reg(args[3]).is_undefined(); + let length_is_auto = !explicit && max_byte_length != 0; + let mut byte_length = live - byte_offset; + if explicit { + byte_length = native_try!(to_index(vm, vm.reg(args[3]), "DataView byteLength out of bounds")); + // byteLength 强转后的重取,按新 live 复验。 + let (_, live, _) = native_try!(revalidate_buffer(vm, args[1])); + if byte_offset + byte_length > live { + return NativeResult::Err(crate::error::create_range_error(vm, "DataView byteLength out of bounds")); } - } else { - default_len - }; - if byte_offset + byte_length > buffer_len { - return NativeResult::Err(crate::error::create_range_error(vm, "DataView byteLength out of bounds")); } // GetPrototypeFromConstructor:普通读触发原型链上的访问器 getter, @@ -259,12 +309,24 @@ pub fn data_view_constructor(vm: &mut H, args: &[u8]) -> NativeResult default_proto }; + // 终校验:proto getter 是用户代码窗(detach/resize 可发生),再重取一次, + // 全部判定用 live';auto 视图存储长按 live' − offset 重算。 + let (buffer, live, _) = native_try!(revalidate_buffer(vm, args[1])); + if byte_offset > live { + return NativeResult::Err(crate::error::create_range_error(vm, "DataView byteOffset out of bounds")); + } + if !length_is_auto && byte_offset + byte_length > live { + return NativeResult::Err(crate::error::create_range_error(vm, "DataView byteLength out of bounds")); + } + let byte_length = if length_is_auto { live - byte_offset } else { byte_length }; + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); obj.type_tag = JsObject::OBJ_TYPE_DATA_VIEW; let data = Box::into_raw(Box::new(DataViewData { buffer, byte_offset, byte_length, + length_is_auto, })); // SAFETY: DataView 对象不可调用,native_fn 存不透明 `Box` 指针, // 与 ArrayBuffer / RegExp 的类型化存储模式一致。 @@ -502,10 +564,12 @@ pub fn data_view_get_big_uint64(vm: &mut H, args: &[u8]) -> NativeRes /// `DataView.prototype.setInt8(byteOffset, value)`:写入 1 字节有符号整数。 /// -/// 步序:品牌 → ToIndex(byteOffset) → ToInt32(value) 回卷 → 越界 RangeError → 写。 +/// 步序:品牌 → immutable 写守卫 → ToIndex(byteOffset) → ToInt32(value) +/// 回卷 → 越界 RangeError → 写。 pub fn data_view_set_int8(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let n = to_int32(native_try!(coerce_value_arg(vm, args, 2))); native_try!(write_bytes::<1, H>(vm, view, offset, [n as u8])); @@ -516,6 +580,7 @@ pub fn data_view_set_int8(vm: &mut H, args: &[u8]) -> NativeResult { pub fn data_view_set_uint8(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let n = to_int32(native_try!(coerce_value_arg(vm, args, 2))); native_try!(write_bytes::<1, H>(vm, view, offset, [n as u8])); @@ -526,6 +591,7 @@ pub fn data_view_set_uint8(vm: &mut H, args: &[u8]) -> NativeResult { pub fn data_view_set_int16(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let n = to_int32(native_try!(coerce_value_arg(vm, args, 2))); let bytes = if is_little_endian(vm, args, 3) { @@ -541,6 +607,7 @@ pub fn data_view_set_int16(vm: &mut H, args: &[u8]) -> NativeResult { pub fn data_view_set_uint16(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let n = to_int32(native_try!(coerce_value_arg(vm, args, 2))); let bytes = if is_little_endian(vm, args, 3) { @@ -556,6 +623,7 @@ pub fn data_view_set_uint16(vm: &mut H, args: &[u8]) -> NativeResult pub fn data_view_set_int32(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let n = to_int32(native_try!(coerce_value_arg(vm, args, 2))); let bytes = if is_little_endian(vm, args, 3) { n.to_le_bytes() } else { n.to_be_bytes() }; @@ -567,6 +635,7 @@ pub fn data_view_set_int32(vm: &mut H, args: &[u8]) -> NativeResult { pub fn data_view_set_uint32(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let n = to_int32(native_try!(coerce_value_arg(vm, args, 2))); let bytes = if is_little_endian(vm, args, 3) { @@ -582,6 +651,7 @@ pub fn data_view_set_uint32(vm: &mut H, args: &[u8]) -> NativeResult pub fn data_view_set_float32(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let value = native_try!(coerce_value_arg(vm, args, 2)) as f32; let bytes = if is_little_endian(vm, args, 3) { @@ -598,6 +668,7 @@ pub fn data_view_set_float32(vm: &mut H, args: &[u8]) -> NativeResult pub fn data_view_set_float16(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let value = native_try!(coerce_value_arg(vm, args, 2)); let bits = crate::math::f64_to_f16_bits(value); @@ -614,6 +685,7 @@ pub fn data_view_set_float16(vm: &mut H, args: &[u8]) -> NativeResult pub fn data_view_set_float64(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let value = native_try!(coerce_value_arg(vm, args, 2)); let bytes = if is_little_endian(vm, args, 3) { @@ -628,10 +700,12 @@ pub fn data_view_set_float64(vm: &mut H, args: &[u8]) -> NativeResult /// `DataView.prototype.setBigInt64(byteOffset, value, littleEndian)`:按 ToBigInt /// 语义接收值(Number 入参抛 TypeError),取低 64 位位模式写入 8 字节有符号整数。 /// -/// 步序:品牌 → ToIndex(byteOffset) → ToBigInt(value) → 越界 RangeError → 写。 +/// 步序:品牌 → immutable 写守卫 → ToIndex(byteOffset) → ToBigInt(value) +/// → 越界 RangeError → 写。 pub fn data_view_set_big_int64(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let b = @@ -651,6 +725,7 @@ pub fn data_view_set_big_int64(vm: &mut H, args: &[u8]) -> NativeResu pub fn data_view_set_big_uint64(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)); + native_try!(dv_buffer_writable(vm, view)); let offset = native_try!(to_index_arg(vm, args, 1)); let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let b = @@ -686,21 +761,22 @@ pub fn data_view_buffer_getter(vm: &mut H, args: &[u8]) -> NativeResu } /// `DataView.prototype.byteOffset` getter:返回视图起始字节在 buffer 内的偏移。 +/// 缓冲已 detach 或视图越界(live 长口径)→ TypeError。 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)); + native_try!(dv_view_bounds(vm, payload_ptr, view)); NativeResult::Ok(JsValue::float(view.byte_offset as f64)) } /// `DataView.prototype.byteLength` getter:返回视图覆盖的字节长度。 -/// 缓冲已 detach → TypeError(规范序:IsDetachedBuffer 检查先于返回值)。 +/// 缓冲已 detach 或视图越界 → TypeError;auto 视图返 live − offset 活读 +/// (存储值仅构造期语义)。 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)); - // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 - if unsafe { &*payload_ptr }.data.is_none() { - return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); - } - NativeResult::Ok(JsValue::float(view.byte_length as f64)) + 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_vm/tests/builtins_dataview_live_tests.rs b/crates/oxide_vm/tests/builtins_dataview_live_tests.rs new file mode 100644 index 00000000..27985cf9 --- /dev/null +++ b/crates/oxide_vm/tests/builtins_dataview_live_tests.rs @@ -0,0 +1,298 @@ +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + vm.run(&Arc::new(module)) +} + +/// 构造器终校验 detach 形:proto getter 内 detach → TypeError, +/// 偏移 ToNumber 恰发生一次(重取点在强转之后)。 +#[test] +fn dv_ctor_custom_proto_detach_throws() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(8); \ + var calls = 0; \ + var byteOffset = { valueOf() { calls++; return 0; } }; \ + var newTarget = function() {}.bind(null); \ + Object.defineProperty(newTarget, 'prototype', { \ + get() { $262.detachArrayBuffer(buffer); return DataView.prototype; } \ + }); \ + var err = null; \ + try { Reflect.construct(DataView, [buffer, byteOffset], newTarget); } catch (e) { err = e; } \ + err instanceof TypeError && calls === 1", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// auto 缺省长终校验重算:proto getter 内 resize(2) 后存储长按 live' − offset 重算。 +#[test] +fn dv_ctor_custom_proto_resize_valid_by_offset() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(3, { maxByteLength: 3 }); \ + var newTarget = function() {}.bind(null); \ + Object.defineProperty(newTarget, 'prototype', { \ + get: function() { buffer.resize(2); return DataView.prototype; } \ + }); \ + var result = Reflect.construct(DataView, [buffer, 2], newTarget); \ + result.constructor === DataView && result.byteLength === 0 && result.byteOffset === 2", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 显式长终校验:resize 至 2 后 1 + 1 ≤ 2 → 成功且 byteLength 保持静态 1。 +#[test] +fn dv_ctor_custom_proto_resize_valid_by_length() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(3, { maxByteLength: 3 }); \ + var newTarget = function() {}.bind(null); \ + Object.defineProperty(newTarget, 'prototype', { \ + get: function() { buffer.resize(2); return DataView.prototype; } \ + }); \ + var result = Reflect.construct(DataView, [buffer, 1, 1], newTarget); \ + result.constructor === DataView && result.byteLength === 1", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 终校验 offset 臂:resize(1) 后 offset 2 > live' → RangeError。 +#[test] +fn dv_ctor_custom_proto_resize_invalid_by_offset() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(3, { maxByteLength: 3 }); \ + var newTarget = function() {}.bind(null); \ + Object.defineProperty(newTarget, 'prototype', { \ + get: function() { buffer.resize(1); return DataView.prototype; } \ + }); \ + var err = null; \ + try { Reflect.construct(DataView, [buffer, 2], newTarget); } catch (e) { err = e; } \ + err instanceof RangeError", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 终校验显式长臂:resize(2) 后 1 + 2 > live' → RangeError。 +#[test] +fn dv_ctor_custom_proto_resize_invalid_by_length() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(3, { maxByteLength: 3 }); \ + var newTarget = function() {}.bind(null); \ + Object.defineProperty(newTarget, 'prototype', { \ + get: function() { buffer.resize(2); return DataView.prototype; } \ + }); \ + var err = null; \ + try { Reflect.construct(DataView, [buffer, 1, 2], newTarget); } catch (e) { err = e; } \ + err instanceof RangeError", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// byteLength auto 活读序列:3 → grow 4 → shrink 2 → 边界 0 → 越界 TypeError。 +#[test] +fn dv_bytelength_auto_tracks_resize() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4, { maxByteLength: 5 }); \ + var dv = new DataView(ab, 1); \ + var ok = dv.byteLength === 3; \ + ab.resize(5); ok = ok && dv.byteLength === 4; \ + ab.resize(3); ok = ok && dv.byteLength === 2; \ + ab.resize(1); ok = ok && dv.byteLength === 0; \ + ab.resize(0); \ + try { dv.byteLength; ok = false; } catch (e) { ok = ok && e instanceof TypeError; } \ + ok", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// byteLength fixed 视图:shrink 到 end 之下 → TypeError,grow 回静态长。 +#[test] +fn dv_bytelength_fixed_shrink_typeerror() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4, { maxByteLength: 5 }); \ + var dv = new DataView(ab, 1, 2); \ + var ok = dv.byteLength === 2; \ + ab.resize(5); ok = ok && dv.byteLength === 2; \ + ab.resize(3); ok = ok && dv.byteLength === 2; \ + ab.resize(2); \ + try { dv.byteLength; ok = false; } catch (e) { ok = ok && e instanceof TypeError; } \ + ab.resize(3); ok = ok && dv.byteLength === 2; \ + ok", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// byteOffset getter detach 守卫:detached 缓冲 → TypeError。 +#[test] +fn dv_byteoffset_detach_throws() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(1); \ + var sample = new DataView(buffer, 0); \ + $262.detachArrayBuffer(buffer); \ + var err = null; \ + try { sample.byteOffset; } catch (e) { err = e; } \ + err instanceof TypeError", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// byteOffset getter OOB 守卫:auto 视图 shrink 至 offset 之下 → TypeError。 +#[test] +fn dv_byteoffset_auto_shrink_typeerror() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4, { maxByteLength: 5 }); \ + var dv = new DataView(ab, 1); \ + var ok = dv.byteOffset === 1; \ + ab.resize(0); \ + try { dv.byteOffset; ok = false; } catch (e) { ok = ok && e instanceof TypeError; } \ + ok", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// get 面 OOB:fixed 视图(0,16)resize(8) 后读 → TypeError(非 RangeError)。 +#[test] +fn dv_get_shrunk_buffer_typeerror() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(24, { maxByteLength: 32 }); \ + var sample = new DataView(buffer, 0, 16); \ + buffer.resize(16); \ + var ok = sample.getInt8(0) === 0; \ + buffer.resize(8); \ + try { sample.getInt8(0); ok = false; } catch (e) { ok = ok && e instanceof TypeError; } \ + ok", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// set 面 OOB:同形写面 → TypeError。 +#[test] +fn dv_set_shrunk_buffer_typeerror() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(24, { maxByteLength: 32 }); \ + var sample = new DataView(buffer, 0, 16); \ + buffer.resize(8); \ + var err = null; \ + try { sample.setInt8(0, 30); } catch (e) { err = e; } \ + err instanceof TypeError", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// set 面 immutable 守卫先于一切强转:偏移与值 valueOf 零调用。 +#[test] +fn dv_set_immutable_before_conversion() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var iab = (new ArrayBuffer(8)).transferToImmutable(); \ + var view = new DataView(iab); \ + var calls = []; \ + var byteOffset = { valueOf() { calls.push('byteOffset.valueOf'); return 0; } }; \ + var value = { valueOf() { calls.push('value.valueOf'); return '1'; } }; \ + view.getInt8(byteOffset) === 0 && \ + (function() { \ + calls = []; \ + try { view.setInt8(byteOffset, value); return false; } catch (e) { return e instanceof TypeError; } \ + })() && calls.length === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// get 面允许 immutable 缓冲:正常读回初值。 +#[test] +fn dv_get_immutable_ok() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var iab = (new ArrayBuffer(8)).transferToImmutable(); \ + var src = new DataView(new ArrayBuffer(8)); \ + src.setInt8(3, 42); \ + var view = new DataView(iab); \ + view.getInt8(0) === 0 && new DataView(iab).byteLength === 8", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 缓冲完好时视图相对越界保持 RangeError 种类;auto 视图 grow 后新区可读。 +#[test] +fn dv_get_oob_kind_split() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buffer = new ArrayBuffer(12); \ + var sample = new DataView(buffer, 0); \ + var err = null; \ + try { sample.getInt8(12); } catch (e) { err = e; } \ + var ok = err instanceof RangeError; \ + var ab = new ArrayBuffer(4, { maxByteLength: 8 }); \ + var auto = new DataView(ab, 0); \ + ab.resize(8); \ + ok = ok && auto.byteLength === 8 && auto.getUint8(7) === 0; \ + ok", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 定长缓冲缺省长构造 + 初校验序钉:offset > bufferByteLength 的 +/// RangeError 先于 proto getter(getter 抛 Test262Error 不可见)。 +#[test] +fn dv_ctor_fixed_default_len_static() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ok = new DataView(new ArrayBuffer(8), 2).byteLength === 6; \ + var buffer = new ArrayBuffer(3); \ + var getterCalled = false; \ + var newTarget = function() {}.bind(null); \ + Object.defineProperty(newTarget, 'prototype', { \ + get: function() { getterCalled = true; throw new Error('getter'); } \ + }); \ + var err = null; \ + try { Reflect.construct(DataView, [buffer, 4], newTarget); } catch (e) { err = e; } \ + ok = ok && err instanceof RangeError && !getterCalled; \ + ok", + ) + .unwrap(); + assert!(result.as_bool()); +}