diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 95bb9f91..9fa840f4 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -368,28 +368,6 @@ pub fn typed_array_element_get(vm: &mut H, obj: &JsObject, index: u32 Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, index as usize))) } -/// 若对象是 TypedArray 且属性键是整数索引,返回 `(索引, 视图长度)`;否则 `None`。 -/// 供 VM 在 receiver ≠ TA 时裁决整数索引的写前语义(越界直接返回,界内落到 receiver)。 -pub fn typed_array_integer_index(vm: &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 index = if is_int_key(prop_name_si) { - int_key_value(prop_name_si) - } else { - let key = vm.kernel_core().perm_interner().lookup(prop_name_si)?; - if key.is_empty() || (key.len() > 1 && key.starts_with('0')) { - return None; - } - key.parse::().ok()? - }; - // 长度走 live 口径:buffer 收缩越界后视同空视图,写前语义不落到 - // receiver(与 ta_view_length 同口径)。 - Some((index as usize, ta_live_length(view))) -} - // ── 统一数值键门 ───────────────────────────────────────────────────────── /// TypedArray 统一数值键门三态:`Ordinary` 非数字串(走普通属性路径); @@ -424,10 +402,11 @@ pub fn ta_index_gate(vm: &H, obj: &JsObject, key_si: u32) -> TaIndexG ta_index_gate_from_text(key, length) } -/// 取 TypedArray 视图的 live 长度(供只需 length 不需门的枚举面消费方); -/// 非 TypedArray 或内部状态无效返回 0。越界(detach、定长窗口被 buffer -/// 收缩裁掉、auto offset 超 buffer)统一 0,界内键经门归数字无效。 -pub(crate) fn ta_view_length(_vm: &H, obj: &JsObject) -> usize { +/// 取 TypedArray 视图的 live 长度(供只需 length 不需门的枚举面与 +/// receiver 界判消费方);非 TypedArray 或内部状态无效返回 0。越界 +/// (detach、定长窗口被 buffer 收缩裁掉、auto offset 超 buffer)统一 0, +/// 界内键经门归数字无效。 +pub fn ta_view_length(_vm: &H, obj: &JsObject) -> usize { let Some(ptr) = typed_array_data_ptr(obj) else { return 0; }; @@ -495,49 +474,6 @@ fn ta_to_number_text(t: &str) -> f64 { } } -/// detach 后数值键静默写判定:buffer 已 detach(载荷缺失,含 buffer 非 -/// ArrayBuffer 对象)且键落在统一数值键门的数字臂(规范整数索引,或 -/// "1.1"/"-0" 类 round-trip 数值串)时,写静默忽略、不落命名属性; -/// 非数字串与 symbol 键返回 false(调用方落普通属性路径)。供 VM 普通 -/// 属性 set 的 typed 分支作 [[Set]] 静默臂。 -pub fn typed_array_detached_numeric_key_noop(vm: &mut H, obj: &JsObject, key_si: u32) -> bool { - let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject); - let Ok(view) = get_typed_array_data(vm, this_val) else { - return false; - }; - typed_array_detached_numeric_key_noop_view(vm, obj, view, key_si) -} - -/// 只读变体:判定逻辑同 `typed_array_detached_numeric_key_noop`,视图直接 -/// 从对象 native 槽读出,不要求可变 VM 访问。供 IC 快路径新属性分流等 -/// 只持读引用的场景(该场景下视图状态与写路径同一口径)。 -pub fn typed_array_detached_numeric_key_noop_ro(vm: &H, obj: &JsObject, key_si: u32) -> bool { - let Some(ptr) = typed_array_data_ptr(obj) else { - return false; - }; - if ptr.is_null() { - return false; - } - // SAFETY: ptr 非空,为 TypedArray 对象 native_fn 槽内的 Box, - // 与对象同生命周期,此处只读拷贝视图字段。 - let view = unsafe { *ptr }; - typed_array_detached_numeric_key_noop_view(vm, obj, view, key_si) -} - -/// 两变体共享尾段:非 TypedArray 对象、buffer 载荷存活(未 detach)均不 -/// 静默;detach 且键落数值臂(界内索引或数字无效)时静默。 -fn typed_array_detached_numeric_key_noop_view( - vm: &H, obj: &JsObject, view: TypedArrayData, key_si: u32, -) -> bool { - if !obj.is_typed_array_obj() { - return false; - } - if ta_buffer_byte_length(view).is_some() { - return false; - } - matches!(ta_index_gate(vm, obj, key_si), TaIndexGate::NumericValid(_) | TaIndexGate::NumericInvalid) -} - /// 写 TypedArray 指定整数索引的元素(供 VM 普通属性 set 的 typed 分支使用)。 /// 先按元素类型转换(副作用/抛错先于界判触发),越界(含 detach)静默忽略 /// (不创建属性、不报错);内部状态非法返回 Err(String)(防御背板)。 @@ -549,6 +485,62 @@ pub fn typed_array_element_set( write_typed_array_element(vm, view, index, value) } +/// 数值键纯强转入口:按元素类型 ToBigInt/ToNumber(副作用/抛错先触发), +/// 不做任何写入。供 [[Set]] 数字无效臂(含 detach:live 长 0 时全数值键落 +/// 此臂)消费——强转后结果丢弃。 +/// +/// # 步骤 +/// 1. 取视图(内部状态非法返回防御背板 Err)。 +/// 2. 强转:深度 0 抛错已就地展开到外围 catch,pc 守卫即停。 +/// 3. 强转结果丢弃。 +/// +/// # 边界与前提 +/// - 调用方须已判定键落门数字无效臂(或 receiver-TA 同臂);本入口不重判键。 +/// +/// # 副作用 +/// - 执行用户代码(valueOf/toString/toPrimitive),可能抛错;抛错时深度 0 +/// 已 unwind、深度 >0 以 kind 前缀文本 `Err` 返回。 +pub fn typed_array_numeric_key_convert_only( + vm: &mut H, obj: &JsObject, 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}"))?; + let pc_before = vm.pc(); + let converted = ta_element_value(vm, view.kind, value); + // pc 守卫:深度 0 强转抛错已展开,直接返回由 dispatch 执行 catch。 + if vm.pc() != pc_before { + return Ok(()); + } + match converted { + Ok(_) => Ok(()), + // 深度 >0:kind 前缀文本由原生调用边界恢复为异常对象。 + Err(err) => vm.raise_captured(err), + } +} + +/// TypedArray receiver 上的 [[Set]] 写路由:门按 receiver 自身取。界内规范 +/// 键强转后写 receiver 元素(可抛、kind 保真,越界静默);数字无效键强转后 +/// 丢弃;非数字键落 receiver 的普通属性路径。 +/// +/// # 边界与前提 +/// - 调用方须已确认 receiver 是 TypedArray 且 receiver ≠ 写入基对象。 +/// +/// # 副作用 +/// - 可能写 receiver buffer / 创建 receiver 属性;强转副作用与自写臂同 +/// (深度 0 抛错已 unwind 到外围 catch,深度 >0 以 `Err` 文本传播)。 +pub fn typed_array_receiver_set( + vm: &mut H, receiver: &mut JsObject, key_si: u32, value: JsValue, +) -> Result<(), String> { + match ta_index_gate(vm, receiver, key_si) { + TaIndexGate::NumericValid(index) => typed_array_element_set(vm, receiver, index, value), + TaIndexGate::NumericInvalid => typed_array_numeric_key_convert_only(vm, receiver, value), + TaIndexGate::Ordinary => { + vm.set_or_create_prop_value(receiver, key_si, value); + Ok(()) + } + } +} + /// 定义 TypedArray 整数索引元素(defineProperty 语义)。 /// /// # 边界与前提 @@ -573,14 +565,19 @@ fn write_typed_array_element( vm: &mut H, view: TypedArrayData, index: u32, value: JsValue, ) -> Result<(), String> { // 先按元素类型转换(valueOf 副作用先于越界判定触发),越界再静默忽略。 - let elem = match ta_element_value(vm, view.kind, value) { + let pc_before = vm.pc(); + let converted = ta_element_value(vm, view.kind, value); + // pc 守卫:深度 0 强转抛错已就地展开到外围 catch,转换结果为残值—— + // 继续即假值写(undefined 强转 NaN)或二次抛错,直接返回由 dispatch + // 执行 catch。 + if vm.pc() != pc_before { + return Ok(()); + } + let elem = match converted { Ok(v) => v, - Err(err) => { - // 转换失败恢复为可捕获的 JS 异常:主 dispatch 下就地展开到外围 catch, - // builtin 内部展开到调用方 try 处理器;uncaught 时以文本上抛。 - let text = element_error_text(vm, err); - return vm.raise_type_error(&text); - } + // 转换失败恢复为可捕获的 JS 异常:深度 0 原值入通道就地展开到外围 + // catch(kind 保真),深度 >0 以 kind 前缀文本由原生调用边界恢复。 + Err(err) => return vm.raise_captured(err), }; // live 边界:越界(含 detach)静默不写(规范 IntegerIndexedElementSet 的 // IsValidIndexedAccess 臂),转换副作用已先发生。 @@ -660,8 +657,9 @@ fn is_bigint_kind(kind: TypedArrayKind) -> bool { matches!(kind, TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64) } -/// 把异常 JsValue 格式化为 `Kind: message` 文本(属性写路径的 String 错误契约用)。 -fn element_error_text(vm: &mut H, err: JsValue) -> String { +/// 把异常 JsValue 格式化为 `Kind: message` 文本(属性写路径的 String 错误 +/// 契约用;原生调用边界据此经 `create_from_text` 恢复 kind)。 +pub fn element_error_text(vm: &mut H, err: JsValue) -> String { if let Some(s) = vm.lookup_str(err) { return s; } diff --git a/crates/oxide_runtime_api/src/host.rs b/crates/oxide_runtime_api/src/host.rs index b68b6df0..6103eeed 100644 --- a/crates/oxide_runtime_api/src/host.rs +++ b/crates/oxide_runtime_api/src/host.rs @@ -195,7 +195,22 @@ pub trait VmHost { // 错误处理 fn checked_object_ptr(&mut self, val: JsValue, error_msg: &str) -> Result, String>; + /// 读当前分派指令位置(pc)。供有界强转调用方在强转前后采样:深度 0 + /// 用户回调抛错已就地展开时 pc 必已变化,调用方须立即停止执行后续步骤 + /// (强转结果为残值,继续即假值写或二次抛错)。 + fn pc(&self) -> usize; fn raise_type_error(&mut self, msg: &str) -> Result<(), String>; + /// 恢复已捕获的原始异常值(强转失败载荷)并走异常展开,保原值 kind: + /// 深度 0 置原值入异常通道就地展开到外围 catch;深度 >0 返回 kind 前缀 + /// 文本,由原生调用边界恢复为异常对象。 + /// + /// # 边界与前提 + /// - `exc` 须为调用方已提取的原始异常值(如经 `take_uncaught_value`); + /// 本入口不重取 uncaught 槽。 + /// + /// # 副作用 + /// - 深度 0 写 `exception_value`/`pending_error_kind`,pc 经展开改写。 + fn raise_captured(&mut self, exc: JsValue) -> Result<(), String>; fn error_message_text(&self, kind: &str, msg: &str) -> String; fn call_stack_function_names(&self) -> Vec; fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue; diff --git a/crates/oxide_vm/src/vm/error.rs b/crates/oxide_vm/src/vm/error.rs index 3ddf6285..bb37e7ea 100644 --- a/crates/oxide_vm/src/vm/error.rs +++ b/crates/oxide_vm/src/vm/error.rs @@ -144,6 +144,29 @@ impl Vm { Err(err.to_string()) } + /// 恢复已捕获的原始异常值(强转失败载荷)并走异常展开,保原值 kind: + /// 深度 0 置原值入异常通道就地展开到外围 catch;深度 >0 返回 kind 前缀 + /// 文本 `Err`,由原生调用边界恢复为异常对象。 + /// + /// # 步骤 + /// 1. 深度 0:原值与 kind 写入异常通道,就地 `unwind`。 + /// 2. 深度 >0:以 `Kind: message` 文本 `Err` 返回,不就地展开。 + /// + /// # 边界与前提 + /// - `exc` 为调用方已提取的原始异常值;本入口不重取 uncaught 槽。 + /// + /// # 副作用 + /// - 深度 0 写 `exception_value`/`pending_error_kind`,pc 经展开改写。 + pub(crate) fn raise_captured(&mut self, exc: JsValue) -> Result<(), String> { + if self.native_call_depth == 0 { + let kind = self.thrown_error_kind(exc); + self.exception_value = Some(exc); + self.pending_error_kind = Some(kind); + return self.unwind(); + } + Err(oxide_builtins::typed_array::element_error_text(self, exc)) + } + /// ToNumber 有界版:先按 number hint 做 ToPrimitive,再转 `f64`。 /// /// # 边界与前提 diff --git a/crates/oxide_vm/src/vm/vmhost.rs b/crates/oxide_vm/src/vm/vmhost.rs index f19ffb90..bb40367c 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -49,6 +49,9 @@ impl oxide_runtime_api::VmHost for Vm { fn epoch(&self) -> &Epoch { self.epoch() } + fn pc(&self) -> usize { + self.pc + } fn take_uncaught_value(&mut self) -> Option { self.last_uncaught_value.take() } @@ -145,6 +148,9 @@ impl oxide_runtime_api::VmHost for Vm { fn raise_type_error(&mut self, msg: &str) -> Result<(), String> { self.raise_type_error(msg) } + fn raise_captured(&mut self, exc: JsValue) -> Result<(), String> { + self.raise_captured(exc) + } fn error_message_text(&self, kind: &str, msg: &str) -> String { self.error_message_text(kind, msg) } diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 0e903b08..0484f8ed 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -368,13 +368,23 @@ impl Vm { } return Ok(()); } - // TypedArray 整数索引:receiver 为 TA 本体时写底层 buffer(越界静默忽略); - // receiver 非 TA 时按规范把写入落到 receiver 对象,不碰 TA buffer。 - // buffer 已 detach 的 TA:数值键(含非规范数值串)写静默忽略、不落 - // 命名属性;非数字串与 symbol 键落普通属性路径。 + // 统一数值键门(exotic [[Set]]):界内规范键写底层 buffer(自臂界内 + // 写 / 越界静默;他臂先按自身判界再按 receiver 分类);数字无效键强转 + // (可抛、kind 保真)后丢弃、receiver 不 consult(detach 同臂:live 长 + // 0 时全数值键落此臂);非规范数值串落下方普通属性路径。 if obj.is_typed_array_obj() { - if let Some(index) = self.array_index_from_property_key(prop_name_si) { - if !std::ptr::eq(receiver.as_js_object_ptr(), obj as *mut JsObject) { + let receiver_is_obj = std::ptr::eq(receiver.as_js_object_ptr(), obj as *mut JsObject); + match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) { + oxide_builtins::typed_array::TaIndexGate::NumericValid(index) => { + if receiver_is_obj { + return oxide_builtins::typed_array::typed_array_element_set(self, obj, index, val); + } + // 界判先于 receiver consult:越界直接返回零副作用(门 + // 保证界内,此处为防御背板)。 + let live = oxide_builtins::typed_array::ta_view_length(self, obj); + if (index as usize) >= live { + return Ok(()); + } return self.set_to_receiver( obj, prop_name_si, @@ -386,12 +396,20 @@ impl Vm { builtin, ); } - return oxide_builtins::typed_array::typed_array_element_set(self, obj, index, val); - } - if std::ptr::eq(receiver.as_js_object_ptr(), obj as *mut JsObject) - && oxide_builtins::typed_array::typed_array_detached_numeric_key_noop(self, obj, prop_name_si) - { - return Ok(()); + oxide_builtins::typed_array::TaIndexGate::NumericInvalid => { + // 强转(副作用/抛错先触发)后按 live 复判落位;receiver + // 不 consult。整数键与规范越界串键携带索引:走元素写入口 + // (强转后 live 复判——强转期 resize 可翻越界为界内); + // 非规范数值串无索引可落,纯强转丢弃。 + if receiver_is_obj { + if let Some(index) = self.array_index_from_property_key(prop_name_si) { + return oxide_builtins::typed_array::typed_array_element_set(self, obj, index, val); + } + return oxide_builtins::typed_array::typed_array_numeric_key_convert_only(self, obj, val); + } + return Ok(()); + } + oxide_builtins::typed_array::TaIndexGate::Ordinary => {} } } // 数组 length 赋值:走 ArraySetLength 语义(两次数值强转、可写性判定与 @@ -478,6 +496,22 @@ impl Vm { ); } } + // receiver 非基对象且为 TypedArray:写入路由到 receiver 的 [[Set]] + // (数值键:界内元素写 / 数字无效强转后丢弃;非数字键:落 receiver + // 属性),不在基对象上建影子属性。 + let receiver_ptr = receiver.as_js_object_ptr(); + if !receiver_ptr.is_null() && !std::ptr::eq(receiver_ptr, obj as *mut JsObject) { + // SAFETY: receiver_ptr 为 receiver 值携带的非空对象指针,对象在 + // 本会话内存活;写路径不移动对象。 + if unsafe { &*receiver_ptr }.is_typed_array_obj() { + return oxide_builtins::typed_array::typed_array_receiver_set( + self, + unsafe { &mut *receiver_ptr }, + prop_name_si, + val, + ); + } + } self.set_or_create_prop_value(obj, prop_name_si, val); Ok(()) } @@ -569,28 +603,58 @@ impl Vm { } } - /// TypedArray 整数索引在 `receiver` ≠ TA 时的 [[Set]] 语义:越界或非对象 - /// receiver 直接返回 true(不写、不 ToNumber);界内对象 receiver 走普通 set - /// 把属性落到 receiver 自身。 + /// TypedArray 界内规范键在 `receiver` ≠ TA 时的 [[Set]] 语义: + /// + /// # 步骤 + /// 1. 基元 receiver:Set 失败(strict 抛 TypeError,sloppy 静默)。 + /// 2. TA receiver:按自身 live 长判界——界内强转后写元素(可抛、kind + /// 保真);越界失败,零强转。 + /// 3. 非 TA 对象 receiver:按自身属性层判——own accessor 失败(setter + /// 不调用);own 不可写数据失败;own 可写数据直写(零强转);无 own + /// 且可扩展建属性(值原样);无 own 且不可扩展失败。 + /// + /// # 边界与前提 + /// - 调用方须已判定键对 TA 自身界内(`ta_index_gate` NumericValid)。 + /// + /// # 副作用 + /// - 可能写 receiver 的 buffer / 属性存储;强转副作用与自写臂同。 #[allow(clippy::too_many_arguments)] fn set_to_receiver( - &mut self, ta_obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, index: usize, - use_frame_push: bool, strict: bool, builtin: bool, + &mut self, _ta_obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, index: usize, + _use_frame_push: bool, _strict: bool, builtin: 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 self.write_protection_failure(builtin, "Cannot set property on a primitive receiver"); + } + // SAFETY: receiver_ptr 为 receiver 值携带的非空对象指针,对象在会话内 + // 存活;此处顺序读写,写路径不移动对象。 + let receiver_obj = unsafe { &*receiver_ptr }; + if receiver_obj.is_typed_array_obj() { + let live = oxide_builtins::typed_array::ta_view_length(self, receiver_obj); + if index >= live { + return self.write_protection_failure(builtin, "TypedArray receiver index out of bounds"); + } + return oxide_builtins::typed_array::typed_array_element_set(self, receiver_obj, index as u32, val); + } + if let Some(pos) = self.get_own_property_slot(receiver_obj, prop_name_si) { + if let Some(meta) = receiver_obj.prop_meta_at(pos) { + // own accessor(setter 不调用)与不可写数据均失败。 + if meta.is_accessor || !meta.attributes.writable() { + return self.write_protection_failure(builtin, "Cannot set property on the receiver"); + } + } + // SAFETY: 同 receiver_ptr 所指对象,写路径不移动对象。 + unsafe { (*receiver_ptr).set_prop_storage(pos as usize, val) }; + self.sync_global_builtin_mirror(receiver_obj, prop_name_si, val); 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, strict, builtin) + if !receiver_obj.is_extensible() { + return self.write_protection_failure(builtin, "The receiver is not extensible"); + } + // SAFETY: 同 receiver_ptr 所指对象,写路径不移动对象。 + self.set_or_create_prop_value(unsafe { &mut *receiver_ptr }, prop_name_si, val); + Ok(()) } /// 调用或帧化访问器 setter:native(或禁止帧化的调用方)同步执行, @@ -819,16 +883,19 @@ impl Vm { } /// 写新属性须分流到完整 ordinary_set 语义的场景:数组 length(ArraySetLength)与 - /// 数组/TA 整数索引键(元素区 / buffer 写)、detach 后 TA 的数值键([[Set]] - /// 静默臂,非规范数值串如 "1.1"/"-0" 不在 shape 链上,CreateDataProperty - /// 快路径会错误地为其建命名属性)。 + /// 数组/TA 整数索引键(元素区 / buffer 写)、TA 的统一数值键门两数值臂 + /// ([[Set]] 元素写 / 强转后丢弃,非规范数值串如 "1.1"/"-0" 不在 shape + /// 链上,CreateDataProperty 快路径会错误地为其建命名属性)。 pub(crate) fn named_prop_create_needs_ordinary_set(&self, obj: &JsObject, prop_name_si: u32) -> bool { (obj.is_array() && prop_name_si == self.length_si) || (obj.is_typed_array_obj() || obj.is_array()) && self.array_index_from_property_key(prop_name_si).is_some() || obj.is_typed_array_obj() - && self.array_index_from_property_key(prop_name_si).is_none() - && oxide_builtins::typed_array::typed_array_detached_numeric_key_noop_ro(self, obj, prop_name_si) + && matches!( + oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si), + oxide_builtins::typed_array::TaIndexGate::NumericValid(_) + | oxide_builtins::typed_array::TaIndexGate::NumericInvalid, + ) } /// 值写入直调路径:REST / SPREAD / builtin 内部等已确定目标对象的场景, @@ -853,15 +920,25 @@ 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 槽; - // buffer 已 detach 的数值键写静默忽略(与 ordinary_set_inner 同臂)。 + // 统一数值键门:界内规范键写 buffer(越界静默),数字无效键强转后 + // 丢弃(detach 同臂),不进入 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; - } - if oxide_builtins::typed_array::typed_array_detached_numeric_key_noop(self, obj, prop_name_si) { - return; + match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) { + oxide_builtins::typed_array::TaIndexGate::NumericValid(index) => { + let _ = oxide_builtins::typed_array::typed_array_element_set(self, obj, index, val); + return; + } + oxide_builtins::typed_array::TaIndexGate::NumericInvalid => { + // 与 ordinary_set_inner 同臂:携带索引的键走元素写入口 + // (强转后 live 复判),非规范数值串纯强转丢弃。 + 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); + } else { + let _ = oxide_builtins::typed_array::typed_array_numeric_key_convert_only(self, obj, val); + } + return; + } + oxide_builtins::typed_array::TaIndexGate::Ordinary => {} } } // 数组下标键写入元素区(维护 array_prop_count),不进入 shape 链。 @@ -920,10 +997,18 @@ 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,越界/非法描述符拒绝)。 + // TypedArray 统一数值键门:界内规范键走元素定义(界内写 buffer); + // 规范越界键(含 detach:live 长 0)拒绝定义;非规范数值串 + // ("+1"/"1.0" 等,round-trip 不成)与 symbol 键落命名属性路径。 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); + match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) { + oxide_builtins::typed_array::TaIndexGate::NumericValid(index) => { + return oxide_builtins::typed_array::typed_array_element_define(self, obj, index, val); + } + oxide_builtins::typed_array::TaIndexGate::NumericInvalid => { + return Err("cannot define property: TypedArray index out of range".to_string()); + } + oxide_builtins::typed_array::TaIndexGate::Ordinary => {} } } if obj.is_array() { diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index 73629f4d..4c31db9b 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -832,8 +832,220 @@ fn ta_gate_detached_numeric_invalid() { let result = eval( &mut vm, "(function () { var ab = new ArrayBuffer(8); var ta = new Int8Array(ab); \ - ta[0] = 9; ab.transfer(); \ - return ta[0] === undefined && !(0 in ta); })()", + ta[0] = 9; ab.transfer(); \ + return ta[0] === undefined && !(0 in ta); })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_set_numeric_invalid_self_silent() { + // 数字无效键(分数/负数/"-0")自写:不建属性、读 undefined,原型预置 + // 同键抛 getter/setter 不被触达。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int8Array([42]); \ + var keys = ['1.1', '-1', '-0']; \ + for (var i = 0; i < keys.length; i++) { \ + Object.defineProperty(Int8Array.prototype, keys[i], { \ + get: function () { throw new Error('proto getter must not fire'); }, \ + set: function () { throw new Error('proto setter must not fire'); }, \ + configurable: true }); } \ + for (var i = 0; i < keys.length; i++) { \ + ta[keys[i]] = 7; \ + if (Object.prototype.hasOwnProperty.call(ta, keys[i])) return false; \ + if (ta[keys[i]] !== undefined) return false; } \ + return true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_set_numeric_invalid_other_no_side_effect() { + // 数字无效键 + receiver ≠ 自身:全 receiver 类 Reflect.set 恒 true, + // target 与 receiver 均无新属性,valueOf 零次,原型 accessor 不触发。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var calls = 0; \ + var value = { valueOf: function () { calls++; return 2.3; } }; \ + var keys = [1, 1.5, -1]; \ + for (var i = 0; i < keys.length; i++) { var key = keys[i]; \ + Object.defineProperty(Int8Array.prototype, key, { \ + get: function () { throw new Error('getter must not fire'); }, \ + set: function () { throw new Error('setter must not fire'); }, \ + configurable: true }); \ + var target = new Int8Array([0]); \ + var receiver = {}; \ + if (Reflect.set(target, key, value, receiver) !== true) return false; \ + if (Object.prototype.hasOwnProperty.call(target, key)) return false; \ + if (Object.prototype.hasOwnProperty.call(receiver, key)) return false; \ + receiver = new Int8Array([1]); \ + if (Reflect.set(target, key, value, receiver) !== true) return false; \ + if (Object.prototype.hasOwnProperty.call(target, key)) return false; \ + if (Object.prototype.hasOwnProperty.call(receiver, key)) return false; \ + receiver = Object.defineProperty({}, key, { \ + get: function () { return 1; }, \ + set: function () { throw new Error('setter must not fire'); }, \ + configurable: true }); \ + if (Reflect.set(target, key, value, receiver) !== true) return false; \ + if (receiver[key] !== 1) return false; \ + receiver = Object.preventExtensions({}); \ + if (Reflect.set(target, key, value, receiver) !== true) return false; \ + if (Object.prototype.hasOwnProperty.call(receiver, key)) return false; \ + delete Int8Array.prototype[key]; } \ + return calls === 0; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_set_ordinary_key_property() { + // 非规范数值串(round-trip 不成)落普通属性:建属性、readback 值、 + // 二次写覆盖、defineProperty 不可写后 Reflect.set false。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var keys = ['1.0', '+1', '1000000000000000000000', '0.0000001']; \ + for (var i = 0; i < keys.length; i++) { \ + var sample = new Int8Array([42]); \ + if (Reflect.set(sample, keys[i], 'ecma262') !== true) return false; \ + if (sample[keys[i]] !== 'ecma262') return false; \ + if (Reflect.set(sample, keys[i], 'es3000') !== true) return false; \ + if (sample[keys[i]] !== 'es3000') return false; \ + Object.defineProperty(sample, keys[i], { value: undefined, writable: false, configurable: true }); \ + if (Reflect.set(sample, keys[i], 42) !== false) return false; \ + if (sample[keys[i]] !== undefined) return false; } \ + return true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_set_valid_receiver_table() { + // 界内规范键 + receiver ≠ 自身六臂表:空对象建属性(值原样零强转)/ + // 同长 TA 强转写 / 短 TA false 零强转 / 不可扩展 false / own accessor + // false 不调 setter / own 不可写 false。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var calls = 0; \ + var value = { valueOf: function () { calls++; return 2.3; } }; \ + var target = new Int8Array([0]); var receiver = {}; \ + if (Reflect.set(target, 0, value, receiver) !== true) return false; \ + if (target[0] !== 0) return false; \ + if (receiver[0] !== value) return false; \ + target = new Int8Array([0]); receiver = new Int8Array([1]); \ + if (Reflect.set(target, 0, new Number(2.3), receiver) !== true) return false; \ + if (target[0] !== 0) return false; \ + if (receiver[0] !== 2) return false; \ + target = new Int8Array([0, 0]); receiver = new Int8Array([1]); \ + if (Reflect.set(target, 1, value, receiver) !== false) return false; \ + if (target[1] !== 0) return false; \ + if (Object.prototype.hasOwnProperty.call(receiver, 1)) return false; \ + target = new Int8Array([0]); receiver = Object.preventExtensions({}); \ + if (Reflect.set(target, 0, value, receiver) !== false) return false; \ + if (Object.prototype.hasOwnProperty.call(receiver, 0)) return false; \ + target = new Int8Array([0]); \ + receiver = { get 0() { return 1; }, set 0(v) { throw new Error('setter must not fire'); } }; \ + if (Reflect.set(target, 0, value, receiver) !== false) return false; \ + if (receiver[0] !== 1) return false; \ + target = new Int8Array([0]); \ + receiver = Object.defineProperty({}, 0, { value: 1, writable: false, configurable: true }); \ + if (Reflect.set(target, 0, value, receiver) !== false) return false; \ + if (receiver[0] !== 1) return false; \ + return calls === 0; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_set_receiver_ta_oob_plain_obj() { + // 普通对象 + 原型链 TA + 越界数值键:写入路由到 receiver 的 [[Set]] + // (强转后丢弃,valueOf 恰一次),基对象上不建属性。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var receiver = new Int32Array(10); \ + var obj = Object.create(receiver); \ + var called = 0; \ + var value = { valueOf: function () { called++; return 1; } }; \ + if (Reflect.set(obj, 100, value, receiver) !== true) return false; \ + if (called !== 1) return false; \ + if (Object.prototype.hasOwnProperty.call(obj, 100)) return false; \ + if (Object.prototype.hasOwnProperty.call(receiver, 100)) return false; \ + return true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_set_coerce_kind_preserved() { + // 强转抛错 kind 保真:valueOf 抛原错误原样传播(非 TypeError 包裹); + // 字符串转 BigInt 非法值保 SyntaxError。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var sample = new Float64Array([42]); \ + var thrown = null; \ + try { sample['0'] = { valueOf: function () { throw new TypeError('boom'); } }; } \ + catch (e) { thrown = e; } \ + if (!(thrown instanceof TypeError) || thrown.message !== 'boom') return false; \ + var bsample = new BigInt64Array([42n]); \ + var bthrown = null; \ + try { bsample['0'] = '1.1'; } catch (e) { bthrown = e; } \ + if (!(bthrown instanceof SyntaxError)) return false; \ + return true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_set_detach_coerce_then_silent() { + // detach 后数值键自写:平凡值静默(不建属性);抛错 valueOf 抛原错误 + // (kind 保真)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(8); \ + var sample = new BigInt64Array(ab); \ + sample[0] = 42n; \ + ab.transfer(); \ + sample[0] = 1n; \ + if (sample[0] !== undefined) return false; \ + sample['1.1'] = 1n; \ + if (sample['1.1'] !== undefined) return false; \ + var thrown = null; \ + try { sample['0'] = { valueOf: function () { throw new RangeError('detach coerce'); } }; } \ + catch (e) { thrown = e; } \ + if (!(thrown instanceof RangeError)) return false; \ + return true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_set_resize_convert_first() { + // 强转先于界判(整数键):valueOf 期间 buffer resize 到界内,强转后 + // live 复判界内写成功(零长键 0 原越界)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(0, { maxByteLength: 1 }); \ + var ta = new Int8Array(ab); \ + var index = 0; \ + var value = { valueOf: function () { ab.resize(1); return 100; } }; \ + ta[index] = value; \ + return ta.length === 1 && ta[0] === 100; })()", ) .unwrap(); assert!(result.as_bool());