feat: TypedArray 入口校验与见证长度语义

This commit is contained in:
2026-09-23 01:15:50 +00:00
parent 1ac6b76f2f
commit 0797ff1dce
6 changed files with 400 additions and 119 deletions
+8 -3
View File
@@ -611,7 +611,8 @@ pub fn array_join<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
Ok(v) => v, Ok(v) => v,
Err(e) => return NativeResult::Err(e), Err(e) => return NativeResult::Err(e),
}; };
let sep = if args.len() > 1 { // 分隔符缺省或 undefined 时取 ","(规范:undefined 视同未给)。
let sep = if args.len() > 1 && !vm.reg(args[1]).is_undefined() {
oxide_runtime_api::to_string(vm.reg(args[1])) oxide_runtime_api::to_string(vm.reg(args[1]))
} else { } else {
",".to_string() ",".to_string()
@@ -636,8 +637,12 @@ pub fn array_join<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `Array.prototype.toString`:委托给 join,默认用 `,` 分隔。 /// `Array.prototype.toString`:委托给 join,默认用 `,` 分隔。
pub fn array_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn array_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// Array.prototype.toString() 委托给 join,默认用 "," 分隔, // 与 TA 原型共享同一函数对象:TA 臂先入口校验(detach 抛 TypeError)
// 按规范忽略自身参数。 // 再按逗号连接(与 join 同口径),Array 臂委托 join。
let this_ptr = vm.reg(args[0]).as_js_object_ptr();
if !this_ptr.is_null() && unsafe { &*this_ptr }.is_typed_array_obj() {
return crate::typed_array::typed_array_join(vm, &[args[0]]);
}
array_join(vm, &[args[0]]) array_join(vm, &[args[0]])
} }
@@ -215,7 +215,12 @@ pub fn array_iterator_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
Ok(view) => view, Ok(view) => view,
Err(err) => return NativeResult::Err(err), Err(err) => return NativeResult::Err(err),
}; };
if (index as usize) >= view.length { // 每次 next 入口校验:detach/越界抛 TypeError,auto 收缩按 live 长度停。
let view = match crate::typed_array::ta_validate(vm, view, false) {
Ok(view) => view,
Err(e) => return NativeResult::Err(e),
};
if (index as usize) >= crate::typed_array::ta_live_length(view) {
vm.set_or_create_prop_value(iter, target_si, JsValue::undefined()); vm.set_or_create_prop_value(iter, target_si, JsValue::undefined());
return NativeResult::Ok(crate::iterator::make_iter_result(vm, JsValue::undefined(), true)); return NativeResult::Ok(crate::iterator::make_iter_result(vm, JsValue::undefined(), true));
} }
+4 -5
View File
@@ -1768,12 +1768,11 @@ fn next_array_like<H: VmHost>(
if is_typed_array_value(inner) { if is_typed_array_value(inner) {
let index = current_index(vm, wrapper, index_si); let index = current_index(vm, wrapper, index_si);
let length_si = vm.kernel_core().perm_interner().intern("length").0;
let obj = unsafe { &*inner.as_js_object_ptr() }; let obj = unsafe { &*inner.as_js_object_ptr() };
let len = vm // 每次 next 入口校验:detach/越界抛 TypeError,auto 收缩按 live 长度停。
.ordinary_get(obj, length_si, inner) let view = crate::typed_array::get_typed_array_data(vm, inner)?;
.map(|v| if v.is_int() { v.as_int().max(0) as usize } else { 0 }) let view = crate::typed_array::ta_validate(vm, view, false)?;
.unwrap_or(0); let len = crate::typed_array::ta_live_length(view);
if index < len { if index < len {
let value = match crate::typed_array::typed_array_element_get(vm, obj, index as u32) { let value = match crate::typed_array::typed_array_element_get(vm, obj, index as u32) {
Ok(v) => v, Ok(v) => v,
+5 -3
View File
@@ -705,6 +705,8 @@ fn stringify_typed_array<H: VmHost>(
let has_space = !space.is_empty(); let has_space = !space.is_empty();
let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject); let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
let view = crate::typed_array::get_typed_array_data(vm, this_val)?; let view = crate::typed_array::get_typed_array_data(vm, this_val)?;
// live 口径:buffer 收缩/detach 后按当前 live 长度枚举(detached 空对象形态)。
let len = crate::typed_array::ta_live_length(view);
out.push('{'); out.push('{');
// 键集:白名单给定时 K = P(列表序,整数索引名读元素、越界名跳过、 // 键集:白名单给定时 K = P(列表序,整数索引名读元素、越界名跳过、
@@ -715,7 +717,7 @@ fn stringify_typed_array<H: VmHost>(
let si = vm.string_key_si(name); let si = vm.string_key_si(name);
let val = if is_int_key(si) { let val = if is_int_key(si) {
let idx = int_key_value(si); let idx = int_key_value(si);
if idx as usize >= view.length { if idx as usize >= len {
continue; continue;
} }
crate::typed_array::typed_array_element_get(vm, obj, idx) crate::typed_array::typed_array_element_get(vm, obj, idx)
@@ -727,8 +729,8 @@ fn stringify_typed_array<H: VmHost>(
} }
list list
} else { } else {
let mut list: Vec<(Vec<u16>, JsValue)> = Vec::with_capacity(view.length); let mut list: Vec<(Vec<u16>, JsValue)> = Vec::with_capacity(len);
for i in 0..view.length { for i in 0..len {
let val = crate::typed_array::typed_array_element_get(vm, obj, i as u32) let val = crate::typed_array::typed_array_element_get(vm, obj, i as u32)
.map_err(|msg| crate::error::create_type_error(vm, &msg))?; .map_err(|msg| crate::error::create_type_error(vm, &msg))?;
list.push((i.to_string().encode_utf16().collect(), val)); list.push((i.to_string().encode_utf16().collect(), val));
+372 -103
View File
@@ -55,29 +55,6 @@ fn to_index<H: VmHost>(vm: &mut H, value: JsValue, msg: &str) -> Result<usize, J
Ok(n.trunc() as usize) Ok(n.trunc() as usize)
} }
/// 按 ToIntegerOrInfinity 语义把索引归一化到 `[0, len]`(越界夹取);符号等不可
/// 转换值透传异常。
fn normalize_index<H: VmHost>(vm: &mut H, value: JsValue, len: usize) -> Result<usize, JsValue> {
let n = match vm.coerce_number_bounded(value) {
Ok(n) => n,
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
};
if n.is_nan() {
return Ok(0);
}
// ToIntegerOrInfinity:±Infinity 不截断,负无穷归 0、正无穷归 len,
// 避免饱和成 isize::MIN 后取负溢出。
if n.is_infinite() {
return Ok(if n < 0.0 { 0 } else { len });
}
let int = n.trunc() as isize;
if int < 0 {
Ok(len.saturating_sub(int.unsigned_abs()))
} else {
Ok((int as usize).min(len))
}
}
fn typed_array_proto_ptr<H: VmHost>(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject { fn typed_array_proto_ptr<H: VmHost>(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject {
let world = vm.session().builtin_world(); let world = vm.session().builtin_world();
match kind { match kind {
@@ -317,13 +294,61 @@ fn ta_is_oob(view: TypedArrayData) -> bool {
/// live 长度(元素面消费口径):越界(含 detach)一律 0,界内取规范 /// live 长度(元素面消费口径):越界(含 detach)一律 0,界内取规范
/// TypedArrayLength。 /// TypedArrayLength。
fn ta_live_length(view: TypedArrayData) -> usize { pub(crate) fn ta_live_length(view: TypedArrayData) -> usize {
if ta_is_oob(view) { if ta_is_oob(view) {
return 0; return 0;
} }
ta_spec_length(view) ta_spec_length(view)
} }
/// ValidateTypedArray 入口校验:越界视图(含 detach)抛 TypeError;原地
/// 写方法(`writable`)加查 buffer 写守卫(immutable 抛 TypeError),只读
/// 方法不查。
pub(crate) fn ta_validate<H: VmHost>(
vm: &mut H, view: TypedArrayData, writable: bool,
) -> Result<TypedArrayData, JsValue> {
if ta_is_oob(view) {
return Err(type_error(vm, "TypedArray is detached or out of bounds"));
}
if writable {
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).immutable } {
return Err(type_error(vm, "ArrayBuffer is immutable"));
}
}
}
}
Ok(view)
}
/// ToIntegerOrInfinity 裸转换(不夹取):NaN → +0,±Infinity 保留,有限值
/// 截断;转换异常(valueOf 抛错)原值上抛。
fn ta_to_integer_or_infinity<H: VmHost>(vm: &mut H, value: JsValue) -> Result<f64, JsValue> {
let n = match vm.coerce_number_bounded(value) {
Ok(n) => n,
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
};
Ok(if n.is_nan() { 0.0 } else { n.trunc() })
}
/// 把 ToIntegerOrInfinity 结果夹到 `[0, len]`:+Infinity → len,负值从尾部
/// 折算(max(len + k, 0),slice/fill/copyWithin/subarray 共用语义),正值夹
/// 到 len。
fn clamp_index_to_len(raw: f64, len: usize) -> usize {
if raw.is_infinite() {
return if raw > 0.0 { len } else { 0 };
}
let int = raw as isize;
if int < 0 {
len.saturating_sub(int.unsigned_abs())
} else {
(int as usize).min(len)
}
}
/// 读 TypedArray 指定整数索引的元素(供 VM 普通属性 get 的 typed 分支使用)。 /// 读 TypedArray 指定整数索引的元素(供 VM 普通属性 get 的 typed 分支使用)。
/// 越界返回 undefined;内部状态非法返回 Err(String)。 /// 越界返回 undefined;内部状态非法返回 Err(String)。
pub fn typed_array_element_get<H: VmHost>(vm: &mut H, obj: &JsObject, index: u32) -> Result<JsValue, String> { pub fn typed_array_element_get<H: VmHost>(vm: &mut H, obj: &JsObject, index: u32) -> Result<JsValue, String> {
@@ -865,8 +890,9 @@ typed_array_ctor!(biguint64array_constructor, BigUint64);
pub fn typed_array_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { 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 this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
// 负索引折算与越界判定走规范 TypedArrayLength 口径(auto 随 buffer、定长 // 负索引折算与越界判定走规范 TypedArrayLength 口径(auto 随 buffer、定长
// 静态),越界视图入口 TypeError 由调用方前置校验。 // 静态)。
let len = ta_spec_length(view); let len = ta_spec_length(view);
let idx: isize = if args.len() > 1 { let idx: isize = if args.len() > 1 {
let n = native_try!(ta_to_number(vm, vm.reg(args[1]))); let n = native_try!(ta_to_number(vm, vm.reg(args[1])));
@@ -894,31 +920,45 @@ pub fn typed_array_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if idx < 0 || idx as usize >= len { if idx < 0 || idx as usize >= len {
return NativeResult::Ok(JsValue::undefined()); return NativeResult::Ok(JsValue::undefined());
} }
let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); // 定长视图越界(含转换窗口内收缩):元素窗口起点已到/过 buffer 尾抛
// SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 // TypeError;窗口起点仍在 buffer 内(含 detach)读回 undefined(语料库
let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { // 双验收口径)。auto 视图经 live 门控自然越界读 undefined。
return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); if !view.auto_length && ta_is_oob(view) {
}; if let Some(buffer_len) = ta_buffer_byte_length(view) {
NativeResult::Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, idx as usize))) if view.byte_offset + (idx as usize) * view.kind.bytes_per_element() >= buffer_len {
return NativeResult::Err(type_error(vm, "TypedArray is detached or out of bounds"));
}
}
return NativeResult::Ok(JsValue::undefined());
}
NativeResult::Ok(native_try!(ta_read(vm, view, idx as usize)))
} }
/// `TypedArray.prototype.fill(value, start, end)`:用给定值填充区间,返回 this。 /// `TypedArray.prototype.fill(value, start, end)`:用给定值填充区间,返回 this。
pub fn typed_array_fill<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn typed_array_fill<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); 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 view = native_try!(get_typed_array_data(vm, this_val));
// 入口校验先于任何参数转换(detach 时参数副作用不可观测);见证长度
// 取入口口径(value 转换可伸缩 buffer,只影响二次校验后的 live 重读)。
native_try!(ta_validate(vm, view, true));
let len = ta_spec_length(view);
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
// 值只转换一次(valueOf 副作用一次),转换结果写入每个目标元素。 // 值只转换一次(valueOf 副作用一次),转换结果写入每个目标元素。
let elem = native_try!(ta_element_value(vm, view.kind, value)); let elem = native_try!(ta_element_value(vm, view.kind, value));
let start = if args.len() > 2 { let start_raw = if args.len() > 2 {
native_try!(normalize_index(vm, vm.reg(args[2]), view.length)) native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[2])))
} else { } else {
0 0.0
}; };
let end = if args.len() > 3 && !vm.reg(args[3]).is_undefined() { let end_raw = if args.len() > 3 && !vm.reg(args[3]).is_undefined() {
native_try!(normalize_index(vm, vm.reg(args[3]), view.length)) native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[3])))
} else { } else {
view.length f64::INFINITY
}; };
// 转换窗口内的 detach 由二次校验捕获;收缩只收 end(仅填仍适用的前缀)。
native_try!(ta_validate(vm, view, true));
let start = clamp_index_to_len(start_raw, len);
let end = clamp_index_to_len(end_raw, len).min(ta_spec_length(view));
let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer));
// SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。
let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else {
@@ -948,8 +988,13 @@ pub fn typed_array_fill<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 返回对象原值,不做身份改写。 /// 返回对象原值,不做身份改写。
/// - 用户 getter / 构造器执行窗口内 O 由寄存器根保活,Rust 局部值拷贝 /// - 用户 getter / 构造器执行窗口内 O 由寄存器根保活,Rust 局部值拷贝
/// 跨窗口有效(该窗口内无 GC 安全点)。 /// 跨窗口有效(该窗口内无 GC 安全点)。
///
/// # 参数
/// - `writable`:~write~ 访问模式(map/filter/slice 取 true,subarray
/// 等纯视图方法取 false);true 时结果过 immutable 写守卫(交付源
/// buffer 子视图豁免)。
fn typed_array_species_create<H: VmHost>( fn typed_array_species_create<H: VmHost>(
vm: &mut H, o_val: JsValue, kind: TypedArrayKind, args: Vec<JsValue>, expect_len: Option<usize>, vm: &mut H, o_val: JsValue, kind: TypedArrayKind, args: Vec<JsValue>, expect_len: Option<usize>, writable: bool,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let o_ptr = o_val.as_js_object_ptr(); let o_ptr = o_val.as_js_object_ptr();
let o_obj = unsafe { &*o_ptr }; let o_obj = unsafe { &*o_ptr };
@@ -982,8 +1027,21 @@ fn typed_array_species_create<H: VmHost>(
return Err(type_error(vm, "Species constructor did not return a TypedArray")); return Err(type_error(vm, "Species constructor did not return a TypedArray"));
} }
let view = get_typed_array_data(vm, result)?; let view = get_typed_array_data(vm, result)?;
// 规范 TypedArrayCreate:构造结果过 ValidateTypedArray(构造器窗口内
// detach/越界结果抛 TypeError,与 count 无关)。写守卫(~write~ 访问
// 模式的 immutable 检查)仅在结果交付的不是源 buffer 时生效:构造器
// 返回源 buffer 子视图正常交付(语料库双验收)。
let write_guard = writable
&& match get_typed_array_data(vm, o_val) {
Ok(src) => src.buffer != view.buffer,
Err(_) => true,
};
ta_validate(vm, view, write_guard)?;
// 长度不足校验取 live 口径(auto 视图的静态长度在 buffer 再伸缩后
// 失真);语料库的 immutable-destination 族红即由本条触发(结果长度
// 小于请求数)。
if let Some(expect) = expect_len { if let Some(expect) = expect_len {
if view.length < expect { if ta_live_length(view) < expect {
return Err(type_error(vm, "Species constructor returned a TypedArray with insufficient length")); return Err(type_error(vm, "Species constructor returned a TypedArray with insufficient length"));
} }
} }
@@ -993,22 +1051,31 @@ fn typed_array_species_create<H: VmHost>(
/// `TypedArray.prototype.slice(start, end)`:复制区间元素生成新同类型 TypedArray。 /// `TypedArray.prototype.slice(start, end)`:复制区间元素生成新同类型 TypedArray。
/// ///
/// # 步骤 /// # 步骤
/// 1. 归一 start/end 得 count,经 TypedArraySpeciesCreate(O, « count ») 建目标。 /// 1. start/end 转换(副作用可 detach)后入口校验,见证长度夹取得 count,
/// 2. 逐元素读源元素 → 转换为目标元素类型 → 写入目标(读转写交错;目标与 /// 经 TypedArraySpeciesCreate(O, « count ») 建目标(零初始化)。
/// 源共享 buffer 别名时,本轮读须看到前一轮写入后的值)。 /// 2. 构造窗口后再校验一次:species 构造器可收缩/分离源 buffer。定长视图
/// 被裁到越界抛 TypeError;auto 视图按新 live 长度收口。
/// 3. 逐元素读→写交错拷贝:源索引越界(构造期收缩)时跳过写入,目标保持
/// 零初始化;别名级联须看到前一轮写入。
pub fn typed_array_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn typed_array_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); 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 view = native_try!(get_typed_array_data(vm, this_val));
let start = if args.len() > 1 { // 入口校验先于 start/end 转换(detach 时转换副作用不可观测),
native_try!(normalize_index(vm, vm.reg(args[1]), view.length)) // 见证长度取入口口径。
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view);
let start_raw = if args.len() > 1 {
native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[1])))
} else { } else {
0 0.0
}; };
let end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() { let end_raw = if args.len() > 2 && !vm.reg(args[2]).is_undefined() {
native_try!(normalize_index(vm, vm.reg(args[2]), view.length)) native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[2])))
} else { } else {
view.length f64::INFINITY
}; };
let start = clamp_index_to_len(start_raw, len);
let end = clamp_index_to_len(end_raw, len);
let count = end.max(start).saturating_sub(start); let count = end.max(start).saturating_sub(start);
let a = native_try!(typed_array_species_create( let a = native_try!(typed_array_species_create(
vm, vm,
@@ -1016,10 +1083,21 @@ pub fn typed_array_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
view.kind, view.kind,
vec![JsValue::int(count as i32)], vec![JsValue::int(count as i32)],
Some(count), Some(count),
true,
)); ));
for k in 0..count { // 逐元素读→写交错:别名级联(目标与源共享 buffer)时本轮读看到前一轮
let elem = native_try!(ta_read(vm, view, start + k)); // 写入;源索引已越界(构造器收缩源 buffer)时跳过写入,目标保持
native_try!(set_typed_array_element(vm, a, k, elem)); // 零初始化。count 为 0 时整段跳过(含构造器 detach 窗口,规范
// "If count > 0" 臂)。
if count > 0 {
// species 构造窗口后再校验源视图(构造器可 resize/detach 源 buffer)。
native_try!(ta_validate(vm, view, false));
for k in 0..count {
if start + k < ta_live_length(view) {
let elem = native_try!(ta_read(vm, view, start + k));
native_try!(set_typed_array_element(vm, a, k, elem));
}
}
} }
NativeResult::Ok(a) NativeResult::Ok(a)
} }
@@ -1033,31 +1111,37 @@ pub fn typed_array_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
pub fn typed_array_subarray<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn typed_array_subarray<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); 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 view = native_try!(get_typed_array_data(vm, this_val));
let start = if args.len() > 1 { // 见证长度先于 start/end 转换:调用时越界/detach 不抛(源长度记 0),
native_try!(normalize_index(vm, vm.reg(args[1]), view.length)) // 转换窗口内的 detach 只影响 species 构造,不改入口口径。
let len = ta_live_length(view);
let start_raw = if args.len() > 1 {
native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[1])))
} else { } else {
0 0.0
}; };
let end_undefined = args.len() <= 2 || vm.reg(args[2]).is_undefined(); let end_undefined = args.len() <= 2 || vm.reg(args[2]).is_undefined();
let end = if !end_undefined { let end_raw = if end_undefined {
native_try!(normalize_index(vm, vm.reg(args[2]), view.length)) f64::INFINITY
} else { } else {
view.length native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[2])))
}; };
let start = clamp_index_to_len(start_raw, len);
let end = clamp_index_to_len(end_raw, len);
let count = end.max(start).saturating_sub(start); let count = end.max(start).saturating_sub(start);
let byte_offset = view.byte_offset + start * view.kind.bytes_per_element(); let byte_offset = view.byte_offset + start * view.kind.bytes_per_element();
// end 未给且底层 buffer 可缩放:构造参数省略 count,子视图随 buffer // 2 参构造(省略 count,子视图随 buffer 伸缩)仅当 end 未给、源视图为
// 伸缩(定长 buffer 恒三参,count 定死区间)。 // auto 且 buffer 可缩放;定长视图恒三参定死区间(窗口超 buffer 由构造器
// 抛 RangeError)。
let resizable = match array_buffer_payload(vm, view.buffer) { let resizable = match array_buffer_payload(vm, view.buffer) {
Ok(p) => unsafe { &*p }.max_byte_length != 0, Ok(p) => unsafe { &*p }.max_byte_length != 0,
Err(_) => false, Err(_) => false,
}; };
let ctor_args = if end_undefined && resizable { let ctor_args = if end_undefined && view.auto_length && resizable {
vec![view.buffer, JsValue::int(byte_offset as i32)] vec![view.buffer, JsValue::int(byte_offset as i32)]
} else { } else {
vec![view.buffer, JsValue::int(byte_offset as i32), JsValue::int(count as i32)] vec![view.buffer, JsValue::int(byte_offset as i32), JsValue::int(count as i32)]
}; };
let result = native_try!(typed_array_species_create(vm, this_val, view.kind, ctor_args, None)); let result = native_try!(typed_array_species_create(vm, this_val, view.kind, ctor_args, None, false));
NativeResult::Ok(result) NativeResult::Ok(result)
} }
@@ -1074,30 +1158,106 @@ pub fn typed_array_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Err(type_error(vm, "TypedArray.set source required")); return NativeResult::Err(type_error(vm, "TypedArray.set source required"));
} }
let source = vm.reg(args[1]); let source = vm.reg(args[1]);
let offset = if args.len() > 2 { // 入口校验(含 immutable 写守卫)先于任何参数副作用;offset 按
native_try!(to_index(vm, vm.reg(args[2]), "TypedArray.set offset out of bounds")) // ToIntegerOrInfinity 取整(负值 / +∞ 抛 RangeError),转换窗口内的
// detach 由二次校验捕获。
native_try!(ta_validate(vm, view, true));
let offset_raw = if args.len() > 2 {
native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[2])))
} else { } else {
0 0.0
}; };
let values = native_try!(collect_array_like(vm, source, false)); if offset_raw < 0.0 || offset_raw.is_infinite() {
if offset > view.length || values.len() > view.length - offset {
return NativeResult::Err(range_error(vm, "TypedArray.set offset out of bounds")); return NativeResult::Err(range_error(vm, "TypedArray.set offset out of bounds"));
} }
let mut converted = Vec::with_capacity(values.len()); let offset = offset_raw as usize;
for v in values { native_try!(ta_validate(vm, view, true));
converted.push(native_try!(ta_element_value(vm, view.kind, v))); let len = ta_spec_length(view);
// 源长度在二次校验后读:TA 源按 live 口径,array-like 源读 `length`
// 属性(ToObject 装箱基元源)。
let source_obj =
native_try!(oxide_runtime_api::to_object(source, vm).map_err(|e| crate::iterator::engine_error(vm, &e)));
let (source_len, source_kind) = match get_typed_array_data(vm, source_obj) {
Ok(sview) => {
native_try!(ta_validate(vm, sview, false));
// 同 buffer 重叠:规范先克隆源字节区,按 live 口径快照源元素,
// 循环中读快照(重叠写不污染未读源位)。
if sview.buffer == view.buffer {
let snap: Vec<JsValue> = native_try!((0..ta_live_length(sview))
.map(|i| ta_read(vm, sview, i))
.collect::<Result<_, _>>());
(snap.len(), SetSourceKind::TypedArraySnapshot(snap))
} else {
(ta_live_length(sview), SetSourceKind::TypedArray(sview))
}
}
Err(_) => (native_try!(set_array_like_length(vm, source_obj)), SetSourceKind::ArrayLike(source_obj)),
};
if (source_len as u64) + (offset as u64) > len as u64 {
return NativeResult::Err(range_error(vm, "TypedArray.set offset out of bounds"));
} }
let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer));
// SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。
let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else {
return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid"));
}; };
for (i, elem) in converted.into_iter().enumerate() { // 逐元素惰性读源→转换→写:循环中的 detach/收缩使后续写入静默失效
write_element(vm, view.kind, buffer, absolute_byte_offset(view, offset + i), elem); // (目标越界不写,与规范 SetValueInBuffer 边界无操作同语义)。
for k in 0..source_len {
let value = match &source_kind {
SetSourceKind::TypedArray(sview) => native_try!(ta_read(vm, *sview, k)),
SetSourceKind::TypedArraySnapshot(snap) => snap[k],
SetSourceKind::ArrayLike(src) => native_try!(set_source_get(vm, *src, k)),
};
let elem = native_try!(ta_element_value(vm, view.kind, value));
if offset + k < ta_live_length(view) {
write_element(vm, view.kind, buffer, absolute_byte_offset(view, offset + k), elem);
}
} }
NativeResult::Ok(JsValue::undefined()) NativeResult::Ok(JsValue::undefined())
} }
/// `set` 的源形态:TA 源直读元素区(同 buffer 先快照);array-like 源
/// 按索引键逐取。
enum SetSourceKind {
TypedArray(TypedArrayData),
TypedArraySnapshot(Vec<JsValue>),
ArrayLike(JsValue),
}
/// array-like 源的 `length` 属性读 + ToLength 收敛(NaN/负取 0,上限 2^53-1)。
fn set_array_like_length<H: VmHost>(vm: &mut H, source: JsValue) -> Result<usize, JsValue> {
let si = vm.kernel_core().perm_interner().intern("length").0;
let ptr = source.as_js_object_ptr();
let len_val = unsafe { vm.ordinary_get(&*ptr, si, source) }.map_err(|e| crate::iterator::engine_error(vm, &e))?;
let n = oxide_runtime_api::to_number_full(len_val, vm).map_err(|e| crate::iterator::engine_error(vm, &e))?;
if n.is_nan() || n <= 0.0 {
return Ok(0);
}
Ok((n.min(9_007_199_254_740_991.0).trunc() as u64) as usize)
}
/// array-like 源第 k 个元素:Array 走密集槽,字符串源按 UTF-16 单元,
/// 其余按 ToString(k) 键普通 Get。
fn set_source_get<H: VmHost>(vm: &mut H, source: JsValue, k: usize) -> Result<JsValue, JsValue> {
let ptr = source.as_js_object_ptr();
if !ptr.is_null() {
let obj = unsafe { &*ptr };
if obj.is_array() {
return Ok(obj.get_prop_at(k));
}
if let Some(units) = crate::array::string_arraylike_units(vm, source) {
return Ok(crate::array::unit_string_value(vm, units[k.min(units.len() - 1)]));
}
}
let key = vm.new_string(&k.to_string());
let key_si = vm.property_key_si(key);
if ptr.is_null() {
return Ok(JsValue::undefined());
}
unsafe { vm.ordinary_get(&*ptr, key_si, source) }.map_err(|e| crate::iterator::engine_error(vm, &e))
}
/// `%TypedArray%` 抽象构造器:不可 new 也不可调用,任何调用方式都抛 TypeError。 /// `%TypedArray%` 抽象构造器:不可 new 也不可调用,任何调用方式都抛 TypeError。
pub fn typed_array_abstract_constructor<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult { pub fn typed_array_abstract_constructor<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
NativeResult::Err(crate::error::create_type_error(vm, "TypedArray is not a constructor")) NativeResult::Err(crate::error::create_type_error(vm, "TypedArray is not a constructor"))
@@ -1234,6 +1394,11 @@ fn ta_read<H: VmHost>(vm: &mut H, view: TypedArrayData, index: usize) -> Result<
/// 把已转换的值写入 TypedArray 指定索引(视图 data 已取出的形式,供原型方法内部使用)。 /// 把已转换的值写入 TypedArray 指定索引(视图 data 已取出的形式,供原型方法内部使用)。
fn ta_write<H: VmHost>(vm: &mut H, view: TypedArrayData, index: usize, value: JsValue) -> Result<(), JsValue> { fn ta_write<H: VmHost>(vm: &mut H, view: TypedArrayData, index: usize, value: JsValue) -> Result<(), JsValue> {
// 越界(含循环中的 detach/收缩)静默失效,与规范 SetValueInBuffer
// 边界无操作同语义。
if index >= ta_live_length(view) {
return Ok(());
}
let payload_ptr = array_buffer_payload(vm, view.buffer)?; let payload_ptr = array_buffer_payload(vm, view.buffer)?;
// SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。
let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else {
@@ -1267,6 +1432,7 @@ 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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
// 入口见证:循环长度一次捕获,回调期 buffer 伸缩不改本轮范围。 // 入口见证:循环长度一次捕获,回调期 buffer 伸缩不改本轮范围。
let len = ta_spec_length(view); let len = ta_spec_length(view);
for i in 0..len { for i in 0..len {
@@ -1286,16 +1452,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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
// 入口见证:目标长度 = 源见证长度,回调期 buffer 伸缩不改本轮范围。 // 入口见证:目标长度 = 源见证长度,回调期 buffer 伸缩不改本轮范围。
let len = ta_spec_length(view); let len = ta_spec_length(view);
// 目标对象经 species 构造(长度 = 源长度);逐元素"回调 → 写入"交错, // 目标对象经 species 构造(长度 = 源长度);逐元素"回调 → 写入"
// 目标与源别名时后续回调看到前一轮写入值。 // 交错,目标与源别名时后续回调看到前一轮写入值。
let a = native_try!(typed_array_species_create( let a = native_try!(typed_array_species_create(
vm, vm,
this_val, this_val,
view.kind, view.kind,
vec![JsValue::int(len as i32)], vec![JsValue::int(len as i32)],
Some(len), Some(len),
true,
)); ));
for i in 0..len { for i in 0..len {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
@@ -1315,6 +1483,7 @@ 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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
// 先全量回调得通过元素(规范序:count 先于目标构造),再经 species // 先全量回调得通过元素(规范序:count 先于目标构造),再经 species
// 构造目标(长度 = 通过数)并顺序写入;循环走入口见证长度。 // 构造目标(长度 = 通过数)并顺序写入;循环走入口见证长度。
let len = ta_spec_length(view); let len = ta_spec_length(view);
@@ -1332,6 +1501,7 @@ pub fn typed_array_filter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
view.kind, view.kind,
vec![JsValue::int(kept.len() as i32)], vec![JsValue::int(kept.len() as i32)],
Some(kept.len()), Some(kept.len()),
true,
)); ));
for (k, elem) in kept.into_iter().enumerate() { for (k, elem) in kept.into_iter().enumerate() {
native_try!(set_typed_array_element(vm, a, k, elem)); native_try!(set_typed_array_element(vm, a, k, elem));
@@ -1353,6 +1523,7 @@ pub fn typed_array_reduce<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Err(type_error(vm, "callback is not a function")); return NativeResult::Err(type_error(vm, "callback is not a function"));
} }
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1]))); let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
native_try!(ta_validate(vm, view, false));
let (mut accumulator, start_idx) = if args.len() > 2 { let (mut accumulator, start_idx) = if args.len() > 2 {
(vm.reg(args[2]), 0usize) (vm.reg(args[2]), 0usize)
} else { } else {
@@ -1383,6 +1554,7 @@ pub fn typed_array_reduce_right<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
return NativeResult::Err(type_error(vm, "callback is not a function")); return NativeResult::Err(type_error(vm, "callback is not a function"));
} }
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1]))); let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
native_try!(ta_validate(vm, view, false));
let (mut accumulator, start_idx): (JsValue, i32) = if args.len() > 2 { let (mut accumulator, start_idx): (JsValue, i32) = if args.len() > 2 {
(vm.reg(args[2]), len as i32 - 1) (vm.reg(args[2]), len as i32 - 1)
} else { } else {
@@ -1409,6 +1581,7 @@ 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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
for i in 0..len { for i in 0..len {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
@@ -1429,6 +1602,7 @@ 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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
for i in 0..len { for i in 0..len {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
@@ -1449,6 +1623,7 @@ 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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
for i in 0..len { for i in 0..len {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
@@ -1469,6 +1644,7 @@ 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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
for i in 0..len { for i in 0..len {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
@@ -1489,6 +1665,7 @@ 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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
for i in (0..len).rev() { for i in (0..len).rev() {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
@@ -1510,6 +1687,7 @@ 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 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 this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
for i in (0..len).rev() { for i in (0..len).rev() {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
@@ -1547,6 +1725,7 @@ 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 { 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 this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
if len == 0 || args.len() < 2 { if len == 0 || args.len() < 2 {
return NativeResult::Ok(JsValue::int(-1)); return NativeResult::Ok(JsValue::int(-1));
@@ -1559,6 +1738,11 @@ pub fn typed_array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
}; };
for i in from_index..len { for i in from_index..len {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
// 越界(转换窗口内 detach/收缩)视同 HasProperty 为假:跳过,
// 不参与相等比较(in-bounds 元素恒非 undefined)。
if elem.is_undefined() {
continue;
}
if oxide_runtime_api::strict_equality(elem, target) { if oxide_runtime_api::strict_equality(elem, target) {
return NativeResult::Ok(JsValue::int(i as i32)); return NativeResult::Ok(JsValue::int(i as i32));
} }
@@ -1570,6 +1754,7 @@ 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 { 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 this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
if len == 0 { if len == 0 {
return NativeResult::Ok(JsValue::int(-1)); return NativeResult::Ok(JsValue::int(-1));
@@ -1607,6 +1792,11 @@ pub fn typed_array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
} }
for i in (0..=from_index as usize).rev() { for i in (0..=from_index as usize).rev() {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
// 越界(转换窗口内 detach/收缩)视同 HasProperty 为假:跳过,
// 不参与相等比较(in-bounds 元素恒非 undefined)。
if elem.is_undefined() {
continue;
}
if oxide_runtime_api::strict_equality(elem, target) { if oxide_runtime_api::strict_equality(elem, target) {
return NativeResult::Ok(JsValue::int(i as i32)); return NativeResult::Ok(JsValue::int(i as i32));
} }
@@ -1619,6 +1809,7 @@ 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 { 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 this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
if len == 0 { if len == 0 {
return NativeResult::Ok(JsValue::bool(false)); return NativeResult::Ok(JsValue::bool(false));
@@ -1642,18 +1833,30 @@ pub fn typed_array_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn typed_array_join<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn typed_array_join<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); 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 view = native_try!(get_typed_array_data(vm, this_val));
let sep = if args.len() > 1 { // 见证先于分隔符转换:转换可伸缩 buffer,循环长度取入口口径(起始长度),
// 伸缩后的新元素不进本轮。
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view);
// 分隔符缺省或 undefined 时取 ","(规范:undefined 视同未给);
// 其余值过完整 ToString(null → "null")。
let sep = if args.len() > 1 && !vm.reg(args[1]).is_undefined() {
native_try!( native_try!(
oxide_runtime_api::to_string_full(vm.reg(args[1]), vm).map_err(|e| crate::iterator::engine_error(vm, &e)) oxide_runtime_api::to_string_full(vm.reg(args[1]), vm).map_err(|e| crate::iterator::engine_error(vm, &e))
) )
} else { } else {
",".to_string() ",".to_string()
}; };
let len = ta_spec_length(view);
let mut parts = Vec::with_capacity(len); let mut parts = Vec::with_capacity(len);
for i in 0..len { for i in 0..len {
let elem = native_try!(ta_read(vm, view, i)); let elem = native_try!(ta_read(vm, view, i));
parts.push(oxide_runtime_api::to_string(elem)); // 越界/detach 读回 undefined(或 null)时拼空串,与 Array.join 同
// 口径。
let part = if elem.is_undefined() || elem.is_null() {
String::new()
} else {
oxide_runtime_api::to_string(elem)
};
parts.push(part);
} }
NativeResult::Ok(vm.new_string(&parts.join(&sep))) NativeResult::Ok(vm.new_string(&parts.join(&sep)))
} }
@@ -1661,7 +1864,8 @@ pub fn typed_array_join<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `%TypedArray%.prototype.values()`:返回迭代元素值的迭代器。 /// `%TypedArray%.prototype.values()`:返回迭代元素值的迭代器。
pub fn typed_array_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn typed_array_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
// TA 迭代器与 Array 共享 %ArrayIteratorPrototype%(规范 CreateArrayIterator // TA 迭代器与 Array 共享 %ArrayIteratorPrototype%(规范 CreateArrayIterator
// 同族),next 由原型提供;接收者合法性已在上方校验。 // 同族),next 由原型提供;接收者合法性已在上方校验。
match crate::array::make_array_iterator(vm, this_val, crate::array::ARRAY_ITER_KIND_VALUES) { match crate::array::make_array_iterator(vm, this_val, crate::array::ARRAY_ITER_KIND_VALUES) {
@@ -1673,7 +1877,8 @@ pub fn typed_array_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `%TypedArray%.prototype.keys()`:返回迭代元素索引的迭代器。 /// `%TypedArray%.prototype.keys()`:返回迭代元素索引的迭代器。
pub fn typed_array_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn typed_array_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
match crate::array::make_array_iterator(vm, this_val, crate::array::ARRAY_ITER_KIND_KEYS) { match crate::array::make_array_iterator(vm, this_val, crate::array::ARRAY_ITER_KIND_KEYS) {
Ok(iter) => NativeResult::Ok(iter), Ok(iter) => NativeResult::Ok(iter),
Err(err) => NativeResult::Err(err), Err(err) => NativeResult::Err(err),
@@ -1683,24 +1888,36 @@ pub fn typed_array_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `%TypedArray%.prototype.entries()`:返回迭代 `[index, element]` 对的迭代器。 /// `%TypedArray%.prototype.entries()`:返回迭代 `[index, element]` 对的迭代器。
pub fn typed_array_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn typed_array_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
match crate::array::make_array_iterator(vm, this_val, crate::array::ARRAY_ITER_KIND_ENTRIES) { match crate::array::make_array_iterator(vm, this_val, crate::array::ARRAY_ITER_KIND_ENTRIES) {
Ok(iter) => NativeResult::Ok(iter), Ok(iter) => NativeResult::Ok(iter),
Err(err) => NativeResult::Err(err), Err(err) => NativeResult::Err(err),
} }
} }
/// 默认数值排序比较:NaN 视为最大排到末尾,其余按数值升序。 /// 默认数值排序比较(规范 CompareTypedArrayElements 无比较器臂):NaN
/// 视为最大排到末尾;-0 排在 +0 之前;其余按数值升序。
fn default_ta_order(a: f64, b: f64) -> std::cmp::Ordering { fn default_ta_order(a: f64, b: f64) -> std::cmp::Ordering {
if a.is_nan() && b.is_nan() { if a.is_nan() && b.is_nan() {
std::cmp::Ordering::Equal return std::cmp::Ordering::Equal;
} else if a.is_nan() {
std::cmp::Ordering::Greater
} else if b.is_nan() {
std::cmp::Ordering::Less
} else {
a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)
} }
if a.is_nan() {
return std::cmp::Ordering::Greater;
}
if b.is_nan() {
return std::cmp::Ordering::Less;
}
if a == 0.0 && b == 0.0 {
return if a.is_sign_negative() && !b.is_sign_negative() {
std::cmp::Ordering::Less
} else if b.is_sign_negative() && !a.is_sign_negative() {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Equal
};
}
a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)
} }
/// `%TypedArray%.prototype.sort(comparefn)`:原地排序,默认按数值升序(NaN 排末尾; /// `%TypedArray%.prototype.sort(comparefn)`:原地排序,默认按数值升序(NaN 排末尾;
@@ -1719,6 +1936,9 @@ pub fn typed_array_sort<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else { } else {
None None
}; };
// 原地写方法:入口校验(detach/越界/immutable 抛 TypeError),比较器
// 检查先于校验(规范序)。
native_try!(ta_validate(vm, view, true));
let len = ta_spec_length(view); let len = ta_spec_length(view);
let mut vals: Vec<JsValue> = Vec::with_capacity(len); let mut vals: Vec<JsValue> = Vec::with_capacity(len);
for i in 0..len { for i in 0..len {
@@ -1732,7 +1952,15 @@ pub fn typed_array_sort<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if let Some(cb) = comparator { if let Some(cb) = comparator {
match crate::array::invoke_native_callback(vm, cb, JsValue::undefined(), &[*a, *b]) { match crate::array::invoke_native_callback(vm, cb, JsValue::undefined(), &[*a, *b]) {
NativeResult::Ok(r) => { NativeResult::Ok(r) => {
let n = oxide_runtime_api::to_number(r); // 规范 CompareTypedArrayElements:结果过完整 ToNumber
// (对象触发 toPrimitive),NaN 视同 +0;异常上抛。
let n = match oxide_runtime_api::to_number_full(r, vm) {
Ok(n) => n,
Err(e) => {
sort_error = Some(crate::iterator::engine_error(vm, &e));
return std::cmp::Ordering::Equal;
}
};
if n.is_nan() || n == 0.0 { if n.is_nan() || n == 0.0 {
std::cmp::Ordering::Equal std::cmp::Ordering::Equal
} else if n < 0.0 { } else if n < 0.0 {
@@ -1778,6 +2006,7 @@ 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 { 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 this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, true));
let len = ta_spec_length(view); let len = ta_spec_length(view);
let mut i = 0; let mut i = 0;
let mut j = len.saturating_sub(1); let mut j = len.saturating_sub(1);
@@ -1796,25 +2025,38 @@ 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 { 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 this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
// 入口校验先于三索引转换(detach 时转换副作用不可观测),见证长度
// 取入口口径(转换窗口内的 detach 不影响入口长度)。
native_try!(ta_validate(vm, view, true));
let len = ta_spec_length(view); let len = ta_spec_length(view);
let target = if args.len() > 1 { let target_raw = if args.len() > 1 {
native_try!(normalize_index(vm, vm.reg(args[1]), len)) native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[1])))
} else { } else {
0 0.0
}; };
let start = if args.len() > 2 { let start_raw = if args.len() > 2 {
native_try!(normalize_index(vm, vm.reg(args[2]), len)) native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[2])))
} else { } else {
0 0.0
}; };
let end = if args.len() > 3 && !vm.reg(args[3]).is_undefined() { let end_raw = if args.len() > 3 && !vm.reg(args[3]).is_undefined() {
native_try!(normalize_index(vm, vm.reg(args[3]), len)) native_try!(ta_to_integer_or_infinity(vm, vm.reg(args[3])))
} else { } else {
len f64::INFINITY
}; };
let count = end.max(start).saturating_sub(start).min(len.saturating_sub(target)); let target = clamp_index_to_len(target_raw, len);
let start = clamp_index_to_len(start_raw, len);
let end = clamp_index_to_len(end_raw, len);
let mut count = end.max(start).saturating_sub(start).min(len.saturating_sub(target));
if count > 0 {
// 转换窗口内的 detach 由二次校验捕获;收缩后的 live 长度再收
// 一次 count(仅拷仍适用的最长前缀)。
native_try!(ta_validate(vm, view, true));
let len2 = ta_spec_length(view);
count = count.min(len2.saturating_sub(start)).min(len2.saturating_sub(target));
}
// 目标区间前移与源区间重叠时逆序遍历,避免覆盖未读的源元素。 // 目标区间前移与源区间重叠时逆序遍历,避免覆盖未读的源元素。
let (mut from, mut to, direction) = if start < target && target < start + count { let (mut from, mut to, direction) = if count > 0 && start < target && target < start + count {
(start + count - 1, target + count - 1, -1isize) (start + count - 1, target + count - 1, -1isize)
} else { } else {
(start, target, 1isize) (start, target, 1isize)
@@ -1852,6 +2094,7 @@ 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 { 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 this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
if len == 0 { if len == 0 {
return NativeResult::Ok(vm.new_string("")); return NativeResult::Ok(vm.new_string(""));
@@ -1872,6 +2115,7 @@ 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 { 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 this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(get_typed_array_data(vm, this_val)); let view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
let a = JsValue::from_js_object(create_same_type_typed_array(vm, view.kind, len)); let a = JsValue::from_js_object(create_same_type_typed_array(vm, view.kind, len));
for i in 0..len { for i in 0..len {
@@ -1886,6 +2130,7 @@ pub fn typed_array_to_reversed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
pub fn typed_array_to_sorted<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn typed_array_to_sorted<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); 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 view = native_try!(get_typed_array_data(vm, this_val));
native_try!(ta_validate(vm, view, false));
let comparator = if args.len() > 1 { let comparator = if args.len() > 1 {
let c = vm.reg(args[1]); let c = vm.reg(args[1]);
if c.is_undefined() { if c.is_undefined() {
@@ -1909,7 +2154,15 @@ pub fn typed_array_to_sorted<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
if let Some(cb) = comparator { if let Some(cb) = comparator {
match crate::array::invoke_native_callback(vm, cb, JsValue::undefined(), &[*a, *b]) { match crate::array::invoke_native_callback(vm, cb, JsValue::undefined(), &[*a, *b]) {
NativeResult::Ok(r) => { NativeResult::Ok(r) => {
let n = oxide_runtime_api::to_number(r); // 规范 CompareTypedArrayElements:结果过完整 ToNumber
// (对象触发 toPrimitive),NaN 视同 +0;异常上抛。
let n = match oxide_runtime_api::to_number_full(r, vm) {
Ok(n) => n,
Err(e) => {
sort_error = Some(crate::iterator::engine_error(vm, &e));
return std::cmp::Ordering::Equal;
}
};
if n.is_nan() || n == 0.0 { if n.is_nan() || n == 0.0 {
std::cmp::Ordering::Equal std::cmp::Ordering::Equal
} else if n < 0.0 { } else if n < 0.0 {
@@ -1960,6 +2213,7 @@ pub fn typed_array_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 { if args.len() < 2 {
return NativeResult::Err(type_error(vm, "TypedArray.prototype.with requires an index")); return NativeResult::Err(type_error(vm, "TypedArray.prototype.with requires an index"));
} }
native_try!(ta_validate(vm, view, false));
let len = ta_spec_length(view); let len = ta_spec_length(view);
// ToIntegerOrInfinity(index),负值折算为 len + index。 // ToIntegerOrInfinity(index),负值折算为 len + index。
let raw = native_try!(ta_to_number(vm, vm.reg(args[1]))); let raw = native_try!(ta_to_number(vm, vm.reg(args[1])));
@@ -1971,15 +2225,17 @@ pub fn typed_array_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
raw.trunc() raw.trunc()
}; };
let actual_index = if relative_index >= 0.0 { relative_index } else { len as f64 + relative_index }; let actual_index = if relative_index >= 0.0 { relative_index } else { len as f64 + relative_index };
// 规范步 7/8:value 先转换(副作用/抛错原样上抛),步 9 才做越界检查。 // 规范序:value 先转换(副作用可伸缩 buffer),越界检查(IsValidIntegerIndex
// 口径)在转换后对当前 live 长度做;结果长度取入口见证。
let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let replacement = native_try!(ta_element_value(vm, view.kind, value)); let replacement = native_try!(ta_element_value(vm, view.kind, value));
if actual_index.is_nan() || actual_index < 0.0 || actual_index >= len as f64 { if actual_index.is_nan() || actual_index < 0.0 || actual_index >= ta_live_length(view) as f64 {
return NativeResult::Err(range_error(vm, "Invalid typed array index")); return NativeResult::Err(range_error(vm, "Invalid typed array index"));
} }
let index = actual_index as usize; let index = actual_index as usize;
let a = JsValue::from_js_object(create_same_type_typed_array(vm, view.kind, len)); let a = JsValue::from_js_object(create_same_type_typed_array(vm, view.kind, len));
// 目标索引写已转换的 value(副作用已先于源读发生),其余索引顺序拷源元素。 // 目标索引写已转换的 value(副作用已先于源读发生),其余索引顺序拷源元素;
// 索引超结果长度时写入自然无操作(SetElement 边界语义)。
for i in 0..len { for i in 0..len {
let elem = if i == index { replacement } else { native_try!(ta_read(vm, view, i)) }; let elem = if i == index { replacement } else { native_try!(ta_read(vm, view, i)) };
native_try!(set_typed_array_element(vm, a, i, elem)); native_try!(set_typed_array_element(vm, a, i, elem));
@@ -2430,6 +2686,19 @@ mod tests {
} }
} }
// M 见证夹取边界:±Infinity 归端点,负值从尾部折算(max(len+k, 0)),
// 有限值截断后夹到长度。
#[test]
fn clamp_index_edges() {
assert_eq!(clamp_index_to_len(f64::INFINITY, 4), 4);
assert_eq!(clamp_index_to_len(f64::NEG_INFINITY, 4), 0);
assert_eq!(clamp_index_to_len(-2.0, 4), 2);
assert_eq!(clamp_index_to_len(-9.0, 4), 0);
assert_eq!(clamp_index_to_len(9.0, 4), 4);
assert_eq!(clamp_index_to_len(2.7, 4), 2);
assert_eq!(clamp_index_to_len(0.0, 0), 0);
}
// A 整数键(length 除注明外取 2)。 // A 整数键(length 除注明外取 2)。
#[test] #[test]
fn gate_text_integer_keys() { fn gate_text_integer_keys() {
@@ -2,7 +2,7 @@ use std::sync::Arc;
use crate::bind_constructor; use crate::bind_constructor;
use crate::bindings::{ use crate::bindings::{
apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, bind_global_value, bind_method_alias, apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, bind_global_value,
bind_well_known_method_alias, configure_native_constructor, bind_well_known_method_alias, configure_native_constructor,
}; };
use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_kernel::kernel::{KernelCore, KernelSession};
@@ -121,8 +121,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
core, core,
&[ &[
("at", oxide_builtins::typed_array::typed_array_at::<crate::vm::Vm> as *const (), 1), ("at", oxide_builtins::typed_array::typed_array_at::<crate::vm::Vm> as *const (), 1),
("fill", oxide_builtins::typed_array::typed_array_fill::<crate::vm::Vm> as *const (), 3), ("fill", oxide_builtins::typed_array::typed_array_fill::<crate::vm::Vm> as *const (), 1),
("set", oxide_builtins::typed_array::typed_array_set::<crate::vm::Vm> as *const (), 2), ("set", oxide_builtins::typed_array::typed_array_set::<crate::vm::Vm> as *const (), 1),
("slice", oxide_builtins::typed_array::typed_array_slice::<crate::vm::Vm> as *const (), 2), ("slice", oxide_builtins::typed_array::typed_array_slice::<crate::vm::Vm> as *const (), 2),
( (
"subarray", "subarray",
@@ -212,7 +212,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
("with", oxide_builtins::typed_array::typed_array_with::<crate::vm::Vm> as *const (), 2), ("with", oxide_builtins::typed_array::typed_array_with::<crate::vm::Vm> as *const (), 2),
], ],
); );
bind_method_alias(core, shared_proto, "values", "keys"); // `keys` 已由方法表独立绑定(索引流 ≠ 值流);@@iterator 与 `values`
// 共享同一函数对象。
bind_well_known_method_alias(core, shared_proto, "values", 0); bind_well_known_method_alias(core, shared_proto, "values", 0);
// 原型访问器:视图属性(buffer/byteOffset/byteLength/length)读内部数据槽, // 原型访问器:视图属性(buffer/byteOffset/byteLength/length)读内部数据槽,