Skip to content

fix(ecmascript): preventExtensions on an Array keeps length writable - #1004

Open
bardrop wants to merge 1 commit into
trynova:mainfrom
bardrop-oss:fix/array-prevent-extensions-length
Open

bardrop wants to merge 1 commit into
trynova:mainfrom
bardrop-oss:fix/array-prevent-extensions-length

Conversation

@bardrop

@bardrop bardrop commented Nov 12, 2026

Copy link
Copy Markdown

Fixes bug 4 from #1002 (bugs 1-3 are in #1003).

Array::internal_set_extensible set elems.len_writable to the new [[Extensible]] value, and the Array code then used len_writable as a stand-in for [[Extensible]] (for example ordinary_define_own_property_for_array checked elements.writable() for "If extensible is false"). So Object.preventExtensions(arr) also made length read-only:

const a = Object.preventExtensions([1, 2]);
a.pop();                                          // TypeError, expected 2
Object.getOwnPropertyDescriptor(a, "length").writable; // false, expected true

shift, splice, a.length = 1, push() / unshift() with no items and pop() on an empty array all threw too. The reverse case was also wrong: [1, , 3] with only length made read-only could not fill the hole.

Change

  • internal_set_extensible no longer touches len_writable. [[Extensible]] stays in the backing object's shape, as for other objects.
  • The places that add an element now check internal_extensible: defining an index ≥ length, defining into a hole, and the unshift fast path (it grows the array directly).
  • Removed ElementsVector::writable(), which had no other users.
  • get_template_object relied on the old behaviour to get a read-only length for the frozen template and raw arrays. It now calls set_length_readonly after try_prevent_extensions, so tagged-template/template-object.js still passes.

Testing

  • New nova_vm/tests/array_tests.rs runs tests/sources/arrayPreventExtensions.test.js: pop/shift/splice/length changes work, while push/unshift/index assignment/defineProperty past the end or into a hole still fail. It fails on main and passes here. Every case matches Node. The one exception is V8, which says Object.isFrozen(Object.preventExtensions([])) is true; JSC and QuickJS say false, as the spec does, and so does Nova.
  • test262: staging/sm/Array/length-01.js now passes (expectations and metrics updated). No other result changed. I ran the full suite once, then re-ran Array, Object, Reflect, Proxy, class, template and staging/sm Array/Object after the template fix.
  • cargo test, cargo fmt --check and cargo clippy --all-targets -- -D warnings pass.

Array::internal_set_extensible set elems.len_writable to the new
[[Extensible]] value and the Array code used len_writable as a stand-in
for [[Extensible]]. So Object.preventExtensions made "length"
read-only: pop, shift, splice and setting length threw a TypeError, and
a hole could not be filled when only length was read-only.

Keep the two flags apart: [[Extensible]] lives in the backing object's
shape, and adding an element past the end, into a hole, or through the
unshift fast path now checks it. Template objects set their length to
read-only explicitly since they relied on the old behaviour.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant