fix(runtime): recover string.length mis-lowered to a 0-arg method call#5726
Conversation
📝 WalkthroughWalkthrough
ChangesString length recovery
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-runtime/src/object/native_call_method.rs`:
- Around line 1293-1298: The zero-arg string length special case in
js_native_call_method is treating real method calls as property reads, so
x.length() incorrectly succeeds instead of throwing TypeError. Update the
lowering/disptach flow around native_call_method and js_native_call_method to
carry a distinct signal for a lowered property read versus an actual call, and
only return the string header utf16_len fast path when that read signal is
present; otherwise let genuine string.length() calls follow the normal callable
path and error behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4ce718b5-e254-4f56-8b6e-19c629d48930
📒 Files selected for processing (1)
crates/perry-runtime/src/object/native_call_method.rs
…od call
A bare member read `str.length` can reach the runtime as a 0-arg native method
call: member reads on a native-instance-classified receiver are lowered to a
0-arg `NativeMethodCall` so FFI getters dispatch (codegen routes that fallback
through `js_native_call_method_nullsafe`). When the HIR mis-classifies the
receiver — a stale/aliased native-instance class tag, the same shape already
documented in this file for the closure-captured array registered as `FormData`
— and the receiver's runtime type is actually a string, `"length"` has no
callable method, so the dispatcher threw `(string).length is not a function`.
This aborts code that reads `.length` per element, e.g. an inlined
`string-width`/`wrap-ansi` text-measurement loop (`H += chunk.length`).
A string's `length` is a data property, never a method, so the nullsafe
member-read entrypoint now returns its value instead of throwing. This is gated
to that entrypoint on purpose: a genuine `("abc" as any).length()` call lowers
to the plain `js_native_call_method`, which keeps throwing the spec-required
TypeError. Native classes with a real FFI `length` getter (cheerio selections)
are objects, not primitives, and dispatch through their own arm — unaffected.
Claude-Session: https://claude.ai/code/session_01EUJtfcsGNWpqrGiufHXHxt
5b8ef28 to
48c1fad
Compare
|
Addressed the CodeRabbit finding (genuine |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/perry-runtime/src/object/native_call_method.rs (1)
1494-1511: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest covers only the positive recovery path.
Consider adding a negative assertion that the plain
js_native_call_methodentrypoint still throws for a 0-arglengthon a string receiver — that's the invariant the whole "scoped to nullsafe" design hinges on, and it's currently unverified by tests. If the FFI throw aborts rather than panics (making it untestable in-process), a nullsafe test withargs_len > 0asserting non-recovery would still guard the boundary.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/perry-runtime/src/object/native_call_method.rs` around lines 1494 - 1511, The new nullsafe length test only checks the recovery path in js_native_call_method_nullsafe; add a negative test around js_native_call_method to verify a string receiver with 0 args still throws, or if that FFI path can’t be asserted in-process, add a nullsafe case with args_len > 0 that confirms the length member-read is not recovered. Use the existing js_native_call_method and js_native_call_method_nullsafe symbols so the scoped-to-nullsafe boundary is explicitly covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/perry-runtime/src/object/native_call_method.rs`:
- Around line 1494-1511: The new nullsafe length test only checks the recovery
path in js_native_call_method_nullsafe; add a negative test around
js_native_call_method to verify a string receiver with 0 args still throws, or
if that FFI path can’t be asserted in-process, add a nullsafe case with args_len
> 0 that confirms the length member-read is not recovered. Use the existing
js_native_call_method and js_native_call_method_nullsafe symbols so the
scoped-to-nullsafe boundary is explicitly covered.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5bd5148a-6129-4e59-ac32-116779a4d905
📒 Files selected for processing (1)
crates/perry-runtime/src/object/native_call_method.rs
Problem
A bare member read
str.lengthcan reachjs_native_call_methodas a 0-arg native method call: member reads on a native-instance-classified receiver are lowered to a 0-argNativeMethodCallso FFI getters dispatch (see the existing per-module guards inexpr_member.rs). When the HIR mis-classifies the receiver — a stale/aliased native-instance class tag, the same shape already documented in this file for the closure-captured array registered asFormData— and the receiver's runtime type is actually a string,"length"has no callable method, so the dispatcher threw:This aborts code paths that read
.lengthper element, e.g. an inlinedstring-width/wrap-ansitext-measurement loop (H += chunk.lengthover[...str]).Fix
A string's
lengthis a data property, never a method. In the primitive-receiver branch of the dispatcher, when a string method-call misses and the "method" islengthwith no args, return the string's UTF-16 length instead of throwing — matching what the mis-lowered property access intended.Native classes with a real FFI
lengthgetter (e.g. cheerio selections,js_cheerio_selection_length) are objects, not primitives, and dispatch through their own arm above, so they are unaffected.Tests
primitive_dataprop_recovery_tests::string_length_method_miss_returns_length— dispatchinglength(0 args) on a string receiver returns its UTF-16 length. Fullperry-runtime+perry-codegensuites pass (1081 + 0 failures).https://claude.ai/code/session_01EUJtfcsGNWpqrGiufHXHxt
Summary by CodeRabbit
Bug Fixes
lengthcould incorrectly throw an error. It now correctly returns the UTF-16 code unit length (e.g.,"helloé"→6).Tests
length, ensuring the expected numeric result is returned.