Skip to content

Commit c85ce3d

Browse files
committed
buffer: cleanup handling different types buffers
PR-URL: #54439 Assisted-by: Claude Code:Opus 4.6
1 parent f1ed8d6 commit c85ce3d

File tree

5 files changed

+170
-53
lines changed

5 files changed

+170
-53
lines changed

lib/buffer.js

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ const {
104104
isAnyArrayBuffer,
105105
isArrayBufferView,
106106
isUint8Array,
107-
isTypedArray,
108107
} = require('internal/util/types');
109108
const {
110109
inspect: utilInspect,
@@ -154,6 +153,14 @@ FastBuffer.prototype.constructor = Buffer;
154153
Buffer.prototype = FastBuffer.prototype;
155154
addBufferPrototypeMethods(Buffer.prototype);
156155

156+
const BUFFER_NAMES = [
157+
'ArrayBuffer',
158+
'Buffer',
159+
'TypedArray',
160+
'DataView',
161+
'SharedArrayBuffer',
162+
];
163+
157164
const constants = ObjectDefineProperties({}, {
158165
MAX_LENGTH: {
159166
__proto__: null,
@@ -232,10 +239,15 @@ function toInteger(n, defaultVal) {
232239
}
233240

234241
function copyImpl(source, target, targetStart, sourceStart, sourceEnd) {
235-
if (!ArrayBufferIsView(source))
236-
throw new ERR_INVALID_ARG_TYPE('source', ['Buffer', 'Uint8Array'], source);
237-
if (!ArrayBufferIsView(target))
238-
throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target);
242+
if (!isArrayBufferView(source) && !isAnyArrayBuffer(source))
243+
throw new ERR_INVALID_ARG_TYPE('source', BUFFER_NAMES, source);
244+
if (isArrayBufferView(target)) {
245+
// Do nothing..
246+
} else if (isAnyArrayBuffer(target)) {
247+
target = new Uint8Array(target);
248+
} else {
249+
throw new ERR_INVALID_ARG_TYPE('target', BUFFER_NAMES, target);
250+
}
239251

240252
if (targetStart === undefined) {
241253
targetStart = 0;
@@ -359,22 +371,29 @@ Buffer.from = function from(value, encodingOrOffset, length) {
359371

360372
throw new ERR_INVALID_ARG_TYPE(
361373
'first argument',
362-
['string', 'Buffer', 'ArrayBuffer', 'Array', 'Array-like Object'],
374+
['string', 'Array', 'Array-like Object', ...BUFFER_NAMES],
363375
value,
364376
);
365377
};
366378

367379
/**
368380
* Creates the Buffer as a copy of the underlying ArrayBuffer of the view
369381
* rather than the contents of the view.
370-
* @param {TypedArray} view
382+
* @param {Buffer|TypedArray|DataView} view
371383
* @param {number} [offset]
372384
* @param {number} [length]
373385
* @returns {Buffer}
374386
*/
375387
Buffer.copyBytesFrom = function copyBytesFrom(view, offset, length) {
376-
if (!isTypedArray(view)) {
377-
throw new ERR_INVALID_ARG_TYPE('view', [ 'TypedArray' ], view);
388+
if (isArrayBufferView(view)) {
389+
// Normalize DataView to Uint8Array so we can use TypedArray primordials.
390+
if (!isUint8Array(view) && view.constructor === DataView) {
391+
view = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
392+
}
393+
} else if (isAnyArrayBuffer(view)) {
394+
view = new Uint8Array(view);
395+
} else {
396+
throw new ERR_INVALID_ARG_TYPE('view', BUFFER_NAMES, view);
378397
}
379398

380399
const viewLength = TypedArrayPrototypeGetLength(view);
@@ -393,22 +412,16 @@ Buffer.copyBytesFrom = function copyBytesFrom(view, offset, length) {
393412

394413
if (length !== undefined) {
395414
validateInteger(length, 'length', 0);
396-
// The old code used TypedArrayPrototypeSlice which clamps internally.
397415
end = MathMin(start + length, viewLength);
398416
}
399417

400418
if (end <= start) return new FastBuffer();
401419

402-
const viewByteLength = TypedArrayPrototypeGetByteLength(view);
403-
const elementSize = viewByteLength / viewLength;
404-
const srcByteOffset = TypedArrayPrototypeGetByteOffset(view) +
405-
start * elementSize;
406-
const srcByteLength = (end - start) * elementSize;
407-
420+
const srcView = TypedArrayPrototypeSubarray(view, start, end);
408421
return fromArrayLike(new Uint8Array(
409-
TypedArrayPrototypeGetBuffer(view),
410-
srcByteOffset,
411-
srcByteLength));
422+
TypedArrayPrototypeGetBuffer(srcView),
423+
TypedArrayPrototypeGetByteOffset(srcView),
424+
TypedArrayPrototypeGetByteLength(srcView)));
412425
};
413426

414427
// Identical to the built-in %TypedArray%.of(), but avoids using the deprecated
@@ -620,12 +633,17 @@ Buffer.concat = function concat(list, length) {
620633
if (length === undefined) {
621634
length = 0;
622635
for (let i = 0; i < list.length; i++) {
623-
const buf = list[i];
624-
if (!isUint8Array(buf)) {
636+
let buf = list[i];
637+
if (isUint8Array(buf)) {
638+
// Do nothing..
639+
} else if (isArrayBufferView(buf) || isAnyArrayBuffer(buf)) {
640+
buf = list[i] = new Uint8Array(buf.buffer ?? buf, buf.byteOffset ?? 0,
641+
buf.byteLength);
642+
} else {
625643
// TODO(BridgeAR): This should not be of type ERR_INVALID_ARG_TYPE.
626644
// Instead, find the proper error code for this.
627645
throw new ERR_INVALID_ARG_TYPE(
628-
`list[${i}]`, ['Buffer', 'Uint8Array'], buf);
646+
`list[${i}]`, BUFFER_NAMES, buf);
629647
}
630648
length += TypedArrayPrototypeGetByteLength(buf);
631649
}
@@ -647,11 +665,17 @@ Buffer.concat = function concat(list, length) {
647665

648666
validateOffset(length, 'length');
649667
for (let i = 0; i < list.length; i++) {
650-
if (!isUint8Array(list[i])) {
668+
if (isUint8Array(list[i])) {
669+
// Do nothing..
670+
} else if (isArrayBufferView(list[i]) || isAnyArrayBuffer(list[i])) {
671+
const buf = list[i];
672+
list[i] = new Uint8Array(buf.buffer ?? buf, buf.byteOffset ?? 0,
673+
buf.byteLength);
674+
} else {
651675
// TODO(BridgeAR): This should not be of type ERR_INVALID_ARG_TYPE.
652676
// Instead, find the proper error code for this.
653677
throw new ERR_INVALID_ARG_TYPE(
654-
`list[${i}]`, ['Buffer', 'Uint8Array'], list[i]);
678+
`list[${i}]`, BUFFER_NAMES, list[i]);
655679
}
656680
}
657681

@@ -834,10 +858,7 @@ function byteLength(string, encoding) {
834858
if (isArrayBufferView(string) || isAnyArrayBuffer(string)) {
835859
return string.byteLength;
836860
}
837-
838-
throw new ERR_INVALID_ARG_TYPE(
839-
'string', ['string', 'Buffer', 'ArrayBuffer'], string,
840-
);
861+
throw new ERR_INVALID_ARG_TYPE('string', ['string', ...BUFFER_NAMES], string);
841862
}
842863

843864
const len = string.length;
@@ -926,15 +947,18 @@ Buffer.prototype.toString = function toString(encoding, start, end) {
926947
};
927948

928949
Buffer.prototype.equals = function equals(otherBuffer) {
929-
if (!isUint8Array(otherBuffer)) {
930-
throw new ERR_INVALID_ARG_TYPE(
931-
'otherBuffer', ['Buffer', 'Uint8Array'], otherBuffer);
950+
if (isArrayBufferView(otherBuffer)) {
951+
// Do nothing..
952+
} else if (isAnyArrayBuffer(otherBuffer)) {
953+
otherBuffer = new Uint8Array(otherBuffer);
954+
} else {
955+
throw new ERR_INVALID_ARG_TYPE('otherBuffer', BUFFER_NAMES, otherBuffer);
932956
}
933957

934958
if (this === otherBuffer)
935959
return true;
936960
const len = TypedArrayPrototypeGetByteLength(this);
937-
if (len !== TypedArrayPrototypeGetByteLength(otherBuffer))
961+
if (len !== otherBuffer.byteLength)
938962
return false;
939963

940964
return len === 0 || _compare(this, otherBuffer) === 0;
@@ -988,9 +1012,14 @@ Buffer.prototype.compare = function compare(target,
9881012
targetEnd,
9891013
sourceStart,
9901014
sourceEnd) {
991-
if (!isUint8Array(target)) {
992-
throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target);
1015+
if (isArrayBufferView(target)) {
1016+
// Do nothing..
1017+
} else if (isAnyArrayBuffer(target)) {
1018+
target = new Uint8Array(target);
1019+
} else {
1020+
throw new ERR_INVALID_ARG_TYPE('target', BUFFER_NAMES, target);
9931021
}
1022+
9941023
if (arguments.length === 1)
9951024
return _compare(this, target);
9961025

@@ -1066,14 +1095,14 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) {
10661095
return ops.indexOf(buffer, val, byteOffset, dir);
10671096
}
10681097

1069-
if (isUint8Array(val)) {
1098+
if (isArrayBufferView(val) || isAnyArrayBuffer(val)) {
10701099
const encodingVal =
10711100
(ops === undefined ? encodingsMap.utf8 : ops.encodingVal);
10721101
return indexOfBuffer(buffer, val, byteOffset, encodingVal, dir);
10731102
}
10741103

10751104
throw new ERR_INVALID_ARG_TYPE(
1076-
'value', ['number', 'string', 'Buffer', 'Uint8Array'], val,
1105+
'value', ['number', 'string', ...BUFFER_NAMES], val,
10771106
);
10781107
}
10791108

@@ -1244,7 +1273,9 @@ Buffer.prototype.subarray = function subarray(start, end) {
12441273
start = adjustOffset(start, srcLength);
12451274
end = end !== undefined ? adjustOffset(end, srcLength) : srcLength;
12461275
const newLength = end > start ? end - start : 0;
1247-
return new FastBuffer(this.buffer, this.byteOffset + start, newLength);
1276+
return new FastBuffer(TypedArrayPrototypeGetBuffer(this),
1277+
TypedArrayPrototypeGetByteOffset(this) + start,
1278+
newLength);
12481279
};
12491280

12501281
Buffer.prototype.slice = function slice(start, end) {
@@ -1328,9 +1359,8 @@ if (internalBinding('config').hasIntl) {
13281359
// Transcodes the Buffer from one encoding to another, returning a new
13291360
// Buffer instance.
13301361
transcode = function transcode(source, fromEncoding, toEncoding) {
1331-
if (!isUint8Array(source)) {
1332-
throw new ERR_INVALID_ARG_TYPE('source',
1333-
['Buffer', 'Uint8Array'], source);
1362+
if (!ArrayBufferIsView(source) && !isAnyArrayBuffer(source)) {
1363+
throw new ERR_INVALID_ARG_TYPE('source', BUFFER_NAMES, source);
13341364
}
13351365
if (source.length === 0) return new FastBuffer();
13361366

@@ -1386,19 +1416,19 @@ function atob(input) {
13861416
}
13871417

13881418
function isUtf8(input) {
1389-
if (isTypedArray(input) || isAnyArrayBuffer(input)) {
1419+
if (isArrayBufferView(input) || isAnyArrayBuffer(input)) {
13901420
return bindingIsUtf8(input);
13911421
}
13921422

1393-
throw new ERR_INVALID_ARG_TYPE('input', ['ArrayBuffer', 'Buffer', 'TypedArray'], input);
1423+
throw new ERR_INVALID_ARG_TYPE('input', BUFFER_NAMES, input);
13941424
}
13951425

13961426
function isAscii(input) {
1397-
if (isTypedArray(input) || isAnyArrayBuffer(input)) {
1427+
if (isArrayBufferView(input) || isAnyArrayBuffer(input)) {
13981428
return bindingIsAscii(input);
13991429
}
14001430

1401-
throw new ERR_INVALID_ARG_TYPE('input', ['ArrayBuffer', 'Buffer', 'TypedArray'], input);
1431+
throw new ERR_INVALID_ARG_TYPE('input', BUFFER_NAMES, input);
14021432
}
14031433

14041434
module.exports = {

test/parallel/test-buffer-compare.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,26 @@ assert.strictEqual(Buffer.compare(Buffer.alloc(1), Buffer.alloc(0)), 1);
3030

3131
assert.throws(() => Buffer.compare(Buffer.alloc(1), 'abc'), {
3232
code: 'ERR_INVALID_ARG_TYPE',
33-
message: 'The "buf2" argument must be an instance of Buffer or Uint8Array. ' +
34-
"Received type string ('abc')"
3533
});
3634
assert.throws(() => Buffer.compare('abc', Buffer.alloc(1)), {
3735
code: 'ERR_INVALID_ARG_TYPE',
38-
message: 'The "buf1" argument must be an instance of Buffer or Uint8Array. ' +
39-
"Received type string ('abc')"
4036
});
4137

4238
assert.throws(() => Buffer.alloc(1).compare('abc'), {
4339
code: 'ERR_INVALID_ARG_TYPE',
44-
name: 'TypeError',
45-
message: 'The "target" argument must be an instance of ' +
46-
"Buffer or Uint8Array. Received type string ('abc')"
4740
});
41+
42+
// compare works with ArrayBuffer and DataView
43+
{
44+
const buf = Buffer.from([1, 2, 3]);
45+
const ab = new ArrayBuffer(3);
46+
new Uint8Array(ab).set([1, 2, 3]);
47+
assert.strictEqual(buf.compare(ab), 0);
48+
49+
const dv = new DataView(ab);
50+
assert.strictEqual(buf.compare(dv), 0);
51+
52+
const ab2 = new ArrayBuffer(3);
53+
new Uint8Array(ab2).set([4, 5, 6]);
54+
assert.strictEqual(buf.compare(ab2), -1);
55+
}

test/parallel/test-buffer-concat.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,35 @@ assert.deepStrictEqual(Buffer.concat([new Uint8Array([0x41, 0x42]),
124124
new Uint8Array([0x43, 0x44])]),
125125
Buffer.from('ABCD'));
126126

127+
// concat works with ArrayBuffer
128+
{
129+
const ab = new ArrayBuffer(4);
130+
new Uint8Array(ab).set([0x41, 0x42, 0x43, 0x44]);
131+
assert.deepStrictEqual(
132+
Buffer.concat([ab]),
133+
Buffer.from('ABCD'));
134+
}
135+
136+
// concat works with mixed types
137+
{
138+
const ab = new ArrayBuffer(2);
139+
new Uint8Array(ab).set([0x41, 0x42]);
140+
const dv = new DataView(new ArrayBuffer(2));
141+
new Uint8Array(dv.buffer).set([0x43, 0x44]);
142+
assert.deepStrictEqual(
143+
Buffer.concat([Buffer.from('EF'), ab, new Uint8Array([0x47]), dv]),
144+
Buffer.from('EFABGCD'));
145+
}
146+
147+
// concat works with ArrayBuffer and explicit length
148+
{
149+
const ab = new ArrayBuffer(4);
150+
new Uint8Array(ab).set([1, 2, 3, 4]);
151+
const result = Buffer.concat([ab], 2);
152+
assert.strictEqual(result.length, 2);
153+
assert.deepStrictEqual(result, Buffer.from([1, 2]));
154+
}
155+
127156
// Spoofed length getter should not cause uninitialized memory exposure
128157
{
129158
const u8_1 = new Uint8Array([1, 2, 3, 4]);

test/parallel/test-buffer-equals.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,20 @@ assert.throws(
1919
{
2020
code: 'ERR_INVALID_ARG_TYPE',
2121
name: 'TypeError',
22-
message: 'The "otherBuffer" argument must be an instance of ' +
23-
"Buffer or Uint8Array. Received type string ('abc')"
2422
}
2523
);
24+
25+
// equals works with ArrayBuffer and DataView
26+
{
27+
const buf = Buffer.from([1, 2, 3]);
28+
const ab = new ArrayBuffer(3);
29+
new Uint8Array(ab).set([1, 2, 3]);
30+
assert.ok(buf.equals(ab));
31+
32+
const dv = new DataView(ab);
33+
assert.ok(buf.equals(dv));
34+
35+
const ab2 = new ArrayBuffer(3);
36+
new Uint8Array(ab2).set([4, 5, 6]);
37+
assert.ok(!buf.equals(ab2));
38+
}

test/parallel/test-buffer-from.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,42 @@ assert.throws(() => {
179179
assert.strictEqual(b.length, 0);
180180
}
181181

182+
// copyBytesFrom: ArrayBuffer
183+
{
184+
const ab = new ArrayBuffer(4);
185+
new Uint8Array(ab).set([1, 2, 3, 4]);
186+
const b = Buffer.copyBytesFrom(ab);
187+
new Uint8Array(ab).set([0, 0, 0, 0]);
188+
assert.strictEqual(b.length, 4);
189+
assert.deepStrictEqual([...b], [1, 2, 3, 4]);
190+
}
191+
192+
// copyBytesFrom: ArrayBuffer with offset and length
193+
{
194+
const ab = new ArrayBuffer(4);
195+
new Uint8Array(ab).set([1, 2, 3, 4]);
196+
const b = Buffer.copyBytesFrom(ab, 1, 2);
197+
assert.strictEqual(b.length, 2);
198+
assert.deepStrictEqual([...b], [2, 3]);
199+
}
200+
201+
// copyBytesFrom: DataView
202+
{
203+
const dv = new DataView(new ArrayBuffer(3));
204+
new Uint8Array(dv.buffer).set([10, 20, 30]);
205+
const b = Buffer.copyBytesFrom(dv);
206+
assert.strictEqual(b.length, 3);
207+
assert.deepStrictEqual([...b], [10, 20, 30]);
208+
}
209+
210+
// copyBytesFrom: DataView with offset
211+
{
212+
const dv = new DataView(new ArrayBuffer(4));
213+
new Uint8Array(dv.buffer).set([1, 2, 3, 4]);
214+
const b = Buffer.copyBytesFrom(dv, 2);
215+
assert.strictEqual(b.length, 2);
216+
assert.deepStrictEqual([...b], [3, 4]);
217+
}
218+
182219
// Invalid encoding is allowed
183220
Buffer.from('asd', 1);

0 commit comments

Comments
 (0)