Skip to content

fix(date): brand-check this on Date.prototype setters + read [[DateValue]] before ToNumber#4412

Merged
proggeramlug merged 1 commit into
mainfrom
fix-date-setters
Jun 4, 2026
Merged

fix(date): brand-check this on Date.prototype setters + read [[DateValue]] before ToNumber#4412
proggeramlug merged 1 commit into
mainfrom
fix-date-setters

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

@proggeramlug proggeramlug commented Jun 4, 2026

Summary

Mirrors the merged getter brand-check fix (#4397) for the Date.prototype setters: setDate / setFullYear / setHours / setMinutes / setMonth / setSeconds / setMilliseconds / setTime / setYear and every setUTC* variant.

Two spec gaps are closed:

  1. Brand-check this on the reflective value path. Date.prototype.setDate.call(nonDate, 1) must throw TypeError (thisTimeValue(this)), but the setters were installed as generic no-op thunks — so a reflective call on a non-Date silently produced garbage and test262 crashed downstream with "Cannot read properties of undefined". New brand-checking variadic thunks in object/date_proto_thunks.rs read the IMPLICIT_THIS receiver, throw on a non-Date, and otherwise dispatch to the same js_date_apply_setter the instance fast path uses (so reflective setter calls now also work).

  2. Read [[DateValue]] before ToNumber. The spec captures thisTimeValue before coercing any argument; a user valueOf that re-enters and mutates the same cell must not be observed by the rebuild (set*/date-value-read-before-tonumber-when-date-is-{valid,invalid}). js_date_apply_setter now snapshots the timestamp first and threads it into the rebuild helpers, which no longer re-read the (possibly mutated) cell. A NaN time value with no year override now returns NaN without writing [[DateValue]], matching the spec's early return.

setYear (annexB, local-only, with the 0..=99 → 1900+y rebase) gets a dedicated runtime field code (8) so the capture-before-ToNumber ordering holds for it too.

Validation

  • test262 built-ins/Date: 122 → 84 failures. Set-diff vs an origin/main baseline build: 40 real fixes, 0 regressions. All set*/this-value-non-date, set*/this-value-non-object, set*/this-not-date, and set*/date-value-read-before-tonumber-* cases now pass. (Two Date.UTC/* entries that the radar flips between runs were confirmed pre-existing — they fail identically on the baseline binary; the radar's run-to-run nondeterminism, not this change.) Remaining setter failures (arg-*-to-number, new-value-time-clip) are pre-existing coercion/TimeClip gaps untouched here.
  • Byte-identical to Node (node --experimental-strip-types) in both PERRY_NO_AUTO_OPTIMIZE=1 and default builds for: reflective non-Date throw, reflective setFullYear.call(realDate), both read-before-ToNumber cases (valid + invalid), and setYear reflective.
  • Unit suite: cargo test --release -p perry-runtime --lib -- --test-threads=1 → 979 passed, 0 failed.

Scope

Runtime-only: crates/perry-runtime/src/{date.rs, object/date_proto_thunks.rs, object/global_this.rs}. No codegen changes.

Out of scope (follow-up): the instance fast path for the legacy setYear/getYear (d.setYear(50)) has no codegen lowering and still no-ops — the annexB/built-ins/Date/prototype/setYear/* instance tests need a new HIR setter variant across the codegen backends. The reflective Date.prototype.setYear.call(...) path is fixed by this PR.

@proggeramlug proggeramlug merged commit b2fa1e0 into main Jun 4, 2026
13 checks passed
@proggeramlug proggeramlug deleted the fix-date-setters branch June 4, 2026 16:06
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