From 2d13f219f689d9ee8e5911b718d4a30a720efd97 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 19:23:56 -0800 Subject: [PATCH] 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. --- crates/js/src/builtins_tests.rs | 3 ++- crates/js/src/interp.rs | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/js/src/builtins_tests.rs b/crates/js/src/builtins_tests.rs index 3e8362e52..f95569749 100644 --- a/crates/js/src/builtins_tests.rs +++ b/crates/js/src/builtins_tests.rs @@ -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)"); diff --git a/crates/js/src/interp.rs b/crates/js/src/interp.rs index 096ad99e7..2293984ce 100644 --- a/crates/js/src/interp.rs +++ b/crates/js/src/interp.rs @@ -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> { 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) }