Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,29 @@ pub extern "C" fn js_object_get_field_by_name(
}
}
}
// A receiver that LOOKS like a bare heap pointer (top 16 bits clear) but does
// not land in the platform heap range is a MIS-decoded primitive, not an
// object. The common case is a `number` whose raw f64 bits alias a sub-heap
// address: a dynamic `arr[i]` read (`js_dyn_index_get`) returns the element's
// JSValue bits, codegen forwards them straight to the object field-read ABI
// on the type-erased path, and a denormal such as `0x0000_0090_8000_0201`
// (~620 GB) arrives here as `obj`. It clears the `>> 48 == 0` check and sits
// ABOVE the 1 MB handle band, so the `is_above_handle_band`-only guards on the
// special-case reads below (and the `own_key_present` / `js_object_get_class_id`
// ObjectHeader derefs they call) passed it straight through — and the read
// dereferenced it as a GcHeader → KERN_INVALID_ADDRESS (real macOS allocations
// sit at ~3–5 TB, never 620 GB). Pair the band check with `is_valid_obj_ptr`
// (the canonical heap-range predicate) and treat a non-heap receiver as a
// property miss: reading any data property off a primitive is `undefined`, and
// the primitive-prototype methods are resolved on the by-name f64 wrapper's own
// path, which never reaches this pointer dereference.
if !key.is_null()
&& ((obj as u64) >> 48) == 0
&& crate::value::addr_class::is_above_handle_band(obj as usize)
&& !crate::value::addr_class::is_valid_obj_ptr(obj as *const u8)
{
return JSValue::undefined();
}
Comment on lines +63 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the denormal reproduction test and check whether it is platform-gated.
fd -t f -e rs | rg -l --iglob '*6291*|*denormal*|*receiver*' 2>/dev/null
rg -nP -C4 '0x0000_0090_8000_0201|0x0000_?0090|denormal' --type=rust
# Inspect is_valid_obj_ptr floor per platform to confirm the Linux/Windows gap.
fd -t f 'addr_class.rs' --exec sed -n '140,175p' {}

Repository: PerryTS/perry

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- relevant file: get_field_by_name.rs (context) ---'
sed -n '1,260p' crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs | cat -n

echo
echo '--- addr_class.rs (predicate definitions) ---'
fd -t f 'addr_class.rs' crates/perry-runtime -x sh -c 'echo "FILE: $1"; sed -n "1,260p" "$1" | cat -n' sh {}

echo
echo '--- search for the denormal / receiver-repro test ---'
rg -n --hidden --glob '!target' --glob '!dist' --glob '!build' --glob '!node_modules' '0x0000_0090_8000_0201|denormal|6291|receiver' crates/perry-runtime crates

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- get_field_by_name.rs around the new guard ---'
sed -n '1,240p' crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs | cat -n

echo
echo '--- addr_class.rs ---'
sed -n '1,240p' crates/perry-runtime/src/value/addr_class.rs | cat -n

echo
echo '--- any test or comment mentioning the repro address / denormal near the field-get path ---'
rg -n -C3 '0x0000_0090_8000_0201|denormal|is_valid_obj_ptr|HANDLE_BAND_MAX|above_handle_band' crates/perry-runtime/src/object crates/perry-runtime/src/value crates/perry-runtime/src

Repository: PerryTS/perry

Length of output: 50370


Reject non-heap receivers before the special-case branches

This guard only blocks macOS/BSD heap aliases; on Linux/Windows the same in-range denormal still passes is_valid_obj_ptr, so it can still reach the later ObjectHeader dereferences. Use is_plausible_heap_addr (or an equivalent band+heap-range check) here so non-heap receivers return undefined on every target.

🤖 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/get_field_by_name.rs` around
lines 63 - 85, Update the receiver guard in the field lookup function to use
is_plausible_heap_addr, or an equivalent platform-independent
band-and-heap-range predicate, instead of relying on is_valid_obj_ptr alone.
Ensure every non-heap denormal receiver returns JSValue::undefined() before
reaching the special-case branches and their ObjectHeader dereferences.

// `class X extends Map | Set` instance — `.size` reads the hidden backing
// collection's size. A subclass CAN still define an own `size` (class field
// or `Object.defineProperty`), so check own-property precedence first and
Expand Down
Loading