Skip to content

REGRESSION from #9326: sloppy-mode array element stores now THROW where they must be silent no-ops #9394

Description

@proggeramlug

Object.freeze(arr); arr[0] = 9 in non-strict code must be a silent no-op (ES §6.2.5.7 / OrdinarySetWithOwnDescriptor — a failed [[Set]] throws only in strict mode). Perry now raises an uncaught TypeError, so ordinary sloppy CJS code crashes where node continues.

Reproduced independently on 757beace0 (current main):

const a1 = [1]; Object.freeze(a1);          a1[0] = 9;   // node: silent, a1[0]===1
const a2 = [1]; Object.freeze(a2);          a2[5] = 9;   // node: silent
const a3 = [1]; Object.defineProperty(a3, 0, {writable:false}); a3[0] = 9;  // node: silent
const a4 = [1]; Object.preventExtensions(a4); a4[5] = 9; // node: silent
const o1 = {x:1}; Object.freeze(o1);        o1.x = 9;    // node: silent
node                          perry (757beace0)
frozen-inbounds  silent       !! Cannot assign to read only property '0' of object '[object Array]'
frozen-oob       silent       !! Cannot add property 5, object is not extensible
nonwritable      silent       !! Cannot assign to read only property '0' of object '[object Array]'
preventext       silent       !! Cannot add property 5, object is not extensible
obj-frozen       silent       => silent            <-- plain objects are CORRECT

Array element stores specifically. Plain-object stores still behave correctly, which localises this to the array [[Set]] path.

Attribution

Introduced by #9326 (the merge of #9297, "indexed writes honour a custom array prototype"). Verified against a pre-#9326 build at #9300, which matches node on every case above. Note #9326 was reverted (#9345) and then re-landed (#9370), so this is live on main today.

The change is correct about what it set out to fix — an inherited index accessor now runs, and a non-writable inherited index now rejects the write. It just rejects it by throwing unconditionally rather than throwing only in strict mode.

Why the fix's own tests could not see it

This is the third instance this week of the same pattern: its 64-check differential and 205-line gap fixture are all green, because they exercise the write through paths that are strict, and never assert that sloppy mode stays silent. The failure needs (a) a frozen/non-extensible/non-writable array element and (b) non-strict code — and cc's bundle is sloppy-mode CJS with 9 Object.freeze sites on arrays.

Suggested fix

The rejection path needs the strictness flag that the ordinary-object [[Set]] path already honours: throw when strict, return false silently otherwise. The regression test should assert both arms — that sloppy is silent and that strict still throws — since asserting only the throw is what let this through.

Found during a differential stress-test of claude-code under perry.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions