Files
scriptc/tests/corpus/2570-buffer-arg-validation.cjs
Chris Tate 62e4317a21 Construct native Buffers from untyped input
- Compile Buffer.from for untyped strings, bytes, arrays, and plain array-like data.
- Preserve copy semantics, element coercion, and catchable errors while keeping unsupported object conversions explicit.
2026-09-28 08:37:22 -05:00

124 lines
5.2 KiB
JavaScript

// @no-deprecation
// Buffer's invalid-argument ladders, Node-exact: Buffer.compare /
// buf.compare / buf.equals over non-buffer values and bad offsets throw
// ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE with Node's argument names and
// determineSpecificType renders; undefined offset slots take their
// defaults; well-typed calls through the same checked path still compute
// real answers. Plus the deprecated `new Buffer(number, enc)` string-arm
// rejection and the ERR_* codes on the write/swap/fill range ladders.
'use strict';
const show = (fn) => {
try {
console.log('ret', fn());
} catch (e) {
console.log(`${e.name}|${e.code}|${e.message}`);
}
};
const a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);
const b = Buffer.from([5, 6, 7, 8, 9, 0, 1, 2, 3, 4]);
// The static form's buf1/buf2 ladder (and the well-typed answer).
show(() => Buffer.compare(Buffer.alloc(1), 'abc'));
show(() => Buffer.compare('abc', Buffer.alloc(1)));
show(() => Buffer.compare(null, Buffer.alloc(1)));
show(() => Buffer.compare(a, b));
// equals: otherBuffer.
show(() => Buffer.alloc(1).equals('abc'));
show(() => Buffer.alloc(1).equals(7));
show(() => a.equals(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])));
// The instance ladder: target first, then each offset slot in order —
// non-numbers ERR_INVALID_ARG_TYPE, non-integers/out-of-range numbers
// ERR_OUT_OF_RANGE (Infinity renders 'an integer'), undefined defaults.
show(() => a.compare());
show(() => a.compare('abc'));
show(() => a.compare(b, '0'));
show(() => a.compare(b, undefined));
show(() => a.compare(b, 0, undefined, 0));
show(() => a.compare(b, 0, null));
show(() => a.compare(b, 0, { valueOf: () => 5 }));
show(() => a.compare(b, Infinity, -Infinity));
show(() => a.compare(b, 0xff));
show(() => a.compare(b, '0xff'));
show(() => a.compare(b, 0, '0xff'));
show(() => a.compare(b, 0, 100, 0));
show(() => a.compare(b, 0, 1, 0, 100));
show(() => a.compare(b, -1));
show(() => a.compare(b, 0, Infinity));
show(() => a.compare(b, 0, 1, -1));
show(() => a.compare(b, -Infinity, Infinity));
show(() => a.compare(b, NaN));
show(() => a.compare(b, 1.5));
show(() => a.compare(b, 6, 10));
show(() => a.compare(b, 6, 10, 0, 0));
show(() => a.compare(b, 0, 0, 0, 0));
show(() => a.compare(b, 0, 5, 4));
// new Buffer(number, encoding): the deprecated ctor's string arm.
show(() => new Buffer(42, 'utf8'));
// The write/swap/fill ladders carry their Node codes.
show(() => Buffer.alloc(9).write('foo', -1));
show(() => Buffer.alloc(9).write('foo', 10));
show(() => Buffer.from('abc').swap16());
show(() => Buffer.alloc(3).fill(Buffer.alloc(0)));
show(() => Buffer.alloc(3).writeUInt8(1, -1));
show(() => Buffer.alloc(3).writeUInt8(256, 0));
show(() => Buffer.alloc(3).writeDoubleLE(1, 1));
show(() => Buffer.alloc(1).readInt16LE(0));
// Untyped native input dispatches at runtime. Each accepted input is copied.
/** @param {unknown} value */
function fromUnknown(value) { return Buffer.from(value); }
/** @param {unknown} value */
function fromHex(value) { return Buffer.from(value, 'hex'); }
show(() => fromUnknown('hé😀').toString('hex'));
show(() => fromUnknown('').length);
show(() => fromHex('6162xx').toString('utf8'));
show(() => fromHex(new Uint8Array([65, 66])).toString('utf8'));
show(() => fromUnknown(Buffer.from('native')).toString('utf8'));
show(() => fromUnknown(JSON.parse('[257,-1,2.9,"65",true,null,{},[66],[]]')).toString('hex'));
show(() => fromUnknown(JSON.parse('{"length":3.9,"0":65,"2":67}')).toString('hex'));
show(() => fromUnknown(JSON.parse('{"length":"3","0":65}')).length);
show(() => fromUnknown(JSON.parse('{"type":"Buffer","data":[65,258,-1]}')).toString('hex'));
show(() => fromUnknown(JSON.parse('{"type":"Buffer","data":[65],"length":0}')).length);
show(() => fromUnknown({ length: -1 }).length);
show(() => fromUnknown({ length: NaN }).length);
show(() => fromUnknown({ length: Infinity }).length);
show(() => fromUnknown({ length: 1e30 }).length);
show(() => fromUnknown({ valueOf: null, length: 1, 0: 90 }).toString('hex'));
show(() => fromUnknown(null));
show(() => fromUnknown(undefined));
show(() => fromUnknown(false));
show(() => fromUnknown(42));
show(() => fromUnknown({}));
show(() => fromUnknown({ type: 'Buffer', data: 'wrong' }));
/** @param {unknown} source */
function copyInput(source) {
const copy = Buffer.from(source);
copy[0] = 90;
console.log('copy', copy.toString('hex'), source[0]);
}
copyInput(new Uint8Array([65, 66]));
copyInput(JSON.parse('[65,66]'));
let coercions = '';
const element = { valueOf() { coercions += 'v'; return '67'; } };
show(() => fromUnknown([element, element]).toString('hex'));
console.log('coercions', coercions);
const badElement = { valueOf() { throw new RangeError('element failed'); } };
show(() => fromUnknown([65, badElement, 66]));
// The stream path retains Buffer flavor at the native callback boundary.
const { Readable } = require('node:stream');
const stream = new Readable({ read() { this.push('stream'); this.push(null); } });
stream.on('data', (chunk) => console.log('stream copy', fromUnknown(chunk).toString('utf8')));
const inputListener = ((chunk) => {
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
console.log('input', data.toString('utf8'));
}).bind(null);
stream.on('data', inputListener);
inputListener('typed text');