diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index b5e5f67e..396c714f 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -350,7 +350,8 @@ fn clamp_index_to_len(raw: f64, len: usize) -> usize { } /// 读 TypedArray 指定整数索引的元素(供 VM 普通属性 get 的 typed 分支使用)。 -/// 越界返回 undefined;内部状态非法返回 Err(String)。 +/// 越界(含 detach)一律返回 undefined(规范 [[Get]] 静默臂);内部状态 +/// 非法返回 Err(String)(防御背板)。 pub fn typed_array_element_get(vm: &mut H, obj: &JsObject, index: u32) -> Result { 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}"))?; @@ -361,7 +362,8 @@ pub fn typed_array_element_get(vm: &mut H, obj: &JsObject, index: u32 let payload_ptr = array_buffer_payload(vm, view.buffer).map_err(|e| format!("{e}"))?; // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { - return Err("ArrayBuffer internal state invalid".into()); + // 载荷缺失即 detach(live 界判后不可达,防御背板):按 [[Get]] 读 undefined。 + return Ok(JsValue::undefined()); }; Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, index as usize))) } @@ -493,8 +495,52 @@ 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 分支使用)。 -/// 越界忽略(不创建属性、不报错);内部状态非法返回 Err(String)。 +/// 先按元素类型转换(副作用/抛错先于界判触发),越界(含 detach)静默忽略 +/// (不创建属性、不报错);内部状态非法返回 Err(String)(防御背板)。 pub fn typed_array_element_set( vm: &mut H, obj: &JsObject, index: u32, value: JsValue, ) -> Result<(), String> { @@ -544,7 +590,9 @@ fn write_typed_array_element( let payload_ptr = array_buffer_payload(vm, view.buffer).map_err(|e| format!("{e}"))?; // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { - return Err("ArrayBuffer internal state invalid".into()); + // 载荷缺失即 detach(live 界判后不可达,防御背板):转换副作用已先 + // 发生,写按 [[Set]] 静默 no-op。 + return Ok(()); }; write_element(vm, view.kind, buffer, absolute_byte_offset(view, index as usize), elem); Ok(()) @@ -706,6 +754,8 @@ fn collect_array_like(vm: &mut H, value: JsValue, consult_iterator: b let payload_ptr = array_buffer_payload(vm, view.buffer)?; // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { + // 源 buffer detach:按规范的源缓冲校验步抛 TypeError;各入口的 + // 校验/活长界判先行,此臂为防御背板。 return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; // 源视图按 live 长度收集:buffer 收缩越界后视同空源(与构造器 @@ -783,6 +833,8 @@ fn typed_array_new(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> let payload_ptr = native_try!(array_buffer_payload(vm, first)); // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(data) = unsafe { &*payload_ptr }.data.as_ref() else { + // 首参 buffer 已 detach:按规范的 ArrayBuffer 校验步抛 TypeError + // (构造器入口的 detach 守卫即本臂)。 return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; let buffer_len = data.len(); @@ -820,6 +872,7 @@ fn typed_array_new(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> let payload_ptr = native_try!(array_buffer_payload(vm, buffer)); // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(buffer_ref) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + // 新建 buffer 载荷恒存活(无人可 detach),此臂为防御背板。 return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; for (idx, value) in values.into_iter().enumerate() { @@ -962,7 +1015,9 @@ pub fn typed_array_fill(vm: &mut H, args: &[u8]) -> NativeResult { let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { - return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + // 转换窗口内 detach:填充写退化为静默 no-op(循环内无重入窗,单次 + // 判即封窗),按 [[Set]] 语义正常返回 this。 + return NativeResult::Ok(this_val); }; for idx in start..end.max(start) { write_element(vm, view.kind, buffer, absolute_byte_offset(view, idx), elem); @@ -1198,11 +1253,10 @@ pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { } let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 - let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { - return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); - }; + let mut buffer = unsafe { &mut *payload_ptr }.data.as_deref_mut(); // 逐元素惰性读源→转换→写:循环中的 detach/收缩使后续写入静默失效 - // (目标越界不写,与规范 SetValueInBuffer 边界无操作同语义)。 + // (目标越界不写,与规范 SetValueInBuffer 边界无操作同语义);载荷缺失 + // 等同 detach,全循环退化为源读副作用与转换、零写入。 for k in 0..source_len { let value = match &source_kind { SetSourceKind::TypedArray(sview) => native_try!(ta_read(vm, *sview, k)), @@ -1211,7 +1265,9 @@ pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { }; 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); + if let Some(buffer) = buffer.as_deref_mut() { + write_element(vm, view.kind, buffer, absolute_byte_offset(view, offset + k), elem); + } } } NativeResult::Ok(JsValue::undefined()) @@ -1353,7 +1409,9 @@ fn set_typed_array_element(vm: &mut H, ta: JsValue, index: usize, val let payload_ptr = array_buffer_payload(vm, view.buffer)?; // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { - return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + // 载荷缺失即 detach(live 界判后不可达,防御背板):转换副作用已先 + // 发生,写按 [[Set]] 静默 no-op。 + return Ok(()); }; write_element(vm, view.kind, buffer, absolute_byte_offset(view, index), elem); Ok(()) @@ -1387,7 +1445,9 @@ fn ta_read(vm: &mut H, view: TypedArrayData, index: usize) -> Result< let payload_ptr = array_buffer_payload(vm, view.buffer)?; // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { - return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + // 载荷缺失即 detach(live 界判后不可达,防御背板):按 [[Get]] 读 + // undefined。 + return Ok(JsValue::undefined()); }; Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, index))) } @@ -1402,7 +1462,8 @@ fn ta_write(vm: &mut H, view: TypedArrayData, index: usize, value: Js let payload_ptr = array_buffer_payload(vm, view.buffer)?; // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { - return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + // 载荷缺失即 detach(live 界判后不可达,防御背板):写静默 no-op。 + return Ok(()); }; write_element(vm, view.kind, buffer, absolute_byte_offset(view, index), value); Ok(()) @@ -2483,6 +2544,8 @@ pub fn uint8array_to_base64(vm: &mut H, args: &[u8]) -> NativeResult let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { + // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, + // 此臂为防御背板。 return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; // 视图范围按 live 长度与当前缓冲长度双收口(缓冲可被 resize 收缩、 @@ -2499,6 +2562,8 @@ pub fn uint8array_to_hex(vm: &mut H, args: &[u8]) -> NativeResult { let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { + // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, + // 此臂为防御背板。 return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; // 视图范围按 live 长度与当前缓冲长度双收口(缓冲可被 resize 收缩、 @@ -2518,6 +2583,8 @@ pub fn uint8array_set_from_base64(vm: &mut H, args: &[u8]) -> NativeR let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, + // 此臂为防御背板。 return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; let start = view.byte_offset; @@ -2543,6 +2610,8 @@ pub fn uint8array_set_from_hex(vm: &mut H, args: &[u8]) -> NativeResu let payload_ptr = native_try!(array_buffer_payload(vm, view.buffer)); // SAFETY: payload_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。 let Some(buffer) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { + // buffer detach:按规范的视图校验步抛 TypeError;入口校验先行, + // 此臂为防御背板。 return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; let start = view.byte_offset; diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index ec5fb4ea..af41c7ea 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -359,6 +359,8 @@ impl Vm { } // TypedArray 整数索引:receiver 为 TA 本体时写底层 buffer(越界静默忽略); // receiver 非 TA 时按规范把写入落到 receiver 对象,不碰 TA buffer。 + // buffer 已 detach 的 TA:数值键(含非规范数值串)写静默忽略、不落 + // 命名属性;非数字串与 symbol 键落普通属性路径。 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) { @@ -375,6 +377,11 @@ impl Vm { } 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(()); + } } // 数组 length 赋值:走 ArraySetLength 语义(两次数值强转、可写性判定与 // 元素区调整),不得落影子命名属性——否则 prop_count / 迭代 / 内置方法 @@ -801,12 +808,16 @@ impl Vm { } /// 写新属性须分流到完整 ordinary_set 语义的场景:数组 length(ArraySetLength)与 - /// 数组/TA 整数索引键(元素区 / buffer 写)——这些键不在 shape 链上,CreateDataProperty - /// 快路径会错误地为其建命名属性。 + /// 数组/TA 整数索引键(元素区 / buffer 写)、detach 后 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) } /// 值写入直调路径:REST / SPREAD / builtin 内部等已确定目标对象的场景, @@ -831,12 +842,16 @@ 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 槽。 + // TypedArray 整数索引写 buffer(越界忽略),不进入 shape/prop 槽; + // buffer 已 detach 的数值键写静默忽略(与 ordinary_set_inner 同臂)。 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; + } } // 数组下标键写入元素区(维护 array_prop_count),不进入 shape 链。 if obj.is_array() {