fix: 数组 for-in 可枚举滤按元素区偏移取命名属性元数据

This commit is contained in:
2026-10-06 00:09:38 +00:00
parent 0a1ca83661
commit cb844ce64d
4 changed files with 191 additions and 20 deletions
Generated
+18 -18
View File
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"chrono",
"fast-float",
@@ -1120,7 +1120,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1128,7 +1128,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1139,7 +1139,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1159,7 +1159,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1168,7 +1168,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1187,7 +1187,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1201,7 +1201,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1216,7 +1216,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"bumpalo",
"dashmap",
@@ -1240,7 +1240,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1252,7 +1252,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"tracing",
"tracing-appender",
@@ -1261,7 +1261,7 @@ dependencies = [
[[package]]
name = "oxide_parser"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1276,7 +1276,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1292,7 +1292,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"num-bigint",
"num-traits",
@@ -1304,7 +1304,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"bumpalo",
"hashbrown 0.14.5",
@@ -1333,7 +1333,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.8.15"
version = "0.8.16"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
]
[workspace.package]
version = "0.8.15"
version = "0.8.16"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+5 -1
View File
@@ -538,7 +538,11 @@ impl Vm {
.kernel_core
.shape_forge()
.lookup_position(cur.shape_id(), shape.property_name)
.and_then(|pos| cur.prop_meta_at(pos))
// 数组命名属性存储下标 = 元素区偏移加 shape 槽位;
// 普通对象槽位即下标,空数组偏移为零。
.and_then(|pos| {
cur.prop_meta_at(if cur.is_array() { cur.array_prop_count + pos } else { pos })
})
.map(|meta| meta.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable());
if enumerable {
@@ -0,0 +1,167 @@
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> String {
let allocator = Allocator::default();
let program = match oxide_parser::parse(&allocator, source) {
Ok(p) => p,
Err(e) => return format!("parse error: {}", e[0].message),
};
let module = match Compiler::new().compile(&program) {
Ok(m) => m,
Err(e) => return format!("compile error: {e}"),
};
let mut vm = Vm::new();
match vm.run(&Arc::new(module)) {
Ok(result) => format!("{result}"),
Err(e) => format!("vm error: {e}"),
}
}
// 数组命名属性可枚举性判定与形状槽位、存储下标两口径对齐:for-in 枚举
// 命名属性时按元素区偏移取元数据,非空数组不可枚举命名属性被滤出、
// 元素不可枚举不连带误滤命名属性,与 Object.keys 同形。
#[test]
fn for_in_array_non_enum_named_property_filtered() {
// 非空数组不可枚举命名属性不进入 for-in 枚举:
// 单元素数组加不可枚举命名属性后仅枚举下标键。
assert_eq!(
eval(
"var g = [1]; \
Object.defineProperty(g, 'x', { value: 9, enumerable: false }); \
var ka = []; for (var k in g) ka.push(k); \
ka.length === 1 && ka[0] === '0'"
),
"true",
"non-enumerable named property on a non-empty array is filtered out of for-in"
);
}
#[test]
fn for_in_array_multi_element_non_enum_named_filtered() {
// 多元素数组可枚举命名属性保留、不可枚举命名属性滤出:
// 下标键升序加可枚举命名属性,无不可枚举命名属性。
assert_eq!(
eval(
"var h = [1, 2, 3]; h.p = 1; \
Object.defineProperty(h, 'q', { value: 2, enumerable: false }); \
var kb = []; for (var k in h) kb.push(k); \
kb.length === 4 && kb[0] === '0' && kb[1] === '1' && kb[2] === '2' && kb[3] === 'p'"
),
"true",
"enumerable named property kept, non-enumerable one filtered on multi-element array"
);
}
#[test]
fn for_in_array_non_enum_element_does_not_filter_named() {
// 元素不可枚举不连带误滤命名属性:for-in 与 Object.keys 同形,
// 两者都只含可枚举命名属性。
assert_eq!(
eval(
"var n = [1]; \
Object.defineProperty(n, '0', { value: 1, enumerable: false }); \
n.named = 5; \
var kc = []; for (var k in n) kc.push(k); \
var keys = Object.keys(n); \
kc.length === 1 && kc[0] === 'named' && keys.length === 1 && keys[0] === 'named'"
),
"true",
"non-enumerable array element does not drag the enumerable named property out of for-in"
);
}
#[test]
fn for_in_array_accessor_presence_unchanged() {
// accessor 命名属性存在性回归:Object.hasOwn / in / hasOwnProperty
// 三判全真,形状槽命中即在场。
assert_eq!(
eval(
"var a = [1]; \
Object.defineProperty(a, 'ctor2', { get: function() { return 'g'; } }); \
Object.hasOwn(a, 'ctor2') && ('ctor2' in a) && a.hasOwnProperty('ctor2')"
),
"true",
"accessor named property on array is present for hasOwn, in, hasOwnProperty"
);
}
#[test]
fn for_in_array_accessor_getter_read_and_keys() {
// accessor 命名属性读值与键表回归:getter 触发读值,
// Object.keys 只含下标键不含 accessor 命名属性。
assert_eq!(
eval(
"var a = [1]; \
Object.defineProperty(a, 'ctor2', { get: function() { return 'g'; } }); \
a.ctor2 === 'g' && Object.keys(a).length === 1 && Object.keys(a)[0] === '0'"
),
"true",
"accessor getter fires on read and Object.keys lists only the index key"
);
}
#[test]
fn for_in_empty_array_accessor_and_data_zero_drift() {
// 空数组 for-in 零扰动:空数组元素区偏移为零,
// accessor 与数据命名属性按插入序全枚举。
assert_eq!(
eval(
"var z = []; \
Object.defineProperty(z, 'a', { get: function() { return 1; }, enumerable: true }); \
z.b = 2; \
var kz = []; for (var k in z) kz.push(k); \
kz.length === 2 && kz[0] === 'a' && kz[1] === 'b'"
),
"true",
"empty array for-in enumerates accessor and data named properties in insertion order"
);
}
#[test]
fn for_in_array_all_elements_enumerable_zero_drift() {
// 全元素可枚举数组 for-in 零扰动:仅下标键升序枚举。
assert_eq!(
eval(
"var t = [1, 2]; \
var kt = []; for (var k in t) kt.push(k); \
kt.length === 2 && kt[0] === '0' && kt[1] === '1'"
),
"true",
"plain array for-in enumerates index keys in ascending order"
);
}
#[test]
fn for_in_ordinary_object_zero_drift() {
// 普通对象 for-in 零漂移:命名属性按插入序全枚举,
// 数组偏移分支不触及普通对象路径。
assert_eq!(
eval(
"var o = { a: 1, b: 2 }; \
var ko = []; for (var k in o) ko.push(k); \
ko.length === 2 && ko[0] === 'a' && ko[1] === 'b'"
),
"true",
"ordinary object for-in keeps insertion order"
);
}
#[test]
fn for_in_array_element_accessor_arm_untouched() {
// 元素区 accessor 定义后读值、存在性、描述符三面不变:
// 下标 0 的 accessor 触发 getter,hasOwn 判在,描述符 get 在场。
assert_eq!(
eval(
"var j = [1, 2]; \
Object.defineProperty(j, 0, { get: function() { return 'e'; } }); \
j[0] === 'e' && Object.hasOwn(j, 0) && Object.getOwnPropertyDescriptor(j, 0).get !== undefined"
),
"true",
"element-area accessor keeps working: getter fires, hasOwn true, descriptor get present"
);
}