From 00074f2270f97cc2e257973ed706a9b105105226 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 14 Jun 2026 19:07:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20DataView=20?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E6=9E=84=E9=80=A0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_kernel/src/builtin.rs | 10 +- .../oxide_vm/src/bindings/bind_data_view.rs | 46 +++ crates/oxide_vm/src/bindings/mod.rs | 2 + crates/oxide_vm/src/builtins/data_view.rs | 377 ++++++++++++++++++ crates/oxide_vm/src/builtins/mod.rs | 1 + .../tests/builtins_data_view_tests.rs | 133 ++++++ 6 files changed, 568 insertions(+), 1 deletion(-) create mode 100644 crates/oxide_vm/src/bindings/bind_data_view.rs create mode 100644 crates/oxide_vm/src/builtins/data_view.rs create mode 100644 crates/oxide_vm/tests/builtins_data_view_tests.rs diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index aa85cc03..0f90b2d0 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -197,6 +197,8 @@ pub struct BuiltinWorld { pub regexp_proto: P, pub array_buffer_constructor: P, pub array_buffer_proto: P, + pub data_view_constructor: P, + pub data_view_proto: P, pub sym_match: P, pub sym_replace: P, pub sym_search: P, @@ -289,6 +291,8 @@ impl BuiltinWorld { make_pair(string_forge, shape_forge, "RegExp", si_prototype, si_constructor, si_name); let (array_buffer_proto, array_buffer_constructor) = make_pair(string_forge, shape_forge, "ArrayBuffer", si_prototype, si_constructor, si_name); + let (data_view_proto, data_view_constructor) = + make_pair(string_forge, shape_forge, "DataView", si_prototype, si_constructor, si_name); let sym_match = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let sym_replace = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); @@ -327,10 +331,11 @@ impl BuiltinWorld { wire_ctor_proto(&set_constructor, &set_proto); wire_ctor_proto(&map_constructor, &map_proto); wire_ctor_proto(&array_buffer_constructor, &array_buffer_proto); + wire_ctor_proto(&data_view_constructor, &data_view_proto); // Wire prototype chains: all constructor prototypes inherit from Object.prototype. let obj_proto_val = JsValue::from_js_object(object_proto.as_ptr() as *mut JsObject); - let non_object_protos: [&P; 12] = [ + let non_object_protos: [&P; 13] = [ &array_proto, &function_proto, &string_proto, @@ -343,6 +348,7 @@ impl BuiltinWorld { &map_proto, ®exp_proto, &array_buffer_proto, + &data_view_proto, ]; for proto in &non_object_protos { let p = proto.as_ptr() as *mut JsObject; @@ -385,6 +391,8 @@ impl BuiltinWorld { regexp_proto, array_buffer_constructor, array_buffer_proto, + data_view_constructor, + data_view_proto, sym_match, sym_replace, sym_search, diff --git a/crates/oxide_vm/src/bindings/bind_data_view.rs b/crates/oxide_vm/src/bindings/bind_data_view.rs new file mode 100644 index 00000000..4c590ebb --- /dev/null +++ b/crates/oxide_vm/src/bindings/bind_data_view.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use crate::bind_constructor_hash; +use crate::bindings::{apply_binding_table, configure_native_constructor}; +use oxide_kernel::kernel::{KernelCore, KernelSession}; +use oxide_types::object::JsObject; + +pub fn bind_data_view(core: &Arc, session: &KernelSession, global: &mut JsObject) { + let ctor_ptr = session.builtin_world().data_view_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &mut *ctor_ptr }; + let proto_ptr = session.builtin_world().data_view_proto.as_ptr() as *mut JsObject; + let proto = unsafe { &mut *proto_ptr }; + + configure_native_constructor(ctor, crate::builtins::data_view::data_view_constructor as *const (), 3); + + apply_binding_table( + session.builtin_world(), + proto, + core, + &[ + ("getInt8", crate::builtins::data_view::data_view_get_int8 as *const (), 1), + ("getUint8", crate::builtins::data_view::data_view_get_uint8 as *const (), 1), + ("getInt16", crate::builtins::data_view::data_view_get_int16 as *const (), 2), + ("getUint16", crate::builtins::data_view::data_view_get_uint16 as *const (), 2), + ("getInt32", crate::builtins::data_view::data_view_get_int32 as *const (), 2), + ("getUint32", crate::builtins::data_view::data_view_get_uint32 as *const (), 2), + ("getFloat32", crate::builtins::data_view::data_view_get_float32 as *const (), 2), + ("getFloat64", crate::builtins::data_view::data_view_get_float64 as *const (), 2), + ("getBigInt64", crate::builtins::data_view::data_view_get_big_int64 as *const (), 2), + ("getBigUint64", crate::builtins::data_view::data_view_get_big_uint64 as *const (), 2), + ("setInt8", crate::builtins::data_view::data_view_set_int8 as *const (), 2), + ("setUint8", crate::builtins::data_view::data_view_set_uint8 as *const (), 2), + ("setInt16", crate::builtins::data_view::data_view_set_int16 as *const (), 3), + ("setUint16", crate::builtins::data_view::data_view_set_uint16 as *const (), 3), + ("setInt32", crate::builtins::data_view::data_view_set_int32 as *const (), 3), + ("setUint32", crate::builtins::data_view::data_view_set_uint32 as *const (), 3), + ("setFloat32", crate::builtins::data_view::data_view_set_float32 as *const (), 3), + ("setFloat64", crate::builtins::data_view::data_view_set_float64 as *const (), 3), + ("setBigInt64", crate::builtins::data_view::data_view_set_big_int64 as *const (), 3), + ("setBigUint64", crate::builtins::data_view::data_view_set_big_uint64 as *const (), 3), + ("toString", crate::builtins::data_view::data_view_to_string as *const (), 0), + ], + ); + + bind_constructor_hash!(core, global, "DataView", ctor_ptr, crate::builtins::data_view::data_view_constructor, 3); +} diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index a3dcd8be..ea659888 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -1,6 +1,7 @@ pub mod bind_array; pub mod bind_array_buffer; pub mod bind_boolean; +pub mod bind_data_view; pub mod bind_date; pub mod bind_error; pub mod bind_function; @@ -89,6 +90,7 @@ pub fn init_kernel_builtins(core: &Arc, session: &mut KernelSession) bind_object::bind_object(core, session, global); bind_array::bind_array(core, session, global); bind_array_buffer::bind_array_buffer(core, session, global); + bind_data_view::bind_data_view(core, session, global); bind_error::bind_error(core, session, global); bind_string::bind_string(core, session, global); bind_number::bind_number(core, session, global); diff --git a/crates/oxide_vm/src/builtins/data_view.rs b/crates/oxide_vm/src/builtins/data_view.rs new file mode 100644 index 00000000..40b8370d --- /dev/null +++ b/crates/oxide_vm/src/builtins/data_view.rs @@ -0,0 +1,377 @@ +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; +use oxide_types::value::JsValue; + +use crate::builtins::array_buffer::array_buffer_data_ptr; +use crate::coercion; +use crate::native::NativeResult; +use crate::vm::Vm; + +#[derive(Clone, Copy)] +struct DataViewData { + buffer: JsValue, + byte_offset: usize, + byte_length: usize, +} + +macro_rules! native_try { + ($expr:expr) => { + match $expr { + Ok(value) => value, + Err(err) => return NativeResult::Err(err), + } + }; +} + +fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result { + let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); + if n.is_nan() { + return Ok(0); + } + if !n.is_finite() || n < 0.0 { + return Err(crate::builtins::error::create_range_error(vm, msg)); + } + Ok(n.trunc() as usize) +} + +fn is_little_endian(vm: &mut Vm, args: &[u8], idx: usize) -> bool { + args.get(idx) + .map(|reg| coercion::to_boolean(vm.reg(*reg), vm.kernel_core().string_forge().as_ref())) + .unwrap_or(false) +} + +fn numeric_arg(vm: &mut Vm, args: &[u8], idx: usize) -> f64 { + args.get(idx) + .map(|reg| coercion::to_number(vm.reg(*reg), vm.kernel_core().string_forge().as_ref())) + .unwrap_or(0.0) +} + +fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { + let si = vm.kernel_core().string_forge().intern(name).0; + let _ = vm.define_data_property(obj, si, value, attributes); +} + +fn get_data_view_data(vm: &mut Vm, this_val: JsValue) -> Result { + if !this_val.is_object() { + return Err(crate::builtins::error::create_type_error( + vm, + "DataView method called on incompatible receiver", + )); + } + let obj_ptr = this_val.as_js_object_ptr(); + if obj_ptr.is_null() { + return Err(crate::builtins::error::create_type_error(vm, "DataView internal state invalid")); + } + let obj = unsafe { &*obj_ptr }; + if !obj.is_data_view_obj() { + return Err(crate::builtins::error::create_type_error( + vm, + "DataView method called on incompatible receiver", + )); + } + let Some(data_ptr) = obj.native_fn() else { + return Err(crate::builtins::error::create_type_error(vm, "DataView internal state invalid")); + }; + let data_ptr = data_ptr.as_ptr() as *const DataViewData; + if data_ptr.is_null() { + return Err(crate::builtins::error::create_type_error(vm, "DataView internal state invalid")); + } + Ok(unsafe { *data_ptr }) +} + +fn checked_absolute_offset(vm: &mut Vm, view: DataViewData, offset: usize, width: usize) -> Result { + let Some(end) = offset.checked_add(width) else { + return Err(crate::builtins::error::create_range_error(vm, "DataView offset out of bounds")); + }; + if end > view.byte_length { + return Err(crate::builtins::error::create_range_error(vm, "DataView offset out of bounds")); + } + Ok(view.byte_offset + offset) +} + +fn read_bytes(vm: &mut Vm, args: &[u8]) -> Result<[u8; N], JsValue> { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let view = get_data_view_data(vm, this_val)?; + let offset = if args.len() > 1 { + to_index(vm, vm.reg(args[1]), "DataView offset out of bounds")? + } else { + 0 + }; + let abs = checked_absolute_offset(vm, view, offset, N)?; + let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; + let buffer = unsafe { &*buffer_ptr }; + if abs + N > buffer.len() { + return Err(crate::builtins::error::create_range_error(vm, "DataView offset out of bounds")); + } + let mut out = [0u8; N]; + out.copy_from_slice(&buffer[abs..abs + N]); + Ok(out) +} + +fn write_bytes(vm: &mut Vm, args: &[u8], bytes: [u8; N]) -> Result<(), JsValue> { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let view = get_data_view_data(vm, this_val)?; + let offset = if args.len() > 1 { + to_index(vm, vm.reg(args[1]), "DataView offset out of bounds")? + } else { + 0 + }; + 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 }; + if abs + N > buffer.len() { + return Err(crate::builtins::error::create_range_error(vm, "DataView offset out of bounds")); + } + buffer[abs..abs + N].copy_from_slice(&bytes); + Ok(()) +} + +pub fn data_view_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 2 { + return NativeResult::Err(crate::builtins::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 byte_offset = if args.len() > 2 { + native_try!(to_index(vm, vm.reg(args[2]), "DataView byteOffset out of bounds")) + } else { + 0 + }; + if byte_offset > buffer_len { + return NativeResult::Err(crate::builtins::error::create_range_error(vm, "DataView byteOffset out of bounds")); + } + let default_len = buffer_len - byte_offset; + let byte_length = if args.len() > 3 { + native_try!(to_index(vm, vm.reg(args[3]), "DataView byteLength out of bounds")) + } else { + default_len + }; + if byte_offset + byte_length > buffer_len { + return NativeResult::Err(crate::builtins::error::create_range_error(vm, "DataView byteLength out of bounds")); + } + + let proto = vm.session().builtin_world().data_view_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_DATA_VIEW; + let data = Box::into_raw(Box::new(DataViewData { + buffer, + byte_offset, + byte_length, + })); + // SAFETY: DataView objects are not callable, so native_fn is an opaque Box + // pointer, matching ArrayBuffer and RegExp typed-storage patterns. + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(data as *const ()) })); + set_named_prop(vm, &mut obj, "buffer", buffer, PropAttributes::new(false, false, false)); + set_named_prop( + vm, + &mut obj, + "byteOffset", + JsValue::int(byte_offset as i32), + PropAttributes::new(false, false, false), + ); + set_named_prop( + vm, + &mut obj, + "byteLength", + JsValue::int(byte_length as i32), + PropAttributes::new(false, false, false), + ); + NativeResult::Ok(JsValue::from_js_object(vm.epoch().alloc(obj))) +} + +pub fn data_view_get_int8(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<1>(vm, args)); + NativeResult::Ok(JsValue::int(i8::from_ne_bytes(bytes) as i32)) +} + +pub fn data_view_get_uint8(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<1>(vm, args)); + NativeResult::Ok(JsValue::int(bytes[0] as i32)) +} + +pub fn data_view_get_int16(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<2>(vm, args)); + let n = if is_little_endian(vm, args, 2) { + i16::from_le_bytes(bytes) + } else { + i16::from_be_bytes(bytes) + }; + NativeResult::Ok(JsValue::int(n as i32)) +} + +pub fn data_view_get_uint16(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<2>(vm, args)); + let n = if is_little_endian(vm, args, 2) { + u16::from_le_bytes(bytes) + } else { + u16::from_be_bytes(bytes) + }; + NativeResult::Ok(JsValue::int(n as i32)) +} + +pub fn data_view_get_int32(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<4>(vm, args)); + let n = if is_little_endian(vm, args, 2) { + i32::from_le_bytes(bytes) + } else { + i32::from_be_bytes(bytes) + }; + NativeResult::Ok(JsValue::int(n)) +} + +pub fn data_view_get_uint32(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<4>(vm, args)); + let n = if is_little_endian(vm, args, 2) { + u32::from_le_bytes(bytes) + } else { + u32::from_be_bytes(bytes) + }; + NativeResult::Ok(JsValue::float(n as f64)) +} + +pub fn data_view_get_float32(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<4>(vm, args)); + let n = if is_little_endian(vm, args, 2) { + f32::from_le_bytes(bytes) + } else { + f32::from_be_bytes(bytes) + }; + NativeResult::Ok(JsValue::float(n as f64)) +} + +pub fn data_view_get_float64(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<8>(vm, args)); + let n = if is_little_endian(vm, args, 2) { + f64::from_le_bytes(bytes) + } else { + f64::from_be_bytes(bytes) + }; + NativeResult::Ok(JsValue::float(n)) +} + +pub fn data_view_get_big_int64(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<8>(vm, args)); + let n = if is_little_endian(vm, args, 2) { + i64::from_le_bytes(bytes) + } else { + i64::from_be_bytes(bytes) + }; + NativeResult::Ok(JsValue::float(n as f64)) +} + +pub fn data_view_get_big_uint64(vm: &mut Vm, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<8>(vm, args)); + let n = if is_little_endian(vm, args, 2) { + u64::from_le_bytes(bytes) + } else { + u64::from_be_bytes(bytes) + }; + NativeResult::Ok(JsValue::float(n as f64)) +} + +pub fn data_view_set_int8(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as i32 as u8 as i8; + native_try!(write_bytes(vm, args, value.to_ne_bytes())); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_uint8(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as i32 as u8; + native_try!(write_bytes(vm, args, [value])); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_int16(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as i32 as u16 as i16; + let bytes = if is_little_endian(vm, args, 3) { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + native_try!(write_bytes(vm, args, bytes)); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_uint16(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as i32 as u16; + let bytes = if is_little_endian(vm, args, 3) { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + native_try!(write_bytes(vm, args, bytes)); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_int32(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as i32; + let bytes = if is_little_endian(vm, args, 3) { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + native_try!(write_bytes(vm, args, bytes)); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_uint32(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as u32; + let bytes = if is_little_endian(vm, args, 3) { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + native_try!(write_bytes(vm, args, bytes)); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_float32(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as f32; + let bytes = if is_little_endian(vm, args, 3) { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + native_try!(write_bytes(vm, args, bytes)); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_float64(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2); + let bytes = if is_little_endian(vm, args, 3) { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + native_try!(write_bytes(vm, args, bytes)); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_big_int64(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as i64; + let bytes = if is_little_endian(vm, args, 3) { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + native_try!(write_bytes(vm, args, bytes)); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_set_big_uint64(vm: &mut Vm, args: &[u8]) -> NativeResult { + let value = numeric_arg(vm, args, 2) as u64; + let bytes = if is_little_endian(vm, args, 3) { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + native_try!(write_bytes(vm, args, bytes)); + NativeResult::Ok(JsValue::undefined()) +} + +pub fn data_view_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + native_try!(get_data_view_data(vm, this_val)); + NativeResult::Ok(vm.intern("[object DataView]")) +} diff --git a/crates/oxide_vm/src/builtins/mod.rs b/crates/oxide_vm/src/builtins/mod.rs index 287e0a6c..5f15a8f4 100644 --- a/crates/oxide_vm/src/builtins/mod.rs +++ b/crates/oxide_vm/src/builtins/mod.rs @@ -1,6 +1,7 @@ pub mod array; pub mod array_buffer; pub mod boolean; +pub mod data_view; pub mod date; pub mod error; pub mod function; diff --git a/crates/oxide_vm/tests/builtins_data_view_tests.rs b/crates/oxide_vm/tests/builtins_data_view_tests.rs new file mode 100644 index 00000000..0dc90999 --- /dev/null +++ b/crates/oxide_vm/tests/builtins_data_view_tests.rs @@ -0,0 +1,133 @@ +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(&module) +} + +fn to_str(vm: &Vm, val: JsValue) -> String { + vm.kernel_core() + .string_forge() + .lookup(val.as_string_index()) + .unwrap_or_default() +} + +#[test] +fn data_view_global_and_methods_exist() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "typeof DataView === 'function' && typeof DataView.prototype.getInt32 === 'function'", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_properties_reference_buffer_offset_and_length() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buf = new ArrayBuffer(16); var dv = new DataView(buf, 4, 8); \ + dv.buffer === buf && dv.byteOffset === 4 && dv.byteLength === 8", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_set_get_int32_big_endian() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buf = new ArrayBuffer(16); var dv = new DataView(buf); \ + dv.setInt32(0, 305419896, false); dv.getInt32(0, false)", + ) + .unwrap(); + assert_eq!(result.as_int(), 305419896); +} + +#[test] +fn data_view_set_get_int32_little_endian_exposes_first_byte() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buf = new ArrayBuffer(16); var dv = new DataView(buf); \ + dv.setInt32(0, 305419896, true); dv.getUint8(0)", + ) + .unwrap(); + assert_eq!(result.as_int(), 120); +} + +#[test] +fn data_view_set_get_uint16_and_int8() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buf = new ArrayBuffer(4); var dv = new DataView(buf); \ + var neg = -1; dv.setUint16(0, 65535, false); dv.setInt8(2, neg); \ + dv.getUint16(0, false) === 65535 && dv.getInt8(2) === -1", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_float64_roundtrip() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buf = new ArrayBuffer(8); var dv = new DataView(buf); \ + dv.setFloat64(0, 3.5, true); dv.getFloat64(0, true)", + ) + .unwrap(); + assert_eq!(result.as_double(), 3.5); +} + +#[test] +fn data_view_offset_view_writes_into_underlying_buffer() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var buf = new ArrayBuffer(16); var base = new DataView(buf); var dv = new DataView(buf, 4, 8); \ + dv.setUint8(0, 77); base.getUint8(4)", + ) + .unwrap(); + assert_eq!(result.as_int(), 77); +} + +#[test] +fn data_view_is_array_buffer_view() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var dv = new DataView(new ArrayBuffer(1)); ArrayBuffer.isView(dv)").unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_rejects_non_array_buffer() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "try { new DataView({}) } catch (e) { e instanceof TypeError }").unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_rejects_out_of_bounds_access() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "try { var dv = new DataView(new ArrayBuffer(4)); dv.getInt32(2); } catch (e) { e instanceof RangeError }", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_to_string_is_identifiable() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var dv = new DataView(new ArrayBuffer(1)); dv.toString()").unwrap(); + assert_eq!(to_str(&vm, result), "[object DataView]"); +}