feat: TypedArray 活长口径与元素面越界语义

This commit is contained in:
2026-09-23 00:54:45 +00:00
parent bb92f17deb
commit 1ac6b76f2f
+300 -127
View File
@@ -17,7 +17,11 @@ pub(crate) struct TypedArrayData {
pub kind: TypedArrayKind,
pub buffer: JsValue,
pub byte_offset: usize,
/// `length` 的两种口径:`auto_length` 为 true(构造省略长度、subarray 省略
/// end 落于可缩放缓冲)时视图长度随底层 buffer 实时伸缩;false 时
/// `length` 为定长,buffer 收缩只触发越界,不改长度。
pub length: usize,
pub auto_length: bool,
}
macro_rules! native_try {
@@ -117,7 +121,7 @@ fn typed_array_ctor_value<H: VmHost>(vm: &mut H, kind: TypedArrayKind) -> JsValu
/// # 边界与前提
/// - 对象须为空槽位(构造帧分配的 this / 新建对象);重复物化会泄漏旧数据盒。
fn materialize_typed_array(
obj: &mut JsObject, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize,
obj: &mut JsObject, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize, auto_length: bool,
) {
obj.type_tag = JsObject::OBJ_TYPE_TYPED_ARRAY;
let data = Box::into_raw(Box::new(TypedArrayData {
@@ -125,6 +129,7 @@ fn materialize_typed_array(
buffer,
byte_offset,
length,
auto_length,
}));
// SAFETY: TypedArray 实例不可调用,native_fn 存不透明 `Box<TypedArrayData>`,
// 与本 VM 中 ArrayBuffer/DataView 的类型化对象存储一致。
@@ -132,21 +137,22 @@ fn materialize_typed_array(
}
fn create_typed_array<H: VmHost>(
vm: &mut H, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize,
vm: &mut H, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize, auto_length: bool,
) -> *mut JsObject {
let proto = typed_array_proto_ptr(vm, kind);
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
materialize_typed_array(&mut obj, kind, buffer, byte_offset, length);
materialize_typed_array(&mut obj, kind, buffer, byte_offset, length, auto_length);
vm.alloc_object(obj)
}
/// TypedArrayCreateSameType 单长度参数形态:按 O 的类型建同长度新对象
/// (规范上忽略 species,toReversed/toSorted/with 的交付语义)。
/// (规范上忽略 species,toReversed/toSorted/with 的交付语义)。定长新缓冲,
/// 不随任何 buffer 伸缩。
fn create_same_type_typed_array<H: VmHost>(vm: &mut H, kind: TypedArrayKind, length: usize) -> *mut JsObject {
let bpe = kind.bytes_per_element();
let buffer =
JsValue::from_js_object(new_array_buffer(vm, vec![0; length * bpe], 0, default_array_buffer_proto(vm)));
create_typed_array(vm, kind, buffer, 0, length)
create_typed_array(vm, kind, buffer, 0, length, false)
}
fn typed_array_data_ptr(obj: &JsObject) -> Option<*mut TypedArrayData> {
@@ -266,12 +272,65 @@ fn absolute_byte_offset(view: TypedArrayData, index: usize) -> usize {
view.byte_offset + index * view.kind.bytes_per_element()
}
/// 读视图引用 buffer 的当前字节长度;buffer 非 ArrayBuffer 对象、无载荷或已
/// detach(`data` 为 `None`)时返回 `None`。
fn ta_buffer_byte_length(view: TypedArrayData) -> Option<usize> {
let buffer_ptr = view.buffer.as_js_object_ptr();
if buffer_ptr.is_null() {
return None;
}
// SAFETY: buffer 对象与视图同生命周期,此处只读载荷存活位与长度。
let payload_ptr = array_buffer_payload_ptr(unsafe { &*buffer_ptr })?;
if payload_ptr.is_null() {
return None;
}
// SAFETY: payload_ptr 经 array_buffer_payload_ptr 校验为合法载荷盒。
unsafe { (*payload_ptr).data.as_ref().map(|data| data.len()) }
}
/// 规范 TypedArrayLength:已 detach 恒 0;定长视图取静态长度(哪怕 buffer
/// 已收缩到越界);auto 视图按 buffer 当前字节数实时折算。
fn ta_spec_length(view: TypedArrayData) -> usize {
let buffer_len = ta_buffer_byte_length(view);
if view.auto_length {
return buffer_len
.map(|len| len.saturating_sub(view.byte_offset) / view.kind.bytes_per_element())
.unwrap_or(0);
}
if buffer_len.is_none() {
return 0;
}
view.length
}
/// 越界判定:detach 恒越界;auto 视图 offset 超 buffer 即越界;定长视图
/// 窗口尾部超 buffer 即越界。
fn ta_is_oob(view: TypedArrayData) -> bool {
let Some(buffer_len) = ta_buffer_byte_length(view) else {
return true;
};
if view.auto_length {
return view.byte_offset > buffer_len;
}
view.byte_offset.saturating_add(view.length * view.kind.bytes_per_element()) > buffer_len
}
/// live 长度(元素面消费口径):越界(含 detach)一律 0,界内取规范
/// TypedArrayLength。
fn ta_live_length(view: TypedArrayData) -> usize {
if ta_is_oob(view) {
return 0;
}
ta_spec_length(view)
}
/// 读 TypedArray 指定整数索引的元素(供 VM 普通属性 get 的 typed 分支使用)。
/// 越界返回 undefined;内部状态非法返回 Err(String)。
pub fn typed_array_element_get<H: VmHost>(vm: &mut H, obj: &JsObject, index: u32) -> Result<JsValue, String> {
let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
let view = get_typed_array_data(vm, this_val).map_err(|e| format!("{e}"))?;
if index as usize >= view.length {
// live 边界:buffer 收缩越界后界内读 undefined(detached 同口径)。
if index as usize >= ta_live_length(view) {
return Ok(JsValue::undefined());
}
let payload_ptr = array_buffer_payload(vm, view.buffer).map_err(|e| format!("{e}"))?;
@@ -299,7 +358,9 @@ pub fn typed_array_integer_index<H: VmHost>(vm: &H, obj: &JsObject, prop_name_si
}
key.parse::<u32>().ok()?
};
Some((index as usize, view.length))
// 长度走 live 口径:buffer 收缩越界后视同空视图,写前语义不落到
// receiver(与 ta_view_length 同口径)。
Some((index as usize, ta_live_length(view)))
}
// ── 统一数值键门 ─────────────────────────────────────────────────────────
@@ -336,9 +397,9 @@ pub fn ta_index_gate<H: VmHost>(vm: &H, obj: &JsObject, key_si: u32) -> TaIndexG
ta_index_gate_from_text(key, length)
}
/// 取 TypedArray 视图长度(供只需 length 不需门的枚举面消费方);
/// 非 TypedArray 或内部状态无效返回 0。底层缓冲已 detach(载荷 `data`
/// 为 `None`)时视图长度统一为 0,界内键经门归数字无效。
/// 取 TypedArray 视图的 live 长度(供只需 length 不需门的枚举面消费方);
/// 非 TypedArray 或内部状态无效返回 0。越界(detach、定长窗口被 buffer
/// 收缩裁掉、auto offset 超 buffer)统一 0,界内键经门归数字无效。
pub(crate) fn ta_view_length<H: VmHost>(_vm: &H, obj: &JsObject) -> usize {
let Some(ptr) = typed_array_data_ptr(obj) else {
return 0;
@@ -349,17 +410,7 @@ pub(crate) fn ta_view_length<H: VmHost>(_vm: &H, obj: &JsObject) -> usize {
// SAFETY: ptr 非空,为 TypedArray 对象 native_fn 槽内的 Box<TypedArrayData>,
// 与对象同生命周期,此处只读拷贝视图字段。
let view = unsafe { *ptr };
// 底层缓冲已 detach:视图长度统一 0。
let buffer_ptr = view.buffer.as_js_object_ptr();
if !buffer_ptr.is_null() {
// SAFETY: buffer 对象与视图同生命周期,此处只读载荷存活位。
if let Some(payload_ptr) = array_buffer_payload_ptr(unsafe { &*buffer_ptr }) {
if !payload_ptr.is_null() && unsafe { (*payload_ptr).data.is_none() } {
return 0;
}
}
}
view.length
ta_live_length(view)
}
/// 纯核(无 VM)整数键臂:界内有效,越界数字无效(length 0 时全无效)。
@@ -441,7 +492,7 @@ pub fn typed_array_element_define<H: VmHost>(
) -> Result<(), String> {
let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
let view = get_typed_array_data(vm, this_val).map_err(|e| format!("{e}"))?;
if index as usize >= view.length {
if index as usize >= ta_live_length(view) {
return Err("cannot define property: TypedArray index out of range".to_string());
}
write_typed_array_element(vm, view, index, value)
@@ -460,7 +511,9 @@ fn write_typed_array_element<H: VmHost>(
return vm.raise_type_error(&text);
}
};
if index as usize >= view.length {
// live 边界:越界(含 detach)静默不写(规范 IntegerIndexedElementSet 的
// IsValidIndexedAccess 臂),转换副作用已先发生。
if index as usize >= ta_live_length(view) {
return Ok(());
}
let payload_ptr = array_buffer_payload(vm, view.buffer).map_err(|e| format!("{e}"))?;
@@ -630,7 +683,9 @@ fn collect_array_like<H: VmHost>(vm: &mut H, value: JsValue, consult_iterator: b
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)
// 源视图按 live 长度收集:buffer 收缩越界后视同空源(与构造器
// TypedArrayCreate 的数组元素收集口径一致)。
return Ok((0..ta_live_length(view))
.map(|i| read_element(vm, view.kind, buffer, absolute_byte_offset(view, i)))
.collect());
}
@@ -696,7 +751,7 @@ fn typed_array_new<H: VmHost>(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_object() {
let (buffer, byte_offset, length, auto_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() {
@@ -717,10 +772,13 @@ fn typed_array_new<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) ->
let remaining = buffer_len - byte_offset;
// 规范以 "length is not undefined" 分支:显式 undefined 与省略同义,
// 取整缓冲剩余长度(live 全长),不落入 ToIndex(undefined)=0。
let length = if args.len() > 3 && !vm.reg(args[3]).is_undefined() {
native_try!(to_index(vm, vm.reg(args[3]), "TypedArray length out of bounds"))
} else {
// auto 口径:省略/显式 undefined → 长度随 buffer 伸缩;显式给定
// 定死。
let auto_length = args.len() <= 3 || vm.reg(args[3]).is_undefined();
let length = if auto_length {
remaining / bpe
} else {
native_try!(to_index(vm, vm.reg(args[3]), "TypedArray length out of bounds"))
};
let Some(byte_length) = length.checked_mul(bpe) else {
return NativeResult::Err(range_error(vm, "TypedArray length out of bounds"));
@@ -728,7 +786,7 @@ fn typed_array_new<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) ->
if byte_length > remaining {
return NativeResult::Err(range_error(vm, "TypedArray length out of bounds"));
}
(first, byte_offset, length)
(first, byte_offset, length, auto_length)
} else {
let values = native_try!(collect_array_like(vm, first, true));
let byte_len = values.len().saturating_mul(bpe);
@@ -743,7 +801,7 @@ fn typed_array_new<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) ->
let elem = native_try!(ta_element_value(vm, kind, value));
write_element(vm, kind, buffer_ref, idx * bpe, elem);
}
(buffer, 0, byte_len / bpe)
(buffer, 0, byte_len / bpe, false)
}
} else {
// 非对象第一参数统一按 ToIndex 语义处理(bool→0/1、null→0、BigInt→数值、
@@ -760,17 +818,24 @@ fn typed_array_new<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) ->
}
let buffer =
JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len], 0, default_array_buffer_proto(vm)));
(buffer, 0, len)
(buffer, 0, len, false)
};
if in_construct {
// SAFETY: receiver 是构造帧分配的 this(本 session 存活),此处只写
// type_tag 与 native_fn 两个槽位,与调用方无别名。
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
materialize_typed_array(obj, kind, buffer, byte_offset, length);
materialize_typed_array(obj, kind, buffer, byte_offset, length, auto_length);
NativeResult::Ok(this_val)
} else {
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, kind, buffer, byte_offset, length)))
NativeResult::Ok(JsValue::from_js_object(create_typed_array(
vm,
kind,
buffer,
byte_offset,
length,
auto_length,
)))
}
}
@@ -800,6 +865,9 @@ typed_array_ctor!(biguint64array_constructor, BigUint64);
pub fn typed_array_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
// 负索引折算与越界判定走规范 TypedArrayLength 口径(auto 随 buffer、定长
// 静态),越界视图入口 TypeError 由调用方前置校验。
let len = ta_spec_length(view);
let idx: isize = if args.len() > 1 {
let n = native_try!(ta_to_number(vm, vm.reg(args[1])));
// ToIntegerOrInfinity:NaN 归 0;±Infinity 不截断,负无穷必然越界、
@@ -808,14 +876,14 @@ pub fn typed_array_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
0
} else if n.is_infinite() {
if n > 0.0 {
view.length as isize
len as isize
} else {
-1
}
} else {
let k = n.trunc() as isize;
if k < 0 {
view.length as isize + k
len as isize + k
} else {
k
}
@@ -823,7 +891,7 @@ pub fn typed_array_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else {
0
};
if idx < 0 || idx as usize >= view.length {
if idx < 0 || idx as usize >= len {
return NativeResult::Ok(JsValue::undefined());
}
let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer));
@@ -1042,25 +1110,30 @@ pub fn typed_array_buffer_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
NativeResult::Ok(view.buffer)
}
/// `%TypedArray%.prototype.byteOffset` 访问器:返回视图相对 buffer 起始的字节偏移。
/// `%TypedArray%.prototype.byteOffset` 访问器:返回视图相对 buffer 起始的
/// 字节偏移;越界视图(含 detach)读 0。
pub fn typed_array_byte_offset_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
NativeResult::Ok(JsValue::int(view.byte_offset as i32))
let offset = if ta_is_oob(view) { 0 } else { view.byte_offset };
NativeResult::Ok(JsValue::int(offset as i32))
}
/// `%TypedArray%.prototype.byteLength` 访问器:返回视图占用的字节数。
/// `%TypedArray%.prototype.byteLength` 访问器:返回视图占用的字节数(live
/// 长度 × 元素字节数;越界视图读 0)。
pub fn typed_array_byte_length_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
NativeResult::Ok(JsValue::int((view.length * view.kind.bytes_per_element()) as i32))
let byte_length = ta_live_length(view) * view.kind.bytes_per_element();
NativeResult::Ok(JsValue::int(byte_length as i32))
}
/// `%TypedArray%.prototype.length` 访问器:返回元素个数。
/// `%TypedArray%.prototype.length` 访问器:返回元素个数(live 口径;越界
/// 视图读 0)。
pub fn typed_array_length_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
NativeResult::Ok(JsValue::int(view.length as i32))
NativeResult::Ok(JsValue::int(ta_live_length(view) as i32))
}
/// `%TypedArray%.prototype[@@toStringTag]` 访问器:返回具体类型名(如
@@ -1112,10 +1185,11 @@ fn allocate_typed_array<H: VmHost>(vm: &mut H, c: JsValue, len: usize) -> Result
/// (BigInt 类型走 ToBigInt、数值类型走 ToNumber,symbol 等不可转换值抛 TypeError)。
fn set_typed_array_element<H: VmHost>(vm: &mut H, ta: JsValue, index: usize, value: JsValue) -> Result<(), JsValue> {
let view = get_typed_array_data(vm, ta)?;
if index >= view.length {
// 先转换(副作用/抛错先于越界判定),越界(含 detach)静默不写。
let elem = ta_element_value(vm, view.kind, value)?;
if index >= ta_live_length(view) {
return Ok(());
}
let elem = ta_element_value(vm, view.kind, value)?;
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 {
@@ -1144,33 +1218,18 @@ pub fn typed_array_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
/// 读 TypedArray 指定索引的元素(视图 data 已取出的形式,供原型方法内部使用);
/// 越界返回 undefined。
/// 越界(含 detach)一律返回 undefined,BigInt 类型不特殊化(规范
/// Get 的越界语义全类型同口径)。
fn ta_read<H: VmHost>(vm: &mut H, view: TypedArrayData, index: usize) -> Result<JsValue, JsValue> {
if index >= view.length {
return Ok(ta_oob_neutral(vm, view.kind));
if index >= ta_live_length(view) {
return Ok(JsValue::undefined());
}
let payload_ptr = array_buffer_payload(vm, view.buffer)?;
// SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。
let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else {
return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid"));
};
let val = read_element(vm, view.kind, buffer, absolute_byte_offset(view, index));
// 底层缓冲被 resize 收缩后越界读返回 undefined;BigInt 类型归一成 0n
// (ToBigInt(undefined) 语义),使原地方法的比较/回写拿到合法 BigInt。
if is_bigint_kind(view.kind) && val.is_undefined() {
return Ok(vm.new_bigint(BigInt::from(0_i64)));
}
Ok(val)
}
/// 越界索引的中性元素值:BigInt 类型给 0n(后续按 BigInt 消费),数值类型
/// 给 undefined(后续 to_number 归 0)。
fn ta_oob_neutral<H: VmHost>(vm: &mut H, kind: TypedArrayKind) -> JsValue {
if is_bigint_kind(kind) {
vm.new_bigint(BigInt::from(0_i64))
} else {
JsValue::undefined()
}
Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, index)))
}
/// 把已转换的值写入 TypedArray 指定索引(视图 data 已取出的形式,供原型方法内部使用)。
@@ -1208,7 +1267,9 @@ pub fn typed_array_for_each<H: VmHost>(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() };
for i in 0..view.length {
// 入口见证:循环长度一次捕获,回调期 buffer 伸缩不改本轮范围。
let len = ta_spec_length(view);
for i in 0..len {
let elem = native_try!(ta_read(vm, view, i));
native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
}
@@ -1225,16 +1286,18 @@ pub fn typed_array_map<H: VmHost>(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() };
// 入口见证:目标长度 = 源见证长度,回调期 buffer 伸缩不改本轮范围。
let len = ta_spec_length(view);
// 目标对象经 species 构造(长度 = 源长度);逐元素"回调 → 写入"交错,
// 目标与源别名时后续回调看到前一轮写入值。
let a = native_try!(typed_array_species_create(
vm,
this_val,
view.kind,
vec![JsValue::int(view.length as i32)],
Some(view.length),
vec![JsValue::int(len as i32)],
Some(len),
));
for i in 0..view.length {
for i in 0..len {
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]));
native_try!(set_typed_array_element(vm, a, i, mapped));
@@ -1253,9 +1316,10 @@ pub fn typed_array_filter<H: VmHost>(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() };
// 先全量回调得通过元素(规范序:count 先于目标构造),再经 species
// 构造目标(长度 = 通过数)并顺序写入。
// 构造目标(长度 = 通过数)并顺序写入;循环走入口见证长度。
let len = ta_spec_length(view);
let mut kept = Vec::new();
for i in 0..view.length {
for i in 0..len {
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) {
@@ -1280,7 +1344,9 @@ pub fn typed_array_filter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn typed_array_reduce<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
if view.length == 0 && args.len() < 3 {
// 见证长度一次捕获:空检与循环同口径(shrink 到 0 的 auto 视图按空抛错)。
let len = ta_spec_length(view);
if len == 0 && args.len() < 3 {
return NativeResult::Err(type_error(vm, "Reduce of empty array with no initial value"));
}
if args.len() < 2 {
@@ -1292,7 +1358,7 @@ pub fn typed_array_reduce<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else {
(native_try!(ta_read(vm, view, 0)), 1)
};
for i in start_idx..view.length {
for i in start_idx..len {
let elem = native_try!(ta_read(vm, view, i));
accumulator = native_try!(invoke_cb(
vm,
@@ -1308,7 +1374,9 @@ pub fn typed_array_reduce<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn typed_array_reduce_right<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
if view.length == 0 && args.len() < 3 {
// 见证长度一次捕获:空检与循环同口径(shrink 到 0 的 auto 视图按空抛错)。
let len = ta_spec_length(view);
if len == 0 && args.len() < 3 {
return NativeResult::Err(type_error(vm, "Reduce of empty array with no initial value"));
}
if args.len() < 2 {
@@ -1316,9 +1384,9 @@ pub fn typed_array_reduce_right<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
}
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
let (mut accumulator, start_idx): (JsValue, i32) = if args.len() > 2 {
(vm.reg(args[2]), view.length as i32 - 1)
(vm.reg(args[2]), len as i32 - 1)
} else {
(native_try!(ta_read(vm, view, view.length - 1)), view.length as i32 - 2)
(native_try!(ta_read(vm, view, len - 1)), len as i32 - 2)
};
for i in (0..=start_idx).rev() {
let elem = native_try!(ta_read(vm, view, i as usize));
@@ -1341,7 +1409,8 @@ pub fn typed_array_every<H: VmHost>(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() };
for i in 0..view.length {
let len = ta_spec_length(view);
for i in 0..len {
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) {
@@ -1360,7 +1429,8 @@ pub fn typed_array_some<H: VmHost>(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() };
for i in 0..view.length {
let len = ta_spec_length(view);
for i in 0..len {
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) {
@@ -1379,7 +1449,8 @@ pub fn typed_array_find<H: VmHost>(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() };
for i in 0..view.length {
let len = ta_spec_length(view);
for i in 0..len {
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) {
@@ -1398,7 +1469,8 @@ pub fn typed_array_find_index<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
}
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() };
for i in 0..view.length {
let len = ta_spec_length(view);
for i in 0..len {
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) {
@@ -1417,7 +1489,8 @@ pub fn typed_array_find_last<H: VmHost>(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() };
for i in (0..view.length).rev() {
let len = ta_spec_length(view);
for i in (0..len).rev() {
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) {
@@ -1437,7 +1510,8 @@ pub fn typed_array_find_last_index<H: VmHost>(vm: &mut H, args: &[u8]) -> Native
}
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() };
for i in (0..view.length).rev() {
let len = ta_spec_length(view);
for i in (0..len).rev() {
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) {
@@ -1473,16 +1547,17 @@ fn normalize_from_index<H: VmHost>(vm: &mut H, value: JsValue, len: usize) -> Re
pub fn typed_array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
if view.length == 0 || args.len() < 2 {
let len = ta_spec_length(view);
if len == 0 || args.len() < 2 {
return NativeResult::Ok(JsValue::int(-1));
}
let target = vm.reg(args[1]);
let from_index = if args.len() >= 3 {
native_try!(normalize_from_index(vm, vm.reg(args[2]), view.length))
native_try!(normalize_from_index(vm, vm.reg(args[2]), len))
} else {
0
};
for i in from_index..view.length {
for i in from_index..len {
let elem = native_try!(ta_read(vm, view, i));
if oxide_runtime_api::strict_equality(elem, target) {
return NativeResult::Ok(JsValue::int(i as i32));
@@ -1495,7 +1570,8 @@ pub fn typed_array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn typed_array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
if view.length == 0 {
let len = ta_spec_length(view);
if len == 0 {
return NativeResult::Ok(JsValue::int(-1));
}
let target = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
@@ -1511,20 +1587,20 @@ pub fn typed_array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
0
} else if f.is_infinite() {
if f > 0.0 {
view.length as isize - 1
len as isize - 1
} else {
-1
}
} else {
let f = f.trunc() as isize;
if f >= 0 {
f.min(view.length as isize - 1)
f.min(len as isize - 1)
} else {
view.length as isize + f
len as isize + f
}
}
} else {
view.length as isize - 1
len as isize - 1
};
if from_index < 0 {
return NativeResult::Ok(JsValue::int(-1));
@@ -1543,16 +1619,17 @@ pub fn typed_array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
pub fn typed_array_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
if view.length == 0 {
let len = ta_spec_length(view);
if len == 0 {
return NativeResult::Ok(JsValue::bool(false));
}
let target = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let from_index = if args.len() >= 3 {
native_try!(normalize_from_index(vm, vm.reg(args[2]), view.length))
native_try!(normalize_from_index(vm, vm.reg(args[2]), len))
} else {
0
};
for i in from_index..view.length {
for i in from_index..len {
let elem = native_try!(ta_read(vm, view, i));
if oxide_runtime_api::same_value_zero(elem, target) {
return NativeResult::Ok(JsValue::bool(true));
@@ -1572,8 +1649,9 @@ pub fn typed_array_join<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else {
",".to_string()
};
let mut parts = Vec::with_capacity(view.length);
for i in 0..view.length {
let len = ta_spec_length(view);
let mut parts = Vec::with_capacity(len);
for i in 0..len {
let elem = native_try!(ta_read(vm, view, i));
parts.push(oxide_runtime_api::to_string(elem));
}
@@ -1641,8 +1719,9 @@ pub fn typed_array_sort<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else {
None
};
let mut vals: Vec<JsValue> = Vec::with_capacity(view.length);
for i in 0..view.length {
let len = ta_spec_length(view);
let mut vals: Vec<JsValue> = Vec::with_capacity(len);
for i in 0..len {
vals.push(native_try!(ta_read(vm, view, i)));
}
let mut sort_error = None;
@@ -1699,8 +1778,9 @@ pub fn typed_array_sort<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn typed_array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
let len = ta_spec_length(view);
let mut i = 0;
let mut j = view.length.saturating_sub(1);
let mut j = len.saturating_sub(1);
while i < j {
let tmp = native_try!(ta_read(vm, view, i));
let jtmp = native_try!(ta_read(vm, view, j));
@@ -1716,7 +1796,7 @@ pub fn typed_array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn typed_array_copy_within<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
let len = view.length;
let len = ta_spec_length(view);
let target = if args.len() > 1 {
native_try!(normalize_index(vm, vm.reg(args[1]), len))
} else {
@@ -1772,11 +1852,12 @@ fn invoke_element_to_locale_string<H: VmHost>(vm: &mut H, element: JsValue) -> R
pub fn typed_array_to_locale_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
if view.length == 0 {
let len = ta_spec_length(view);
if len == 0 {
return NativeResult::Ok(vm.new_string(""));
}
let mut parts = Vec::with_capacity(view.length);
for i in 0..view.length {
let mut parts = Vec::with_capacity(len);
for i in 0..len {
let elem = native_try!(ta_read(vm, view, i));
let r = native_try!(invoke_element_to_locale_string(vm, elem));
let s =
@@ -1791,9 +1872,10 @@ pub fn typed_array_to_locale_string<H: VmHost>(vm: &mut H, args: &[u8]) -> Nativ
pub fn typed_array_to_reversed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val));
let a = JsValue::from_js_object(create_same_type_typed_array(vm, view.kind, view.length));
for i in 0..view.length {
let elem = native_try!(ta_read(vm, view, view.length - 1 - i));
let len = ta_spec_length(view);
let a = JsValue::from_js_object(create_same_type_typed_array(vm, view.kind, len));
for i in 0..len {
let elem = native_try!(ta_read(vm, view, len - 1 - i));
native_try!(set_typed_array_element(vm, a, i, elem));
}
NativeResult::Ok(a)
@@ -1814,8 +1896,9 @@ pub fn typed_array_to_sorted<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
} else {
None
};
let mut vals: Vec<JsValue> = Vec::with_capacity(view.length);
for i in 0..view.length {
let len = ta_spec_length(view);
let mut vals: Vec<JsValue> = Vec::with_capacity(len);
for i in 0..len {
vals.push(native_try!(ta_read(vm, view, i)));
}
let mut sort_error = None;
@@ -1877,6 +1960,7 @@ pub fn typed_array_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(type_error(vm, "TypedArray.prototype.with requires an index"));
}
let len = ta_spec_length(view);
// ToIntegerOrInfinity(index),负值折算为 len + index。
let raw = native_try!(ta_to_number(vm, vm.reg(args[1])));
let relative_index = if raw.is_nan() || raw == 0.0 {
@@ -1886,21 +1970,17 @@ pub fn typed_array_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else {
raw.trunc()
};
let actual_index = if relative_index >= 0.0 {
relative_index
} else {
view.length as f64 + relative_index
};
let actual_index = if relative_index >= 0.0 { relative_index } else { len as f64 + relative_index };
// 规范步 7/8:value 先转换(副作用/抛错原样上抛),步 9 才做越界检查。
let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
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 {
if actual_index.is_nan() || actual_index < 0.0 || actual_index >= len as f64 {
return NativeResult::Err(range_error(vm, "Invalid typed array index"));
}
let index = actual_index as usize;
let a = JsValue::from_js_object(create_same_type_typed_array(vm, view.kind, view.length));
let a = JsValue::from_js_object(create_same_type_typed_array(vm, view.kind, len));
// 目标索引写已转换的 value(副作用已先于源读发生),其余索引顺序拷源元素。
for i in 0..view.length {
for i in 0..len {
let elem = if i == index { replacement } else { native_try!(ta_read(vm, view, i)) };
native_try!(set_typed_array_element(vm, a, i, elem));
}
@@ -2149,9 +2229,10 @@ pub fn uint8array_to_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else {
return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid"));
};
// 视图范围按当前缓冲长度收口(缓冲可被 resize 收缩)。
// 视图范围按 live 长度与当前缓冲长度双收口(缓冲可被 resize 收缩、
// 视图可越界)。
let start = view.byte_offset.min(buffer.len());
let end = (view.byte_offset + view.length).min(buffer.len());
let end = (view.byte_offset.saturating_add(ta_live_length(view))).min(buffer.len());
NativeResult::Ok(vm.new_string_owned(crate::ta_codec::encode_base64(&buffer[start..end], alphabet, omit_padding)))
}
@@ -2164,9 +2245,10 @@ pub fn uint8array_to_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else {
return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid"));
};
// 视图范围按当前缓冲长度收口(缓冲可被 resize 收缩)。
// 视图范围按 live 长度与当前缓冲长度双收口(缓冲可被 resize 收缩、
// 视图可越界)。
let start = view.byte_offset.min(buffer.len());
let end = (view.byte_offset + view.length).min(buffer.len());
let end = (view.byte_offset.saturating_add(ta_live_length(view))).min(buffer.len());
NativeResult::Ok(vm.new_string_owned(crate::ta_codec::encode_hex(&buffer[start..end])))
}
@@ -2183,12 +2265,13 @@ pub fn uint8array_set_from_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeR
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| {
// 缓冲可被 resize 收缩:目标字节越界时静默不写(live 视图越界语义)。
if start + idx < buffer.len() {
buffer[start + idx] = b;
}
}));
let (read, written) =
native_try!(ta_decode_base64(vm, string, options, Some(ta_live_length(view)), &mut |idx, b| {
// 缓冲可被 resize 收缩:目标字节越界时静默不写(live 视图越界语义)。
if start + idx < buffer.len() {
buffer[start + idx] = b;
}
}));
NativeResult::Ok(ta_read_written_result(vm, read, written))
}
@@ -2208,7 +2291,7 @@ pub fn uint8array_set_from_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
};
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| {
let (read, written) = match crate::ta_codec::decode_hex(&units, Some(ta_live_length(view)), &mut |idx, b| {
// 缓冲可被 resize 收缩:目标字节越界时静默不写(live 视图越界语义)。
if start + idx < buffer.len() {
buffer[start + idx] = b;
@@ -2229,7 +2312,14 @@ pub fn uint8array_from_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
native_try!(ta_decode_base64(vm, string, options, None, &mut |_, b| bytes.push(b)));
let len = bytes.len();
let buffer = JsValue::from_js_object(new_array_buffer(vm, bytes, 0, default_array_buffer_proto(vm)));
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, TypedArrayKind::Uint8, buffer, 0, len)))
NativeResult::Ok(JsValue::from_js_object(create_typed_array(
vm,
TypedArrayKind::Uint8,
buffer,
0,
len,
false,
)))
}
/// `Uint8Array.fromHex(string)`:不读 this;奇数长度最前判定抛 SyntaxError。
@@ -2246,17 +2336,100 @@ pub fn uint8array_from_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
let len = bytes.len();
let buffer = JsValue::from_js_object(new_array_buffer(vm, bytes, 0, default_array_buffer_proto(vm)));
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, TypedArrayKind::Uint8, buffer, 0, len)))
NativeResult::Ok(JsValue::from_js_object(create_typed_array(
vm,
TypedArrayKind::Uint8,
buffer,
0,
len,
false,
)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::array_buffer::ArrayBufferPayload;
fn gate(text: &str, length: usize) -> TaIndexGate {
ta_index_gate_from_text(text, length)
}
/// 手工构造带载荷盒的 ArrayBuffer 对象(不经 Vm,只验 live 长度纯核)。
fn ab_object_with_data(data: Option<Vec<u8>>) -> JsObject {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER;
let payload = ArrayBufferPayload {
data,
max_byte_length: 0,
immutable: false,
};
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
}
fn ta_view(
buffer: &JsObject, kind: TypedArrayKind, byte_offset: usize, length: usize, auto: bool,
) -> TypedArrayData {
TypedArrayData {
kind,
buffer: JsValue::from_js_object(buffer as *const JsObject as *mut JsObject),
byte_offset,
length,
auto_length: auto,
}
}
// J auto 视图 live 长度随 buffer 伸缩(floor 折算、offset 裁剪)。
#[test]
fn live_length_auto_tracks_buffer() {
let full = ab_object_with_data(Some(vec![0; 8]));
let v = ta_view(&full, TypedArrayKind::Uint8, 0, 8, true);
assert_eq!(ta_live_length(v), 8);
assert!(!ta_is_oob(v));
assert_eq!(ta_spec_length(v), 8);
let shrunk = ab_object_with_data(Some(vec![0; 6]));
let v = ta_view(&shrunk, TypedArrayKind::Int32, 0, 8, true);
assert_eq!(ta_live_length(v), 1, "6 字节缓冲按 4 字节元素 floor 折算为 1");
let past = ab_object_with_data(Some(vec![0; 2]));
let v = ta_view(&past, TypedArrayKind::Uint8, 4, 8, true);
assert!(ta_is_oob(v), "auto offset 超 buffer 即越界");
assert_eq!(ta_live_length(v), 0);
}
// K 定长视图:界内取静态长度;窗口被 buffer 收缩裁掉即越界 0,
// 规范长度(TypedArrayLength)仍取静态值。
#[test]
fn live_length_fixed_static_then_oob() {
let full = ab_object_with_data(Some(vec![0; 8]));
let v = ta_view(&full, TypedArrayKind::Uint8, 0, 4, false);
assert!(!ta_is_oob(v));
assert_eq!(ta_live_length(v), 4);
assert_eq!(ta_spec_length(v), 4);
let shrunk = ab_object_with_data(Some(vec![0; 6]));
let v = ta_view(&shrunk, TypedArrayKind::Uint16, 0, 4, false);
assert!(ta_is_oob(v), "窗口尾 8 字节超 6 字节缓冲");
assert_eq!(ta_live_length(v), 0);
assert_eq!(ta_spec_length(v), 4, "规范长度对定长视图恒取静态值");
}
// L detach:auto/定长同口径越界 0。
#[test]
fn live_length_detached_is_zero() {
let detached = ab_object_with_data(None);
for auto in [true, false] {
let v = ta_view(&detached, TypedArrayKind::Uint8, 0, 4, auto);
assert!(ta_is_oob(v));
assert_eq!(ta_live_length(v), 0);
assert_eq!(ta_spec_length(v), 0);
}
}
// A 整数键(length 除注明外取 2)。
#[test]
fn gate_text_integer_keys() {