Skip to content
Merged
Show file tree
Hide file tree
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: 21 additions & 2 deletions crates/perry-runtime/src/object/buffer_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,7 @@ pub unsafe fn dispatch_buffer_method(
// non-callable callback) nor iterating. Delegate any method the Buffer
// API doesn't claim to the shared uint8 typed-array dispatcher (it
// validates callbacks and reads the typed store); it returns `None` for
// names it doesn't implement, preserving the `undefined` fallback.
// names it doesn't implement.
_ => {
if super::typed_array_proto_thunks::is_typed_array_buffer(addr) {
if let Some(r) = super::typed_array_proto_thunks::dispatch_uint8_buffer_method(
Expand All @@ -1111,7 +1111,26 @@ pub unsafe fn dispatch_buffer_method(
return r;
}
}
f64::from_bits(crate::value::TAG_UNDEFINED)
// Internal perry hooks (`using` disposal probes and friends) may
// reach any receiver; they are duck-typed and must stay non-throwing.
if method_name.starts_with("__perry_") {
return f64::from_bits(crate::value::TAG_UNDEFINED);
}
// A method neither the Buffer API nor %TypedArray%.prototype
// implements must throw like Node (`buf.charCodeAt is not a
// function`), not silently return undefined. The silent fallback
// turned a readFileSync-now-returns-Buffer migration into invisible
// data corruption downstream: `content.charCodeAt(i)` yielded
// undefined in every comparison, so e.g. a NUL-byte binary-file
// scan misclassified every text file while the program kept
// running as if the calls had worked. Same shape as the string /
// number primitive catch-alls, which already route here.
crate::error::js_throw_type_error_not_a_function(
b"Buffer".as_ptr(),
6,
method_name.as_ptr(),
method_name.len(),
)
}
}
}
45 changes: 45 additions & 0 deletions test-files/test_gap_buffer_unknown_method_throws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Calling a method that neither the Buffer API nor %TypedArray%.prototype
// implements must throw a TypeError like Node — not silently return undefined.
//
// Pre-fix, `dispatch_buffer_method`'s catch-all returned undefined for any
// unknown name. That silence turned the readFileSync Node-parity migration
// (no encoding → Buffer, not string) into invisible data corruption for
// callers still treating the result as a string: `content.charCodeAt(i)`
// yielded undefined in every comparison and the program kept running as if
// the calls had worked (real case: an editor's NUL-byte binary-file scan
// misclassified every text file it opened).

const buf = Buffer.from('hello', 'utf8');

// The implemented Buffer API surface keeps working.
console.log('toString:', buf.toString('utf8'));
console.log('indexOf:', buf.indexOf('l'));

// Inherited %TypedArray%.prototype methods keep working via delegation.
console.log('every:', buf.every((b) => b > 0));

// A String.prototype method reached through a Buffer receiver throws.
try {
(buf as any).charCodeAt(0);
console.log('charCodeAt: no throw');
} catch (e) {
console.log('charCodeAt throws TypeError:', e instanceof TypeError);
const msg = (e as Error).message;
console.log('mentions method:', msg.includes('charCodeAt'));
console.log('mentions not-a-function:', msg.includes('is not a function'));
}

// A method that exists nowhere throws too.
try {
(buf as any).definitelyNotAMethod(1, 2);
console.log('bogus: no throw');
} catch (e) {
console.log('bogus throws TypeError:', e instanceof TypeError);
console.log(
'mentions not-a-function:',
(e as Error).message.includes('is not a function')
);
}

// The throw is catchable and execution continues normally afterwards.
console.log('still running:', buf.readUInt8(0));
Loading