Skip to content

Buffer.isBuffer(new Uint8Array(...)) returns true — registry conflates Buffers with Uint8Arrays #9173

Description

@proggeramlug

Summary

Buffer.isBuffer(new Uint8Array([1, 2])) returns true. Node returns false. A plain Uint8Array is not a Buffer, and code that uses Buffer.isBuffer to tell the two apart — which is its entire purpose — takes the wrong branch.

Separately, Buffer's prototype chain does not reach Uint8Array.prototype.

Repro

const b = Buffer.from([1, 2, 3]);
const u = new Uint8Array([4, 5]);
console.log(Buffer.isBuffer(b));
console.log(b instanceof Uint8Array);
console.log(Buffer.isBuffer(u));
console.log(u instanceof Uint8Array);
console.log(Object.getPrototypeOf(Object.getPrototypeOf(b)) === Uint8Array.prototype);
perry node
Buffer.isBuffer(buf) true true
buf instanceof Uint8Array true true
Buffer.isBuffer(u8) true false
u8 instanceof Uint8Array true true
getPrototypeOf(getPrototypeOf(buf)) === Uint8Array.prototype false true

Root cause

js_buffer_is_buffer (crates/perry-runtime/src/buffer/query.rs:72) is exactly a registry membership test:

if is_registered_buffer(addr as usize) { 1 } else { 0 }

new Uint8Array(...) registers into that same BUFFER_REGISTRY, so the predicate cannot distinguish the two. The information needed is already recorded — mark_as_uint8array (buffer/header.rs) exists precisely to note which registered addresses came from the Uint8Array constructor, and its doc comment says so:

Mark this buffer as one that came from new Uint8Array(...) so it formats as Uint8Array(N) [ ... ] rather than <Buffer ...>.

It is consulted for formatting but not for isBuffer.

Suggested fix

if is_registered_buffer(addr) && !is_uint8array_buffer(addr) { 1 } else { 0 }

Worth confirming first that Buffer.from and the other Buffer constructors do not route through mark_as_uint8array, or real Buffers would start reporting false. The formatting behaviour (<Buffer ...> vs Uint8Array(N) [...]) already depends on that separation holding, which is evidence it does.

The prototype-chain difference is likely a separate defect in how the Buffer class is installed, and may not have the same cause.

Why it matters

Buffer.isBuffer is the standard way Node code branches between Buffer and Uint8Array handling — encoding paths, stream writes, and serialization all use it. Returning true for a plain Uint8Array sends that code down the Buffer path with a value that lacks Buffer's methods.

Found via

A behavioural differential written while working on the buffer registry probes (#9160-adjacent perf work). The perf change itself is behaviour-neutral — the guarded and unguarded builds produce identical output — and both differ from node in exactly this one place, so this defect is pre-existing and independent.

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