Found while landing #6559 (PR #6584) — pure compiled-code bug, no new Function involved:
function f() {}
f.errors = null
console.log(f.errors === null) // node: true — perry: false
console.log(typeof f.errors) // node: "object" — perry: "object" but String(f.errors) → "[object Object]"
A compiled dot-read of a function expando whose stored value is null returns a hole/placeholder sentinel instead of null (=== null false, String() → "[object Object]"). The computed read path (runtime-built key, e.g. f["err" + "ors"]) returns the stored null correctly — so the divergence is in the static-key property-load lane for function receivers, presumably a missing null-vs-hole distinction when the expando table stores a null payload.
Real-world impact: ajv's validate.errors === null success-path idiom misbehaves (the failure path — reading the error array — works, which is why ajv still functions overall). Six-line repro above; also noted in PR #6584's "pre-existing bugs" section.
Found while landing #6559 (PR #6584) — pure compiled-code bug, no
new Functioninvolved:A compiled dot-read of a function expando whose stored value is
nullreturns a hole/placeholder sentinel instead of null (=== nullfalse,String()→"[object Object]"). The computed read path (runtime-built key, e.g.f["err" + "ors"]) returns the stored null correctly — so the divergence is in the static-key property-load lane for function receivers, presumably a missing null-vs-hole distinction when the expando table stores a null payload.Real-world impact: ajv's
validate.errors === nullsuccess-path idiom misbehaves (the failure path — reading the error array — works, which is why ajv still functions overall). Six-line repro above; also noted in PR #6584's "pre-existing bugs" section.