Skip to content

DataView and ArrayBuffer are byte-indexable; Object.keys(Buffer) is empty #8149

Description

@proggeramlug

Summary

A DataView and a raw ArrayBuffer are byte-indexable in Perry. Node's are
not — they have no integer-indexed own properties at all. Separately,
Object.keys on a real Buffer returns [] where Node lists the byte
indices.

Found while settling #8116's reachability question with a
receiver-representation probe. It is not #8116: all three reproduce with
no typed-array static hint anywhere in the program, so the defect is one
receiver-kind question further out — in js_dyn_index_get's
registered-buffer arm, not in the typed-array element helpers.

Reproduction

const ab = new ArrayBuffer(8);
const D = new DataView(ab);
console.log((D as any)[0]);            // perry 0        node undefined
console.log((D as any)[3]);            // perry 0        node undefined
console.log((D as any).length);        // perry 8        node undefined
console.log(0 in (D as any));          // perry true     node false
console.log((ab as any)[0]);           // perry 0        node undefined

const b = Buffer.from([1, 2, 3]);
console.log((b as any)[1]);            // perry 2        node 2        (correct)
console.log(JSON.stringify(Object.keys(b as any)));
                                       // perry []       node ["0","1","2"]

Object.keys(D) is [] in both, so the in/index divergence is not visible
through enumeration — only through the element read.

Perry 0.5.1510 @ fb87d8923, --profile perry-dev, oracle Node 26.5.1
(the .node-version pin).

Where it comes from

buffer::from::js_data_view_new builds a real BufferHeader, registers it
(buffer::view::register) and calls mark_as_data_view. So
is_registered_buffer(addr) is true for a DataView, and every consumer that
triages a receiver as "registered buffer ⇒ byte-indexable" serves it:

  • value::dyn_index — the is_registered_buffer(raw_ptr) arm reads
    js_buffer_get;
  • object::polymorphic_index — same shape;
  • typed_feedback::js_typed_feedback_array_index_get_fallback_boxed — same
    shape.

None of them asks is_data_view, although the flag is recorded at
construction, and the sibling discrimination (is_uint8array_buffer) is
already made right next to it in typedarray_props::typed_array_owner_kind.
A raw ArrayBuffer (buffer::js_array_buffer_new_value) appears to reach the
same arm.

The Object.keys half is the mirror image: the enumeration path does not
synthesize the integer-indexed own properties a Node Buffer (an ordinary
Uint8Array object) exposes.

Suggested shape

This is the same family as #8090 / #8109 / #8111 / #8119 / #8120 / #8124 /
#8141: a receiver-specific fast path claims an operation before the
receiver-kind question is asked.
The fix shape those established is to add
the discriminating arm ABOVE the generic one — here, is_data_view (and the
plain-ArrayBuffer case) before the byte-index arm, answering undefined for
an integer index and false for in.

Worth checking in the same pass, since they share the arm: .length,
for…in, Object.getOwnPropertyNames, hasOwnProperty, and the store side
((dv as any)[0] = 1 presumably writes a byte where Node creates an ordinary
expando property).

A regression test must assert VALUES against the Node oracle, and — per
#8116's experience — be run against a build with the fix removed, since a
DataView receiver reaches several of these arms by different routes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions