Skip to content

fix(runtime): a class static setter receives the assigned value, not the class (#11669) - #11672

Merged
proggeramlug merged 1 commit into
mainfrom
fix/11669-static-setter-value
Sep 29, 2026
Merged

proggeramlug merged 1 commit into
mainfrom
fix/11669-static-setter-value

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Closes #11669

Root cause

#11651 made class static accessors real accessor properties of the class function object. install_declared_static_accessor (object/class_value.rs) fills each pair's reflected closure halves with class_accessor_function_value. That builds the instance thunks, which call the compiled entry as raw(this) / raw(this, value). A compiled static accessor has no this parameter: it is fn() -> f64 / fn(value), and it reads this from the static-this override. So a static setter invoked through its closure half received the receiver, meaning the class itself, as its value, and never had this armed.

Before #11651 those closures existed only for descriptor reflection, which is why nothing noticed. Now the class function object's own accessor property is what the generic [[Set]] paths invoke. An instrumented build confirmed that Statics.sv = 2 (js_put_value_set_packed_miss) reaches class_accessor_setter_thunk. The same thing happens through a dynamic receiver (o.tag = v), an inherited setter on a subclass, Reflect.set, Object.assign, and Object.getOwnPropertyDescriptor(C, k).set.call(C, v).

Fix

The static accessor halves now get static thunks (class_static_accessor_{getter,setter}_thunk, object/class_registry/registration.rs). They follow class_value::class_static_accessor_call_{get,set}'s protocol: arm static this with the receiver, push the declaring class as the private-static owner (a class id kept as a Number capture), and call fn() / fn(value). class_accessor_source_func_ptr recognises them too, so toString of a reflected static accessor is unchanged. #11651's design is untouched: static accessors are still accessor properties of the class function object.

Tests

New test-files/test_gap_11669_class_static_setter_value.ts covers direct, dynamic-receiver, computed-key and inherited (subclass) writes. It also covers a private static reached from a setter invoked through a subclass, a setter with a return, the reflected .set.call(C, v) / .get.call(Sub), Reflect.set, Object.assign, and a write after a generic defineProperty. On pristine main it fails (NaN, set Base class Base {…}, this.name wrong for Sub). With the fix it is byte-identical to Node 26.5.1.

Validation

Linux x86_64 (perrymaster), release with CODEGEN_UNITS=16, PERRY_NO_AUTO_OPTIMIZE=1, Node 26.5.1 (/opt/node-v26.5.1-linux-x64). The pristine arm is origin/main b0bf0ae, built the same way.

  • Gap subset (all 148 test_gap_* whose name contains class, static, define_property or accessor, including feat(runtime): class static accessors are accessor properties of the class function object #11651's three tests), compiled, run, and byte-compared to Node:
    • fix: 147 pass;
    • main: 144 pass;
    • the only differences are test_gap_10480_define_property_generic_descriptor_accessors, test_gap_11499_class_object_static_write and the new test, which fail on main and pass with the fix;
    • test_gap_2159_defineproperty_class_prototype fails on both arms (known_failures.json).
  • RUST_TEST_THREADS=1 cargo test --release -p perry-runtime: 4798 passed, 0 failed.
  • cargo fmt --check and check_file_size.sh pass.
  • run_lint_gates.sh (SKIP_COMPILE_GATES=1): 103 of 105 pass. The two failures are the known ones: cargo xwin check, because cargo-xwin is not installed on the host, and "Public benchmark evidence freshness", which is red on main.

Not run:

  • the full gap suite (only the subset above);
  • cargo test -p perry-codegen, because codegen is untouched;
  • the compile tier of the lint gates;
  • macOS;
  • an instruction-count A/B: the only change is to which thunk a reflected static accessor closure uses. Before this fix those calls produced wrong results, so there is no valid baseline to compare against.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed static accessor setters so they receive the assigned value, rather than the class object, across direct and subclass assignments, computed keys, and reflection-based assignment.
    • Corrected static setter behavior for private static accessors and assignments performed through Reflect.set, Object.assign, and property definitions.
    • Added regression coverage for static setter behavior across these assignment scenarios.

proggeramlug pushed a commit that referenced this pull request Sep 29, 2026
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 03925ad7-f629-4d61-81fa-ab8ffc258b49

📥 Commits

Reviewing files that changed from the base of the PR and between e95e17c and 035befc.

📒 Files selected for processing (3)
  • crates/perry-runtime/src/object/class_registry.rs
  • crates/perry-runtime/src/object/class_registry/registration.rs
  • crates/perry-runtime/src/object/class_value.rs

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

Static accessor closures now use dedicated getter and setter thunks. They carry the declaring class ID and establish static accessor context when called. Static accessor installation uses the new constructor, and a regression test covers assignment and reflection-related setter paths.

Changes

Static Accessor Invocation

Layer / File(s) Summary
Static accessor thunks and context
crates/perry-runtime/src/object/class_registry/registration.rs
Static getter and setter thunks establish and restore static context and invoke the raw accessor body. The setter returns undefined, and accessor-source lookup recognizes the static thunks.
Static accessor closure registration
crates/perry-runtime/src/object/class_registry/registration.rs, crates/perry-runtime/src/object/class_registry.rs, crates/perry-runtime/src/object/class_value.rs, test-files/test_gap_11669_class_static_setter_value.ts, changelog.d/11672-class-static-setter-value.md
The shared closure constructor captures the class ID for static accessors. Static accessor installation uses it. The regression test exercises assignment and reflection-related paths. The changelog records the fix.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 035be

This change restores passing the assigned value to class static setters. No concrete merge-blocking defect was identified in the supplied context.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 035be

Static setters now receive the assigned value while retaining the declaring class’s context. Review found no demonstrated expansion of authority, but the change touches security-relevant runtime state and exception behavior.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The affected exposure is static accessor execution by code able to assign to or invoke a reflected class accessor. The examined dispatch does not establish a new service, credential, or infrastructure authority.

Trust Boundaries and Controls

  • inferred — A caller can influence the receiver and assigned value, but the examined closure-construction path supplies the declaring class ID separately; changing the receiver does not itself select the private-static owner.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the primary fix: class static setters now receive the assigned value instead of the class object.
Description check ✅ Passed The description is detailed and covers the root cause, fix, affected behavior, related issue, regression tests, validation results, and limitations. It does not reproduce the template headings or chec…
Linked Issues check ✅ Passed The changes address #11669. install_declared_static_accessor now uses dedicated static accessor thunks. The thunks pass the assigned setter value, preserve static this, and provide the declaring c…
Out of Scope Changes check ✅ Passed The changes stay within #11669. The runtime changes fix static accessor closure behavior. The regression test covers the reported write paths. The re-export and changelog entry support the implementat…
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 4 files.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug
proggeramlug force-pushed the fix/11669-static-setter-value branch from e95e17c to 035befc Compare September 29, 2026 17:43
@proggeramlug
proggeramlug merged commit 2c20e92 into main Sep 29, 2026
57 of 60 checks passed
@proggeramlug
proggeramlug deleted the fix/11669-static-setter-value branch September 29, 2026 19:34
proggeramlug pushed a commit that referenced this pull request Sep 29, 2026
Keep the static accessor thunks of this branch; main fixture #11669 retained.
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.

class static setter receives the class object as its value since #11651 (gap 11499 / 10480 regress on main)

1 participant