Summary
An own property on a Buffer shadows the same-named Buffer.prototype method in Node (a Buffer is an ordinary Uint8Array). Perry honors this on every dynamic dispatch path, but not when codegen can statically prove the receiver is a buffer and the method name is known at compile time — those calls fold to the inline byte-load/store intrinsic (try_emit_buffer_read_intrinsic / its write twin), which reads the bytes directly and never consults the property table.
const b = Buffer.alloc(8);
b[0] = 0xab;
b.readUInt8 = function () { return "shadowed"; };
b.readUInt8(0); // node: "shadowed" perry: 171 <-- folds to the intrinsic
b["readUInt8"](0); // node: "shadowed" perry: 171 <-- literal key also folds
const k = "readUInt8";
b[k](0); // node: "shadowed" perry: "shadowed" (dynamic path, correct)
Why it is narrow
The fold requires lower_buffer_access_proof to succeed, i.e. the receiver's buffer-ness is provable at compile time. In minified/untyped bundles the proof usually fails, so the call goes through js_native_call_method → dispatch_buffer_method, which checks own props first and is correct. That is why mysql2's MockBuffer idiom (replace the write methods of a zero-length Buffer with no-ops to measure a packet) works in practice.
Fixing it
The honest fix costs a guard on Perry's hottest byte-access path, so it wants a deliberate decision:
- Runtime flag + branch — a process-global "some buffer has own props" bit, checked before the inline load; the slow path routes to
js_native_call_method. One predictable branch per buffer read.
- Whole-program compile-time flag — disable the intrinsic only for programs that assign to a property whose name is a
Buffer.prototype method name. Zero runtime cost, but needs a pre-codegen scan across modules.
Own-property support itself (storage, reads, method-VALUE reads, in, dynamic-key calls, byte indices, stale-address clearing) landed separately; this issue covers only the static-fold case.
Summary
An own property on a
Buffershadows the same-namedBuffer.prototypemethod in Node (a Buffer is an ordinaryUint8Array). Perry honors this on every dynamic dispatch path, but not when codegen can statically prove the receiver is a buffer and the method name is known at compile time — those calls fold to the inline byte-load/store intrinsic (try_emit_buffer_read_intrinsic/ its write twin), which reads the bytes directly and never consults the property table.Why it is narrow
The fold requires
lower_buffer_access_proofto succeed, i.e. the receiver's buffer-ness is provable at compile time. In minified/untyped bundles the proof usually fails, so the call goes throughjs_native_call_method→dispatch_buffer_method, which checks own props first and is correct. That is why mysql2'sMockBufferidiom (replace the write methods of a zero-length Buffer with no-ops to measure a packet) works in practice.Fixing it
The honest fix costs a guard on Perry's hottest byte-access path, so it wants a deliberate decision:
js_native_call_method. One predictable branch per buffer read.Buffer.prototypemethod name. Zero runtime cost, but needs a pre-codegen scan across modules.Own-property support itself (storage, reads, method-VALUE reads,
in, dynamic-key calls, byte indices, stale-address clearing) landed separately; this issue covers only the static-fold case.