feat: DataView 活界判与构造器 proto 读后终校验落地

This commit is contained in:
2026-09-23 13:54:18 +00:00
parent 3d50306853
commit ede3db8ad8
2 changed files with 432 additions and 58 deletions
+134 -58
View File
@@ -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<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<DataVi
Ok(unsafe { *data_ptr })
}
fn checked_absolute_offset<H: VmHost>(
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<H: VmHost>(
vm: &mut H, payload_ptr: *mut ArrayBufferPayload, view: DataViewData,
) -> Result<usize, JsValue> {
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<H: VmHost>(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<H: VmHost>(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<const N: usize, H: VmHost>(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<const N: usize, H: VmHost>(
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<DataViewData>` 指针,
// 与 ArrayBuffer / RegExp 的类型化存储模式一致。
@@ -502,10 +564,12 @@ pub fn data_view_get_big_uint64<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn data_view_set_uint8<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn data_view_set_int16<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn data_view_set_uint16<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn data_view_set_int32<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn data_view_set_uint32<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn data_view_set_float32<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn data_view_set_float16<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn data_view_set_float64<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
pub fn data_view_set_big_uint64<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
}
/// `DataView.prototype.byteOffset` getter:返回视图起始字节在 buffer 内的偏移。
/// 缓冲已 detach 或视图越界(live 长口径)→ TypeError。
pub fn data_view_byte_offset_getter<H: VmHost>(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<H: VmHost>(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))
}