From 9e16d7687cb9487a6c6f790abbe0a882015ba207 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 15 Aug 2026 20:04:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20BigInt64Array/BigUint64Array=20=E4=B8=8E?= =?UTF-8?q?=20DataView=20BigInt=20=E5=AD=98=E5=8F=96=E8=B5=B0=E7=9C=9F=20B?= =?UTF-8?q?igInt=20=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TA 元素读写按 kind 分流:BigInt 类型走 ToBigInt(Number 入参抛 TypeError、 写入 mod 2^64 截断、读出为真 BigInt 值),数值类型走 ToNumber 且显式拒绝 BigInt 值;消除 64 位整数经 f64 中转的静默精度丢失。 - runtime_api 新增公共 to_bigint_full / string_to_bigint_full,bigint 构造器 与 asIntN/asUintN 改调公共版消除双实现 - sort/toSorted 默认序对 BigInt 类型按 i64/u64 值比较;reverse/copyWithin/ filter/toReversed/with 消除无谓数值中转 - 构造器第一参数非对象统一走 ToIndex(bool/null/BigInt/字符串),Symbol 抛 TypeError - DataView get/setBigInt64/getBigUint64 返回 BigInt 值、入参按 ToBigInt 校验 - 测试:精度往返 / TypeError / mod 2^64 / 字节序 / 排序 / 回调值类型断言 --- crates/oxide_builtins/src/bigint.rs | 97 +----- crates/oxide_builtins/src/data_view.rs | 45 ++- crates/oxide_builtins/src/typed_array.rs | 308 ++++++++++++------ crates/oxide_runtime_api/src/lib.rs | 71 ++++ .../tests/builtins_data_view_tests.rs | 71 ++++ .../tests/builtins_typed_array_tests.rs | 144 +++++++- 6 files changed, 525 insertions(+), 211 deletions(-) diff --git a/crates/oxide_builtins/src/bigint.rs b/crates/oxide_builtins/src/bigint.rs index 1df5fe09..6d7be2ac 100644 --- a/crates/oxide_builtins/src/bigint.rs +++ b/crates/oxide_builtins/src/bigint.rs @@ -234,95 +234,14 @@ fn bigint_arg(vm: &mut H, args: &[u8]) -> JsValue { /// ToBigInt 抽象操作(§7.1.14):BigInt 原样;String → StringToBigInt; /// Boolean → 0/1;Number/Symbol/undefined/null → TypeError;对象先 ToPrimitive 再递归。 +/// +/// 语义主体在 `oxide_runtime_api::to_bigint_full`,此处只把 String 错误恢复为 +/// 对应 kind 的异常对象(用户回调抛出的原始值经 last_uncaught_value 透传)。 fn to_bigint(vm: &mut H, val: JsValue) -> Result { - if val.is_bigint() { - return Ok(val); + match oxide_runtime_api::to_bigint_full(val, vm) { + Ok(v) => Ok(v), + Err(e) => Err(vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_from_text(vm, &e))), } - if val.is_bool() { - return Ok(vm.new_bigint(BigInt::from(if val.as_bool() { 1 } else { 0 }))); - } - if val.is_int() { - // ToBigInt(Number) 抛 TypeError(与构造器不同:构造器先 ToPrimitive 再 - // NumberToBigInt;ToBigInt 直接拒绝 Number)。 - return Err(crate::error::create_type_error(vm, "Cannot convert a Number value to a BigInt")); - } - if val.is_double() { - return Err(crate::error::create_type_error(vm, "Cannot convert a Number value to a BigInt")); - } - if val.is_string() { - let s = oxide_runtime_api::to_string(val); - return match string_to_bigint(vm, &s) { - Ok(v) => Ok(v), - Err(e) => Err(e), - }; - } - if val.is_object() { - let prim = match to_primitive(val, ToPrimitiveHint::Default, vm) { - Ok(p) => p, - Err(_) => { - if let Some(exc) = vm.take_uncaught_value() { - return Err(exc); - } - return Err(crate::error::create_type_error(vm, "Cannot convert value to a BigInt")); - } - }; - return to_bigint(vm, prim); - } - // undefined / null / symbol。 - Err(crate::error::create_type_error(vm, "Cannot convert value to a BigInt")) -} - -/// StringToBigInt(§7.1.14 步骤):去首尾空白,可选 +/- 号与 0x/0o/0b 前缀; -/// 空串 → 0n;非法 → SyntaxError。 -fn string_to_bigint(vm: &mut H, s: &str) -> Result { - let trimmed = s.trim(); - if trimmed.is_empty() { - return Ok(vm.new_bigint(BigInt::from(0))); - } - let (neg, rest) = if let Some(r) = trimmed.strip_prefix('-') { - (true, r) - } else if let Some(r) = trimmed.strip_prefix('+') { - (false, r) - } else { - (false, trimmed) - }; - // StringIntegerLiteral:符号只允许出现在纯十进制前;0x/0o/0b 前缀前带 - // +/-(如 "-0x1")属于非法语法(test262 constructor-from-string-syntax-errors)。 - let (radix, digits) = if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) { - if neg { - return Err(crate::error::create_syntax_error( - vm, - "Cannot convert string to BigInt: invalid integer literal", - )); - } - (16u32, hex) - } else if let Some(oct) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) { - if neg { - return Err(crate::error::create_syntax_error( - vm, - "Cannot convert string to BigInt: invalid integer literal", - )); - } - (8u32, oct) - } else if let Some(bin) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) { - if neg { - return Err(crate::error::create_syntax_error( - vm, - "Cannot convert string to BigInt: invalid integer literal", - )); - } - (2u32, bin) - } else { - (10u32, rest) - }; - if digits.is_empty() { - return Err(crate::error::create_syntax_error( - vm, - "Cannot convert string to BigInt: invalid integer literal", - )); - } - let m = BigInt::parse_bytes(digits.as_bytes(), radix).ok_or_else(|| { - crate::error::create_syntax_error(vm, "Cannot convert string to BigInt: invalid integer literal") - })?; - Ok(vm.new_bigint(if neg { -m } else { m })) } diff --git a/crates/oxide_builtins/src/data_view.rs b/crates/oxide_builtins/src/data_view.rs index 1cd353c9..1c217c40 100644 --- a/crates/oxide_builtins/src/data_view.rs +++ b/crates/oxide_builtins/src/data_view.rs @@ -2,6 +2,9 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; +use num_bigint::BigInt; +use num_traits::ToPrimitive; + use crate::array_buffer::array_buffer_data_ptr; use oxide_runtime_api::{NativeResult, VmHost}; @@ -323,7 +326,8 @@ pub fn data_view_get_float64(vm: &mut H, args: &[u8]) -> NativeResult NativeResult::Ok(JsValue::float(n)) } -/// `DataView.prototype.getBigInt64(byteOffset, littleEndian)`:读取 8 字节有符号 64 位整数。 +/// `DataView.prototype.getBigInt64(byteOffset, littleEndian)`:读取 8 字节有符号 64 位 +/// 整数并返回 BigInt 值。 pub fn data_view_get_big_int64(vm: &mut H, args: &[u8]) -> NativeResult { let bytes = native_try!(read_bytes::<8, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { @@ -331,10 +335,11 @@ pub fn data_view_get_big_int64(vm: &mut H, args: &[u8]) -> NativeResu } else { i64::from_be_bytes(bytes) }; - NativeResult::Ok(JsValue::float(n as f64)) + NativeResult::Ok(vm.new_bigint(BigInt::from(n))) } -/// `DataView.prototype.getBigUint64(byteOffset, littleEndian)`:读取 8 字节无符号 64 位整数。 +/// `DataView.prototype.getBigUint64(byteOffset, littleEndian)`:读取 8 字节无符号 +/// 64 位整数并返回 BigInt 值。 pub fn data_view_get_big_uint64(vm: &mut H, args: &[u8]) -> NativeResult { let bytes = native_try!(read_bytes::<8, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { @@ -342,7 +347,7 @@ pub fn data_view_get_big_uint64(vm: &mut H, args: &[u8]) -> NativeRes } else { u64::from_be_bytes(bytes) }; - NativeResult::Ok(JsValue::float(n as f64)) + NativeResult::Ok(vm.new_bigint(BigInt::from(n))) } /// `DataView.prototype.setInt8(byteOffset, value)`:写入 1 字节有符号整数。 @@ -431,30 +436,46 @@ pub fn data_view_set_float64(vm: &mut H, args: &[u8]) -> NativeResult NativeResult::Ok(JsValue::undefined()) } -/// `DataView.prototype.setBigInt64(byteOffset, value, littleEndian)`:写入 8 字节有符号 64 位整数。 +/// `DataView.prototype.setBigInt64(byteOffset, value, littleEndian)`:按 ToBigInt +/// 语义接收值(Number 入参抛 TypeError),取低 64 位位模式写入 8 字节有符号整数。 pub fn data_view_set_big_int64(vm: &mut H, args: &[u8]) -> NativeResult { - let value = numeric_arg(vm, args, 2) as i64; + let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + let b = + native_try!(oxide_runtime_api::to_bigint_full(value, vm).map_err(|e| crate::iterator::engine_error(vm, &e))); + let low = low64(vm, b) as i64; let bytes = if is_little_endian(vm, args, 3) { - value.to_le_bytes() + low.to_le_bytes() } else { - value.to_be_bytes() + low.to_be_bytes() }; native_try!(write_bytes(vm, args, bytes)); NativeResult::Ok(JsValue::undefined()) } -/// `DataView.prototype.setBigUint64(byteOffset, value, littleEndian)`:写入 8 字节无符号 64 位整数。 +/// `DataView.prototype.setBigUint64(byteOffset, value, littleEndian)`:按 ToBigInt +/// 语义接收值(Number 入参抛 TypeError),取低 64 位位模式写入 8 字节无符号整数。 pub fn data_view_set_big_uint64(vm: &mut H, args: &[u8]) -> NativeResult { - let value = numeric_arg(vm, args, 2) as u64; + let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + let b = + native_try!(oxide_runtime_api::to_bigint_full(value, vm).map_err(|e| crate::iterator::engine_error(vm, &e))); + let low = low64(vm, b); let bytes = if is_little_endian(vm, args, 3) { - value.to_le_bytes() + low.to_le_bytes() } else { - value.to_be_bytes() + low.to_be_bytes() }; native_try!(write_bytes(vm, args, bytes)); NativeResult::Ok(JsValue::undefined()) } +/// 取 BigInt 值低 64 位位模式:与 2^64-1 掩码后恒非负且可转 u64。 +fn low64(vm: &mut H, val: JsValue) -> u64 { + let v = vm.bigint_value(val); + (v & (BigInt::from(u64::MAX))) + .to_u64() + .expect("与 u64::MAX 掩码后恒在 u64 范围") +} + /// `DataView.prototype.toString`:校验 receiver 后返回 `[object DataView]`。 pub fn data_view_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 13672cd9..afd5dbe3 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -1,3 +1,5 @@ +use num_bigint::BigInt; +use num_traits::ToPrimitive; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr, TypedArrayKind}; use oxide_types::private_key::{int_key_value, is_int_key}; @@ -204,7 +206,7 @@ pub fn typed_array_element_get(vm: &mut H, obj: &JsObject, index: u32 } let buffer_ptr = array_buffer_data_ptr(vm, view.buffer).map_err(|e| format!("{e}"))?; let buffer = unsafe { &*buffer_ptr }; - Ok(read_element(view.kind, buffer, absolute_byte_offset(view, index as usize))) + Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, index as usize))) } /// 若对象是 TypedArray 且属性键是整数索引,返回 `(索引, 视图长度)`;否则 `None`。 @@ -260,19 +262,29 @@ pub fn typed_array_element_define( fn write_typed_array_element( vm: &mut H, view: TypedArrayData, index: u32, value: JsValue, ) -> Result<(), String> { - // 先 ToNumber(valueOf 副作用先于越界判定触发),越界再静默忽略。 - let n = numeric_value(vm, value); + // 先按元素类型转换(valueOf 副作用先于越界判定触发),越界再静默忽略。 + let elem = match ta_element_value(vm, view.kind, value) { + Ok(v) => v, + Err(err) => { + // 转换失败恢复为可捕获的 JS 异常:主 dispatch 下就地展开到外围 catch, + // builtin 内部展开到调用方 try 处理器;uncaught 时以文本上抛。 + let text = element_error_text(vm, err); + return vm.raise_type_error(&text); + } + }; 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 }; - write_element(view.kind, buffer, absolute_byte_offset(view, index as usize), n); + write_element(vm, view.kind, buffer, absolute_byte_offset(view, index as usize), elem); Ok(()) } -fn read_element(kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue { +/// 读 TypedArray 元素并转为对应 JS 值:BigInt 类型读为 BigInt 值(i64/u64 位模式 +/// 原样搬运,无精度损失),数值类型按位模式读为 Number。 +fn read_element(vm: &mut H, kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue { match kind { TypedArrayKind::Int8 => JsValue::int(bytes[offset] as i8 as i32), TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => JsValue::int(bytes[offset] as i32), @@ -289,31 +301,95 @@ fn read_element(kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue { } TypedArrayKind::Float64 => JsValue::float(f64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap())), TypedArrayKind::BigInt64 => { - JsValue::float(i64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap()) as f64) + let n = i64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap()); + vm.new_bigint(BigInt::from(n)) } TypedArrayKind::BigUint64 => { - JsValue::float(u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap()) as f64) + let n = u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap()); + vm.new_bigint(BigInt::from(n)) } } } -fn numeric_value(vm: &mut H, value: JsValue) -> f64 { - vm.coerce_number_bounded(value).unwrap_or(f64::NAN) +/// 元素写入统一转换入口:BigInt 类型走 ToBigInt,数值类型走 ToNumber。 +/// +/// 数值类型显式拒绝 BigInt 值(规范 ToNumber(BigInt) 抛 TypeError),避免 +/// 经 f64 近似的静默精度丢失。 +fn ta_element_value(vm: &mut H, kind: TypedArrayKind, value: JsValue) -> Result { + if is_bigint_kind(kind) { + return oxide_runtime_api::to_bigint_full(value, vm).map_err(|e| crate::iterator::engine_error(vm, &e)); + } + let prim = oxide_runtime_api::to_primitive(value, oxide_runtime_api::ToPrimitiveHint::Number, vm) + .map_err(|e| crate::iterator::engine_error(vm, &e))?; + if prim.is_bigint() { + return Err(type_error(vm, "Cannot convert a BigInt value to a number")); + } + let n = oxide_runtime_api::to_number_full(prim, vm).map_err(|e| crate::iterator::engine_error(vm, &e))?; + Ok(JsValue::float(n)) } -fn write_element(kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: f64) { +/// 元素类型是否为 BigInt 语义(BigInt64/BigUint64)。 +fn is_bigint_kind(kind: TypedArrayKind) -> bool { + matches!(kind, TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64) +} + +/// 把异常 JsValue 格式化为 `Kind: message` 文本(属性写路径的 String 错误契约用)。 +fn element_error_text(vm: &mut H, err: JsValue) -> String { + if let Some(s) = vm.lookup_str(err) { + return s; + } + if err.is_object() { + let obj = unsafe { &*err.as_js_object_ptr() }; + let name_si = vm.kernel_core().perm_interner().intern("name").0; + let message_si = vm.kernel_core().perm_interner().intern("message").0; + let name = vm + .resolve_property(obj, name_si) + .and_then(|v| vm.lookup_str(v)) + .unwrap_or_else(|| "Error".to_string()); + let message = vm + .resolve_property(obj, message_si) + .and_then(|v| vm.lookup_str(v)) + .unwrap_or_default(); + if name.is_empty() { + message + } else if message.is_empty() { + name + } else { + format!("{name}: {message}") + } + } else { + format!("{err}") + } +} + +/// 把已按元素类型转换的值(数值 kind 为 Number、BigInt kind 为 BigInt)按位模式 +/// 截断写入底层 buffer。转换由调用方 [`ta_element_value`] 完成,本函数无副作用。 +fn write_element(vm: &mut H, kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: JsValue) { + let n = oxide_runtime_api::to_number(value); match kind { - TypedArrayKind::Int8 => bytes[offset] = value as i32 as u8 as i8 as u8, - TypedArrayKind::Uint8 => bytes[offset] = value as i32 as u8, - TypedArrayKind::Uint8Clamped => bytes[offset] = value.clamp(0.0, 255.0).round() as u8, - TypedArrayKind::Int16 => bytes[offset..offset + 2].copy_from_slice(&(value as i32 as u16 as i16).to_ne_bytes()), - TypedArrayKind::Uint16 => bytes[offset..offset + 2].copy_from_slice(&(value as i32 as u16).to_ne_bytes()), - TypedArrayKind::Int32 => bytes[offset..offset + 4].copy_from_slice(&(value as i32).to_ne_bytes()), - TypedArrayKind::Uint32 => bytes[offset..offset + 4].copy_from_slice(&(value as u32).to_ne_bytes()), - TypedArrayKind::Float32 => bytes[offset..offset + 4].copy_from_slice(&(value as f32).to_ne_bytes()), - TypedArrayKind::Float64 => bytes[offset..offset + 8].copy_from_slice(&value.to_ne_bytes()), - TypedArrayKind::BigInt64 => bytes[offset..offset + 8].copy_from_slice(&(value as i64).to_ne_bytes()), - TypedArrayKind::BigUint64 => bytes[offset..offset + 8].copy_from_slice(&(value as u64).to_ne_bytes()), + TypedArrayKind::Int8 => bytes[offset] = n as i32 as u8 as i8 as u8, + TypedArrayKind::Uint8 => bytes[offset] = n as i32 as u8, + TypedArrayKind::Uint8Clamped => bytes[offset] = n.clamp(0.0, 255.0).round() as u8, + TypedArrayKind::Int16 => bytes[offset..offset + 2].copy_from_slice(&(n as i32 as u16 as i16).to_ne_bytes()), + TypedArrayKind::Uint16 => bytes[offset..offset + 2].copy_from_slice(&(n as i32 as u16).to_ne_bytes()), + TypedArrayKind::Int32 => bytes[offset..offset + 4].copy_from_slice(&(n as i32).to_ne_bytes()), + TypedArrayKind::Uint32 => bytes[offset..offset + 4].copy_from_slice(&(n as u32).to_ne_bytes()), + TypedArrayKind::Float32 => bytes[offset..offset + 4].copy_from_slice(&(n as f32).to_ne_bytes()), + TypedArrayKind::Float64 => bytes[offset..offset + 8].copy_from_slice(&n.to_ne_bytes()), + TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => { + // 取低 64 位位模式:与 2^64-1 掩码后恒非负且可转 u64,BigInt64 按位 + // 模式重解释为 i64(二进制补码)。 + let v = vm.bigint_value(value); + let low = (v & (BigInt::from(u64::MAX))) + .to_u64() + .expect("与 u64::MAX 掩码后恒在 u64 范围"); + let bytes64 = if matches!(kind, TypedArrayKind::BigInt64) { + (low as i64).to_ne_bytes() + } else { + low.to_ne_bytes() + }; + bytes[offset..offset + 8].copy_from_slice(&bytes64); + } } } @@ -335,7 +411,7 @@ fn collect_array_like(vm: &mut H, value: JsValue) -> Result(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> let first = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::int(0) }; let bpe = kind.bytes_per_element(); - let (buffer, byte_offset, length) = if first.is_int() || first.is_double() || first.is_undefined() { - let len = native_try!(to_index(vm, first, "invalid TypedArray length")); - let Some(byte_len) = len.checked_mul(bpe) else { - return NativeResult::Err(range_error(vm, "invalid TypedArray length")); - }; - if byte_len > MAX_ARRAY_BUFFER_LENGTH { - return NativeResult::Err(range_error(vm, "invalid TypedArray length")); - } - let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len])); - (buffer, 0, len) - } else if first.is_object() { + let (buffer, byte_offset, length) = if first.is_object() { let first_ptr = first.as_js_object_ptr(); let first_obj = unsafe { &*first_ptr }; if first_obj.is_array_buffer_obj() { @@ -424,13 +490,26 @@ fn typed_array_new(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> let buffer_ptr = native_try!(array_buffer_data_ptr(vm, buffer)); let buffer_ref = unsafe { &mut *buffer_ptr }; for (idx, value) in values.into_iter().enumerate() { - let n = numeric_value(vm, value); - write_element(kind, buffer_ref, idx * bpe, n); + let elem = native_try!(ta_element_value(vm, kind, value)); + write_element(vm, kind, buffer_ref, idx * bpe, elem); } (buffer, 0, byte_len / bpe) } } else { - return NativeResult::Err(type_error(vm, "invalid TypedArray constructor argument")); + // 非对象第一参数统一按 ToIndex 语义处理(bool→0/1、null→0、BigInt→数值、 + // 字符串→解析、undefined→0);Symbol 按规范抛 TypeError。 + if first.is_symbol() { + return NativeResult::Err(type_error(vm, "invalid TypedArray length")); + } + let len = native_try!(to_index(vm, first, "invalid TypedArray length")); + let Some(byte_len) = len.checked_mul(bpe) else { + return NativeResult::Err(range_error(vm, "invalid TypedArray length")); + }; + if byte_len > MAX_ARRAY_BUFFER_LENGTH { + return NativeResult::Err(range_error(vm, "invalid TypedArray length")); + } + let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len])); + (buffer, 0, len) }; NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, kind, buffer, byte_offset, length))) @@ -473,7 +552,7 @@ pub fn typed_array_at(vm: &mut H, args: &[u8]) -> NativeResult { } let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); let buffer = unsafe { &*buffer_ptr }; - NativeResult::Ok(read_element(view.kind, buffer, absolute_byte_offset(view, idx as usize))) + NativeResult::Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, idx as usize))) } /// `TypedArray.prototype.fill(value, start, end)`:用给定值填充区间,返回 this。 @@ -481,7 +560,8 @@ pub fn typed_array_fill(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let n = native_try!(ta_to_number(vm, value)); + // 值只转换一次(valueOf 副作用一次),转换结果写入每个目标元素。 + let elem = native_try!(ta_element_value(vm, view.kind, value)); let start = if args.len() > 2 { native_try!(normalize_index(vm, vm.reg(args[2]), view.length)) } else { @@ -495,7 +575,7 @@ pub fn typed_array_fill(vm: &mut H, args: &[u8]) -> NativeResult { let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); let buffer = unsafe { &mut *buffer_ptr }; for idx in start..end.max(start) { - write_element(view.kind, buffer, absolute_byte_offset(view, idx), n); + write_element(vm, view.kind, buffer, absolute_byte_offset(view, idx), elem); } NativeResult::Ok(this_val) } @@ -569,11 +649,14 @@ pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { if offset > view.length || values.len() > view.length - offset { return NativeResult::Err(range_error(vm, "TypedArray.set offset out of bounds")); } - let numbers: Vec = values.into_iter().map(|v| numeric_value(vm, v)).collect(); + let mut converted = Vec::with_capacity(values.len()); + 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 }; - for (i, n) in numbers.into_iter().enumerate() { - write_element(view.kind, buffer, absolute_byte_offset(view, offset + i), n); + for (i, elem) in converted.into_iter().enumerate() { + write_element(vm, view.kind, buffer, absolute_byte_offset(view, offset + i), elem); } NativeResult::Ok(JsValue::undefined()) } @@ -648,12 +731,18 @@ fn allocate_typed_array(vm: &mut H, c: JsValue, len: usize) -> Result Ok(result) } -/// 按 IntegerIndexedElementSet 语义把元素写入 TypedArray:先 ToNumber(symbol 等 -/// 不可转换值抛 TypeError),越界索引静默忽略。 +/// 按 IntegerIndexedElementSet 语义把元素写入 TypedArray:先按元素类型转换 +/// (BigInt 类型走 ToBigInt、数值类型走 ToNumber,symbol 等不可转换值抛 TypeError)。 fn set_typed_array_element(vm: &mut H, ta: JsValue, index: usize, value: JsValue) -> Result<(), JsValue> { - let n = oxide_runtime_api::to_number_full(value, vm).map_err(|e| crate::iterator::engine_error(vm, &e))?; - let obj = unsafe { &*ta.as_js_object_ptr() }; - typed_array_element_set(vm, obj, index as u32, JsValue::float(n)).map_err(|e| crate::iterator::engine_error(vm, &e)) + let view = get_typed_array_data(vm, ta)?; + if index >= view.length { + 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 }; + write_element(vm, view.kind, buffer, absolute_byte_offset(view, index), elem); + Ok(()) } /// `%TypedArray%.of(...items)`:以实参为元素构造一个以 `this`(构造器 C)为类型的 @@ -682,28 +771,29 @@ fn ta_read(vm: &mut H, view: TypedArrayData, index: usize) -> Result< } let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; let buffer = unsafe { &*buffer_ptr }; - Ok(read_element(view.kind, buffer, absolute_byte_offset(view, index))) + 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: f64) -> Result<(), JsValue> { +/// 把已转换的值写入 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 }; - write_element(view.kind, buffer, absolute_byte_offset(view, index), value); + write_element(vm, view.kind, buffer, absolute_byte_offset(view, index), value); Ok(()) } -/// 把一组数值写成同类型的新 TypedArray(map/filter/toReversed/toSorted/with 共用)。 -fn create_ta_from_numbers( - vm: &mut H, kind: TypedArrayKind, numbers: Vec, +/// 把一组已按元素类型转换的值写成同类型的新 TypedArray +/// (map/filter/toReversed/toSorted/with 共用)。 +fn create_ta_from_values( + vm: &mut H, kind: TypedArrayKind, values: Vec, ) -> Result<*mut JsObject, JsValue> { let bpe = kind.bytes_per_element(); - let len = numbers.len(); + let len = values.len(); let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; len * bpe])); let buffer_ptr = array_buffer_data_ptr(vm, buffer)?; let buffer_ref = unsafe { &mut *buffer_ptr }; - for (idx, n) in numbers.into_iter().enumerate() { - write_element(kind, buffer_ref, idx * bpe, n); + for (idx, v) in values.into_iter().enumerate() { + write_element(vm, kind, buffer_ref, idx * bpe, v); } Ok(create_typed_array(vm, kind, buffer, 0, len)) } @@ -717,7 +807,8 @@ fn invoke_cb(vm: &mut H, cb: JsValue, this_arg: JsValue, cb_args: &[J } } -/// ToNumber 并恢复引擎错误为原始异常值。 +/// ToNumber 并恢复引擎错误为原始异常值(索引类参数转换用;元素转换走 +/// [`ta_element_value`] 按类型分流)。 fn ta_to_number(vm: &mut H, value: JsValue) -> Result { oxide_runtime_api::to_number_full(value, vm).map_err(|e| crate::iterator::engine_error(vm, &e)) } @@ -739,7 +830,7 @@ pub fn typed_array_for_each(vm: &mut H, args: &[u8]) -> NativeResult } /// `%TypedArray%.prototype.map(callback, thisArg)`:对每个元素调用 callback, -/// 结果 ToNumber 后写入同类型的新 TypedArray。 +/// 结果按元素类型转换后写入同类型的新 TypedArray。 pub fn typed_array_map(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); @@ -748,13 +839,13 @@ pub fn typed_array_map(vm: &mut H, args: &[u8]) -> NativeResult { } let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1]))); let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - let mut numbers = Vec::with_capacity(view.length); + let mut values = Vec::with_capacity(view.length); for i in 0..view.length { let elem = native_try!(ta_read(vm, view, i)); let mapped = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val])); - numbers.push(native_try!(ta_to_number(vm, mapped))); + values.push(native_try!(ta_element_value(vm, view.kind, mapped))); } - let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, numbers)); + let new_obj = native_try!(create_ta_from_values(vm, view.kind, values)); NativeResult::Ok(JsValue::from_js_object(new_obj)) } @@ -768,15 +859,15 @@ pub fn typed_array_filter(vm: &mut H, args: &[u8]) -> NativeResult { } let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1]))); let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - let mut numbers = Vec::new(); + let mut values = Vec::new(); for i in 0..view.length { let elem = native_try!(ta_read(vm, view, i)); let result = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val])); if oxide_runtime_api::to_boolean(result) { - numbers.push(native_try!(ta_to_number(vm, elem))); + values.push(elem); } } - let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, numbers)); + let new_obj = native_try!(create_ta_from_values(vm, view.kind, values)); NativeResult::Ok(JsValue::from_js_object(new_obj)) } @@ -1122,7 +1213,9 @@ fn default_ta_order(a: f64, b: f64) -> std::cmp::Ordering { } } -/// `%TypedArray%.prototype.sort(comparefn)`:原地排序,默认按数值升序(NaN 排末尾)。 +/// `%TypedArray%.prototype.sort(comparefn)`:原地排序,默认按数值升序(NaN 排末尾; +/// BigInt 类型按 BigInt 值升序)。比较器回调收到的是元素原值(BigInt 类型为 +/// BigInt,数值类型为 Number)。 pub fn typed_array_sort(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); @@ -1136,10 +1229,9 @@ pub fn typed_array_sort(vm: &mut H, args: &[u8]) -> NativeResult { } else { None }; - let mut vals: Vec = Vec::with_capacity(view.length); + let mut vals: Vec = Vec::with_capacity(view.length); for i in 0..view.length { - let elem = native_try!(ta_read(vm, view, i)); - vals.push(native_try!(ta_to_number(vm, elem))); + vals.push(native_try!(ta_read(vm, view, i))); } let mut sort_error = None; vals.sort_by(|a, b| { @@ -1147,12 +1239,7 @@ pub fn typed_array_sort(vm: &mut H, args: &[u8]) -> NativeResult { return std::cmp::Ordering::Equal; } if let Some(cb) = comparator { - match crate::array::invoke_native_callback( - vm, - cb, - JsValue::undefined(), - &[JsValue::float(*a), JsValue::float(*b)], - ) { + match crate::array::invoke_native_callback(vm, cb, JsValue::undefined(), &[*a, *b]) { NativeResult::Ok(r) => { let n = oxide_runtime_api::to_number(r); if n.is_nan() || n == 0.0 { @@ -1173,15 +1260,25 @@ pub fn typed_array_sort(vm: &mut H, args: &[u8]) -> NativeResult { std::cmp::Ordering::Equal } } + } else if view.kind == TypedArrayKind::BigInt64 { + // BigInt64 元素恒在 i64 范围(写入按 mod 2^64 截断为有符号位模式),按有符号序比较。 + let a_int = vm.bigint_value(*a).to_i64().unwrap_or(0); + let b_int = vm.bigint_value(*b).to_i64().unwrap_or(0); + a_int.cmp(&b_int) + } else if is_bigint_kind(view.kind) { + // BigUint64 元素恒在 u64 范围,按数值序比较。 + let a_uint = vm.bigint_value(*a).to_u64().unwrap_or(0); + let b_uint = vm.bigint_value(*b).to_u64().unwrap_or(0); + a_uint.cmp(&b_uint) } else { - default_ta_order(*a, *b) + default_ta_order(oxide_runtime_api::to_number(*a), oxide_runtime_api::to_number(*b)) } }); if let Some(err) = sort_error { return NativeResult::Err(err); } - for (i, n) in vals.into_iter().enumerate() { - native_try!(ta_write(vm, view, i, n)); + for (i, v) in vals.into_iter().enumerate() { + native_try!(ta_write(vm, view, i, v)); } NativeResult::Ok(this_val) } @@ -1194,11 +1291,9 @@ pub fn typed_array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { let mut j = view.length.saturating_sub(1); while i < j { let tmp = native_try!(ta_read(vm, view, i)); - let n = native_try!(ta_to_number(vm, tmp)); let jtmp = native_try!(ta_read(vm, view, j)); - let jv = native_try!(ta_to_number(vm, jtmp)); - native_try!(ta_write(vm, view, j, n)); - native_try!(ta_write(vm, view, i, jv)); + native_try!(ta_write(vm, view, j, tmp)); + native_try!(ta_write(vm, view, i, jtmp)); i += 1; j = j.saturating_sub(1); } @@ -1234,8 +1329,7 @@ pub fn typed_array_copy_within(vm: &mut H, args: &[u8]) -> NativeResu }; for _ in 0..count { let elem = native_try!(ta_read(vm, view, from)); - let v = native_try!(ta_to_number(vm, elem)); - native_try!(ta_write(vm, view, to, v)); + native_try!(ta_write(vm, view, to, elem)); from = (from as isize + direction) as usize; to = (to as isize + direction) as usize; } @@ -1284,12 +1378,11 @@ pub fn typed_array_to_locale_string(vm: &mut H, args: &[u8]) -> Nativ pub fn typed_array_to_reversed(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); - let mut numbers = Vec::with_capacity(view.length); + let mut values = Vec::with_capacity(view.length); for i in (0..view.length).rev() { - let elem = native_try!(ta_read(vm, view, i)); - numbers.push(native_try!(ta_to_number(vm, elem))); + values.push(native_try!(ta_read(vm, view, i))); } - let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, numbers)); + let new_obj = native_try!(create_ta_from_values(vm, view.kind, values)); NativeResult::Ok(JsValue::from_js_object(new_obj)) } @@ -1308,10 +1401,9 @@ pub fn typed_array_to_sorted(vm: &mut H, args: &[u8]) -> NativeResult } else { None }; - let mut vals: Vec = Vec::with_capacity(view.length); + let mut vals: Vec = Vec::with_capacity(view.length); for i in 0..view.length { - let elem = native_try!(ta_read(vm, view, i)); - vals.push(native_try!(ta_to_number(vm, elem))); + vals.push(native_try!(ta_read(vm, view, i))); } let mut sort_error = None; vals.sort_by(|a, b| { @@ -1319,12 +1411,7 @@ pub fn typed_array_to_sorted(vm: &mut H, args: &[u8]) -> NativeResult return std::cmp::Ordering::Equal; } if let Some(cb) = comparator { - match crate::array::invoke_native_callback( - vm, - cb, - JsValue::undefined(), - &[JsValue::float(*a), JsValue::float(*b)], - ) { + match crate::array::invoke_native_callback(vm, cb, JsValue::undefined(), &[*a, *b]) { NativeResult::Ok(r) => { let n = oxide_runtime_api::to_number(r); if n.is_nan() || n == 0.0 { @@ -1345,14 +1432,22 @@ pub fn typed_array_to_sorted(vm: &mut H, args: &[u8]) -> NativeResult std::cmp::Ordering::Equal } } + } else if view.kind == TypedArrayKind::BigInt64 { + let a_int = vm.bigint_value(*a).to_i64().unwrap_or(0); + let b_int = vm.bigint_value(*b).to_i64().unwrap_or(0); + a_int.cmp(&b_int) + } else if is_bigint_kind(view.kind) { + let a_uint = vm.bigint_value(*a).to_u64().unwrap_or(0); + let b_uint = vm.bigint_value(*b).to_u64().unwrap_or(0); + a_uint.cmp(&b_uint) } else { - default_ta_order(*a, *b) + default_ta_order(oxide_runtime_api::to_number(*a), oxide_runtime_api::to_number(*b)) } }); if let Some(err) = sort_error { return NativeResult::Err(err); } - let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, vals)); + let new_obj = native_try!(create_ta_from_values(vm, view.kind, vals)); NativeResult::Ok(JsValue::from_js_object(new_obj)) } @@ -1378,29 +1473,28 @@ pub fn typed_array_with(vm: &mut H, args: &[u8]) -> NativeResult { } else { view.length as f64 + relative_index }; - // 先 ToNumber(value)(可触发副作用/抛错),再做索引范围校验。 + // 先按元素类型转换 value(可触发副作用/抛错),再做索引范围校验。 let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - let replacement = native_try!(ta_to_number(vm, value)); + let replacement = native_try!(ta_element_value(vm, view.kind, value)); if actual_index.is_nan() || actual_index < 0.0 || actual_index >= view.length as f64 { return NativeResult::Err(range_error(vm, "Invalid typed array index")); } let index = actual_index as usize; - let mut numbers = Vec::with_capacity(view.length); + let mut values = Vec::with_capacity(view.length); for i in 0..view.length { if i == index { - numbers.push(replacement); + values.push(replacement); continue; } - let elem = native_try!(ta_read(vm, view, i)); - numbers.push(native_try!(ta_to_number(vm, elem))); + values.push(native_try!(ta_read(vm, view, i))); } - let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, numbers)); + let new_obj = native_try!(create_ta_from_values(vm, view.kind, values)); NativeResult::Ok(JsValue::from_js_object(new_obj)) } /// `%TypedArray%.from(source, mapfn?, thisArg?)`:从可迭代对象或 array-like 构造 /// 以 `this`(构造器 C)为类型的 TypedArray;`mapfn` 逐元素映射(`thisArg` 作回调 -/// this),元素经 ToNumber 写入。 +/// this),元素按结果类型转换(BigInt 类型走 ToBigInt、数值类型走 ToNumber)写入。 /// /// # 步骤 /// 1. `source` 为 null/undefined 抛 TypeError;`mapfn` 非 undefined 时须可调用。 diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index 0608b194..69391d8c 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -890,6 +890,77 @@ pub fn to_string_full(val: JsValue, host: &mut H) -> Result(val: JsValue, host: &mut H) -> Result { + let primitive = to_primitive(val, ToPrimitiveHint::Default, host)?; + if primitive.is_bigint() { + return Ok(primitive); + } + if primitive.is_bool() { + let v = if primitive.as_bool() { 1 } else { 0 }; + return Ok(host.new_bigint(num_bigint::BigInt::from(v))); + } + if primitive.is_int() || primitive.is_double() { + return Err(host.error_message_text("TypeError", "Cannot convert a Number value to a BigInt")); + } + if primitive.is_string() { + return string_to_bigint_full(host, &to_string(primitive)); + } + // undefined / null / symbol。 + Err(host.error_message_text("TypeError", "Cannot convert value to a BigInt")) +} + +/// StringToBigInt(§7.1.14 步骤):去首尾空白,可选 +/- 号与 0x/0o/0b 前缀; +/// 空串 → 0n;非法 → SyntaxError。 +fn string_to_bigint_full(host: &mut H, s: &str) -> Result { + let trimmed = s.trim(); + if trimmed.is_empty() { + return Ok(host.new_bigint(num_bigint::BigInt::from(0))); + } + let (neg, rest) = if let Some(r) = trimmed.strip_prefix('-') { + (true, r) + } else if let Some(r) = trimmed.strip_prefix('+') { + (false, r) + } else { + (false, trimmed) + }; + // StringIntegerLiteral:符号只允许出现在纯十进制前;0x/0o/0b 前缀前带 + // +/-(如 "-0x1")属于非法语法。 + let (radix, digits) = if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) { + if neg { + return Err( + host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal") + ); + } + (16u32, hex) + } else if let Some(oct) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) { + if neg { + return Err( + host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal") + ); + } + (8u32, oct) + } else if let Some(bin) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) { + if neg { + return Err( + host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal") + ); + } + (2u32, bin) + } else { + (10u32, rest) + }; + if digits.is_empty() { + return Err(host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal")); + } + let m = num_bigint::BigInt::parse_bytes(digits.as_bytes(), radix).ok_or_else(|| { + host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal") + })?; + Ok(host.new_bigint(if neg { -m } else { m })) +} + /// `String()` 构造器的字符串转换(§21.1.1.1):Symbol 值(及本引擎以空对象表示的 /// well-known symbol)返回描述串 `Symbol(desc)`;其余走完整 ToString。 pub fn to_string_for_string_constructor(val: JsValue, host: &mut H) -> Result { diff --git a/crates/oxide_vm/tests/builtins_data_view_tests.rs b/crates/oxide_vm/tests/builtins_data_view_tests.rs index ebe923f6..2b97bdf1 100644 --- a/crates/oxide_vm/tests/builtins_data_view_tests.rs +++ b/crates/oxide_vm/tests/builtins_data_view_tests.rs @@ -128,3 +128,74 @@ fn data_view_to_string_is_identifiable() { let result = eval(&mut vm, "var dv = new DataView(new ArrayBuffer(1)); dv.toString()").unwrap(); assert_eq!(to_str(&vm, result), "[object DataView]"); } + +#[test] +fn data_view_get_big_int64_returns_bigint() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "new DataView(new ArrayBuffer(8)).getBigInt64(0)").unwrap(); + assert!(result.is_bigint()); +} + +#[test] +fn data_view_set_big_int64_with_number_throws() { + let mut vm = Vm::new(); + // ToBigInt 语义:Number 入参抛 TypeError。 + let result = eval( + &mut vm, + "try { new DataView(new ArrayBuffer(8)).setBigInt64(0, 1); false } catch (e) { e instanceof TypeError }", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_big_int64_precision_roundtrip() { + let mut vm = Vm::new(); + // 2^63-1(i64::MAX)超出 f64 精确范围,真 BigInt 语义读回必须原值。 + let result = eval( + &mut vm, + "var dv = new DataView(new ArrayBuffer(8)); \ + dv.setBigInt64(0, 9223372036854775807n); dv.getBigInt64(0) === 9223372036854775807n", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_big_uint64_precision_roundtrip() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var dv = new DataView(new ArrayBuffer(8)); \ + dv.setBigUint64(0, 18446744073709551615n, true); dv.getBigUint64(0, true) === 18446744073709551615n", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_big_int64_little_endian_byte_order() { + let mut vm = Vm::new(); + // 小端写入后首字节为低字节;同一字节序读回原值。 + let result = eval( + &mut vm, + "var dv = new DataView(new ArrayBuffer(8)); \ + dv.setBigInt64(0, 0x0102030405060708n, true); \ + dv.getUint8(0) === 8 && dv.getBigInt64(0, true) === 0x0102030405060708n", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn data_view_set_big_int64_mod_2_64_truncation() { + let mut vm = Vm::new(); + // 超 64 位值按 mod 2^64 截断写入。 + let result = eval( + &mut vm, + "var dv = new DataView(new ArrayBuffer(8)); \ + dv.setBigInt64(0, 1180591620717411303429n); dv.getBigInt64(0) === 5n", + ) + .unwrap(); + assert!(result.as_bool()); +} diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index 7995dfd1..43042f92 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -122,10 +122,27 @@ fn typed_array_float64_roundtrips() { } #[test] -fn typed_array_bigint_fallback_returns_float() { +fn typed_array_bigint_roundtrips_bigint_values() { let mut vm = Vm::new(); - let result = eval(&mut vm, "var ta = new BigInt64Array(1); ta.fill(42); ta.at(0)").unwrap(); - assert_eq!(result.as_double(), 42.0); + // BigInt64Array 元素读写为真 BigInt 值(非 f64 近似)。 + let result = eval(&mut vm, "var ta = new BigInt64Array(1); ta.fill(42n); ta.at(0)").unwrap(); + assert!(result.is_bigint()); + assert_eq!(vm.bigint_value(result).to_string(), "42"); +} + +#[test] +fn typed_array_bigint_fill_with_number_throws() { + let mut vm = Vm::new(); + // 数值 TA 写 BigInt 值按规范抛 TypeError;BigInt TA 写 Number 同样抛 TypeError。 + let result = eval( + &mut vm, + "var ok = true; \ + try { new BigInt64Array(1).fill(42); ok = false; } catch (e) { ok = e instanceof TypeError; } \ + try { new Int8Array(1)[0] = 1n; ok = ok && false; } catch (e) { ok = ok && e instanceof TypeError; } \ + ok", + ) + .unwrap(); + assert!(result.as_bool()); } #[test] @@ -190,3 +207,124 @@ fn typed_array_reflect_set_writes_to_receiver() { .unwrap(); assert!(result.as_bool()); } + +#[test] +fn typed_array_bigint64_precision_roundtrip() { + let mut vm = Vm::new(); + // 2^63-1(i64::MAX)超出 f64 精确范围:f64 会舍入为 2^63,真 BigInt 存储读回必须原值。 + let result = eval( + &mut vm, + "var a = new BigInt64Array(1); a[0] = 9223372036854775807n; a[0] === 9223372036854775807n", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_bigint64_write_overflow_wraps() { + let mut vm = Vm::new(); + // 2^63 写入 i64 槽按二进制补码回读为 -2^63。 + let result = eval( + &mut vm, + "var a = new BigInt64Array(1); a[0] = 9223372036854775808n; a[0] === -9223372036854775808n", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_biguint64_precision_roundtrip() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var a = new BigUint64Array(1); a[0] = 18446744073709551615n; a[0] === 18446744073709551615n && a[0] > Number.MAX_SAFE_INTEGER", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_bigint_negative_roundtrip() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var a = new BigInt64Array(1); a[0] = -1n; a[0] === -1n").unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_bigint_mod_2_64_truncation() { + let mut vm = Vm::new(); + // 写入按 mod 2^64 截断:2^70 + 5 的低 64 位为 5。 + let result = eval(&mut vm, "var a = new BigInt64Array(1); a[0] = 1180591620717411303429n; a[0] === 5n").unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_bigint_write_number_throws() { + let mut vm = Vm::new(); + let result = + eval(&mut vm, "try { new BigInt64Array(1)[0] = 1; false } catch (e) { e instanceof TypeError }").unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_numeric_write_bigint_throws() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "try { new Int8Array(1)[0] = 1n; false } catch (e) { e instanceof TypeError }").unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_constructor_bigint_length() { + let mut vm = Vm::new(); + // G6:第一参数 BigInt 按 ToIndex 语义建数组。 + let result = eval( + &mut vm, + "new BigInt64Array(5n).length === 5 && new Int8Array(true).length === 1 && new Int8Array(null).length === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_bigint_from_array_and_set() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var a = new BigInt64Array([1n, 2n]); \ + a.length === 2 && a[0] === 1n && a[1] === 2n && \ + BigInt64Array.from([3n]).at(0) === 3n && \ + (function () { var t = new BigInt64Array(2); t.set([9n, 8n]); return t[0] === 9n && t[1] === 8n; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_bigint_sort_uses_bigint_compare() { + let mut vm = Vm::new(); + // 超出 f64 精度的 BigInt 排序仍按 BigInt 值升序(f64 中转会静默错序)。 + let result = eval( + &mut vm, + "var a = new BigInt64Array([9223372036854775807n, -1n, 0n, 1n]); a.sort(); \ + a[0] === -1n && a[1] === 0n && a[2] === 1n && a[3] === 9223372036854775807n", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn typed_array_bigint_methods_receive_bigint_values() { + let mut vm = Vm::new(); + // map/filter/reduce 回调收到的元素是真 BigInt;map 结果按元素类型转换回写。 + let result = eval( + &mut vm, + "var a = new BigInt64Array([1n, 2n]); \ + var seen = []; \ + var m = a.map(function (v) { seen.push(typeof v); return v + 1n; }); \ + seen[0] === 'bigint' && seen[1] === 'bigint' && m[0] === 2n && m[1] === 3n && \ + a.filter(function (v) { return v > 1n; }).length === 1 && \ + a.reduce(function (acc, v) { return acc + v; }, 0n) === 3n", + ) + .unwrap(); + assert!(result.as_bool()); +}