Skip to content

Commit 177b731

Browse files
committed
buffer: improve Buffer#fill performance
1) This improves the performance for Buffer#fill by using shortcuts. 2) It also ports throwing errors to JS. That way they contain the proper error code. 3) Using negative `end` values will from now on result in an error instead of just doing nothing. 4) Passing in `null` as encoding is from now on accepted as 'utf8'. PR-URL: #18790 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent d3af120 commit 177b731

File tree

6 files changed

+85
-136
lines changed

6 files changed

+85
-136
lines changed

doc/api/buffer.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,9 @@ console.log(buf1.equals(buf3));
12021202
<!-- YAML
12031203
added: v0.5.0
12041204
changes:
1205+
- version: REPLACEME
1206+
pr-url: https://github.com/nodejs/node/pull/18790
1207+
description: Negative `end` values throw an `ERR_INDEX_OUT_OF_RANGE` error.
12051208
- version: REPLACEME
12061209
pr-url: https://github.com/nodejs/node/pull/18129
12071210
description: Attempting to fill a non-zero length buffer with a zero length

lib/buffer.js

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -254,16 +254,8 @@ function assertSize(size) {
254254
**/
255255
Buffer.alloc = function alloc(size, fill, encoding) {
256256
assertSize(size);
257-
if (size > 0 && fill !== undefined) {
258-
// Since we are filling anyway, don't zero fill initially.
259-
// Only pay attention to encoding if it's a string. This
260-
// prevents accidentally sending in a number that would
261-
// be interpreted as a start offset.
262-
if (typeof encoding !== 'string')
263-
encoding = undefined;
264-
const ret = createUnsafeBuffer(size);
265-
if (fill_(ret, fill, encoding) > 0)
266-
return ret;
257+
if (fill !== undefined && size > 0) {
258+
return _fill(createUnsafeBuffer(size), fill, encoding);
267259
}
268260
return new FastBuffer(size);
269261
};
@@ -834,14 +826,12 @@ Buffer.prototype.includes = function includes(val, byteOffset, encoding) {
834826
// buffer.fill(buffer[, offset[, end]])
835827
// buffer.fill(string[, offset[, end]][, encoding])
836828
Buffer.prototype.fill = function fill(val, start, end, encoding) {
837-
fill_(this, val, start, end, encoding);
838-
return this;
829+
return _fill(this, val, start, end, encoding);
839830
};
840831

841-
function fill_(buf, val, start, end, encoding) {
842-
// Handle string cases:
832+
function _fill(buf, val, start, end, encoding) {
843833
if (typeof val === 'string') {
844-
if (typeof start === 'string') {
834+
if (start === undefined || typeof start === 'string') {
845835
encoding = start;
846836
start = 0;
847837
end = buf.length;
@@ -850,46 +840,60 @@ function fill_(buf, val, start, end, encoding) {
850840
end = buf.length;
851841
}
852842

853-
if (encoding !== undefined && typeof encoding !== 'string') {
854-
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'encoding',
855-
'string', encoding);
856-
}
857-
var normalizedEncoding = normalizeEncoding(encoding);
843+
const normalizedEncoding = normalizeEncoding(encoding);
858844
if (normalizedEncoding === undefined) {
845+
if (typeof encoding !== 'string') {
846+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'encoding', 'string',
847+
encoding);
848+
}
859849
throw new errors.TypeError('ERR_UNKNOWN_ENCODING', encoding);
860850
}
861851

862852
if (val.length === 0) {
863-
// Previously, if val === '', the Buffer would not fill,
864-
// which is rather surprising.
853+
// If val === '' default to zero.
865854
val = 0;
866855
} else if (val.length === 1) {
867-
var code = val.charCodeAt(0);
868-
if ((normalizedEncoding === 'utf8' && code < 128) ||
869-
normalizedEncoding === 'latin1') {
870-
// Fast path: If `val` fits into a single byte, use that numeric value.
871-
val = code;
856+
// Fast path: If `val` fits into a single byte, use that numeric value.
857+
if (normalizedEncoding === 'utf8') {
858+
const code = val.charCodeAt(0);
859+
if (code < 128) {
860+
val = code;
861+
}
862+
} else if (normalizedEncoding === 'latin1') {
863+
val = val.charCodeAt(0);
872864
}
873865
}
874-
} else if (typeof val === 'number') {
875-
val = val & 255;
866+
} else {
867+
encoding = undefined;
876868
}
877869

878-
// Invalid ranges are not set to a default, so can range check early.
879-
if (start < 0 || end > buf.length)
880-
throw new errors.RangeError('ERR_INDEX_OUT_OF_RANGE');
881-
882-
if (end <= start)
883-
return 0;
884-
885-
start = start >>> 0;
886-
end = end === undefined ? buf.length : end >>> 0;
887-
const fillLength = bindingFill(buf, val, start, end, encoding);
870+
if (start === undefined) {
871+
start = 0;
872+
end = buf.length;
873+
} else {
874+
// Invalid ranges are not set to a default, so can range check early.
875+
if (end === undefined) {
876+
if (start < 0)
877+
throw new errors.RangeError('ERR_INDEX_OUT_OF_RANGE');
878+
end = buf.length;
879+
} else {
880+
if (start < 0 || end > buf.length || end < 0)
881+
throw new errors.RangeError('ERR_INDEX_OUT_OF_RANGE');
882+
end = end >>> 0;
883+
}
884+
start = start >>> 0;
885+
if (start >= end)
886+
return buf;
887+
}
888888

889-
if (fillLength === -1)
890-
throw new errors.TypeError('ERR_INVALID_ARG_VALUE', 'value', val);
889+
const res = bindingFill(buf, val, start, end, encoding);
890+
if (res < 0) {
891+
if (res === -1)
892+
throw new errors.TypeError('ERR_INVALID_ARG_VALUE', 'value', val);
893+
throw new errors.RangeError('ERR_INDEX_OUT_OF_RANGE');
894+
}
891895

892-
return fillLength;
896+
return buf;
893897
}
894898

895899

lib/internal/util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function assertCrypto() {
102102
// function in order to avoid the performance hit.
103103
// Return undefined if there is no match.
104104
function normalizeEncoding(enc) {
105-
if (!enc) return 'utf8';
105+
if (enc == null || enc === '') return 'utf8';
106106
let retried;
107107
while (true) {
108108
switch (enc) {

src/node_buffer.cc

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -571,10 +571,10 @@ void Fill(const FunctionCallbackInfo<Value>& args) {
571571
Local<String> str_obj;
572572
size_t str_length;
573573
enum encoding enc;
574-
THROW_AND_RETURN_IF_OOB(start <= end);
575-
THROW_AND_RETURN_IF_OOB(fill_length + start <= ts_obj_length);
576574

577-
args.GetReturnValue().Set(static_cast<double>(fill_length));
575+
// OOB Check. Throw the error in JS.
576+
if (start > end || fill_length + start > ts_obj_length)
577+
return args.GetReturnValue().Set(-2);
578578

579579
// First check if Buffer has been passed.
580580
if (Buffer::HasInstance(args[1])) {
@@ -593,29 +593,24 @@ void Fill(const FunctionCallbackInfo<Value>& args) {
593593

594594
str_obj = args[1]->ToString(env->context()).ToLocalChecked();
595595
enc = ParseEncoding(env->isolate(), args[4], UTF8);
596-
str_length =
597-
enc == UTF8 ? str_obj->Utf8Length() :
598-
enc == UCS2 ? str_obj->Length() * sizeof(uint16_t) : str_obj->Length();
599-
600-
if (str_length == 0) {
601-
args.GetReturnValue().Set(0);
602-
return;
603-
}
604596

605597
// Can't use StringBytes::Write() in all cases. For example if attempting
606598
// to write a two byte character into a one byte Buffer.
607599
if (enc == UTF8) {
600+
str_length = str_obj->Utf8Length();
608601
node::Utf8Value str(env->isolate(), args[1]);
609602
memcpy(ts_obj_data + start, *str, MIN(str_length, fill_length));
610603

611604
} else if (enc == UCS2) {
605+
str_length = str_obj->Length() * sizeof(uint16_t);
612606
node::TwoByteValue str(env->isolate(), args[1]);
613607
if (IsBigEndian())
614608
SwapBytes16(reinterpret_cast<char*>(&str[0]), str_length);
615609

616610
memcpy(ts_obj_data + start, *str, MIN(str_length, fill_length));
617611

618612
} else {
613+
str_length = str_obj->Length();
619614
// Write initial String to Buffer, then use that memory to copy remainder
620615
// of string. Correct the string length for cases like HEX where less than
621616
// the total string length is written.

0 commit comments

Comments
 (0)