JavaScript: getOwnPropertyNames lists an array's length after its other names

Section 15.2.3.4 leaves the order of the names open. An array's length now comes after its indices and other properties, so Object.getOwnPropertyNames([0, 1, 2]) gives 0,1,2,length, as later editions and test262 expect. length is not enumerable, so Object.keys and for-in are unchanged.
This commit is contained in:
dotCooCoo
2026-11-07 19:23:56 -08:00
parent 813fe78a8c
commit 2d13f219f6
2 changed files with 12 additions and 7 deletions
+2 -1
View File
@@ -100,7 +100,8 @@ fn object_functions_list_create_and_lock_objects() {
);
truth("Object.getPrototypeOf(Object.create(null)) === null && Object.getPrototypeOf([]) === Array.prototype");
assert_eq!(string("Object.getOwnPropertyNames(Object('ab')).join()"), "0,1,length");
assert_eq!(string("var a = [5]; a.k = 1; Object.getOwnPropertyNames(a).join()"), "length,0,k");
assert_eq!(string("Object.getOwnPropertyNames([0, 1, 2]).join()"), "0,1,2,length");
assert_eq!(string("var a = [5]; a.k = 1; Object.getOwnPropertyNames(a).join()"), "0,k,length");
// Object.keys lists names in the order for-in visits them (section 15.2.3.14).
assert_eq!(string("var o = {b: 1, a: 2, 3: 3}, s = ''; for (var k in o) s += k; Object.keys(o).join('') === s ? s : 'differs'"), "ba3");
truth("var o = Object.seal({x: 1}); o.x = 2; delete o.x; o.y = 1; o.x === 2 && o.y === undefined && Object.isSealed(o) && !Object.isFrozen(o)");
+10 -6
View File
@@ -1173,27 +1173,31 @@ impl Interp {
}
/// The names of an object's own properties (section 15.2.3.4): a String object's indices and
/// an array's or String object's `length` first, then the rest in the order they were added,
/// which is the order `for-in` visits them. Each name takes a unit of fuel, and the heap for
/// the list is checked first.
/// `length` first, then the rest in the order they were added, which is the order `for-in`
/// visits them, then an array's `length`. Each name takes a unit of fuel, and the heap for the
/// list is checked first.
pub(crate) fn own_keys(&mut self, id: ObjId) -> JsResult<Vec<Key>> {
let o = self.object(id)?;
let string_len = if let Class::String(s) = &o.class { s.len() } else { 0 };
let has_length = matches!(o.class, Class::Array { .. } | Class::String(_));
let count = o.props.len().saturating_add(string_len).saturating_add(usize::from(has_length));
let is_string = matches!(o.class, Class::String(_));
let is_array = matches!(o.class, Class::Array { .. });
let count = o.props.len().saturating_add(string_len).saturating_add(usize::from(is_string || is_array));
self.reserve_heap(index_keys_bytes(string_len).saturating_add(count.saturating_mul(KEY_ENTRY_BYTES)))?;
let mut out = Vec::with_capacity(count);
for i in 0..string_len {
self.use_fuel()?;
out.push(index_key(u32::try_from(i).unwrap_or(u32::MAX)));
}
if has_length {
if is_string {
out.push(key("length"));
}
for _ in 0..self.object(id)?.props.len() {
self.use_fuel()?;
}
out.extend(self.object(id)?.props.keys());
if is_array {
out.push(key("length"));
}
Ok(out)
}