Skip to content

fix(runtime): <index> in <Uint8Array> / Buffer resolves numeric indices (#6148) - #6163

Merged
proggeramlug merged 1 commit into
mainfrom
fix/6148-typedarray-in
Jul 9, 2026
Merged

fix(runtime): <index> in <Uint8Array> / Buffer resolves numeric indices (#6148)#6163
proggeramlug merged 1 commit into
mainfrom
fix/6148-typedarray-in

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #6148 (the index case).

Summary

0 in new Uint8Array([1,2,3]) returned false (as did "length" in u8),
while every other typed-array kind worked:

0 in new Uint8Array([1,2,3])   // was false → true
0 in new Int16Array([1,2,3])   // already true
0 in new Float64Array([1,2,3]) // already true

Uint8Array / Buffer are backed by a header-less registered buffer (not
TYPED_ARRAY_REGISTRY), so the in operator's typed-array arm — which gates on
lookup_typed_array_kind — never saw them. It was Uint8Array/Buffer-specific,
independent of size.

Fix

Add a buffer branch to js_object_has_property, mirroring the property-get path
(get_field_by_name_tail.rs): detect via is_registered_buffer, then

  • a numeric index is present iff it's in [0, js_buffer_length)
  • the own view slots (length / byteLength / byteOffset /
    BYTES_PER_ELEMENT / buffer) are present

Scope / follow-up

Inherited prototype members via in on a buffer ("subarray" in u8,
"toString" in u8) are left as a follow-up: it's the same string-key gap the
registered typed-array arm already has, tied to lazy %TypedArray%.prototype
population (order-dependent), so it deserves its own fix covering both.

Validation

Byte-for-byte vs node --experimental-strip-types: index in for Uint8Array
(small + large), Buffer.from, Float64Array, Int16Array; negative and
out-of-bounds indices; the five view slots; plus an in regression sweep (plain
object, array, registered typed array, Object.create, primitive-throw).

Summary by CodeRabbit

  • Bug Fixes
    • Improved property-existence checks for buffer values so the in operator now returns the expected results for numeric indices and supported buffer-related properties.
    • Fixed cases where certain buffer-backed objects were not recognized correctly during property lookup, preventing incorrect false results.

…ices (#6148)

`0 in new Uint8Array([1,2,3])` returned false (and `"length" in u8` too).
`Uint8Array`/`Buffer` are backed by a header-less registered *buffer*, not
`TYPED_ARRAY_REGISTRY`, so the `in` operator's typed-array arm (which gates on
`lookup_typed_array_kind`) never saw them — every other typed-array kind
(Int16Array, Uint32Array, Float64Array, …) already worked at any size, so this
was Uint8Array/Buffer-specific.

Add a buffer branch to `js_object_has_property` mirroring the property-get path:
detect via `is_registered_buffer`, then a numeric index is present iff it is in
`[0, js_buffer_length)`, and the own view slots
(`length`/`byteLength`/`byteOffset`/`BYTES_PER_ELEMENT`/`buffer`) are present.

Inherited prototype *members* via `in` on a buffer (`"subarray" in u8`,
`"toString" in u8`) are left as a follow-up — the same string-key gap the
registered typed-array arm has (tied to lazy `%TypedArray%.prototype`
population), tracked separately.

Validated vs Node: index `in` for Uint8Array (small + large), `Buffer.from`,
Float64Array, Int16Array; negative/out-of-bounds indices; the view slots; plus
an `in` regression sweep (plain object / array / registered typed array /
Object.create / primitive-throw).
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a fast-path branch in js_object_has_property for registered Buffer receivers, handling the in operator by returning true for in-bounds numeric indices and specific buffer-related string properties (length, byteLength, byteOffset, BYTES_PER_ELEMENT, buffer), and false otherwise.

Changes

Buffer in-operator fast path

Layer / File(s) Summary
Registered Buffer handling in in-operator
crates/perry-runtime/src/object/field_get_set/has_property.rs
Adds a branch detecting registered Buffer objects and resolving key existence for numeric indices and specific buffer-related string properties, returning false otherwise.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#6024: Both PRs modify has_property.rs, with the earlier PR adding a js_in_operator wrapper that delegates to js_object_has_property, which this PR's Buffer branch extends.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The change fixes numeric indices and view-slot properties, but explicitly leaves inherited members like subarray/toString for later, so #6148 is only partially met. Extend the property-existence logic to cover inherited prototype members, or split that behavior into a linked follow-up issue.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main runtime fix for in on typed arrays/Buffer numeric indices.
Out of Scope Changes check ✅ Passed All changes stay within the typed-array/Buffer in-operator fix and add no unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/6148-typedarray-in

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/field_get_set/has_property.rs`:
- Around line 325-342: The buffer-branch handling in has_property is only
checking the named view slots, so string-encoded indices like "0" are
incorrectly falling through to false; update this branch to also recognize
canonical array-index string keys the same way the typed-array path delegates to
typed_array_has_property. Use the existing key parsing in has_property (the
key_val.is_any_string() block) and add an index check before returning false so
in-bounds numeric strings on buffer-backed views are reported as present, while
still preserving the current named-slot behavior and the intended exclusion for
inherited prototype members.
🪄 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: b09541f1-d033-468d-bca1-a0672bb7de85

📥 Commits

Reviewing files that changed from the base of the PR and between d1d3ddf and b8787da.

📒 Files selected for processing (1)
  • crates/perry-runtime/src/object/field_get_set/has_property.rs

Comment on lines +325 to +342
if key_val.is_any_string() {
// Own view slots, always present. Inherited prototype MEMBERS
// (`subarray`, `map`, `toString`, …) via `in` on a buffer are a
// separate follow-up — the same string-key gap the registered
// typed-array arm above has, tied to lazy `%TypedArray%`
// prototype population.
let mut sso = [0u8; crate::value::SHORT_STRING_MAX_LEN];
if let Some(name) = unsafe { crate::string::js_string_key_bytes(key_val, &mut sso) }
.and_then(|b| std::str::from_utf8(b).ok())
{
if matches!(
name,
"length" | "byteLength" | "byteOffset" | "BYTES_PER_ELEMENT" | "buffer"
) {
return nanbox_true;
}
}
return nanbox_false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

String-encoded numeric indices ("0" in buf) incorrectly return false.

The buffer branch only matches string keys against the five named view slots and returns false for everything else. This means "0" in new Uint8Array([1,2,3]) returns false, when per spec it should return trueToPropertyKey("0") resolves to an in-bounds index.

The typed-array branch above (lines 272–281) delegates all string keys to typed_array_has_property, which handles string-encoded indices. The buffer branch lacks equivalent handling. The comment at lines 326–330 claims this is "the same string-key gap the registered typed-array arm above has," but the typed-array arm handles string keys comprehensively — the string-encoded index gap is unique to this buffer branch.

This is not covered by the PR's stated scope limitation (which only excludes inherited prototype members like "subarray" in u8). String-encoded numeric indices are own properties, not inherited members.

🐛 Proposed fix: add `canonical_array_index` check for string-encoded indices
             if key_val.is_any_string() {
                 // Own view slots, always present. Inherited prototype MEMBERS
                 // (`subarray`, `map`, `toString`, …) via `in` on a buffer are a
                 // separate follow-up — the same string-key gap the registered
                 // typed-array arm above has, tied to lazy `%TypedArray%`
                 // prototype population.
                 let mut sso = [0u8; crate::value::SHORT_STRING_MAX_LEN];
                 if let Some(name) = unsafe { crate::string::js_string_key_bytes(key_val, &mut sso) }
                     .and_then(|b| std::str::from_utf8(b).ok())
                 {
                     if matches!(
                         name,
                         "length" | "byteLength" | "byteOffset" | "BYTES_PER_ELEMENT" | "buffer"
                     ) {
                         return nanbox_true;
                     }
+                    // String-encoded numeric indices (e.g. `"0" in buf`).
+                    if let Some(idx) = super::super::canonical_array_index(name) {
+                        if (idx as i32) < len {
+                            return nanbox_true;
+                        }
+                    }
                 }
                 return nanbox_false;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if key_val.is_any_string() {
// Own view slots, always present. Inherited prototype MEMBERS
// (`subarray`, `map`, `toString`, …) via `in` on a buffer are a
// separate follow-up — the same string-key gap the registered
// typed-array arm above has, tied to lazy `%TypedArray%`
// prototype population.
let mut sso = [0u8; crate::value::SHORT_STRING_MAX_LEN];
if let Some(name) = unsafe { crate::string::js_string_key_bytes(key_val, &mut sso) }
.and_then(|b| std::str::from_utf8(b).ok())
{
if matches!(
name,
"length" | "byteLength" | "byteOffset" | "BYTES_PER_ELEMENT" | "buffer"
) {
return nanbox_true;
}
}
return nanbox_false;
if key_val.is_any_string() {
// Own view slots, always present. Inherited prototype MEMBERS
// (`subarray`, `map`, `toString`, …) via `in` on a buffer are a
// separate follow-up — the same string-key gap the registered
// typed-array arm above has, tied to lazy `%TypedArray%`
// prototype population.
let mut sso = [0u8; crate::value::SHORT_STRING_MAX_LEN];
if let Some(name) = unsafe { crate::string::js_string_key_bytes(key_val, &mut sso) }
.and_then(|b| std::str::from_utf8(b).ok())
{
if matches!(
name,
"length" | "byteLength" | "byteOffset" | "BYTES_PER_ELEMENT" | "buffer"
) {
return nanbox_true;
}
// String-encoded numeric indices (e.g. `"0" in buf`).
if let Some(idx) = super::super::canonical_array_index(name) {
if (idx as i32) < len {
return nanbox_true;
}
}
}
return nanbox_false;
🤖 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/field_get_set/has_property.rs` around lines
325 - 342, The buffer-branch handling in has_property is only checking the named
view slots, so string-encoded indices like "0" are incorrectly falling through
to false; update this branch to also recognize canonical array-index string keys
the same way the typed-array path delegates to typed_array_has_property. Use the
existing key parsing in has_property (the key_val.is_any_string() block) and add
an index check before returning false so in-bounds numeric strings on
buffer-backed views are reported as present, while still preserving the current
named-slot behavior and the intended exclusion for inherited prototype members.

@proggeramlug
proggeramlug merged commit 5bf7513 into main Jul 9, 2026
25 checks passed
@proggeramlug
proggeramlug deleted the fix/6148-typedarray-in branch July 9, 2026 08:09
proggeramlug pushed a commit that referenced this pull request Jul 9, 2026
…ers, order-independent (#6164)

`"subarray" in ta`, `"map" in ta`, `"toString" in ta` and other inherited
`%TypedArray%.prototype` / `Object.prototype` members returned false or gave
creation-order-dependent results, because the string-key membership walk
consulted the shared `%TypedArray%.prototype` intrinsic only if it happened to
have been built already (first registered typed-array creation) — and the
buffer-backed `Uint8Array` branch never populated it and only matched the five
named view slots.

- `typed_array_prototype_chain_has` now builds the intrinsic on demand
  (`ensure_typed_array_intrinsic`, idempotent) before consulting it, so the
  result no longer depends on creation order.
- The buffer branch of `js_object_has_property` routes non-slot string keys
  through the same walk (`Uint8Array`/`Buffer` is a `%TypedArray%`), so inherited
  prototype members resolve there too — and a canonical numeric-index string
  (`"0" in u8`) is reported as an own index (folds in #6163's review follow-up,
  superseding #6168).

Buffer-specific `Buffer.prototype` methods (`readUInt8`, …) are not covered here.

Validated vs Node: `subarray`/`map`/`filter`/`slice`/`join`/`indexOf`,
`toString`/`valueOf`/`hasOwnProperty`/`constructor`, `Symbol.iterator`, numeric
+ canonical-index strings, and view slots — for registered typed arrays AND
buffer-backed `Uint8Array`, buffer-first and typed-array-first; plus an `in`
regression sweep (plain object / array / Object.create / class-ref).
proggeramlug added a commit that referenced this pull request Jul 9, 2026
…order-independent (#6164) (#6170)

* fix(runtime): `<key> in` typed array / Buffer resolves prototype members, order-independent (#6164)

`"subarray" in ta`, `"map" in ta`, `"toString" in ta` and other inherited
`%TypedArray%.prototype` / `Object.prototype` members returned false or gave
creation-order-dependent results, because the string-key membership walk
consulted the shared `%TypedArray%.prototype` intrinsic only if it happened to
have been built already (first registered typed-array creation) — and the
buffer-backed `Uint8Array` branch never populated it and only matched the five
named view slots.

- `typed_array_prototype_chain_has` now builds the intrinsic on demand
  (`ensure_typed_array_intrinsic`, idempotent) before consulting it, so the
  result no longer depends on creation order.
- The buffer branch of `js_object_has_property` routes non-slot string keys
  through the same walk (`Uint8Array`/`Buffer` is a `%TypedArray%`), so inherited
  prototype members resolve there too — and a canonical numeric-index string
  (`"0" in u8`) is reported as an own index (folds in #6163's review follow-up,
  superseding #6168).

Buffer-specific `Buffer.prototype` methods (`readUInt8`, …) are not covered here.

Validated vs Node: `subarray`/`map`/`filter`/`slice`/`join`/`indexOf`,
`toString`/`valueOf`/`hasOwnProperty`/`constructor`, `Symbol.iterator`, numeric
+ canonical-index strings, and view slots — for registered typed arrays AND
buffer-backed `Uint8Array`, buffer-first and typed-array-first; plus an `in`
regression sweep (plain object / array / Object.create / class-ref).

* fix(runtime): canonical numeric index on typed array/Buffer `in` never inherits (#6164)

Address CodeRabbit review: per the IntegerIndexedExotic [[HasProperty]]
(ECMA-262 §10.4.9.2), a CanonicalNumericIndexString is present iff it is a valid
in-bounds integer index and otherwise absent — it must NOT fall through to the
prototype chain. The prior code only short-circuited the in-bounds case; an
out-of-bounds (`"100"` on a length-5 view), negative (`"-1"`), `"-0"`, or
fractional (`"1.5"`) canonical index leaked into `typed_array_prototype_chain_has`
(harmless today only because the intrinsic has no numeric-named members).

Add `is_canonical_numeric_index_string` (round-trips `ToString(ToNumber(s))`,
plus the `"-0"` special case) and short-circuit any canonical numeric index to
the in-bounds result, skipping the prototype scan. Non-canonical numeric-looking
forms (`"00"`, `"1e3"`, `"0x1"`) remain ordinary string keys and still consult
the prototype. Verified byte-for-byte vs Node.

---------

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
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.

<index> in <typed array> and "length"/method in <typed array> report false

1 participant