diff --git a/lib/buffer.js b/lib/buffer.js index 4d96a536a15f8b..04a7d314ff2cfb 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -104,7 +104,6 @@ const { isAnyArrayBuffer, isArrayBufferView, isUint8Array, - isTypedArray, } = require('internal/util/types'); const { inspect: utilInspect, @@ -154,6 +153,14 @@ FastBuffer.prototype.constructor = Buffer; Buffer.prototype = FastBuffer.prototype; addBufferPrototypeMethods(Buffer.prototype); +const BUFFER_NAMES = [ + 'ArrayBuffer', + 'Buffer', + 'TypedArray', + 'DataView', + 'SharedArrayBuffer', +]; + const constants = ObjectDefineProperties({}, { MAX_LENGTH: { __proto__: null, @@ -232,10 +239,18 @@ function toInteger(n, defaultVal) { } function copyImpl(source, target, targetStart, sourceStart, sourceEnd) { - if (!ArrayBufferIsView(source)) - throw new ERR_INVALID_ARG_TYPE('source', ['Buffer', 'Uint8Array'], source); - if (!ArrayBufferIsView(target)) - throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target); + if (source?.constructor === DataView || isAnyArrayBuffer(source)) { + source = new Uint8Array(source.buffer ?? source, source.byteOffset ?? 0, + source.byteLength); + } else if (!isArrayBufferView(source)) { + throw new ERR_INVALID_ARG_TYPE('source', BUFFER_NAMES, source); + } + if (target?.constructor === DataView || isAnyArrayBuffer(target)) { + target = new Uint8Array(target.buffer ?? target, target.byteOffset ?? 0, + target.byteLength); + } else if (!isArrayBufferView(target)) { + throw new ERR_INVALID_ARG_TYPE('target', BUFFER_NAMES, target); + } if (targetStart === undefined) { targetStart = 0; @@ -359,7 +374,7 @@ Buffer.from = function from(value, encodingOrOffset, length) { throw new ERR_INVALID_ARG_TYPE( 'first argument', - ['string', 'Buffer', 'ArrayBuffer', 'Array', 'Array-like Object'], + ['string', 'Array', 'Array-like Object', ...BUFFER_NAMES], value, ); }; @@ -367,14 +382,21 @@ Buffer.from = function from(value, encodingOrOffset, length) { /** * Creates the Buffer as a copy of the underlying ArrayBuffer of the view * rather than the contents of the view. - * @param {TypedArray} view + * @param {Buffer|TypedArray|DataView} view * @param {number} [offset] * @param {number} [length] * @returns {Buffer} */ Buffer.copyBytesFrom = function copyBytesFrom(view, offset, length) { - if (!isTypedArray(view)) { - throw new ERR_INVALID_ARG_TYPE('view', [ 'TypedArray' ], view); + if (view?.constructor === DataView) { + // Normalize DataView to Uint8Array so we can use TypedArray primordials. + view = new Uint8Array(view.buffer, view.byteOffset, view.byteLength); + } else if (isArrayBufferView(view)) { + // Do nothing.. + } else if (isAnyArrayBuffer(view)) { + view = new Uint8Array(view); + } else { + throw new ERR_INVALID_ARG_TYPE('view', BUFFER_NAMES, view); } const viewLength = TypedArrayPrototypeGetLength(view); @@ -399,16 +421,11 @@ Buffer.copyBytesFrom = function copyBytesFrom(view, offset, length) { if (end <= start) return new FastBuffer(); - const viewByteLength = TypedArrayPrototypeGetByteLength(view); - const elementSize = viewByteLength / viewLength; - const srcByteOffset = TypedArrayPrototypeGetByteOffset(view) + - start * elementSize; - const srcByteLength = (end - start) * elementSize; - + const srcView = TypedArrayPrototypeSubarray(view, start, end); return fromArrayLike(new Uint8Array( - TypedArrayPrototypeGetBuffer(view), - srcByteOffset, - srcByteLength)); + TypedArrayPrototypeGetBuffer(srcView), + TypedArrayPrototypeGetByteOffset(srcView), + TypedArrayPrototypeGetByteLength(srcView))); }; // Identical to the built-in %TypedArray%.of(), but avoids using the deprecated @@ -620,12 +637,17 @@ Buffer.concat = function concat(list, length) { if (length === undefined) { length = 0; for (let i = 0; i < list.length; i++) { - const buf = list[i]; - if (!isUint8Array(buf)) { + let buf = list[i]; + if (buf?.constructor === DataView || isAnyArrayBuffer(buf)) { + buf = list[i] = new Uint8Array(buf.buffer ?? buf, buf.byteOffset ?? 0, + buf.byteLength); + } else if (isArrayBufferView(buf)) { + // Do nothing.. + } else { // TODO(BridgeAR): This should not be of type ERR_INVALID_ARG_TYPE. // Instead, find the proper error code for this. throw new ERR_INVALID_ARG_TYPE( - `list[${i}]`, ['Buffer', 'Uint8Array'], buf); + `list[${i}]`, BUFFER_NAMES, buf); } length += TypedArrayPrototypeGetByteLength(buf); } @@ -647,11 +669,17 @@ Buffer.concat = function concat(list, length) { validateOffset(length, 'length'); for (let i = 0; i < list.length; i++) { - if (!isUint8Array(list[i])) { + if (list[i]?.constructor === DataView || isAnyArrayBuffer(list[i])) { + const buf = list[i]; + list[i] = new Uint8Array(buf.buffer ?? buf, buf.byteOffset ?? 0, + buf.byteLength); + } else if (isArrayBufferView(list[i])) { + // Do nothing.. + } else { // TODO(BridgeAR): This should not be of type ERR_INVALID_ARG_TYPE. // Instead, find the proper error code for this. throw new ERR_INVALID_ARG_TYPE( - `list[${i}]`, ['Buffer', 'Uint8Array'], list[i]); + `list[${i}]`, BUFFER_NAMES, list[i]); } } @@ -834,10 +862,7 @@ function byteLength(string, encoding) { if (isArrayBufferView(string) || isAnyArrayBuffer(string)) { return string.byteLength; } - - throw new ERR_INVALID_ARG_TYPE( - 'string', ['string', 'Buffer', 'ArrayBuffer'], string, - ); + throw new ERR_INVALID_ARG_TYPE('string', ['string', ...BUFFER_NAMES], string); } const len = string.length; @@ -926,15 +951,18 @@ Buffer.prototype.toString = function toString(encoding, start, end) { }; Buffer.prototype.equals = function equals(otherBuffer) { - if (!isUint8Array(otherBuffer)) { - throw new ERR_INVALID_ARG_TYPE( - 'otherBuffer', ['Buffer', 'Uint8Array'], otherBuffer); + if (isArrayBufferView(otherBuffer)) { + // Do nothing.. + } else if (isAnyArrayBuffer(otherBuffer)) { + otherBuffer = new Uint8Array(otherBuffer); + } else { + throw new ERR_INVALID_ARG_TYPE('otherBuffer', BUFFER_NAMES, otherBuffer); } if (this === otherBuffer) return true; const len = TypedArrayPrototypeGetByteLength(this); - if (len !== TypedArrayPrototypeGetByteLength(otherBuffer)) + if (len !== otherBuffer.byteLength) return false; return len === 0 || _compare(this, otherBuffer) === 0; @@ -988,9 +1016,14 @@ Buffer.prototype.compare = function compare(target, targetEnd, sourceStart, sourceEnd) { - if (!isUint8Array(target)) { - throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target); + if (isArrayBufferView(target)) { + // Do nothing.. + } else if (isAnyArrayBuffer(target)) { + target = new Uint8Array(target); + } else { + throw new ERR_INVALID_ARG_TYPE('target', BUFFER_NAMES, target); } + if (arguments.length === 1) return _compare(this, target); @@ -1066,14 +1099,14 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { return ops.indexOf(buffer, val, byteOffset, dir); } - if (isUint8Array(val)) { + if (isArrayBufferView(val) || isAnyArrayBuffer(val)) { const encodingVal = (ops === undefined ? encodingsMap.utf8 : ops.encodingVal); return indexOfBuffer(buffer, val, byteOffset, encodingVal, dir); } throw new ERR_INVALID_ARG_TYPE( - 'value', ['number', 'string', 'Buffer', 'Uint8Array'], val, + 'value', ['number', 'string', ...BUFFER_NAMES], val, ); } @@ -1244,7 +1277,9 @@ Buffer.prototype.subarray = function subarray(start, end) { start = adjustOffset(start, srcLength); end = end !== undefined ? adjustOffset(end, srcLength) : srcLength; const newLength = end > start ? end - start : 0; - return new FastBuffer(this.buffer, this.byteOffset + start, newLength); + return new FastBuffer(TypedArrayPrototypeGetBuffer(this), + TypedArrayPrototypeGetByteOffset(this) + start, + newLength); }; Buffer.prototype.slice = function slice(start, end) { @@ -1328,9 +1363,8 @@ if (internalBinding('config').hasIntl) { // Transcodes the Buffer from one encoding to another, returning a new // Buffer instance. transcode = function transcode(source, fromEncoding, toEncoding) { - if (!isUint8Array(source)) { - throw new ERR_INVALID_ARG_TYPE('source', - ['Buffer', 'Uint8Array'], source); + if (!ArrayBufferIsView(source) && !isAnyArrayBuffer(source)) { + throw new ERR_INVALID_ARG_TYPE('source', BUFFER_NAMES, source); } if (source.length === 0) return new FastBuffer(); @@ -1386,19 +1420,19 @@ function atob(input) { } function isUtf8(input) { - if (isTypedArray(input) || isAnyArrayBuffer(input)) { + if (isArrayBufferView(input) || isAnyArrayBuffer(input)) { return bindingIsUtf8(input); } - throw new ERR_INVALID_ARG_TYPE('input', ['ArrayBuffer', 'Buffer', 'TypedArray'], input); + throw new ERR_INVALID_ARG_TYPE('input', BUFFER_NAMES, input); } function isAscii(input) { - if (isTypedArray(input) || isAnyArrayBuffer(input)) { + if (isArrayBufferView(input) || isAnyArrayBuffer(input)) { return bindingIsAscii(input); } - throw new ERR_INVALID_ARG_TYPE('input', ['ArrayBuffer', 'Buffer', 'TypedArray'], input); + throw new ERR_INVALID_ARG_TYPE('input', BUFFER_NAMES, input); } module.exports = { diff --git a/test/parallel/test-buffer-compare.js b/test/parallel/test-buffer-compare.js index 4a1e1acccb9a58..c8ee94a4147501 100644 --- a/test/parallel/test-buffer-compare.js +++ b/test/parallel/test-buffer-compare.js @@ -30,18 +30,54 @@ assert.strictEqual(Buffer.compare(Buffer.alloc(1), Buffer.alloc(0)), 1); assert.throws(() => Buffer.compare(Buffer.alloc(1), 'abc'), { code: 'ERR_INVALID_ARG_TYPE', - message: 'The "buf2" argument must be an instance of Buffer or Uint8Array. ' + - "Received type string ('abc')" }); assert.throws(() => Buffer.compare('abc', Buffer.alloc(1)), { code: 'ERR_INVALID_ARG_TYPE', - message: 'The "buf1" argument must be an instance of Buffer or Uint8Array. ' + - "Received type string ('abc')" }); assert.throws(() => Buffer.alloc(1).compare('abc'), { code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - message: 'The "target" argument must be an instance of ' + - "Buffer or Uint8Array. Received type string ('abc')" }); + +// compare works with ArrayBuffer +{ + const buf = Buffer.from([1, 2, 3]); + const ab = new ArrayBuffer(3); + new Uint8Array(ab).set([1, 2, 3]); + assert.strictEqual(buf.compare(ab), 0); + + const ab2 = new ArrayBuffer(3); + new Uint8Array(ab2).set([4, 5, 6]); + assert.strictEqual(buf.compare(ab2), -1); + + const ab3 = new ArrayBuffer(3); + new Uint8Array(ab3).set([0, 0, 0]); + assert.strictEqual(buf.compare(ab3), 1); +} + +// compare works with DataView +{ + const buf = Buffer.from([1, 2, 3]); + const dv = new DataView(new ArrayBuffer(3)); + new Uint8Array(dv.buffer).set([1, 2, 3]); + assert.strictEqual(buf.compare(dv), 0); + + const dv2 = new DataView(new ArrayBuffer(3)); + new Uint8Array(dv2.buffer).set([4, 5, 6]); + assert.strictEqual(buf.compare(dv2), -1); +} + +// compare works with SharedArrayBuffer +{ + const buf = Buffer.from([1, 2, 3]); + const sab = new SharedArrayBuffer(3); + new Uint8Array(sab).set([1, 2, 3]); + assert.strictEqual(buf.compare(sab), 0); +} + +// compare works with other TypedArrays +{ + const buf = Buffer.from([0x01, 0x00, 0x02, 0x00]); + const u16 = new Uint16Array([1, 2]); + assert.strictEqual(buf.compare(u16), 0); +} diff --git a/test/parallel/test-buffer-concat.js b/test/parallel/test-buffer-concat.js index b955ce1a8bf6ec..6f15c79d9b4ad1 100644 --- a/test/parallel/test-buffer-concat.js +++ b/test/parallel/test-buffer-concat.js @@ -60,8 +60,6 @@ assert.strictEqual(flatLongLen.toString(), check); Buffer.concat(value); }, { code: 'ERR_INVALID_ARG_TYPE', - message: 'The "list[0]" argument must be an instance of Buffer ' + - `or Uint8Array.${common.invalidArgTypeHelper(value[0])}` }); }); @@ -69,8 +67,6 @@ assert.throws(() => { Buffer.concat([Buffer.from('hello'), 3]); }, { code: 'ERR_INVALID_ARG_TYPE', - message: 'The "list[1]" argument must be an instance of Buffer ' + - 'or Uint8Array. Received type number (3)' }); assert.throws(() => { @@ -124,6 +120,79 @@ assert.deepStrictEqual(Buffer.concat([new Uint8Array([0x41, 0x42]), new Uint8Array([0x43, 0x44])]), Buffer.from('ABCD')); +// concat works with ArrayBuffer +{ + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([0x41, 0x42, 0x43, 0x44]); + assert.deepStrictEqual( + Buffer.concat([ab]), + Buffer.from('ABCD')); +} + +// concat works with mixed types +{ + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([0x41, 0x42]); + const dv = new DataView(new ArrayBuffer(2)); + new Uint8Array(dv.buffer).set([0x43, 0x44]); + assert.deepStrictEqual( + Buffer.concat([Buffer.from('EF'), ab, new Uint8Array([0x47]), dv]), + Buffer.from('EFABGCD')); +} + +// concat works with ArrayBuffer and explicit length +{ + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const result = Buffer.concat([ab], 2); + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result, Buffer.from([1, 2])); +} + +// concat works with DataView alone +{ + const dv1 = new DataView(new ArrayBuffer(2)); + new Uint8Array(dv1.buffer).set([0x41, 0x42]); + const dv2 = new DataView(new ArrayBuffer(2)); + new Uint8Array(dv2.buffer).set([0x43, 0x44]); + assert.deepStrictEqual( + Buffer.concat([dv1, dv2]), + Buffer.from('ABCD')); +} + +// concat works with SharedArrayBuffer +{ + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([0x41, 0x42, 0x43, 0x44]); + assert.deepStrictEqual( + Buffer.concat([sab]), + Buffer.from('ABCD')); +} + +// concat works with Uint16Array +{ + const u16 = new Uint16Array([0x4241, 0x4443]); + assert.deepStrictEqual( + Buffer.concat([u16]), + Buffer.from('ABCD')); +} + +// concat works with DataView and explicit length (truncation) +{ + const dv = new DataView(new ArrayBuffer(4)); + new Uint8Array(dv.buffer).set([1, 2, 3, 4]); + const result = Buffer.concat([dv], 2); + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result, Buffer.from([1, 2])); +} + +// concat invalid types in list (with explicit length path) +assert.throws(() => { + Buffer.concat([42], 10); +}, { + code: 'ERR_INVALID_ARG_TYPE', +}); + // Spoofed length getter should not cause uninitialized memory exposure { const u8_1 = new Uint8Array([1, 2, 3, 4]); diff --git a/test/parallel/test-buffer-equals.js b/test/parallel/test-buffer-equals.js index b6993246f81019..ea71c1f8be586d 100644 --- a/test/parallel/test-buffer-equals.js +++ b/test/parallel/test-buffer-equals.js @@ -19,7 +19,59 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "otherBuffer" argument must be an instance of ' + - "Buffer or Uint8Array. Received type string ('abc')" } ); + +// equals works with ArrayBuffer +{ + const buf = Buffer.from([1, 2, 3]); + const ab = new ArrayBuffer(3); + new Uint8Array(ab).set([1, 2, 3]); + assert.ok(buf.equals(ab)); + + const ab2 = new ArrayBuffer(3); + new Uint8Array(ab2).set([4, 5, 6]); + assert.ok(!buf.equals(ab2)); + + // Different length + assert.ok(!buf.equals(new ArrayBuffer(4))); +} + +// equals works with DataView +{ + const buf = Buffer.from([1, 2, 3]); + const dv = new DataView(new ArrayBuffer(3)); + new Uint8Array(dv.buffer).set([1, 2, 3]); + assert.ok(buf.equals(dv)); + + const dv2 = new DataView(new ArrayBuffer(3)); + new Uint8Array(dv2.buffer).set([4, 5, 6]); + assert.ok(!buf.equals(dv2)); +} + +// equals works with SharedArrayBuffer +{ + const buf = Buffer.from([1, 2, 3]); + const sab = new SharedArrayBuffer(3); + new Uint8Array(sab).set([1, 2, 3]); + assert.ok(buf.equals(sab)); + + const sab2 = new SharedArrayBuffer(3); + new Uint8Array(sab2).set([4, 5, 6]); + assert.ok(!buf.equals(sab2)); +} + +// equals works with other TypedArrays +{ + const buf = Buffer.from([0x01, 0x00, 0x02, 0x00]); + const u16 = new Uint16Array([1, 2]); + assert.ok(buf.equals(u16)); +} + +// equals with empty buffers of different types +{ + const buf = Buffer.alloc(0); + assert.ok(buf.equals(new ArrayBuffer(0))); + assert.ok(buf.equals(new DataView(new ArrayBuffer(0)))); + assert.ok(buf.equals(new SharedArrayBuffer(0))); +} diff --git a/test/parallel/test-buffer-from.js b/test/parallel/test-buffer-from.js index b8f4a5d2a2a67b..1ab24d2142caf1 100644 --- a/test/parallel/test-buffer-from.js +++ b/test/parallel/test-buffer-from.js @@ -49,15 +49,12 @@ assert.deepStrictEqual( undefined, null, ].forEach((input) => { - const errObj = { + assert.throws(() => Buffer.from(input), { code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - message: 'The first argument must be of type string or an instance of ' + - 'Buffer, ArrayBuffer, or Array or an Array-like Object.' + - common.invalidArgTypeHelper(input) - }; - assert.throws(() => Buffer.from(input), errObj); - assert.throws(() => Buffer.from(input, 'hex'), errObj); + }); + assert.throws(() => Buffer.from(input, 'hex'), { + code: 'ERR_INVALID_ARG_TYPE', + }); }); Buffer.allocUnsafe(10); // Should not throw. @@ -179,5 +176,99 @@ assert.throws(() => { assert.strictEqual(b.length, 0); } +// copyBytesFrom: ArrayBuffer +{ + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const b = Buffer.copyBytesFrom(ab); + new Uint8Array(ab).set([0, 0, 0, 0]); + assert.strictEqual(b.length, 4); + assert.deepStrictEqual([...b], [1, 2, 3, 4]); +} + +// copyBytesFrom: ArrayBuffer with offset and length +{ + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const b = Buffer.copyBytesFrom(ab, 1, 2); + assert.strictEqual(b.length, 2); + assert.deepStrictEqual([...b], [2, 3]); +} + +// copyBytesFrom: DataView +{ + const dv = new DataView(new ArrayBuffer(3)); + new Uint8Array(dv.buffer).set([10, 20, 30]); + const b = Buffer.copyBytesFrom(dv); + assert.strictEqual(b.length, 3); + assert.deepStrictEqual([...b], [10, 20, 30]); +} + +// copyBytesFrom: DataView with offset +{ + const dv = new DataView(new ArrayBuffer(4)); + new Uint8Array(dv.buffer).set([1, 2, 3, 4]); + const b = Buffer.copyBytesFrom(dv, 2); + assert.strictEqual(b.length, 2); + assert.deepStrictEqual([...b], [3, 4]); +} + +// copyBytesFrom: DataView with offset and length +{ + const dv = new DataView(new ArrayBuffer(5)); + new Uint8Array(dv.buffer).set([1, 2, 3, 4, 5]); + const b = Buffer.copyBytesFrom(dv, 1, 3); + assert.strictEqual(b.length, 3); + assert.deepStrictEqual([...b], [2, 3, 4]); +} + +// copyBytesFrom: empty ArrayBuffer +{ + const b = Buffer.copyBytesFrom(new ArrayBuffer(0)); + assert.strictEqual(b.length, 0); +} + +// copyBytesFrom: empty DataView +{ + const b = Buffer.copyBytesFrom(new DataView(new ArrayBuffer(0))); + assert.strictEqual(b.length, 0); +} + +// copyBytesFrom: SharedArrayBuffer +{ + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([10, 20, 30, 40]); + const b = Buffer.copyBytesFrom(sab); + assert.strictEqual(b.length, 4); + assert.deepStrictEqual([...b], [10, 20, 30, 40]); +} + +// copyBytesFrom: SharedArrayBuffer with offset and length +{ + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([10, 20, 30, 40]); + const b = Buffer.copyBytesFrom(sab, 1, 2); + assert.strictEqual(b.length, 2); + assert.deepStrictEqual([...b], [20, 30]); +} + +// copyBytesFrom: Buffer input +{ + const src = Buffer.from([1, 2, 3, 4]); + const b = Buffer.copyBytesFrom(src, 1, 2); + src[1] = 0; + assert.strictEqual(b.length, 2); + assert.deepStrictEqual([...b], [2, 3]); +} + +// copyBytesFrom: Int32Array preserves element-based offset/length +{ + const i32 = new Int32Array([1, 2, 3]); + const b = Buffer.copyBytesFrom(i32, 1, 1); + assert.strictEqual(b.length, 4); + const view = new Int32Array(b.buffer, b.byteOffset, 1); + assert.strictEqual(view[0], 2); +} + // Invalid encoding is allowed Buffer.from('asd', 1);