feat: TA 枚举面接 live 长元素键与完整性家族跳键

This commit is contained in:
2026-09-23 10:42:53 +00:00
parent ff1abe0131
commit c0f8970912
3 changed files with 282 additions and 18 deletions
+40 -6
View File
@@ -3,7 +3,7 @@ use oxide_kernel::string_forge::PermInterner;
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::private_key::{
int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, make_well_known_symbol_key,
symbol_index_from_key, well_known_symbol_id_from_key,
symbol_index_from_key, well_known_symbol_id_from_key, INT_KEY_COUNT,
};
use oxide_types::value::JsValue;
@@ -22,9 +22,19 @@ fn is_integer_index(key: &str) -> bool {
/// 收集对象全部自身属性(数组元素区 + shape 链),按规范顺序排列:整数索引在前
/// 升序,其余保持插入序。si 指 interned 字符串键编号(PermInterner 分配的 u32 id)。
/// 返回 `(属性键 si, 绝对存储索引)`:数组对象元素区索引即绝对下标,命名属性 =
/// `array_prop_count + shape 槽位`;普通对象即 shape 槽位。
/// `array_prop_count + shape 槽位`;普通对象即 shape 槽位。TA 元素键(0..live
/// 长)以哨兵存储索引 `u32::MAX` 推入(无槽位),越界(含 detach)live 长 0 键集空。
pub fn walk_own_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
let mut keys: Vec<(u32, u32)> = Vec::new();
// TA 元素键:整数下标 0..live 长是可枚举自身属性(元素住 buffer,不在形状
// 链);越界(含 detach)live 长 0,键集自然空。哨兵存储下标 u32::MAX =
// 无槽位(meta 读一律 None,取值消费方按键路由元素读)。
if obj.is_typed_array_obj() {
let len = crate::typed_array::ta_view_length(vm, obj) as u32;
for i in 0..len.min(INT_KEY_COUNT) {
keys.push((make_int_key(i), u32::MAX));
}
}
// 数组元素区:整数下标是可枚举自身属性(hole 视为不存在),排在命名属性之前。
if obj.is_array() {
for i in 0..obj.array_prop_count {
@@ -537,10 +547,17 @@ fn namespace_export_get(obj: &JsObject, key_si: u32) -> Result<Option<JsValue>,
/// values/entries 族单自身属性值读(EnumerableOwnProperties 的 Get):模块命名空间
/// 导出走活值查询(未初始化抛 ReferenceError);访问器属性触发 getter(this = 对象
/// 自身),异常传播原始抛出值;数据属性直读存储槽。
/// 自身),异常传播原始抛出值;数据属性直读存储槽。TA 元素键无存储槽:live 界判
/// (含 detach/收缩)直读元素值,访问器语义不适用。
fn own_property_value<H: VmHost>(
vm: &mut H, obj: &JsObject, obj_val: JsValue, si: u32, offset: u32,
) -> Result<JsValue, JsValue> {
// TA 元素键无存储槽(元素住 buffer):live 界判含 detach/收缩,直读元素值。
if obj.is_typed_array_obj() && is_int_key(si) {
return Ok(
crate::typed_array::typed_array_element_get(vm, obj, int_key_value(si)).unwrap_or(JsValue::undefined())
);
}
match namespace_export_get(obj, si) {
Ok(Some(value)) => return Ok(value),
Ok(None) => {}
@@ -1227,6 +1244,7 @@ fn seal_own_prop_meta(obj: &mut JsObject, store: u32) {
/// # 副作用
/// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续
/// 写/define 一律回落 ordinary_set / define 检查(writable/configurable 判定)
/// - TA 元素键无 meta 槽,循环跳键(元素零修改)
pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.freeze called on non-object"));
@@ -1248,7 +1266,12 @@ pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj = unsafe { &mut *obj_ptr };
// 命名属性:walk_own_keys 返回绝对存储索引(数组含元素区偏移)。
let keys = walk_own_keys(vm, obj);
for (_si, pos) in keys {
for (si, pos) in keys {
// TA 元素键无 meta 槽(元素住 buffer):完整性处理不触元素,跳键免
// set_meta_at 越界扩属性表。
if obj.is_typed_array_obj() && is_int_key(si) {
continue;
}
freeze_own_prop_meta(obj, pos);
}
// 数组元素区独立于 shape 链(hole 非 own 属性,跳过)。
@@ -1271,6 +1294,7 @@ pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// # 副作用
/// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续
/// define 回落 non-configurable 检查(已有属性写仍可正常进行)
/// - TA 元素键无 meta 槽,循环跳键(元素零修改)
pub fn object_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.seal called on non-object"));
@@ -1283,7 +1307,12 @@ pub fn object_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
{
let obj = unsafe { &mut *obj_ptr };
let keys = walk_own_keys(vm, obj);
for (_si, pos) in keys {
for (si, pos) in keys {
// TA 元素键无 meta 槽(元素住 buffer):完整性处理不触元素,跳键免
// set_meta_at 越界扩属性表。
if obj.is_typed_array_obj() && is_int_key(si) {
continue;
}
seal_own_prop_meta(obj, pos);
}
if obj.is_array() {
@@ -1373,7 +1402,12 @@ pub fn object_is_sealed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Ok(JsValue::bool(false));
}
let keys = walk_own_keys(vm, obj);
for (_si, offset) in keys {
for (si, offset) in keys {
// 同款跳键:元素键描述符 configurable:true,PE/sealed 的 TA 判 sealed
// (V8 同口径)。
if obj.is_typed_array_obj() && is_int_key(si) {
continue;
}
if let Some(meta) = obj.prop_meta_at(offset) {
if meta.attributes.configurable() {
return NativeResult::Ok(JsValue::bool(false));
+9 -1
View File
@@ -4,7 +4,7 @@ use oxide_runtime_api::{push_units_to, to_boolean, to_units_full};
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::{
int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, make_well_known_symbol_key,
WELL_KNOWN_SYMBOL_HAS_INSTANCE,
INT_KEY_COUNT, WELL_KNOWN_SYMBOL_HAS_INSTANCE,
};
use oxide_types::value::JsValue;
@@ -453,6 +453,14 @@ impl Vm {
}
}
}
// TA 元素键:整数下标 0..live 长升序排前(尾稳定排序负责序);
// 越界(含 detach)live 长 0 零枚;元素住 buffer,不在形状链。
if arr.is_typed_array_obj() {
let len = oxide_builtins::typed_array::ta_view_length(self, arr) as u32;
for i in 0..len.min(INT_KEY_COUNT) {
keys_vec.push((self.new_string(&i.to_string()), make_int_key(i)));
}
}
}
let mut depth = 0usize;
@@ -1102,7 +1102,7 @@ fn ta_dop_constraint_false() {
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; })()",
return Object.getOwnPropertyNames(ta).join(',') === '0,1'; })()",
)
.unwrap();
assert!(result.as_bool());
@@ -1121,7 +1121,7 @@ fn ta_dop_constraint_throws() {
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; })()",
return t1 === 1 && t2 === 1 && ta[0] === 9 && Object.getOwnPropertyNames(ta).join(',') === '0,1'; })()",
)
.unwrap();
assert!(result.as_bool());
@@ -1139,7 +1139,7 @@ fn ta_dop_numeric_invalid_false() {
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; })()",
return Object.getOwnPropertyNames(ta).join(',') === '0,1'; })()",
)
.unwrap();
assert!(result.as_bool());
@@ -1218,7 +1218,7 @@ fn ta_dop_value_throws_original() {
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; })()",
return caught === marker && caught.tag === 'marker' && Object.getOwnPropertyNames(ta).join(',') === '0,1'; })()",
)
.unwrap();
assert!(result.as_bool());
@@ -1656,13 +1656,235 @@ fn ta_gopd_real_prop_attrs_passthrough() {
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array([42]); \
Object.defineProperty(ta, '1.0', { \
value: 7, writable: true, enumerable: true, configurable: false }); \
var d = Object.getOwnPropertyDescriptor(ta, '1.0'); \
if (!d || d.value !== 7) return false; \
if (d.writable !== true || d.enumerable !== true || d.configurable !== false) return false; \
return ta.hasOwnProperty('1.0') === true \
&& ta.propertyIsEnumerable('1.0') === true; })()",
Object.defineProperty(ta, '1.0', { \
value: 7, writable: true, enumerable: true, configurable: false }); \
var d = Object.getOwnPropertyDescriptor(ta, '1.0'); \
if (!d || d.value !== 7) return false; \
if (d.writable !== true || d.enumerable !== true || d.configurable !== false) return false; \
return ta.hasOwnProperty('1.0') === true \
&& ta.propertyIsEnumerable('1.0') === true; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_ownkeys_integer_indexes() {
// ownKeys 元素键 = 0..live 长升序:len-3 三键、len-4 四键、subarray 视图
// 相对下标、len-0 空、BigInt 族同形。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Uint8Array([1, 2, 3]); \
if (Reflect.ownKeys(ta).join(',') !== '0,1,2') return false; \
if (Reflect.ownKeys(new Uint8Array(4)).join(',') !== '0,1,2,3') return false; \
var base = new Int8Array(4); var sub = base.subarray(2); \
if (Reflect.ownKeys(sub).join(',') !== '0,1') return false; \
if (Reflect.ownKeys(new Uint8Array(0)).length !== 0) return false; \
var b = new BigInt64Array([1n, 2n, 3n]); \
return Reflect.ownKeys(b).join(',') === '0,1,2'; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_ownkeys_order_indexes_strings_symbols() {
// 三段序:整数下标升序 → 真串键创建序 → 符号创建序;原型同键(含整数
// 索引形态)不入 ownKeys。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Uint8Array([1, 2]); \
ta.test262 = 'a'; ta.ecma262 = 'b'; \
var s1 = Symbol('s1'), s2 = Symbol('s2'); \
ta[s1] = 1; ta[s2] = 2; \
Uint8Array.prototype[3] = 'x'; Uint8Array.prototype.bar = 'y'; \
var ks = Reflect.ownKeys(ta); \
return ks.length === 6 && ks[0] === '0' && ks[1] === '1' \
&& ks[2] === 'test262' && ks[3] === 'ecma262' && ks[4] === s1 && ks[5] === s2; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_keys_values_entries_strings_only() {
// keys = 下标 + 可枚举真串键(符号不入、e:false 真属性不入);values/entries
// 同序含元素值与真属性值。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Uint8Array([1, 2]); \
ta.foo = 42; \
Object.defineProperty(ta, 'bar', { value: 7, enumerable: false }); \
if (Object.keys(ta).join(',') !== '0,1,foo') return false; \
var vs = Object.values(ta); \
if (vs.length !== 3 || vs[0] !== 1 || vs[1] !== 2 || vs[2] !== 42) return false; \
var es = Object.entries(ta); \
return es.length === 3 && es[0][0] === '0' && es[0][1] === 1 \
&& es[1][0] === '1' && es[1][1] === 2 && es[2][0] === 'foo' && es[2][1] === 42; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_gopn_gopds_include_indexes() {
// getOwnPropertyNames = 下标 + 真串键;getOwnPropertyDescriptors 下标描述符
// 常量四字段(value + w/e/c 全真),真属性原样。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Uint8Array([1, 2]); \
ta.foo = 42; \
if (Object.getOwnPropertyNames(ta).join(',') !== '0,1,foo') return false; \
var ds = Object.getOwnPropertyDescriptors(ta); \
var d0 = ds['0']; \
if (!d0 || d0.value !== 1 || d0.writable !== true \
|| d0.enumerable !== true || d0.configurable !== true) return false; \
var dfoo = ds['foo']; \
return !!dfoo && dfoo.value === 42 \
&& dfoo.writable === true && dfoo.enumerable === true && dfoo.configurable === true; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_for_in_indexes_then_strings() {
// for-in = 下标升序 + 真串键(符号不入);len-0 与 detach 形仅真属性、无下标。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Uint8Array([1, 2]); \
ta.foo = 42; var s = Symbol('s'); ta[s] = 1; \
var ks = []; for (var k in ta) ks.push(k); \
if (ks.length !== 3 || ks[0] !== '0' || ks[1] !== '1' || ks[2] !== 'foo') return false; \
var e = new Uint8Array(0); \
var ke = 0; for (var k2 in e) ke++; \
if (ke !== 0) return false; \
var ab = new ArrayBuffer(8); var d = new Uint8Array(ab); d.foo = 1; \
ab.transfer(); \
var kd = []; for (var k3 in d) kd.push(k3); \
return kd.length === 1 && kd[0] === 'foo'; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_assign_spread_rest_copy_indexes() {
// Object.assign / 展开 / 解构 rest 均拷下标值 + 可枚举真属性(rest 排除
// 已绑定键)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Uint8Array([1, 2]); \
ta.foo = 9; \
var o1 = Object.assign({}, ta); \
if (Object.keys(o1).sort().join(',') !== '0,1,foo') return false; \
if (o1['0'] !== 1 || o1['1'] !== 2 || o1.foo !== 9) return false; \
var o2 = { ...ta }; \
if (o2['0'] !== 1 || o2['1'] !== 2 || o2.foo !== 9) return false; \
var { foo, ...rest } = ta; \
return Object.keys(rest).sort().join(',') === '0,1' \
&& rest['0'] === 1 && rest['1'] === 2 && rest.foo === undefined; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_resize_auto_follows_live_length() {
// auto 视图 ownKeys 跟随 live 长:grow/shrink/边界(maxByteLength)四步;
// offset 超 buffer 越界形零枚。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ab = new ArrayBuffer(4, { maxByteLength: 12 }); \
var ta = new Uint8Array(ab); \
if (Reflect.ownKeys(ta).join(',') !== '0,1,2,3') return false; \
ab.resize(8); \
if (Reflect.ownKeys(ta).join(',') !== '0,1,2,3,4,5,6,7') return false; \
ab.resize(2); \
if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \
ab.resize(12); \
if (Reflect.ownKeys(ta).join(',') !== '0,1,2,3,4,5,6,7,8,9,10,11') return false; \
var v = new Uint8Array(ab, 12); \
return Reflect.ownKeys(v).length === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_resize_fixed_clip_oob_empty() {
// fixed 视图(offset 2 len 2):grow 与窗口内 shrink 键集保持 0,1;
// buffer 收缩裁出窗口即越界零枚。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ab = new ArrayBuffer(8, { maxByteLength: 16 }); \
var ta = new Uint8Array(ab, 2, 2); \
if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \
ab.resize(16); \
if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \
ab.resize(4); \
if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \
ab.resize(3); \
return Reflect.ownKeys(ta).length === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_detached_empty_enum() {
// detach 后 live 长 0:ownKeys/keys/values/for-in 仅真属性(序不变、无下标),
// 真属性值不动。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ab = new ArrayBuffer(8); \
var ta = new Uint8Array(ab); \
ta.foo = 42; var s = Symbol('s'); ta[s] = 5; \
ab.transfer(); \
var ks = Reflect.ownKeys(ta); \
if (ks.length !== 2 || ks[0] !== 'foo' || ks[1] !== s) return false; \
if (Object.keys(ta).join(',') !== 'foo') return false; \
var vs = Object.values(ta); \
if (vs.length !== 1 || vs[0] !== 42) return false; \
var kf = []; for (var k in ta) kf.push(k); \
return kf.length === 1 && kf[0] === 'foo' && ta.foo === 42; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_integrity_freeze_seal_no_element_touch() {
// 完整性家族跳键:seal 后 isSealed 真、元素写仍通、加真属性失败、键集不变、
// 无损坏(读/长/缓冲不动);freeze len-0 → isFrozen 真;fresh len>0 非冻结;
// PE 的 TA 判 sealed(元素键描述符 c:true 口径)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array([1, 2]); \
Object.seal(ta); \
if (Object.isSealed(ta) !== true) return false; \
ta[0] = 9; \
if (ta[0] !== 9 || ta[1] !== 2 || ta.length !== 2) return false; \
if (Reflect.defineProperty(ta, 'x', { value: 1, configurable: true }) !== false) return false; \
if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \
var f = new Int8Array(0); \
Object.freeze(f); \
if (Object.isFrozen(f) !== true) return false; \
var t2 = new Int8Array([5, 6]); \
if (Object.isFrozen(t2) !== false || Object.isSealed(t2) !== false) return false; \
var t3 = new Int8Array(2); \
Object.preventExtensions(t3); \
return Object.isSealed(t3) === true; })()",
)
.unwrap();
assert!(result.as_bool());