fix(runtime): exclude class objects from the store-plan cache (#6595)#6596
Merged
proggeramlug merged 2 commits intoJul 18, 2026
Merged
Conversation
added 2 commits
July 18, 2026 16:21
…S#6595) The PerryTS#6541 receiver-based [[Set]] records a store-plan verdict BEFORE the store flows through target_set -> js_object_set_field_by_name, so the very same write hits the mirror-free top fast lane there. For a per-evaluation CLASS OBJECT (what a capture-carrying class materializes as — bundled zod's entire ZodType family) that lane skips the PerryTS#6530 mirror_class_object_static_write hook: from the second same-shaped class on, the shape-transition cache also hits, the post-class static (ZodX.create = ...) lands only on the class object's own fields, and ClassRef-world static dispatch misses — returning the receiver, so .optional() yielded the class and every ._def read blew up (errorMap/_getType/description). Class objects are now ineligible for the store-plan cache at all four gates (ordinary_set fast path, top fast lane, in-body check + record): their writes always take the mirroring completions, and their template cid — shared with their instances — can no longer conflate two different prototype chains in one cache line. Statics writes are cold; instances (OBJECT_TYPE_REGULAR) keep the fast lanes unchanged.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughRuntime setter fast paths and store-plan caching now exclude class objects, while a regression test validates repeated capture-carrying class static writes across optional, nullable, and chained calls. ChangesClass-object write safety
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6595.
Root cause
#6541 consults and records the shared store-plan cache in
ordinary_set_with_receiverbefore the store flows throughtarget_set→js_object_set_field_by_name. That ordering is the regression: the very store being vetted arrives atjs_object_set_field_by_namewith its own(class_id, key)plan freshly recorded, so it takes the top fast lane — which performs a raw slot store and skips every in-body write completion, including the #6530/#6537mirror_class_object_static_writehook that syncs per-evaluation class objects intoCLASS_DYNAMIC_PROPS.For a class object (what a capture-carrying class materializes as — bundled zod's entire
ZodTypefamily) the failure needs one more ingredient, which is why the single-class #6537 gap test stayed green: the lane's raw store also needs a shape-transition cache hit, and class objects repeat identical key-append sequences. So the first class of a shape falls through to the mirroring path (simple cases pass), and from the second same-shaped class on (zodlocal.cjshas 32ZodX.create = ...writes) the static lands only on the class object's own fields.js_class_static_method_callthrough the INT32 ClassRef then misses and returns the receiver —.optional()evaluates to the class itself,this._defisundefined, and every downstream read blows up (errorMap/_getType/description), exactly the #6530 symptom set.Confirmed with a temporary diagnostic on the bundled-zod repro — per class, in order:
Fix
Class objects (
object_type == OBJECT_TYPE_CLASS) are now ineligible for the store-plan cache at all four gates:proxy.rsordinary_set_with_receiverfast path (plan_eligible— blocks both check and record),field_set_by_name.rstop fast lane gate,field_set_by_name.rsin-bodyplan_eligible(check),field_set_by_name.rsin-body record site.Their writes always take the mirroring completions again (statics writes are cold — no measurable cost), and this also closes the latent cross-poisoning both ways: a class object shares its template cid with its instances, so a plan learned on one chain could falsely certify a store on the other (the interception verdict is per-chain, and the two chains differ). Instances (
OBJECT_TYPE_REGULAR) keep the #6539/#6541 fast lanes unchanged.Regression test
test_gap_6595_repeated_capture_class_statics.ts— two same-shaped capture-carrying classes with post-class arrow statics, dispatched from a sibling method through the ClassRef world. Two subtleties baked into the test (both learned the hard way):Fails on current
main(null typeof: function,_def set: false, then theerrorMapTypeError); byte-identical tonode --experimental-strip-typeswith the fix.Validation
next/dist/compiled/zod/index.cjscopied locally): both cases match node byte-for-byte with the fix (undefined/{"a":{"p":"x"}}); both threw on baseline.test_gap_class_capture_forward_static.ts(fix(runtime,codegen): bundled-zod capture-class family — statics dispatch, value-super, ctor args, constructor identity (#6530) #6537),test_gap_put_value_plan_cache.ts(perf(runtime): share the store-plan cache with receiver-based [[Set]] #6541),test_gap_field_lane_semantics.ts(perf(runtime): get/set fast lanes for plain arena class instances #6539).test_gap_prop_plan_cache_invalidation.ts(perf(runtime): store-plan cache — skip the per-store interception vet on class instances #6532): same TypeError, same exit code; the receiver rendering differs from node (#<Object>vs#<H>) — verified pre-existing on unmodifiedmainby rebuilding the baseline runtime and re-running (the frozen-write throw path sits behind the frozen-family flag checks, before every gate this PR touches).cargo test -p perry-runtimegreen single-threaded;cargo fmt --checkclean on the touched files.Summary by CodeRabbit
Bug Fixes
Tests