Skip to content

fix(runtime): js_get_iterator must reject a handle-band near-null pointer as non-iterable#5549

Merged
proggeramlug merged 2 commits into
mainfrom
fix/iterator-reject-near-null
Jun 22, 2026
Merged

fix(runtime): js_get_iterator must reject a handle-band near-null pointer as non-iterable#5549
proggeramlug merged 2 commits into
mainfrom
fix/iterator-reject-near-null

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Problem

for…of (and other iteration) over a pointer-tagged value whose payload is a small handle-band address (e.g. POINTER_TAG | 1 → a "pointer" to address 1) did not throw. js_get_iterator read [Symbol.iterator] off the near-null address, found nothing, and returned the bogus value as its own iterator; .next() then yielded undefined, producing a misleading, far-removed TypeError: Iterator result is not an object instead of a correct "is not iterable" at the point of iteration.

js_get_iterator(undefined) already throws correctly; the gap was specifically the non-dereferenceable handle-band pointer.

Fix

In js_get_iterator (after the primitive check, before the [Symbol.iterator] lookup), reject a pointer-tagged value whose raw address is in the handle band (addr_class::is_handle_band) with the normal "is not iterable" TypeError. This stops the runtime manufacturing an iterator from a near-null pointer and surfaces the error at the correct location.

Verification

cargo test -p perry-runtime --lib: 1072 passed. Found while driving a large bundle whose deep config path produced such a value through an upstream binding-wiring defect; with this guard the iteration throws cleanly at the right place instead of much later. (No isolated TS repro: the trigger is an internal mis-tagged value, not something source code can directly construct; the guard is defensive and spec-aligned regardless.)

Summary by CodeRabbit

  • Bug Fixes
    • Improved runtime iterator handling by adding an early validation to immediately reject certain invalid pointer-tagged inputs that are not truly iterable. This ensures they fail fast with the standard “not iterable” TypeError, avoiding misleading attempts to treat near-null or handle-like values as iterators.

…able

A pointer-tagged value whose payload lies in the small-handle band (e.g. a
near-null POINTER_TAG|1) is not a dereferenceable heap object. js_get_iterator
previously read [Symbol.iterator] off it, found nothing, and returned the bogus
value UNCHANGED as its own iterator; the lazy for-of then called .next() on it,
got undefined, and threw a misleading late 'Iterator result is not an object'
far from the real fault. Throw the correct 'is not iterable' at the point of
iteration instead, and stop manufacturing a bogus iterator from a non-object.

This clears a corpus wall where a deep async config-resolution chain feeds a
corrupted .errors value (POINTER_TAG|1) into a for-of: the binary now advances
past that iterator wall to the sibling WeakMap-key wall (same upstream
value-wiring corruption, separate manifestation). 1072 runtime lib tests pass.
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c52e6cdf-a48e-4410-9a24-6c8793769ff0

📥 Commits

Reviewing files that changed from the base of the PR and between 245de5e and b63032c.

📒 Files selected for processing (1)
  • crates/perry-runtime/src/symbol.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry-runtime/src/symbol.rs

📝 Walkthrough

Walkthrough

In js_get_iterator within symbol.rs, an early guard is added that detects pointer-tagged input values whose raw pointer payload falls within the small-handle "handle band." When detected, the function now immediately throws a throw_value_not_iterable TypeError instead of proceeding into the [Symbol.iterator] lookup path.

Changes

Iterator Handle-Band Guard

Layer / File(s) Summary
Early handle-band check in js_get_iterator
crates/perry-runtime/src/symbol.rs
Adds 20 lines of guard logic: when the input value is pointer-tagged and its underlying raw pointer lies in the handle band, throw_value_not_iterable is called immediately, bypassing the normal [Symbol.iterator] resolution path that previously returned the input unchanged as a bogus iterator.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Possibly related PRs

  • PerryTS/perry#5543: Adds a parallel handle-band guard in js_error_get_errors to prevent corrupted handle-band values from being returned when the receiver is not an Error object — the same class of defensive fix applied to a different runtime function.

Poem

🐇 A handle-band pointer crept into my iterator stew,
But now I catch it early and throw it right on through!
No bogus iterators lurking, no corruption in the night,
not iterable TypeError — I set those bad pointers right.
Hoppity-hop, the runtime stays tight! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers the problem, fix, and verification, but is missing explicit checkboxes confirmation and doesn't clearly reference which files were changed or include a formal test plan checklist. Complete the test plan checklist (cargo build, cargo test, etc.) and explicitly confirm all items in the Checklist section to match the template requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a guard in js_get_iterator to reject handle-band near-null pointers as non-iterable, which aligns with the summary and description.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/iterator-reject-near-null

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

@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/symbol.rs`:
- Around line 2263-2267: The guard that checks is_handle_band(raw) and calls
throw_value_not_iterable() is incorrectly blocking iteration dispatch for
handle-backed iterable values like fetch Headers. Remove the entire if statement
that checks is_handle_band(raw) and calls throw_value_not_iterable(), allowing
the code to continue to the iterator resolver path. This will permit
Symbol.iterator dispatch to work correctly for small handles as intended by the
code at lines 1724-1730.
🪄 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: 711406a6-0113-4fd4-9ef9-b072086d9970

📥 Commits

Reviewing files that changed from the base of the PR and between f392dba and 245de5e.

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

Comment thread crates/perry-runtime/src/symbol.rs Outdated
…okup

The initial guard ran before the `[Symbol.iterator]` lookup and used
is_handle_band (`addr < 0x100000`), which also covers the Web Fetch band
[0x40000, 0xE0000) (Headers/Request/Response), the zlib band, and the
revocable-Proxy band. Those are genuinely iterable handle-backed values
that resolve @@iterator via the small-handle dispatch in
js_object_get_symbol_property, so the early guard threw "not iterable"
before that dispatch ran — breaking `for (const [k,v] of headers)`,
`[...headers]`, and the addr_class_handle_bands integration test.

Move the rejection to the no-method fallthrough, just before
`return val_f64`: iterable handles resolve @@iterator and return early;
only a handle-band pointer that resolved no iterator method (a corrupt
near-null reference) now throws, instead of being returned as its own
bogus iterator. Addresses CodeRabbit review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H689Q1ongNpQmg9HdYQayS
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.

2 participants