// sloppy (.cjs, no "use strict")
const o = {x:1}; Object.freeze(o);
o.x += 1; // node: silent no-op perry: TypeError
o.x++; // node: silent perry: silent ✓
for (o.x of [7]) {} // node: silent perry: TypeError
[o.x] = [7]; // node: silent perry: TypeError
Over-throwing, not under-throwing — the mirror image of #9394 and #9422, on the object path.
+= lowers to Expr::PropertySet, which carries no strictness field at all, and its codegen reaches js_typed_feedback_object_set_field_by_name, which has no strict parameter and rejects by throwing unconditionally.
The contrast that proves it: o.x++ is correct, because it lowers to Expr::PropertyUpdate, which does carry ctx.current_strict. Two spellings of the same operation, one with the field and one without.
Why this is worse than it looks
A CommonJS bundle is sloppy code from top to bottom — that is where #9394 surfaced. A spurious TypeError on o.x += 1 against a frozen object stops a program that node runs to completion, so this is a hard failure rather than a wrong value.
Fix shape
Add the strictness field to Expr::PropertySet and thread it to the runtime entry, mirroring what Expr::PropertyUpdate already does and what #9426 did for Expr::IndexSet (assignment_strict: bool, rendered as (I32, strict_flag)).
Expr::PropertySet has 181 construction sites, which is why it was not folded into PR #9458 — it is its own change, and most of those sites are one-line updates that want a careful default rather than a blanket false.
The runtime side needs a sloppy twin for the rejection path, the same split #9426 made for array element stores (js_array_set_f64_extend_sloppy alongside the strict entry).
Verification bar
A .cts fixture (CommonJS in both runtimes — this repo is "type": "module", so a .ts is strict ESM and cannot express the sloppy arm) byte-compared to node --experimental-strip-types, demonstrated failing on unfixed origin/main. Assert both arms: every shape above in sloppy code where it must be silent, and in "use strict" code where it must throw. Asserting only one arm is exactly what let #9326 and #9394 through.
Cover +=, -=, *=, &&=, ||=, ??=, for (o.x of …), destructuring [o.x] = arr and ({a: o.x} = obj), on a frozen object, a sealed object, a non-writable own property, an inherited non-writable property, and a getter-only accessor.
Found while fixing #9422/#9423.
Over-throwing, not under-throwing — the mirror image of #9394 and #9422, on the object path.
+=lowers toExpr::PropertySet, which carries no strictness field at all, and its codegen reachesjs_typed_feedback_object_set_field_by_name, which has nostrictparameter and rejects by throwing unconditionally.The contrast that proves it:
o.x++is correct, because it lowers toExpr::PropertyUpdate, which does carryctx.current_strict. Two spellings of the same operation, one with the field and one without.Why this is worse than it looks
A CommonJS bundle is sloppy code from top to bottom — that is where #9394 surfaced. A spurious
TypeErrorono.x += 1against a frozen object stops a program that node runs to completion, so this is a hard failure rather than a wrong value.Fix shape
Add the strictness field to
Expr::PropertySetand thread it to the runtime entry, mirroring whatExpr::PropertyUpdatealready does and what #9426 did forExpr::IndexSet(assignment_strict: bool, rendered as(I32, strict_flag)).Expr::PropertySethas 181 construction sites, which is why it was not folded into PR #9458 — it is its own change, and most of those sites are one-line updates that want a careful default rather than a blanketfalse.The runtime side needs a sloppy twin for the rejection path, the same split #9426 made for array element stores (
js_array_set_f64_extend_sloppyalongside the strict entry).Verification bar
A
.ctsfixture (CommonJS in both runtimes — this repo is"type": "module", so a.tsis strict ESM and cannot express the sloppy arm) byte-compared tonode --experimental-strip-types, demonstrated failing on unfixedorigin/main. Assert both arms: every shape above in sloppy code where it must be silent, and in"use strict"code where it must throw. Asserting only one arm is exactly what let #9326 and #9394 through.Cover
+=,-=,*=,&&=,||=,??=,for (o.x of …), destructuring[o.x] = arrand({a: o.x} = obj), on a frozen object, a sealed object, a non-writable own property, an inherited non-writable property, and a getter-only accessor.Found while fixing #9422/#9423.