Skip to content

Commit beca324

Browse files
committed
buffer: allow Uint8Array input to methods
Allow all methods on `buffer` and `Buffer` to take `Uint8Array` arguments where it makes sense. On the native side, there is effectively no difference, and as a bonus the `isUint8Array` check is faster than `instanceof Buffer`. PR-URL: #10236 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
1 parent 6cb33c0 commit beca324

10 files changed

Lines changed: 81 additions & 45 deletions

File tree

‎doc/api/buffer.md‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -635,8 +635,8 @@ actual byte length is returned.
635635
added: v0.11.13
636636
-->
637637

638-
*`buf1` {Buffer}
639-
*`buf2` {Buffer}
638+
*`buf1` {Buffer|Uint8Array}
639+
*`buf2` {Buffer|Uint8Array}
640640
* Returns: {Integer}
641641

642642
Compares `buf1` to `buf2` typically for the purpose of sorting arrays of
@@ -660,7 +660,7 @@ console.log(arr.sort(Buffer.compare));
660660
added: v0.7.11
661661
-->
662662

663-
*`list` {Array} List of `Buffer` instances to concat
663+
*`list` {Array} List of `Buffer`or [`Uint8Array`]instances to concat
664664
*`totalLength` {Integer} Total length of the `Buffer` instances in `list`
665665
when concatenated
666666
* Returns: {Buffer}
@@ -882,7 +882,7 @@ console.log(buf.toString('ascii'));
882882
added: v0.11.13
883883
-->
884884

885-
*`target` {Buffer} A `Buffer` to compare to
885+
*`target` {Buffer|Uint8Array} A `Buffer` or [`Uint8Array`] to compare to
886886
*`targetStart` {Integer} The offset within `target` at which to begin
887887
comparison. **Default:**`0`
888888
*`targetEnd` {Integer} The offset with `target` at which to end comparison
@@ -1037,7 +1037,7 @@ for (const pair of buf.entries()) {
10371037
added: v0.11.13
10381038
-->
10391039

1040-
*`otherBuffer` {Buffer} A `Buffer` to compare to
1040+
*`otherBuffer` {Buffer} A `Buffer`or [`Uint8Array`]to compare to
10411041
* Returns: {Boolean}
10421042

10431043
Returns `true` if both `buf` and `otherBuffer` have exactly the same bytes,
@@ -1099,7 +1099,7 @@ console.log(Buffer.allocUnsafe(3).fill('\u0222'));
10991099
added: v1.5.0
11001100
-->
11011101

1102-
*`value` {String | Buffer | Integer} What to search for
1102+
*`value` {String | Buffer | Uint8Array | Integer} What to search for
11031103
*`byteOffset` {Integer} Where to begin searching in `buf`. **Default:**`0`
11041104
*`encoding` {String} If `value` is a string, this is its encoding.
11051105
**Default:**`'utf8'`
@@ -1110,8 +1110,8 @@ If `value` is:
11101110

11111111
* a string, `value` is interpreted according to the character encoding in
11121112
`encoding`.
1113-
* a `Buffer`, `value` will be used in its entirety. To compare a partial
1114-
`Buffer` use [`buf.slice()`].
1113+
* a `Buffer` or [`Uint8Array`], `value` will be used in its entirety.
1114+
To compare a partial `Buffer`, use [`buf.slice()`].
11151115
* a number, `value` will be interpreted as an unsigned 8-bit integer
11161116
value between `0` and `255`.
11171117

@@ -1221,7 +1221,7 @@ for (const key of buf.keys()) {
12211221
added: v6.0.0
12221222
-->
12231223

1224-
*`value` {String | Buffer | Integer} What to search for
1224+
*`value` {String | Buffer | Uint8Array | Integer} What to search for
12251225
*`byteOffset` {Integer} Where to begin searching in `buf`.
12261226
**Default:**[`buf.length`]` - 1`
12271227
*`encoding` {String} If `value` is a string, this is its encoding.
@@ -2313,12 +2313,12 @@ Note that this is a property on the `buffer` module returned by
23132313
added: v7.1.0
23142314
-->
23152315

2316-
*`source` {Buffer} A `Buffer` instance
2316+
*`source` {Buffer|Uint8Array} A `Buffer` or `Uint8Array` instance
23172317
*`fromEnc` {String} The current encoding
23182318
*`toEnc` {String} To target encoding
23192319

2320-
Re-encodes the given `Buffer` instance from one character encoding to another.
2321-
Returns a new `Buffer` instance.
2320+
Re-encodes the given `Buffer`or `Uint8Array`instance from one character
2321+
encoding to another. Returns a new `Buffer` instance.
23222322

23232323
Throws if the `fromEnc` or `toEnc` specify invalid character encodings or if
23242324
conversion from `fromEnc` to `toEnc` is not permitted.

‎lib/buffer.js‎

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
'use strict';
33

44
constbinding=process.binding('buffer');
5-
const{ isArrayBuffer, isSharedArrayBuffer }=process.binding('util');
5+
const{ isArrayBuffer, isSharedArrayBuffer, isUint8Array }=
6+
process.binding('util');
67
constbindingObj={};
78
constinternalUtil=require('internal/util');
89

@@ -251,13 +252,13 @@ function fromArrayBuffer(obj, byteOffset, length) {
251252
}
252253

253254
functionfromObject(obj){
254-
if(objinstanceofBuffer){
255+
if(isUint8Array(obj)){
255256
constb=allocate(obj.length);
256257

257258
if(b.length===0)
258259
returnb;
259260

260-
obj.copy(b,0,0,obj.length);
261+
binding.copy(obj,b,0,0,obj.length);
261262
returnb;
262263
}
263264

@@ -287,9 +288,8 @@ Buffer.isBuffer = function isBuffer(b) {
287288

288289

289290
Buffer.compare=functioncompare(a,b){
290-
if(!(ainstanceofBuffer)||
291-
!(binstanceofBuffer)){
292-
thrownewTypeError('Arguments must be Buffers');
291+
if(!isUint8Array(a)||!isUint8Array(b)){
292+
thrownewTypeError('Arguments must be Buffers or Uint8Arrays');
293293
}
294294

295295
if(a===b){
@@ -306,10 +306,13 @@ Buffer.isEncoding = function(encoding) {
306306
};
307307
Buffer[internalUtil.kIsEncodingSymbol]=Buffer.isEncoding;
308308

309+
constkConcatErrMsg='"list" argument must be an Array '+
310+
'of Buffer or Uint8Array instances';
311+
309312
Buffer.concat=function(list,length){
310313
vari;
311314
if(!Array.isArray(list))
312-
thrownewTypeError('"list" argument must be an Array of Buffers');
315+
thrownewTypeError(kConcatErrMsg);
313316

314317
if(list.length===0)
315318
returnnewFastBuffer();
@@ -326,9 +329,9 @@ Buffer.concat = function(list, length) {
326329
varpos=0;
327330
for(i=0;i<list.length;i++){
328331
varbuf=list[i];
329-
if(!Buffer.isBuffer(buf))
330-
thrownewTypeError('"list" argument must be an Array of Buffers');
331-
buf.copy(buffer,pos);
332+
if(!isUint8Array(buf))
333+
thrownewTypeError(kConcatErrMsg);
334+
binding.copy(buf,buffer,pos);
332335
pos+=buf.length;
333336
}
334337

@@ -495,6 +498,9 @@ function slowToString(encoding, start, end) {
495498
}
496499
}
497500

501+
Buffer.prototype.copy=function(target,targetStart,sourceStart,sourceEnd){
502+
returnbinding.copy(this,target,targetStart,sourceStart,sourceEnd);
503+
};
498504

499505
Buffer.prototype.toString=function(){
500506
letresult;
@@ -510,8 +516,8 @@ Buffer.prototype.toString = function() {
510516

511517

512518
Buffer.prototype.equals=functionequals(b){
513-
if(!(binstanceofBuffer))
514-
thrownewTypeError('Argument must be a Buffer');
519+
if(!isUint8Array(b))
520+
thrownewTypeError('Argument must be a Buffer or Uint8Array');
515521

516522
if(this===b)
517523
returntrue;
@@ -539,8 +545,8 @@ Buffer.prototype.compare = function compare(target,
539545
thisStart,
540546
thisEnd){
541547

542-
if(!(targetinstanceofBuffer))
543-
thrownewTypeError('Argument must be a Buffer');
548+
if(!isUint8Array(target))
549+
thrownewTypeError('Argument must be a Buffer or Uint8Array');
544550

545551
if(start===undefined)
546552
start=0;
@@ -604,13 +610,14 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) {
604610
returnbinding.indexOfString(buffer,val,byteOffset,encoding,dir);
605611
}
606612
returnslowIndexOf(buffer,val,byteOffset,encoding,dir);
607-
}elseif(valinstanceofBuffer){
613+
}elseif(isUint8Array(val)){
608614
returnbinding.indexOfBuffer(buffer,val,byteOffset,encoding,dir);
609615
}elseif(typeofval==='number'){
610616
returnbinding.indexOfNumber(buffer,val,byteOffset,dir);
611617
}
612618

613-
thrownewTypeError('"val" argument must be string, number or Buffer');
619+
thrownewTypeError('"val" argument must be string, number, Buffer '+
620+
'or Uint8Array');
614621
}
615622

616623

@@ -1037,8 +1044,8 @@ Buffer.prototype.readDoubleBE = function readDoubleBE(offset, noAssert) {
10371044

10381045

10391046
functioncheckInt(buffer,value,offset,ext,max,min){
1040-
if(!(bufferinstanceofBuffer))
1041-
thrownewTypeError('"buffer" argument must be a Buffer instance');
1047+
if(!isUint8Array(buffer))
1048+
thrownewTypeError('"buffer" argument must be a Buffer or Uint8Array');
10421049
if(value>max||value<min)
10431050
thrownewTypeError('"value" argument is out of bounds');
10441051
if(offset+ext>buffer.length)

‎lib/internal/buffer.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ const normalizeEncoding = require('internal/util').normalizeEncoding;
88
constBuffer=require('buffer').Buffer;
99

1010
consticu=process.binding('icu');
11+
const{ isUint8Array }=process.binding('util');
1112

1213
// Transcodes the Buffer from one encoding to another, returning a new
1314
// Buffer instance.
1415
exports.transcode=functiontranscode(source,fromEncoding,toEncoding){
15-
if(!Buffer.isBuffer(source))
16-
thrownewTypeError('"source" argument must be a Buffer');
16+
if(!isUint8Array(source))
17+
thrownewTypeError('"source" argument must be a Buffer or Uint8Array');
1718
if(source.length===0)returnBuffer.alloc(0);
1819

1920
fromEncoding=normalizeEncoding(fromEncoding)||fromEncoding;
2021
toEncoding=normalizeEncoding(toEncoding)||toEncoding;
2122
constresult=icu.transcode(source,fromEncoding,toEncoding);
22-
if(Buffer.isBuffer(result))
23+
if(typeofresult!=='number')
2324
returnresult;
2425

2526
constcode=icu.icuErrName(result);

‎src/node_buffer.cc‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -519,23 +519,24 @@ void Base64Slice(const FunctionCallbackInfo<Value>& args) {
519519
}
520520

521521

522-
// bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]);
522+
// bytesCopied = copy(buffer, target[, targetStart][, sourceStart][, sourceEnd])
523523
voidCopy(const FunctionCallbackInfo<Value> &args) {
524524
Environment* env = Environment::GetCurrent(args);
525525

526-
THROW_AND_RETURN_UNLESS_BUFFER(env, args.This());
527526
THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
528-
Local<Object> target_obj = args[0].As<Object>();
529-
SPREAD_BUFFER_ARG(args.This(), ts_obj);
527+
THROW_AND_RETURN_UNLESS_BUFFER(env, args[1]);
528+
Local<Object> buffer_obj = args[0].As<Object>();
529+
Local<Object> target_obj = args[1].As<Object>();
530+
SPREAD_BUFFER_ARG(buffer_obj, ts_obj);
530531
SPREAD_BUFFER_ARG(target_obj, target);
531532

532533
size_t target_start;
533534
size_t source_start;
534535
size_t source_end;
535536

536-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[1], 0, &target_start));
537-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[2], 0, &source_start));
538-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[3], ts_obj_length, &source_end));
537+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[2], 0, &target_start));
538+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[3], 0, &source_start));
539+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[4], ts_obj_length, &source_end));
539540

540541
// Copy 0 bytes; we're done
541542
if (target_start >= target_length || source_start >= source_end)
@@ -1203,8 +1204,6 @@ void SetupBufferJS(const FunctionCallbackInfo<Value>& args) {
12031204
env->SetMethod(proto, "ucs2Write", Ucs2Write);
12041205
env->SetMethod(proto, "utf8Write", Utf8Write);
12051206

1206-
env->SetMethod(proto, "copy", Copy);
1207-
12081207
if (auto zero_fill_field = env->isolate_data()->zero_fill_field()) {
12091208
CHECK(args[1]->IsObject());
12101209
auto binding_object = args[1].As<Object>();
@@ -1227,6 +1226,7 @@ void Initialize(Local<Object> target,
12271226
env->SetMethod(target, "createFromString", CreateFromString);
12281227

12291228
env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8);
1229+
env->SetMethod(target, "copy", Copy);
12301230
env->SetMethod(target, "compare", Compare);
12311231
env->SetMethod(target, "compareOffset", CompareOffset);
12321232
env->SetMethod(target, "fill", Fill);

‎src/node_util.cc‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ using v8::Value;
2929
V(isSet, IsSet) \
3030
V(isSetIterator, IsSetIterator) \
3131
V(isSharedArrayBuffer, IsSharedArrayBuffer) \
32-
V(isTypedArray, IsTypedArray)
32+
V(isTypedArray, IsTypedArray) \
33+
V(isUint8Array, IsUint8Array)
3334

3435

3536
#defineV(_, ucname) \

‎test/parallel/test-buffer-compare.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ const assert = require('assert');
66
constb=Buffer.alloc(1,'a');
77
constc=Buffer.alloc(1,'c');
88
constd=Buffer.alloc(2,'aa');
9+
conste=newUint8Array([0x61,0x61]);// ASCII 'aa', same as d
910

1011
assert.strictEqual(b.compare(c),-1);
1112
assert.strictEqual(c.compare(d),1);
1213
assert.strictEqual(d.compare(b),1);
14+
assert.strictEqual(d.compare(e),0);
1315
assert.strictEqual(b.compare(d),-1);
1416
assert.strictEqual(b.compare(b),0);
1517

@@ -18,6 +20,9 @@ assert.strictEqual(Buffer.compare(c, d), 1);
1820
assert.strictEqual(Buffer.compare(d,b),1);
1921
assert.strictEqual(Buffer.compare(b,d),-1);
2022
assert.strictEqual(Buffer.compare(c,c),0);
23+
assert.strictEqual(Buffer.compare(e,e),0);
24+
assert.strictEqual(Buffer.compare(d,e),0);
25+
assert.strictEqual(Buffer.compare(d,b),1);
2126

2227
assert.strictEqual(Buffer.compare(Buffer.alloc(0),Buffer.alloc(0)),0);
2328
assert.strictEqual(Buffer.compare(Buffer.alloc(0),Buffer.alloc(1)),-1);

‎test/parallel/test-buffer-concat.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ function assertWrongList(value) {
3535
Buffer.concat(value);
3636
},function(err){
3737
returnerrinstanceofTypeError&&
38-
err.message==='"list" argument must be an Array of Buffers';
38+
err.message==='"list" argument must be an Array of Buffer '+
39+
'or Uint8Array instances';
3940
});
4041
}
4142

@@ -60,3 +61,7 @@ assert.deepStrictEqual(Buffer.concat([empty], 4096), Buffer.alloc(4096));
6061
assert.deepStrictEqual(
6162
Buffer.concat([random10],40),
6263
Buffer.concat([random10,Buffer.alloc(30)]));
64+
65+
assert.deepStrictEqual(Buffer.concat([newUint8Array([0x41,0x42]),
66+
newUint8Array([0x43,0x44])]),
67+
Buffer.from('ABCD'));

‎test/parallel/test-buffer-equals.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ assert.ok(b.equals(c));
1212
assert.ok(!c.equals(d));
1313
assert.ok(!d.equals(e));
1414
assert.ok(d.equals(d));
15+
assert.ok(d.equals(newUint8Array([0x61,0x62,0x63,0x64,0x65])));
1516

1617
assert.throws(()=>Buffer.alloc(1).equals('abc'));

‎test/parallel/test-buffer-indexof.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,11 @@ assert.equal(0, reallyLong.lastIndexOf(pattern));
524524
assert.strictEqual(buf.indexOf(0xff),-1);
525525
assert.strictEqual(buf.indexOf(0xffff),-1);
526526
}
527+
528+
// Test that Uint8Array arguments are okay.
529+
{
530+
constneedle=newUint8Array([0x66,0x6f,0x6f]);
531+
consthaystack=Buffer.from('a foo b foo');
532+
assert.strictEqual(haystack.indexOf(needle),2);
533+
assert.strictEqual(haystack.lastIndexOf(needle),haystack.length-3);
534+
}

‎test/parallel/test-icu-transcode.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ for (const test in tests) {
4040

4141
assert.throws(
4242
()=>buffer.transcode(null,'utf8','ascii'),
43-
/^TypeError:"source"argumentmustbeaBuffer$/
43+
/^TypeError:"source"argumentmustbeaBufferorUint8Array$/
4444
);
4545

4646
assert.throws(
@@ -62,3 +62,11 @@ assert.deepStrictEqual(
6262
assert.deepStrictEqual(
6363
buffer.transcode(Buffer.from('hä','latin1'),'latin1','utf16le'),
6464
Buffer.from('hä','utf16le'));
65+
66+
// Test that Uint8Array arguments are okay.
67+
{
68+
constuint8array=newUint8Array([...Buffer.from('hä','latin1')]);
69+
assert.deepStrictEqual(
70+
buffer.transcode(uint8array,'latin1','utf16le'),
71+
Buffer.from('hä','utf16le'));
72+
}

0 commit comments

Comments
 (0)