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.
Summary
Buffer.isBuffer(new Uint8Array([1, 2]))returnstrue. Node returnsfalse. A plainUint8Arrayis not a Buffer, and code that usesBuffer.isBufferto tell the two apart — which is its entire purpose — takes the wrong branch.Separately,
Buffer's prototype chain does not reachUint8Array.prototype.Repro
Buffer.isBuffer(buf)buf instanceof Uint8ArrayBuffer.isBuffer(u8)u8 instanceof Uint8ArraygetPrototypeOf(getPrototypeOf(buf)) === Uint8Array.prototypeRoot cause
js_buffer_is_buffer(crates/perry-runtime/src/buffer/query.rs:72) is exactly a registry membership test:new Uint8Array(...)registers into that sameBUFFER_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 theUint8Arrayconstructor, and its doc comment says so:It is consulted for formatting but not for
isBuffer.Suggested fix
Worth confirming first that
Buffer.fromand the other Buffer constructors do not route throughmark_as_uint8array, or real Buffers would start reportingfalse. The formatting behaviour (<Buffer ...>vsUint8Array(N) [...]) already depends on that separation holding, which is evidence it does.The prototype-chain difference is likely a separate defect in how the
Bufferclass is installed, and may not have the same cause.Why it matters
Buffer.isBufferis the standard way Node code branches between Buffer and Uint8Array handling — encoding paths, stream writes, and serialization all use it. Returningtruefor a plainUint8Arraysends 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.