Skip to content

fix(interp): JSON.stringify throws real TypeError; hasOwnProperty sees setter-only accessors#967

Merged
nickna merged 1 commit into
wrk/interp-realm-extras-leakfrom
wrk/json-typeerror-and-accessor-hasown
Jun 27, 2026
Merged

fix(interp): JSON.stringify throws real TypeError; hasOwnProperty sees setter-only accessors#967
nickna merged 1 commit into
wrk/interp-realm-extras-leakfrom
wrk/json-typeerror-and-accessor-hasown

Conversation

@nickna

@nickna nickna commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes two interpreter bugs surfaced by the now-deterministic interpreted Test262 baseline (#964 follow-up; depends on #966). Both are interpreter-only — compiled mode already behaved correctly (separate hand-emitted IL twins).

Net impact: 93 interpreted Test262 tests flip Fail → Pass.

Stacked on #966 (wrk/interp-realm-extras-leak) — a deterministic baseline regen requires that fix. Base auto-retargets to main when #966 merges.

Bug 1 — JSON.stringify threw strings, not TypeError objects (5 tests)

Runtime/BuiltIns/JSONBuiltIns.cs correctly detected circular structures and BigInt values but threw a bare string via new ThrowException("TypeError: …"). Since the guest-error-identity change (PR #906/#920) binds caught values verbatim, guest catch received a string, so e instanceof TypeError was false and every assert.throws(TypeError, …) failed.

Fix: the 5 sites now throw new ThrowException(new SharpTSTypeError(…)) — the pattern already used in Interpreter.Properties.cs:138 and ArrayStaticBuiltIns.cs:24. Confirmed: JSON.stringify of a cycle / BigInt now throws a value with instanceof TypeError === true and .name === "TypeError".

Bug 2 — hasOwnProperty / in ignored setter-only accessors (88 tests)

SharpTSObject.HasProperty checked _fields and _getters but not _setters, so a property defined via Object.defineProperty(o, p, { set }) (no getter) was invisible to hasOwnProperty even though its setter ran on assignment.

Fix: HasProperty now also consults _setters. This single change fixes the entire accessor-descriptor clusterObject/defineProperty/*, defineProperties/*, create/*, Object/prototype/hasOwnProperty/*, and Array/prototype/* accessor cases — far beyond the one Math-descriptor test (15.2.3.6-3-253) that first exposed it. It was masked before #966 by Math-descriptor pollution turning such descriptors into data descriptors.

Validation

  • Full xUnit suite: 14392 passed, 0 failed, 16 skipped.
  • Interpreted Test262 baseline: refreshed (+93 Fail→Pass), verified 0-drift.
  • 9 new tests (JsonTypeErrorAndAccessorHasOwnTests): JSON circular/toJSON/BigInt throw real TypeError and setter-only hasOwnProperty/in — dual-mode where both modes agree.

Out of scope / noted

  • SharpTSObject.cs:163/212 existence checks (_fields || HasGetter) have no observable setter-only gap — the setter-call path precedes them (probed) — left unchanged.
  • Compiled-mode in does not yet see setter-only accessors (though compiled hasOwnProperty does) — a distinct pre-existing gap, deferred (the corresponding bonus test is interp-only).
  • Promise/resolve-thenable-deferred.js is an intermittent async-timing flake (different mechanism than the realm leak) — pinned to its majority value (Fail) in the baseline; separate follow-up.

…s setter-only accessors

Fixes two interpreter bugs surfaced by the now-deterministic interpreted Test262
baseline (#964 follow-up). Both are interpreter-only; compiled mode already
behaved correctly (separate hand-emitted IL twins).

Bug 1 — JSON.stringify threw strings, not TypeError objects:
  Runtime/BuiltIns/JSONBuiltIns.cs detected circular structures and BigInt
  values but threw a bare string via `new ThrowException("TypeError: ...")`.
  Since the guest-error-identity change binds caught values verbatim, guest
  `catch` received a string, so `e instanceof TypeError` was false and every
  `assert.throws(TypeError, ...)` failed. Switched the 5 sites to throw a real
  `new SharpTSTypeError(...)`, matching the pattern already used in
  Interpreter.Properties and ArrayStaticBuiltIns.

Bug 2 — hasOwnProperty / `in` ignored setter-only accessors:
  SharpTSObject.HasProperty checked _fields and _getters but not _setters, so a
  property defined via Object.defineProperty(o, p, {set}) (no getter) was
  invisible to hasOwnProperty even though its setter ran. Added the _setters
  check. This was masked before the realm-leak fix (#966) by Math-descriptor
  pollution turning such descriptors into data descriptors.

Impact: 93 interpreted Test262 tests flip Fail->Pass — Bug 2 alone fixes the
entire accessor-descriptor cluster (Object/defineProperty, defineProperties,
create, hasOwnProperty, and Array.prototype.* accessor cases), far beyond the
single Math-descriptor test that first exposed it.

Validation:
- Full xUnit suite: 14392 passed, 0 failed, 16 skipped.
- Interpreted Test262 baseline refreshed (+93 passes) and verified 0-drift.
- 9 new dual-mode/interp tests.

Out of scope / noted:
- The audited SharpTSObject.cs:163/212 existence checks show no observable
  setter-only gap (the setter-call path precedes them) — left unchanged.
- Compiled-mode `in` does not yet see setter-only accessors (though compiled
  hasOwnProperty does) — a distinct pre-existing gap, deferred.
- Promise/resolve-thenable-deferred.js is an intermittent async-timing flake
  (unrelated mechanism); pinned to its majority value (Fail) in the baseline and
  left as a separate follow-up.
@nickna nickna merged commit 7e99f85 into wrk/interp-realm-extras-leak Jun 27, 2026
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