fix: TypedArray 元素写入进 buffer + BYTES_PER_ELEMENT 属性
This commit is contained in:
@@ -227,6 +227,67 @@ pub fn typed_array_element_get<H: VmHost>(vm: &mut H, obj: &JsObject, index: u32
|
||||
Ok(read_element(view.kind, buffer, absolute_byte_offset(view, index as usize)))
|
||||
}
|
||||
|
||||
/// 若对象是 TypedArray 且属性键是整数索引,返回 `(索引, 视图长度)`;否则 `None`。
|
||||
/// 供 VM 在 receiver ≠ TA 时裁决整数索引的写前语义(越界直接返回,界内落到 receiver)。
|
||||
pub fn typed_array_integer_index<H: VmHost>(vm: &mut H, obj: &JsObject, prop_name_si: u32) -> Option<(usize, usize)> {
|
||||
let ptr = typed_array_data_ptr(obj)?;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
let view = unsafe { *ptr };
|
||||
let key = vm.kernel_core().perm_interner().lookup(prop_name_si)?;
|
||||
if key.is_empty() || (key.len() > 1 && key.starts_with('0')) {
|
||||
return None;
|
||||
}
|
||||
let index = key.parse::<u32>().ok()?;
|
||||
Some((index as usize, view.length))
|
||||
}
|
||||
|
||||
/// 写 TypedArray 指定整数索引的元素(供 VM 普通属性 set 的 typed 分支使用)。
|
||||
/// 越界忽略(不创建属性、不报错);内部状态非法返回 Err(String)。
|
||||
pub fn typed_array_element_set<H: VmHost>(
|
||||
vm: &mut H, obj: &JsObject, index: u32, value: JsValue,
|
||||
) -> 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}"))?;
|
||||
write_typed_array_element(vm, view, index, value)
|
||||
}
|
||||
|
||||
/// 定义 TypedArray 整数索引元素(defineProperty 语义)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 索引越界:拒绝定义(Err),与整数索引 exotic 对象的 [[DefineOwnProperty]] 一致
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 把值 ToNumber 后写入底层 buffer
|
||||
// ponytail: 不校验 writable 描述符——调用方把"省略 writable"折叠为 false,
|
||||
// 无法与显式 false 区分;TA 元素天然可写,直接写入。
|
||||
pub fn typed_array_element_define<H: VmHost>(
|
||||
vm: &mut H, obj: &JsObject, index: u32, value: JsValue,
|
||||
) -> 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 {
|
||||
return Err("cannot define property: TypedArray index out of range".to_string());
|
||||
}
|
||||
write_typed_array_element(vm, view, index, value)
|
||||
}
|
||||
|
||||
fn write_typed_array_element<H: VmHost>(
|
||||
vm: &mut H, view: TypedArrayData, index: u32, value: JsValue,
|
||||
) -> Result<(), String> {
|
||||
// 先 ToNumber(valueOf 副作用先于越界判定触发),越界再静默忽略。
|
||||
let n = numeric_value(vm, value);
|
||||
if index as usize >= view.length {
|
||||
return Ok(());
|
||||
}
|
||||
let buffer_ptr = array_buffer_data_ptr(vm, view.buffer).map_err(|e| format!("{e}"))?;
|
||||
// SAFETY: buffer_ptr 经 array_buffer_data_ptr 校验为合法 ArrayBuffer。
|
||||
let buffer = unsafe { &mut *buffer_ptr };
|
||||
write_element(view.kind, buffer, absolute_byte_offset(view, index as usize), n);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_element(kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue {
|
||||
match kind {
|
||||
TypedArrayKind::Int8 => JsValue::int(bytes[offset] as i8 as i32),
|
||||
|
||||
@@ -3,13 +3,40 @@ use std::sync::Arc;
|
||||
use crate::bind_constructor;
|
||||
use crate::bindings::{apply_binding_table, configure_native_constructor};
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// 给构造器与对应原型设置 `BYTES_PER_ELEMENT`(只读、不可枚举、不可配置)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 构造器走 hash_props 存储(与 `bind_constructor` 的 hash 路径一致)。
|
||||
/// 2. 原型走 shape 属性存储(与 `bind_method`/`bind_accessor_getter` 一致)。
|
||||
fn set_bypes_per_element(core: &Arc<KernelCore>, ctor_ptr: *mut JsObject, proto_ptr: *mut JsObject, bpe: u32) {
|
||||
let bpe_si = core.perm_interner().intern("BYTES_PER_ELEMENT").0;
|
||||
let bpe_val = JsValue::int(bpe as i32);
|
||||
let attrs = PropAttributes::new(false, false, false);
|
||||
|
||||
let ctor = unsafe { &mut *ctor_ptr };
|
||||
ctor.ensure_hash_props().push(bpe_val);
|
||||
let ctor_pos = ctor.ensure_hash_props().len() - 1;
|
||||
let ctor_shape = core.shape_forge().make_shape(ctor.shape_id(), bpe_si);
|
||||
ctor.set_shape_id(ctor_shape);
|
||||
ctor.set_data_meta(ctor_pos as u32, attrs);
|
||||
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
let proto_shape = core.shape_forge().make_shape(proto.shape_id(), bpe_si);
|
||||
proto.set_shape_id(proto_shape);
|
||||
let proto_pos = proto.push_prop(bpe_val);
|
||||
proto.set_data_meta(proto_pos as u32, attrs);
|
||||
proto.bump_generation();
|
||||
}
|
||||
|
||||
macro_rules! bind_typed_array_constructor {
|
||||
($core:expr, $global:expr, $name:literal, $ctor_ptr:expr, $ctor_fn:path) => {{
|
||||
($core:expr, $global:expr, $name:literal, $ctor_ptr:expr, $proto_ptr:expr, $bpe:expr, $ctor_fn:path) => {{
|
||||
let ctor = unsafe { &mut *$ctor_ptr };
|
||||
configure_native_constructor(ctor, ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const (), 1);
|
||||
bind_constructor!($core, $global, $name, $ctor_ptr, $ctor_fn, 1, hash: true);
|
||||
set_bypes_per_element($core, $ctor_ptr, $proto_ptr, $bpe);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -44,6 +71,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Int8Array",
|
||||
session.builtin_world().int8array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().int8array_proto.as_ptr() as *mut JsObject,
|
||||
1,
|
||||
oxide_builtins::typed_array::int8array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -51,6 +80,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Uint8Array",
|
||||
session.builtin_world().uint8array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().uint8array_proto.as_ptr() as *mut JsObject,
|
||||
1,
|
||||
oxide_builtins::typed_array::uint8array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -58,6 +89,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Uint8ClampedArray",
|
||||
session.builtin_world().uint8clampedarray_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().uint8clampedarray_proto.as_ptr() as *mut JsObject,
|
||||
1,
|
||||
oxide_builtins::typed_array::uint8clampedarray_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -65,6 +98,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Int16Array",
|
||||
session.builtin_world().int16array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().int16array_proto.as_ptr() as *mut JsObject,
|
||||
2,
|
||||
oxide_builtins::typed_array::int16array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -72,6 +107,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Uint16Array",
|
||||
session.builtin_world().uint16array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().uint16array_proto.as_ptr() as *mut JsObject,
|
||||
2,
|
||||
oxide_builtins::typed_array::uint16array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -79,6 +116,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Int32Array",
|
||||
session.builtin_world().int32array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().int32array_proto.as_ptr() as *mut JsObject,
|
||||
4,
|
||||
oxide_builtins::typed_array::int32array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -86,6 +125,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Uint32Array",
|
||||
session.builtin_world().uint32array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().uint32array_proto.as_ptr() as *mut JsObject,
|
||||
4,
|
||||
oxide_builtins::typed_array::uint32array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -93,6 +134,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Float32Array",
|
||||
session.builtin_world().float32array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().float32array_proto.as_ptr() as *mut JsObject,
|
||||
4,
|
||||
oxide_builtins::typed_array::float32array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -100,6 +143,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"Float64Array",
|
||||
session.builtin_world().float64array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().float64array_proto.as_ptr() as *mut JsObject,
|
||||
8,
|
||||
oxide_builtins::typed_array::float64array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -107,6 +152,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"BigInt64Array",
|
||||
session.builtin_world().bigint64array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().bigint64array_proto.as_ptr() as *mut JsObject,
|
||||
8,
|
||||
oxide_builtins::typed_array::bigint64array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
@@ -114,6 +161,8 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
global,
|
||||
"BigUint64Array",
|
||||
session.builtin_world().biguint64array_constructor.as_ptr() as *mut JsObject,
|
||||
session.builtin_world().biguint64array_proto.as_ptr() as *mut JsObject,
|
||||
8,
|
||||
oxide_builtins::typed_array::biguint64array_constructor
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,6 +164,16 @@ impl Vm {
|
||||
prop_name_si,
|
||||
use_frame_push
|
||||
);
|
||||
// TypedArray 整数索引:receiver 为 TA 本体时写底层 buffer(越界静默忽略);
|
||||
// receiver 非 TA 时按规范把写入落到 receiver 对象,不碰 TA buffer。
|
||||
if obj.is_typed_array_obj() {
|
||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||
if receiver.as_js_object_ptr() != obj as *const JsObject as *mut JsObject {
|
||||
return self.set_to_receiver(obj, prop_name_si, val, receiver, index as usize, use_frame_push);
|
||||
}
|
||||
return oxide_builtins::typed_array::typed_array_element_set(self, obj, index, val);
|
||||
}
|
||||
}
|
||||
if let Some(pos) = self.get_own_property_slot(obj, prop_name_si) {
|
||||
if let Some(meta) = obj.prop_meta_at(pos) {
|
||||
if meta.is_accessor {
|
||||
@@ -197,6 +207,29 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// TypedArray 整数索引在 `receiver` ≠ TA 时的 [[Set]] 语义:越界或非对象
|
||||
/// receiver 直接返回 true(不写、不 ToNumber);界内对象 receiver 走普通 set
|
||||
/// 把属性落到 receiver 自身。
|
||||
fn set_to_receiver(
|
||||
&mut self, ta_obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, index: usize,
|
||||
use_frame_push: bool,
|
||||
) -> Result<(), String> {
|
||||
let Some((_, length)) = oxide_builtins::typed_array::typed_array_integer_index(self, ta_obj, prop_name_si)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
if index >= length {
|
||||
return Ok(());
|
||||
}
|
||||
let receiver_ptr = receiver.as_js_object_ptr();
|
||||
if receiver_ptr.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
let promoted = self.promote_if_needed_for_write_ptr(receiver_ptr, val);
|
||||
let receiver_obj = unsafe { &mut *receiver_ptr };
|
||||
self.ordinary_set_inner(receiver_obj, prop_name_si, promoted, receiver, use_frame_push)
|
||||
}
|
||||
|
||||
fn call_or_push_setter(
|
||||
&mut self, setter: JsValue, receiver: JsValue, val: JsValue, use_frame_push: bool,
|
||||
) -> Result<(), String> {
|
||||
@@ -310,6 +343,13 @@ impl Vm {
|
||||
|
||||
pub(crate) fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
|
||||
vm_trace!("set_or_create_prop_value: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
|
||||
// TypedArray 整数索引写 buffer(越界忽略),不进入 shape/prop 槽。
|
||||
if obj.is_typed_array_obj() {
|
||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||
let _ = oxide_builtins::typed_array::typed_array_element_set(self, obj, index, val);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 数组下标键写入元素区(维护 array_prop_count),不进入 shape 链。
|
||||
if obj.is_array() {
|
||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||
@@ -333,6 +373,12 @@ impl Vm {
|
||||
) -> Result<(), String> {
|
||||
vm_trace!("define_data_property: shape={} prop_si={}", obj.shape_id(), prop_name_si);
|
||||
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
|
||||
// TypedArray 整数索引:走元素定义(界内写 buffer,越界/非法描述符拒绝)。
|
||||
if obj.is_typed_array_obj() {
|
||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||
return oxide_builtins::typed_array::typed_array_element_define(self, obj, index, val);
|
||||
}
|
||||
}
|
||||
if obj.is_array() {
|
||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||
// 数组索引属性存元素区并维护 array_prop_count(元素数随索引增长)。
|
||||
|
||||
@@ -134,3 +134,67 @@ fn typed_array_to_string_is_identifiable() {
|
||||
let result = eval(&mut vm, "var ta = new Uint8Array(1); ta.toString()").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "[object TypedArray]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_element_write_roundtrips() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Int32Array(2); ta[0] = 42; ta[1] = ta[0] * 2; ta[1] === 84",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_element_write_clamps_int8() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var ta = new Int8Array(1); ta[0] = 300; ta[0]").unwrap();
|
||||
assert_eq!(result.as_int(), 44);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_out_of_bounds_write_is_ignored() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Int8Array(1); ta[5] = 1; ta.length === 1 && ta[5] === undefined",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_bypes_per_element_on_ctor_and_proto() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"Int8Array.BYTES_PER_ELEMENT === 1 && Float64Array.BYTES_PER_ELEMENT === 8 && \
|
||||
Int8Array.prototype.BYTES_PER_ELEMENT === 1 && Uint32Array.prototype.BYTES_PER_ELEMENT === 4",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_define_property_writes_element() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Uint8Array(3); Object.defineProperty(ta, 1, { value: 9 }); ta[1] === 9",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_reflect_set_writes_to_receiver() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Uint8Array(3); var o = {}; \
|
||||
Reflect.set(ta, 1, 5, o) && o[1] === 5 && ta[1] === 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user