diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index 316c2dfb..ec62f7ac 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -1,11 +1,48 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; -use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; +use oxide_types::object::{JsObject, NativeFnPtr}; use oxide_types::value::JsValue; use oxide_runtime_api::{NativeResult, VmHost}; pub(crate) const MAX_ARRAY_BUFFER_LENGTH: usize = 1 << 30; +/// ArrayBuffer 载荷:字节缓冲与状态位。`data` 为 `None` 即缓冲区已 detach; +/// `max_byte_length` 为 0 即定长缓冲;`immutable` 为字节缓冲写守卫标志。 +/// 载荷经 `Box::into_raw` 存于对象 `native_fn` 槽,GC 三自由函数 +/// (clone/size/drop)对整结构体操作,克隆须连标志位一并拷贝。 +#[derive(Clone)] +pub(crate) struct ArrayBufferPayload { + pub(crate) data: Option>, + // 状态位:resizable 上限与 immutable 写守卫,当前读路径尚未消费。 + #[cfg_attr( + not(test), + expect( + dead_code, + reason = "状态位随构造器写入与克隆拷贝,消费方在 options/resize/detach 语义路径" + ) + )] + pub(crate) max_byte_length: usize, + #[cfg_attr( + not(test), + expect( + dead_code, + reason = "状态位随构造器写入与克隆拷贝,消费方在 options/resize/detach 语义路径" + ) + )] + pub(crate) immutable: bool, +} + +impl ArrayBufferPayload { + /// 构造定长已附着载荷(构造器/切片路径的默认形态)。 + fn attached(data: Vec) -> Self { + ArrayBufferPayload { + data: Some(data), + max_byte_length: 0, + immutable: false, + } + } +} + macro_rules! native_try { ($expr:expr) => { match $expr { @@ -48,78 +85,74 @@ fn normalize_index(vm: &mut H, value: JsValue, len: usize) -> usize { } } -fn set_named_prop(vm: &mut H, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { - let si = vm.kernel_core().perm_interner().intern(name).0; - let _ = vm.define_data_property(obj, si, value, attributes); -} - pub(crate) fn new_array_buffer(vm: &mut H, data: Vec) -> *mut JsObject { let proto = vm.session().builtin_world().array_buffer_proto.as_ptr() as *mut JsObject; let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)); obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; - let len = data.len(); - let data_ptr = Box::into_raw(Box::new(data)); - // SAFETY: ArrayBuffer 对象不可调用,故 native_fn 槽复用作不透明 `Box>` + // byteLength 经原型访问器读(构造器不写 own 数据属性,实例零命名属性)。 + let payload_ptr = Box::into_raw(Box::new(ArrayBufferPayload::attached(data))); + // SAFETY: ArrayBuffer 对象不可调用,故 native_fn 槽复用作不透明载荷盒 // 指针,与既有 RegExp 存储模式一致。 - obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(data_ptr as *const ()) })); - set_named_prop( - vm, - &mut obj, - "byteLength", - JsValue::int(len as i32), - PropAttributes::new(false, false, false), - ); + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); vm.alloc_object(obj) } -fn array_buffer_vec_ptr(obj: &JsObject) -> Option<*mut Vec> { +fn array_buffer_payload_ptr(obj: &JsObject) -> Option<*mut ArrayBufferPayload> { if !obj.is_array_buffer_obj() { return None; } - obj.native_fn().map(|ptr| ptr.as_ptr() as *mut Vec) + obj.native_fn().map(|ptr| ptr.as_ptr() as *mut ArrayBufferPayload) } -/// 克隆 ArrayBuffer 的字节数据到新对象(跨 epoch 克隆流程用)。 +/// 克隆 ArrayBuffer 载荷(字节缓冲 + 状态位)到新对象(跨 epoch 克隆流程用)。 pub fn clone_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObject) { - let Some(ptr) = array_buffer_vec_ptr(old_obj) else { + let Some(ptr) = array_buffer_payload_ptr(old_obj) else { return; }; if ptr.is_null() { return; } - let cloned = unsafe { (&*ptr).clone() }; + // SAFETY: ptr 非空且指向存活载荷盒(克隆/晋升臂由 GC 持有源对象)。 + let cloned = unsafe { &*ptr }.clone(); let cloned_ptr = Box::into_raw(Box::new(cloned)); new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) })); } -/// 只读核算 ArrayBuffer 字节缓冲字节(不释放)。 +/// 只读核算 ArrayBuffer 载荷字节(不释放):结构体尺寸 + 附着缓冲容量, +/// detached 载荷仅计结构体尺寸。 pub fn array_buffer_native_size(obj: &JsObject) -> u64 { - let Some(data_ptr) = array_buffer_vec_ptr(obj) else { + let Some(payload_ptr) = array_buffer_payload_ptr(obj) else { return 0; }; - if data_ptr.is_null() { + if payload_ptr.is_null() { return 0; } - unsafe { (std::mem::size_of::>() + (*data_ptr).capacity()) as u64 } + // SAFETY: payload_ptr 非空且指向存活载荷盒。 + let payload = unsafe { &*payload_ptr }; + (std::mem::size_of::() + payload.data.as_ref().map_or(0, |d| d.capacity())) as u64 } -/// 释放 ArrayBuffer 的字节缓冲(native_fn 槽中的 `Box>`),返回字节数。 +/// 释放 ArrayBuffer 载荷盒(native_fn 槽),返回字节数;槽置空后重复调用零释放。 pub fn drop_array_buffer_native(obj: &mut JsObject) -> u64 { let bytes = array_buffer_native_size(obj); if bytes == 0 { return 0; } - let Some(data_ptr) = array_buffer_vec_ptr(obj) else { + let Some(payload_ptr) = array_buffer_payload_ptr(obj) else { return 0; }; - // SAFETY: data_ptr 非空(array_buffer_native_size 已验证),Box::from_raw 恰好释放一次。 - let data = unsafe { Box::from_raw(data_ptr) }; - drop(data); + // SAFETY: payload_ptr 非空(array_buffer_native_size 已验证),Box::from_raw 恰好释放一次。 + let payload = unsafe { Box::from_raw(payload_ptr) }; + drop(payload); obj.set_native_fn(None); bytes } -pub(crate) fn array_buffer_data_ptr(vm: &mut H, this_val: JsValue) -> Result<*mut Vec, JsValue> { +/// 读入口:校验 receiver 为 ArrayBuffer 并取载荷指针。detach(载荷 `data` 为 +/// `None`)不在此分叉,由消费方现读 `data` 时按既有错误形态处理。 +pub(crate) fn array_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")); } @@ -131,14 +164,14 @@ pub(crate) fn array_buffer_data_ptr(vm: &mut H, this_val: JsValue) -> if !obj.is_array_buffer_obj() { return Err(crate::error::create_type_error(vm, "ArrayBuffer method called on incompatible receiver")); } - let Some(data_ptr) = obj.native_fn() else { + let Some(payload_ptr) = obj.native_fn() else { return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; - let data_ptr = data_ptr.as_ptr() as *mut Vec; - if data_ptr.is_null() { + let payload_ptr = payload_ptr.as_ptr() as *mut ArrayBufferPayload; + if payload_ptr.is_null() { return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); } - Ok(data_ptr) + Ok(payload_ptr) } /// `ArrayBuffer(length)` 构造逻辑:分配 length 字节(0 填充)的新缓冲区。 @@ -157,15 +190,22 @@ pub fn array_buffer_constructor(vm: &mut H, args: &[u8]) -> NativeRes /// `ArrayBuffer.prototype.byteLength` getter:返回缓冲区字节数。 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 data_ptr = native_try!(array_buffer_data_ptr(vm, this_val)); - NativeResult::Ok(JsValue::int(unsafe { (*data_ptr).len() } as i32)) + 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)) } /// `ArrayBuffer.prototype.slice(start, end)`:复制字节区间生成新 ArrayBuffer。 pub fn array_buffer_slice(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - let data_ptr = native_try!(array_buffer_data_ptr(vm, this_val)); - let data = unsafe { &*data_ptr }; + 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")); + }; let len = data.len(); let start = if args.len() > 1 { normalize_index(vm, vm.reg(args[1]), len) } else { 0 }; let end = if args.len() > 2 { normalize_index(vm, vm.reg(args[2]), len) } else { len }; @@ -190,8 +230,99 @@ pub fn array_buffer_is_view(vm: &mut H, args: &[u8]) -> NativeResult /// `ArrayBuffer.prototype.toString`:校验 receiver 后返回 `[object ArrayBuffer]`。 pub fn array_buffer_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - if array_buffer_data_ptr(vm, this_val).is_err() { + if array_buffer_payload(vm, this_val).is_err() { return type_error(vm, "ArrayBuffer.prototype.toString called on incompatible receiver"); } NativeResult::Ok(vm.new_string("[object ArrayBuffer]")) } + +#[cfg(test)] +mod tests { + use super::*; + + /// 手工构造带载荷盒的 ArrayBuffer 对象(不经 Vm,只验 GC 三自由函数)。 + fn ab_object_with_payload(payload: ArrayBufferPayload) -> JsObject { + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); + obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; + let payload_ptr = Box::into_raw(Box::new(payload)); + // SAFETY: 载荷盒形态与 new_array_buffer 的 native_fn 槽存储一致,测试结束前恰好释放一次。 + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); + obj + } + + /// 克隆目标对象与真实 GC 克隆体同形:AB 类型标签 + 空 native_fn 槽。 + fn ab_clone_target() -> JsObject { + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); + obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; + obj + } + + #[test] + fn payload_clone_propagates_detached() { + let old = ab_object_with_payload(ArrayBufferPayload { + data: None, + max_byte_length: 0, + immutable: false, + }); + let mut new_obj = ab_clone_target(); + clone_array_buffer_native(&old, &mut new_obj); + let ptr = array_buffer_payload_ptr(&new_obj).expect("克隆体应携带载荷盒"); + // SAFETY: ptr 由 clone_array_buffer_native 新建的存活盒。 + let cloned = unsafe { &*ptr }; + assert!(cloned.data.is_none()); + unsafe { drop(Box::from_raw(ptr)) }; + } + + #[test] + fn payload_clone_copies_flags() { + let old = ab_object_with_payload(ArrayBufferPayload { + data: Some(vec![1, 2, 3]), + max_byte_length: 42, + immutable: true, + }); + let mut new_obj = ab_clone_target(); + clone_array_buffer_native(&old, &mut new_obj); + let ptr = array_buffer_payload_ptr(&new_obj).expect("克隆体应携带载荷盒"); + // SAFETY: ptr 由 clone_array_buffer_native 新建的存活盒。 + let cloned = unsafe { &*ptr }; + assert_eq!(cloned.max_byte_length, 42); + assert!(cloned.immutable); + assert_eq!(cloned.data.as_ref().expect("附着缓冲应随克隆").as_slice(), &[1, 2, 3]); + unsafe { drop(Box::from_raw(ptr)) }; + } + + #[test] + fn detached_size_and_drop_idempotent() { + let mut obj = ab_object_with_payload(ArrayBufferPayload { + data: None, + max_byte_length: 0, + immutable: false, + }); + let struct_bytes = std::mem::size_of::() as u64; + assert_eq!(array_buffer_native_size(&obj), struct_bytes); + // 首次 drop 释放结构体(无缓冲容量);槽置空后二次 drop 零释放。 + assert_eq!(drop_array_buffer_native(&mut obj), struct_bytes); + assert_eq!(drop_array_buffer_native(&mut obj), 0); + } + + #[test] + fn attached_size_and_drop() { + let mut obj = ab_object_with_payload(ArrayBufferPayload::attached(vec![0u8; 8])); + let bytes = (std::mem::size_of::() + 8) as u64; + assert_eq!(array_buffer_native_size(&obj), bytes); + assert_eq!(drop_array_buffer_native(&mut obj), bytes); + assert_eq!(drop_array_buffer_native(&mut obj), 0); + } + + #[test] + fn payload_ptr_brand_guard() { + // 非 ArrayBuffer 类型标签(PLAIN)对象取载荷指针恒 None。 + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); + let payload_ptr = Box::into_raw(Box::new(ArrayBufferPayload::attached(vec![0u8; 4]))); + // SAFETY: 同 ab_object_with_payload 的载荷盒形态。 + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); + assert!(array_buffer_payload_ptr(&obj).is_none()); + // SAFETY: 测试构造的盒,恰好释放一次。 + unsafe { drop(Box::from_raw(payload_ptr)) }; + } +} diff --git a/crates/oxide_builtins/src/data_view.rs b/crates/oxide_builtins/src/data_view.rs index 84a4476d..44d79d7d 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_data_ptr; +use crate::array_buffer::array_buffer_payload; use oxide_runtime_api::{NativeResult, VmHost}; @@ -121,8 +121,11 @@ fn checked_absolute_offset( /// GetViewValue 后半:按已校验的视图与偏移读 N 字节(越界抛 RangeError)。 fn read_bytes_at(vm: &mut H, view: DataViewData, offset: usize) -> Result<[u8; N], JsValue> { let abs = checked_absolute_offset(vm, view, offset, N)?; - let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; - let buffer = unsafe { &*buffer_ptr }; + 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")); + }; if abs + N > buffer.len() { return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); } @@ -136,8 +139,11 @@ fn write_bytes( vm: &mut H, view: DataViewData, offset: usize, bytes: [u8; N], ) -> Result<(), JsValue> { let abs = checked_absolute_offset(vm, view, offset, N)?; - let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; - let buffer = unsafe { &mut *buffer_ptr }; + let payload_ptr = array_buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; if abs + N > buffer.len() { return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); } @@ -195,8 +201,12 @@ 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 buffer_ptr = native_try!(array_buffer_data_ptr(vm, buffer)); - let buffer_len = unsafe { (*buffer_ptr).len() }; + let payload_ptr = native_try!(array_buffer_payload(vm, buffer)); + // 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")); + }; + let buffer_len = data.len(); let byte_offset = if args.len() > 2 { native_try!(to_index(vm, vm.reg(args[2]), "DataView byteOffset out of bounds")) } else { diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 59f33fba..c8878270 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -5,7 +5,7 @@ use oxide_types::object::{JsObject, NativeFnPtr, TypedArrayKind}; use oxide_types::private_key::{int_key_value, is_int_key, make_well_known_symbol_key, WELL_KNOWN_SYMBOL_SPECIES}; use oxide_types::value::JsValue; -use crate::array_buffer::{array_buffer_data_ptr, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH}; +use crate::array_buffer::{array_buffer_payload, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH}; use oxide_runtime_api::{NativeResult, VmHost}; @@ -270,8 +270,11 @@ pub fn typed_array_element_get(vm: &mut H, obj: &JsObject, index: u32 if index as usize >= view.length { return Ok(JsValue::undefined()); } - let buffer_ptr = array_buffer_data_ptr(vm, view.buffer).map_err(|e| format!("{e}"))?; - let buffer = unsafe { &*buffer_ptr }; + let payload_ptr = array_buffer_payload(vm, view.buffer).map_err(|e| format!("{e}"))?; + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { + return Err("ArrayBuffer internal state invalid".into()); + }; Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, index as usize))) } @@ -341,9 +344,11 @@ fn write_typed_array_element( if index as usize >= view.length { return Ok(()); } - let buffer_ptr = array_buffer_data_ptr(vm, view.buffer).map_err(|e| format!("{e}"))?; - // SAFETY: buffer_ptr 经 array_buffer_data_ptr 校验为合法 ArrayBuffer。 - let buffer = unsafe { &mut *buffer_ptr }; + let payload_ptr = array_buffer_payload(vm, view.buffer).map_err(|e| format!("{e}"))?; + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return Err("ArrayBuffer internal state invalid".into()); + }; write_element(vm, view.kind, buffer, absolute_byte_offset(view, index as usize), elem); Ok(()) } @@ -489,8 +494,11 @@ 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 buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; - let buffer = unsafe { &*buffer_ptr }; + 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")); + }; return Ok((0..view.length) .map(|i| read_element(vm, view.kind, buffer, absolute_byte_offset(view, i))) .collect()); @@ -558,8 +566,12 @@ fn typed_array_new(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> let first_ptr = first.as_js_object_ptr(); let first_obj = unsafe { &*first_ptr }; if first_obj.is_array_buffer_obj() { - let buffer_ptr = native_try!(array_buffer_data_ptr(vm, first)); - let buffer_len = unsafe { (*buffer_ptr).len() }; + let payload_ptr = native_try!(array_buffer_payload(vm, first)); + // 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")); + }; + let buffer_len = data.len(); let byte_offset = if args.len() > 2 { native_try!(to_index(vm, vm.reg(args[2]), "TypedArray byteOffset out of bounds")) } else { @@ -585,8 +597,11 @@ fn typed_array_new(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> let values = native_try!(collect_array_like(vm, first, true)); let byte_len = values.len().saturating_mul(bpe); let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len])); - let buffer_ptr = native_try!(array_buffer_data_ptr(vm, buffer)); - let buffer_ref = unsafe { &mut *buffer_ptr }; + let payload_ptr = native_try!(array_buffer_payload(vm, buffer)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer_ref) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; for (idx, value) in values.into_iter().enumerate() { let elem = native_try!(ta_element_value(vm, kind, value)); write_element(vm, kind, buffer_ref, idx * bpe, elem); @@ -673,8 +688,11 @@ pub fn typed_array_at(vm: &mut H, args: &[u8]) -> NativeResult { if idx < 0 || idx as usize >= view.length { return NativeResult::Ok(JsValue::undefined()); } - let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); - let buffer = unsafe { &*buffer_ptr }; + let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; NativeResult::Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, idx as usize))) } @@ -695,8 +713,11 @@ pub fn typed_array_fill(vm: &mut H, args: &[u8]) -> NativeResult { } else { view.length }; - let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); - let buffer = unsafe { &mut *buffer_ptr }; + let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; for idx in start..end.max(start) { write_element(vm, view.kind, buffer, absolute_byte_offset(view, idx), elem); } @@ -854,8 +875,11 @@ pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { for v in values { converted.push(native_try!(ta_element_value(vm, view.kind, v))); } - let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); - let buffer = unsafe { &mut *buffer_ptr }; + let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; for (i, elem) in converted.into_iter().enumerate() { write_element(vm, view.kind, buffer, absolute_byte_offset(view, offset + i), elem); } @@ -948,8 +972,11 @@ fn set_typed_array_element(vm: &mut H, ta: JsValue, index: usize, val return Ok(()); } let elem = ta_element_value(vm, view.kind, value)?; - let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; - let buffer = unsafe { &mut *buffer_ptr }; + let payload_ptr = array_buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; write_element(vm, view.kind, buffer, absolute_byte_offset(view, index), elem); Ok(()) } @@ -978,15 +1005,21 @@ fn ta_read(vm: &mut H, view: TypedArrayData, index: usize) -> Result< if index >= view.length { return Ok(JsValue::undefined()); } - let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; - let buffer = unsafe { &*buffer_ptr }; + 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")); + }; Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, index))) } /// 把已转换的值写入 TypedArray 指定索引(视图 data 已取出的形式,供原型方法内部使用)。 fn ta_write(vm: &mut H, view: TypedArrayData, index: usize, value: JsValue) -> Result<(), JsValue> { - let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; - let buffer = unsafe { &mut *buffer_ptr }; + let payload_ptr = array_buffer_payload(vm, view.buffer)?; + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; write_element(vm, view.kind, buffer, absolute_byte_offset(view, index), value); Ok(()) } @@ -1951,9 +1984,11 @@ 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 buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); - let buffer = unsafe { &*buffer_ptr }; - // SAFETY: buffer_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; NativeResult::Ok(vm.new_string_owned(crate::ta_codec::encode_base64( &buffer[view.byte_offset..view.byte_offset + view.length], alphabet, @@ -1965,9 +2000,11 @@ 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 buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); - let buffer = unsafe { &*buffer_ptr }; - // SAFETY: buffer_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; NativeResult::Ok( vm.new_string_owned(crate::ta_codec::encode_hex(&buffer[view.byte_offset..view.byte_offset + view.length])), ) @@ -1980,9 +2017,11 @@ 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 buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); - let buffer = unsafe { &mut *buffer_ptr }; - // SAFETY: buffer_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; let start = view.byte_offset; let (read, written) = native_try!(ta_decode_base64(vm, string, options, Some(view.length), &mut |idx, b| { buffer[start + idx] = b; @@ -1999,9 +2038,11 @@ 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 buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); - let buffer = unsafe { &mut *buffer_ptr }; - // SAFETY: buffer_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); + // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 + let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + }; let start = view.byte_offset; let units = vm.string_units(string).into_owned(); let (read, written) = match crate::ta_codec::decode_hex(&units, Some(view.length), &mut |idx, b| { diff --git a/crates/oxide_vm/src/bindings/bind_array_buffer.rs b/crates/oxide_vm/src/bindings/bind_array_buffer.rs index b8160884..99695c4f 100644 --- a/crates/oxide_vm/src/bindings/bind_array_buffer.rs +++ b/crates/oxide_vm/src/bindings/bind_array_buffer.rs @@ -1,7 +1,9 @@ use std::sync::Arc; use crate::bind_constructor; -use crate::bindings::{apply_binding_table, bind_accessor_getter_key, configure_native_constructor}; +use crate::bindings::{ + apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, configure_native_constructor, +}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; @@ -45,11 +47,6 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global proto, core, &[ - ( - "byteLength", - oxide_builtins::array_buffer::array_buffer_byte_length:: as *const (), - 0, - ), ("slice", oxide_builtins::array_buffer::array_buffer_slice:: as *const (), 2), ( "toString", @@ -59,6 +56,15 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global ], ); + // byteLength 原型访问器(set 恒 undefined):读载荷字节数,实例无 own 属性。 + bind_accessor_getter( + core, + session, + proto, + "byteLength", + oxide_builtins::array_buffer::array_buffer_byte_length:: as *const (), + ); + bind_constructor!( core, global, diff --git a/crates/oxide_vm/tests/builtins_array_buffer_tests.rs b/crates/oxide_vm/tests/builtins_array_buffer_tests.rs index 34587810..eab4e6f3 100644 --- a/crates/oxide_vm/tests/builtins_array_buffer_tests.rs +++ b/crates/oxide_vm/tests/builtins_array_buffer_tests.rs @@ -24,10 +24,16 @@ fn array_buffer_global_and_static_method_exist() { } #[test] -fn array_buffer_constructor_sets_byte_length_property() { +fn array_buffer_constructor_reads_byte_length_via_proto_accessor() { + // byteLength 经原型访问器可读,实例无 own 数据属性。 let mut vm = Vm::new(); - let result = eval(&mut vm, "var buf = new ArrayBuffer(8); buf.byteLength").unwrap(); - assert_eq!(result.as_int(), 8); + let result = eval( + &mut vm, + "var buf = new ArrayBuffer(8); buf.byteLength === 8 \ + && Object.getOwnPropertyDescriptor(buf, 'byteLength') === undefined", + ) + .unwrap(); + assert!(result.as_bool()); } #[test] diff --git a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs new file mode 100644 index 00000000..afc6bafb --- /dev/null +++ b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs @@ -0,0 +1,71 @@ +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)) +} + +/// 原型 byteLength 描述符钉:get 为函数、set 缺失、不可枚举、可配置。 +#[test] +fn ab_bytelength_proto_is_accessor() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength'); \ + typeof d.get === 'function' && d.set === undefined \ + && d.enumerable === false && d.configurable === true", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 访问器 getter 函数对象元数据钉:name 标签与 length。 +#[test] +fn ab_bytelength_getter_name_and_length() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength'); \ + d.get.name === 'get byteLength' && d.get.length === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 访问器返回值钉:构造长度经原型访问器读回。 +#[test] +fn ab_bytelength_returns_constructed_len() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "new ArrayBuffer(0).byteLength === 0 && new ArrayBuffer(42).byteLength === 42").unwrap(); + assert!(result.as_bool()); +} + +/// receiver 校验钉:非 ArrayBuffer this(原型自身 / undefined)抛 TypeError。 +#[test] +fn ab_bytelength_this_checks() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength'); \ + var proto_threw = false; var undef_threw = false; \ + try { ArrayBuffer.prototype.byteLength } catch (e) { proto_threw = e instanceof TypeError; } \ + try { d.get.call(undefined) } catch (e) { undef_threw = e instanceof TypeError; } \ + proto_threw && undef_threw", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 实例无 own byteLength 属性钉(构造器不写数据属性)。 +#[test] +fn ab_instance_has_no_own_bytelength() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Object.getOwnPropertyNames(new ArrayBuffer(8)).length === 0").unwrap(); + assert!(result.as_bool()); +}