feat: TA 定义自身属性数值索引臂与原型链门口径

This commit is contained in:
2026-09-23 08:31:47 +00:00
parent 0645a3d2e4
commit 18b80f2f41
7 changed files with 343 additions and 13 deletions
+44 -4
View File
@@ -731,6 +731,8 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
let get_field = own_field(vm, desc_val, get_si);
let set_field = own_field(vm, desc_val, set_si);
let writable_field = own_field(vm, desc_val, writable_si);
let enumerable_field = own_field(vm, desc_val, enumerable_si);
let configurable_field = own_field(vm, desc_val, configurable_si);
// 数组 length 是无 shape 槽的虚拟数据属性:需与普通已有属性一样参与描述符
// 缺省回填(writable 保持当前值、value 保持当前长度),其当前描述符由元素
@@ -779,10 +781,10 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
}
// 修改已有属性时缺省字段回填现有值,仅定义新属性时缺省才为 false。
let enumerable = own_field(vm, desc_val, enumerable_si)
let enumerable = enumerable_field
.map(oxide_runtime_api::to_boolean)
.unwrap_or_else(|| existing_meta.map(|m| m.attributes.enumerable()).unwrap_or(false));
let configurable = own_field(vm, desc_val, configurable_si)
let configurable = configurable_field
.map(oxide_runtime_api::to_boolean)
.unwrap_or_else(|| existing_meta.map(|m| m.attributes.configurable()).unwrap_or(false));
@@ -801,8 +803,9 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
})
};
let obj = unsafe { &mut *obj_ptr };
if has_accessor {
// 访问器描述符:解析 get/set(缺失字段回填现有属性)并做可调用性校验——
// 规范步序先于索引/约束检查。
let (get, set) = if has_accessor {
let get = get_field.unwrap_or_else(|| {
existing_meta
.filter(|m| m.is_accessor)
@@ -818,6 +821,43 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
if (!get.is_undefined() && !is_callable(get)) || (!set.is_undefined() && !is_callable(set)) {
return Err("accessor descriptor get/set must be callable or undefined".to_string());
}
(get, set)
} else {
(JsValue::undefined(), JsValue::undefined())
};
// TA 数值索引臂:置于混入/可调用检查之后、define 路由之前。字段在场四检查
// 取原始描述符字段(非缺省回填后的属性位);有效索引值臂缺省读当前元素
// (通用描述符);数字无效索引零副作用直接失败;非规范数字串 / symbol 键
// 落真实自有属性路径。
if unsafe { &*obj_ptr }.is_typed_array_obj() {
match crate::typed_array::ta_index_gate(vm, unsafe { &*obj_ptr }, key_si) {
crate::typed_array::TaIndexGate::NumericValid(index) => {
if has_accessor
|| writable_field.is_some_and(|w| !oxide_runtime_api::to_boolean(w))
|| enumerable_field.is_some_and(|e| !oxide_runtime_api::to_boolean(e))
|| configurable_field.is_some_and(|c| !oxide_runtime_api::to_boolean(c))
{
return Err(
"cannot define property: TypedArray index only accepts a writable, enumerable, configurable data descriptor"
.to_string(),
);
}
let value = value_field.unwrap_or_else(|| {
crate::typed_array::typed_array_element_get(vm, unsafe { &*obj_ptr }, index)
.unwrap_or(JsValue::undefined())
});
return crate::typed_array::typed_array_element_define(vm, unsafe { &mut *obj_ptr }, index, value);
}
crate::typed_array::TaIndexGate::NumericInvalid => {
return Err("cannot define property: TypedArray index out of range".to_string());
}
crate::typed_array::TaIndexGate::Ordinary => {}
}
}
let obj = unsafe { &mut *obj_ptr };
if has_accessor {
vm.define_accessor_property(obj, key_si, get, set, PropAttributes::new(false, enumerable, configurable))?;
} else if has_data {
let value = if has_existing {
+21 -2
View File
@@ -543,11 +543,18 @@ pub fn typed_array_receiver_set<H: VmHost>(
/// 定义 TypedArray 整数索引元素(defineProperty 语义)。
///
/// # 步骤
/// 1. 取视图(内部状态非法返回防御背板 Err)。
/// 2. 界判:越界(含 detach:live 长 0)拒绝定义。
/// 3. 清 uncaught 槽后强转写入;强转失败把原值转存 define 专用槽。
///
/// # 边界与前提
/// - 索引越界:拒绝定义(Err),与整数索引 exotic 对象的 [[DefineOwnProperty]] 一致
///
/// # 副作用
/// - 把值 ToNumber 后写入底层 buffer
/// - 强转期用户回调抛错时原值入 define 专用槽,Object/Reflect 入口据此
/// 原值重抛
// ponytail: 不校验 writable 描述符——调用方把"省略 writable"折叠为 false,
// 无法与显式 false 区分;TA 元素天然可写,直接写入。
pub fn typed_array_element_define<H: VmHost>(
@@ -558,7 +565,14 @@ pub fn typed_array_element_define<H: VmHost>(
if index as usize >= ta_live_length(view) {
return Err("cannot define property: TypedArray index out of range".to_string());
}
write_typed_array_element(vm, view, index, value)
// 强转前清 uncaught 槽(length 路径同纪律):失败后槽内值必为本次强转
// 产生的原值。
vm.clear_uncaught_value();
let r = write_typed_array_element(vm, view, index, value);
if r.is_err() {
vm.move_uncaught_to_pending_length();
}
r
}
fn write_typed_array_element<H: VmHost>(
@@ -577,7 +591,12 @@ fn write_typed_array_element<H: VmHost>(
Ok(v) => v,
// 转换失败恢复为可捕获的 JS 异常:深度 0 原值入通道就地展开到外围
// catch(kind 保真),深度 >0 以 kind 前缀文本由原生调用边界恢复。
Err(err) => return vm.raise_captured(err),
Err(err) => {
// 原值放回 uncaught 槽:define 通道专用槽转存依赖槽内原值
// (set 通道经文本消费,槽不改变其可观测行为)。
vm.restore_uncaught_value(Some(err));
return vm.raise_captured(err);
}
};
// live 边界:越界(含 detach)静默不写(规范 IntegerIndexedElementSet 的
// IsValidIndexedAccess 臂),转换副作用已先发生。
+8
View File
@@ -192,6 +192,14 @@ pub trait VmHost {
/// 写入的值,本槽仅由 length 强转失败写入,入口据此区分「强转异常」与
/// 「描述符收敛失败」。
fn take_pending_length_exception(&mut self) -> Option<JsValue>;
/// 清空 uncaught 槽。define 通道强转入口先清后转:强转失败后槽内值必为
/// 本次强转产生的原值,先前操作的残留不得被误转存为本次原值(与 length
/// 路径同纪律)。
fn clear_uncaught_value(&mut self);
/// 把 uncaught 槽移入 define 专用槽(强转期用户回调抛出的原值):
/// Object/Reflect define 入口失败时先取专用槽原值重抛,不投影为 false
/// 或改写成引擎错误。
fn move_uncaught_to_pending_length(&mut self);
// 错误处理
fn checked_object_ptr(&mut self, val: JsValue, error_msg: &str) -> Result<Option<*mut JsObject>, String>;
+5 -5
View File
@@ -216,10 +216,10 @@ impl Vm {
/// 属性即存在。数组 length 虚拟属性恒存在;数组元素区 hole 视同缺失;
/// 其余按 shape 槽判定。
///
/// 顶层 TypedArray 经统一数值键门(exotic [[HasProperty]]):界内整数索引
/// TypedArray 经统一数值键门(exotic [[HasProperty]]):界内整数索引
/// 判存在;数字无效键(负/分数/±Infinity/NaN/越界,含 "-0" 特例)立即
/// false,不查自身命名属性也不走原型链;非规范数字串落普通路径。原型链上
/// 的 TA 按普通对象查 shape 槽。
/// false,不查自身命名属性也不走原型链;非规范数字串落普通路径。原型链
/// 上的 TA 按同口径经门判定。
///
/// 与 `resolve_property` 的差异:原型链上每层都检查数组元素区
/// (`resolve_property` 只在顶层检查元素区,链上仅走 shape 槽),
@@ -233,8 +233,8 @@ impl Vm {
return true;
}
// 统一数值键门(exotic [[HasProperty]]):同 `ordinary_get_inner`
// 的口径,门只落在顶层对象。
if obj.is_typed_array_obj() && depth == 0 {
// 的口径,链上每层同口径判定。
if obj.is_typed_array_obj() {
match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) {
oxide_builtins::typed_array::TaIndexGate::NumericValid(_) => return true,
oxide_builtins::typed_array::TaIndexGate::NumericInvalid => return false,
+6
View File
@@ -61,6 +61,12 @@ impl oxide_runtime_api::VmHost for Vm {
fn take_pending_length_exception(&mut self) -> Option<JsValue> {
self.pending_length_exception.take()
}
fn clear_uncaught_value(&mut self) {
self.last_uncaught_value = None;
}
fn move_uncaught_to_pending_length(&mut self) {
self.pending_length_exception = self.last_uncaught_value.take();
}
fn property_key_si(&mut self, val: JsValue) -> u32 {
// 对象键转换(to_string_full)失败时降级为空键:本 trait 路径供
// Reflect/Object 内置使用,这些内置按规范不传播键转换异常;计算属性
+13 -2
View File
@@ -103,8 +103,8 @@ impl Vm {
// 统一数值键门(exotic [[Get]]):界内整数读底层 buffer;数字无效
// 键(负/分数/±Infinity/NaN/越界,含 "-0" 特例)立即 undefined 不
// 查自身命名属性也不走原型链;非规范数字串落下方普通属性路径。
// 门只落在顶层对象:原型链上的 TA 维持普通 shape 槽查找。
if obj.is_typed_array_obj() && depth == 0 {
// 链上每层同口径:原型链上的 TA 亦按 exotic 语义判定。
if obj.is_typed_array_obj() {
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_get(self, obj, index);
@@ -1105,6 +1105,17 @@ impl Vm {
let target_ptr = obj as *mut JsObject;
let get = self.promote_if_needed_for_write_ptr(target_ptr, get);
let set = self.promote_if_needed_for_write_ptr(target_ptr, set);
// TA 数值索引臂:界内 / 数字无效索引不接受 accessor(false);非规范数字
// 串 / symbol 键落真实 accessor 属性。
if obj.is_typed_array_obj() {
match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) {
oxide_builtins::typed_array::TaIndexGate::NumericValid(_)
| oxide_builtins::typed_array::TaIndexGate::NumericInvalid => {
return Err("cannot define property: TypedArray index only accepts a data descriptor".to_string());
}
oxide_builtins::typed_array::TaIndexGate::Ordinary => {}
}
}
if obj.is_array() {
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
return self.define_array_index_element(obj, index, JsValue::undefined(), attributes, true, get, set);
@@ -1050,3 +1050,249 @@ fn ta_set_resize_convert_first() {
.unwrap();
assert!(result.as_bool());
}
// ── DefineOwnProperty 数值索引臂 ───────────────────────────────────────────
#[test]
fn ta_dop_valid_write() {
// 界内规范键 + 全 true 数据描述符:定义成功、元素写入、length 不变
// (Reflect 与 Object.defineProperty 同臂)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Uint8Array(4); \
var r1 = Reflect.defineProperty(ta, '1', { value: 200, writable: true, enumerable: true, configurable: true }); \
Object.defineProperty(ta, 3, { value: 7, writable: true, enumerable: true, configurable: true }); \
return r1 === true && ta[1] === 200 && ta[3] === 7 && ta.length === 4 && ta[0] === 0 && ta[2] === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_partial_and_generic() {
// 部分描述符(缺省字段非显式 false)与通用描述符(无 [[Value]] 读当前
// 元素回写)均定义成功;元素值除显式 value 外不变(NaN 样本)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Float64Array(2); \
ta[0] = NaN; ta[1] = NaN; \
var r1 = Reflect.defineProperty(ta, '0', { value: 5 }); \
var r2 = Reflect.defineProperty(ta, '1', { writable: true, enumerable: true, configurable: true }); \
return r1 === true && ta[0] === 5 && r2 === true && Number.isNaN(ta[1]); })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_constraint_false() {
// 字段在场四检查(writable/enumerable/configurable 显式 false、accessor
// 描述符):Reflect 投影 false、元素不动、不建自有属性。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int16Array(2); ta[0] = 9; \
var shapes = [ \
{ value: 3, writable: false, enumerable: true, configurable: true }, \
{ value: 3, writable: true, enumerable: false, configurable: true }, \
{ value: 3, writable: true, enumerable: true, configurable: false }, \
{ get: function () { return 1; }, set: function () {} } ]; \
for (var i = 0; i < shapes.length; i++) { \
if (Reflect.defineProperty(ta, '0', shapes[i]) !== false) return false; \
if (ta[0] !== 9) return false; } \
return Object.getOwnPropertyNames(ta).length === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_constraint_throws() {
// 同四约束形经 Object.defineProperty:抛 TypeError、元素不动、无属性。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int16Array(2); ta[0] = 9; \
var t1 = 0; \
try { Object.defineProperty(ta, '0', { value: 3, writable: false, enumerable: true, configurable: true }); } \
catch (e) { if (!(e instanceof TypeError)) return false; t1 = 1; } \
var t2 = 0; \
try { Object.defineProperty(ta, '0', { get: function () { return 1; } }); } \
catch (e) { if (!(e instanceof TypeError)) return false; t2 = 1; } \
return t1 === 1 && t2 === 1 && ta[0] === 9 && Object.getOwnPropertyNames(ta).length === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_numeric_invalid_false() {
// 数字无效键("-0"/负/越界/分数):false、不抛、元素不动、不建属性。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array(2); ta[0] = 5; \
var keys = ['-0', '-1', '2', '0.1', '0.000001', '3.5']; \
for (var i = 0; i < keys.length; i++) { \
var d = { value: 1, writable: true, enumerable: true, configurable: true }; \
if (Reflect.defineProperty(ta, keys[i], d) !== false) return false; \
if (ta[0] !== 5) return false; } \
return Object.getOwnPropertyNames(ta).length === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_ordinary_key_property() {
// 非规范数字串键 round-trip 不成:定义真实自有属性(数据与访问器),
// 键 "1.0" 与 "1" 独立互不干扰。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array(1); \
var keys = ['1.0', '+1', '1000000000000000000000', '0.0000001']; \
for (var i = 0; i < keys.length; i++) { \
var r = Reflect.defineProperty(ta, keys[i], { value: i + 1, writable: true, enumerable: true, configurable: true }); \
if (r !== true) return false; \
if (!Object.prototype.hasOwnProperty.call(ta, keys[i])) return false; \
if (ta[keys[i]] !== i + 1) return false; } \
var r2 = Reflect.defineProperty(ta, '1.0', { get: function () { return 'baz'; }, configurable: true }); \
return r2 === true && ta['1.0'] === 'baz' && ta['1'] === undefined; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_detach_false_no_throw() {
// detach 后 live 长 0:全数值键归数字无效,false 不抛(投错值不触发
// 强转)、不建属性。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ab = new ArrayBuffer(8); var ta = new Int8Array(ab); ta[0] = 7; \
ab.transfer(); \
var keys = ['0', '-1', '1.1', '-0', '2']; \
for (var i = 0; i < keys.length; i++) { \
var r = Reflect.defineProperty(ta, keys[i], { \
value: { valueOf: function () { throw new Error('no'); } }, \
writable: true, enumerable: true, configurable: true }); \
if (r !== false) return false; } \
return Object.getOwnPropertyNames(ta).length === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_detach_in_valueof_true() {
// valueOf 期 detach:强转成功、写期 live 复判 detach 静默——定义 true、
// 元素保持 undefined。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ab = new ArrayBuffer(8); var ta = new Int8Array(ab); \
var v = { valueOf: function () { ab.transfer(); return 9; } }; \
var r = Reflect.defineProperty(ta, '0', { value: v, writable: true, enumerable: true, configurable: true }); \
return r === true && ta[0] === undefined; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_value_throws_original() {
// 强转期用户回调抛出原值经专用槽重抛:catch 收到原对象身份(构造器
// 身份不可丢)、不建属性。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array(2); \
function Marker() { this.tag = 'marker'; } \
var marker = new Marker(); \
var v = { valueOf: function () { throw marker; } }; \
var caught = null; \
try { Object.defineProperty(ta, '0', { value: v, writable: true, enumerable: true, configurable: true }); \
return false; } \
catch (e) { caught = e; } \
return caught === marker && caught.tag === 'marker' && Object.getOwnPropertyNames(ta).length === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_bigint_content_conversion() {
// BigInt 元素内容强转:Number 值 TypeError、非整数字符串 SyntaxError、
// BigInt 值成功写入。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new BigInt64Array(2); \
var t1 = 0; \
try { Object.defineProperty(ta, '0', { value: 42, writable: true, enumerable: true, configurable: true }); } \
catch (e) { if (!(e instanceof TypeError)) return false; t1 = 1; } \
var t2 = 0; \
try { Object.defineProperty(ta, '0', { value: '1.5', writable: true, enumerable: true, configurable: true }); } \
catch (e) { if (!(e instanceof SyntaxError)) return false; t2 = 1; } \
var r3 = Reflect.defineProperty(ta, '1', { value: 7n, writable: true, enumerable: true, configurable: true }); \
return t1 === 1 && t2 === 1 && r3 === true && ta[1] === 7n; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_non_extensible() {
// 不可扩展 TA:界内索引定义不受限;数字无效/新命名/新 symbol 键 false;
// 既有真实命名属性重定义不受限。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array(2); ta[0] = 1; ta.newProp = 5; \
Object.preventExtensions(ta); \
var r1 = Reflect.defineProperty(ta, '1', { value: 9, writable: true, enumerable: true, configurable: true }); \
var r2 = Reflect.defineProperty(ta, '2', { value: 9, writable: true, enumerable: true, configurable: true }); \
var r3 = Reflect.defineProperty(ta, 'brandnew', { value: 1, writable: true, enumerable: true, configurable: true }); \
var r4 = Reflect.defineProperty(ta, 'newProp', { value: 6, writable: true, enumerable: true, configurable: true }); \
var r5 = Reflect.defineProperty(ta, Symbol('s'), { value: 1, writable: true, enumerable: true, configurable: true }); \
return r1 === true && ta[1] === 9 && r2 === false && r3 === false && r4 === true && ta.newProp === 6 && r5 === false; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_int_key_same_arm() {
// 整数键(非字符串)与字符串键同臂:界内定义成功、detach 后归数字无效。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ab = new ArrayBuffer(8); var ta = new Int8Array(ab); \
var r1 = Reflect.defineProperty(ta, 1, { value: 4, writable: true, enumerable: true, configurable: true }); \
if (r1 !== true || ta[1] !== 4) return false; \
ab.transfer(); \
var r2 = Reflect.defineProperty(ta, 0, { value: 7, writable: true, enumerable: true, configurable: true }); \
return r2 === false; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_dop_get_dir_arm4() {
// 非规范数字串键 + accessor 描述符:定义真实 accessor 属性、getter 经
// 普通读路径触发(Get 目录 key-is-not-canonical-index 联绿臂)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array(2); \
var r = Reflect.defineProperty(ta, '+1', { get: function () { return 'baz'; }, set: function () {}, configurable: true }); \
return r === true && ta['+1'] === 'baz'; })()",
)
.unwrap();
assert!(result.as_bool());
}