feat: TA Set 面接统一数值键门与 receiver 四分类裁决

This commit is contained in:
2026-09-23 07:35:53 +00:00
parent 897cdf678e
commit 0645a3d2e4
6 changed files with 462 additions and 123 deletions
+76 -78
View File
@@ -368,28 +368,6 @@ pub fn typed_array_element_get<H: VmHost>(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<H: VmHost>(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::<u32>().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<H: VmHost>(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<H: VmHost>(_vm: &H, obj: &JsObject) -> usize {
/// 取 TypedArray 视图的 live 长度(供只需 length 不需门的枚举面与
/// receiver 界判消费方);非 TypedArray 或内部状态无效返回 0。越界
/// (detach、定长窗口被 buffer 收缩裁掉、auto offset 超 buffer)统一 0,
/// 界内键经门归数字无效。
pub fn ta_view_length<H: VmHost>(_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<H: VmHost>(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<H: VmHost>(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<TypedArrayData>,
// 与对象同生命周期,此处只读拷贝视图字段。
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<H: VmHost>(
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<H: VmHost>(
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<H: VmHost>(
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<H: VmHost>(
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<H: VmHost>(
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<H: VmHost>(vm: &mut H, err: JsValue) -> String {
/// 把异常 JsValue 格式化为 `Kind: message` 文本(属性写路径的 String 错误
/// 契约用;原生调用边界据此经 `create_from_text` 恢复 kind)。
pub fn element_error_text<H: VmHost>(vm: &mut H, err: JsValue) -> String {
if let Some(s) = vm.lookup_str(err) {
return s;
}
+15
View File
@@ -195,7 +195,22 @@ pub trait VmHost {
// 错误处理
fn checked_object_ptr(&mut self, val: JsValue, error_msg: &str) -> Result<Option<*mut JsObject>, 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<String>;
fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue;
+23
View File
@@ -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`。
///
/// # 边界与前提
+6
View File
@@ -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<JsValue> {
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)
}
+128 -43
View File
@@ -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() {
@@ -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());