mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 18:51:13 +08:00
feat: TA Set 面接统一数值键门与 receiver 四分类裁决
This commit is contained in:
@@ -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`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
|
||||
@@ -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
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user