Skip to content

Commit 6275249

Browse files
committed
buffer: add Buffer.allocUnsafeSlow(size)
Aligns the functionality of SlowBuffer with the new Buffer constructor API. Next step is to docs-only deprecate SlowBuffer. Replace the internal uses of SlowBuffer with `Buffer.allocUnsafeSlow(size)` PR-URL: #5833 Reviewed-By: Сковорода Никита Андреевич <[email protected]> Reviewed-By: Trevor Norris <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]>
1 parent b488b19 commit 6275249

File tree

5 files changed

+116
-40
lines changed

5 files changed

+116
-40
lines changed

benchmark/buffers/buffer-creation.js

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const bench = common.createBenchmark(main, {
88
'fast-alloc',
99
'fast-alloc-fill',
1010
'fast-allocUnsafe',
11+
'slow-allocUnsafe',
1112
'slow',
1213
'buffer()'],
1314
len: [10, 1024, 2048, 4096, 8192],
@@ -39,6 +40,13 @@ function main(conf) {
3940
}
4041
bench.end(n);
4142
break;
43+
case 'slow-allocUnsafe':
44+
bench.start();
45+
for (let i = 0; i < n * 1024; i++) {
46+
Buffer.allocUnsafeSlow(len);
47+
}
48+
bench.end(n);
49+
break;
4250
case 'slow':
4351
bench.start();
4452
for (let i = 0; i < n * 1024; i++) {

doc/api/buffer.markdown

+72-22
Original file line numberDiff line numberDiff line change
@@ -87,42 +87,45 @@ to one of these new APIs.*
8787
containing a *copy* of the provided string.
8888
* [`Buffer.alloc(size[, fill[, encoding]])`][buffer_alloc] returns a "filled"
8989
`Buffer` instance of the specified size. This method can be significantly
90-
slower than [`Buffer.allocUnsafe(size)`][buffer_allocunsafe] but ensures that
91-
newly created `Buffer` instances never contain old and potentially sensitive
92-
data.
93-
* [`Buffer.allocUnsafe(size)`][buffer_allocunsafe] returns a new `Buffer` of
94-
the specified `size` whose content *must* be initialized using either
95-
[`buf.fill(0)`][] or written to completely.
90+
slower than [`Buffer.allocUnsafe(size)`][buffer_allocunsafe] but ensures
91+
that newly created `Buffer` instances never contain old and potentially
92+
sensitive data.
93+
* [`Buffer.allocUnsafe(size)`][buffer_allocunsafe] and
94+
[`Buffer.allocUnsafeSlow(size)`][buffer_allocunsafeslow] each return a
95+
new `Buffer` of the specified `size` whose content *must* be initialized
96+
using either [`buf.fill(0)`][] or written to completely.
9697

9798
`Buffer` instances returned by `Buffer.allocUnsafe(size)` *may* be allocated
98-
off a shared internal memory pool if the `size` is less than or equal to half
99-
`Buffer.poolSize`.
99+
off a shared internal memory pool if `size` is less than or equal to half
100+
`Buffer.poolSize`. Instances returned by `Buffer.allocUnsafeSlow(size)` *never*
101+
use the shared internal memory pool.
100102

101103
### The `--zero-fill-buffers` command line option
102104

103105
Node.js can be started using the `--zero-fill-buffers` command line option to
104106
force all newly allocated `Buffer` and `SlowBuffer` instances created using
105-
either `new Buffer(size)`, `Buffer.allocUnsafe(size)`, or
106-
`new SlowBuffer(size)` to be *automatically zero-filled* upon creation. Use of
107-
this flag *changes the default behavior* of these methods and *can have a
108-
significant impact* on performance. Use of the `--zero-fill-buffers` option is
109-
recommended only when absolutely necessary to enforce that newly allocated
110-
`Buffer` instances cannot contain potentially sensitive data.
107+
either `new Buffer(size)`, `Buffer.allocUnsafe(size)`,
108+
`Buffer.allocUnsafeSlow(size)` or `new SlowBuffer(size)` to be *automatically
109+
zero-filled* upon creation. Use of this flag *changes the default behavior* of
110+
these methods and *can have a significant impact* on performance. Use of the
111+
`--zero-fill-buffers` option is recommended only when absolutely necessary to
112+
enforce that newly allocated `Buffer` instances cannot contain potentially
113+
sensitive data.
111114

112115
```
113116
$ node --zero-fill-buffers
114117
> Buffer.allocUnsafe(5);
115118
<Buffer 00 00 00 00 00>
116119
```
117120

118-
### What makes `Buffer.allocUnsafe(size)` "unsafe"?
121+
### What makes `Buffer.allocUnsafe(size)` and `Buffer.allocUnsafeSlow(size)` "unsafe"?
119122

120-
When calling `Buffer.allocUnsafe()`, the segment of allocated memory is
121-
*uninitialized* (it is not zeroed-out). While this design makes the allocation
122-
of memory quite fast, the allocated segment of memory might contain old data
123-
that is potentially sensitive. Using a `Buffer` created by
124-
`Buffer.allocUnsafe(size)` without *completely* overwriting the memory can
125-
allow this old data to be leaked when the `Buffer` memory is read.
123+
When calling `Buffer.allocUnsafe()` (and `Buffer.allocUnsafeSlow()`), the
124+
segment of allocated memory is *uninitialized* (it is not zeroed-out). While
125+
this design makes the allocation of memory quite fast, the allocated segment of
126+
memory might contain old data that is potentially sensitive. Using a `Buffer`
127+
created by `Buffer.allocUnsafe()` without *completely* overwriting the memory
128+
can allow this old data to be leaked when the `Buffer` memory is read.
126129

127130
While there are clear performance advantages to using `Buffer.allocUnsafe()`,
128131
extra care *must* be taken in order to avoid introducing security
@@ -466,6 +469,52 @@ Buffer pool if `size` is less than or equal to half `Buffer.poolSize`. The
466469
difference is subtle but can be important when an application requires the
467470
additional performance that `Buffer.allocUnsafe(size)` provides.
468471

472+
### Class Method: Buffer.allocUnsafeSlow(size)
473+
474+
* `size` {Number}
475+
476+
Allocates a new *non-zero-filled* and non-pooled `Buffer` of `size` bytes. The
477+
`size` must be less than or equal to the value of
478+
`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is
479+
`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. If a `size` less than 0
480+
is specified, a zero-length `Buffer` will be created.
481+
482+
The underlying memory for `Buffer` instances created in this way is *not
483+
initialized*. The contents of the newly created `Buffer` are unknown and
484+
*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such
485+
`Buffer` instances to zeroes.
486+
487+
When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances,
488+
allocations under 4KB are, by default, sliced from a single pre-allocated
489+
`Buffer`. This allows applications to avoid the garbage collection overhead of
490+
creating many individually allocated Buffers. This approach improves both
491+
performance and memory usage by eliminating the need to track and cleanup as
492+
many `Persistent` objects.
493+
494+
However, in the case where a developer may need to retain a small chunk of
495+
memory from a pool for an indeterminate amount of time, it may be appropriate
496+
to create an un-pooled Buffer instance using `Buffer.allocUnsafeSlow()` then
497+
copy out the relevant bits.
498+
499+
```js
500+
// need to keep around a few small chunks of memory
501+
const store = [];
502+
503+
socket.on('readable', () => {
504+
const data = socket.read();
505+
// allocate for retained data
506+
const sb = Buffer.allocUnsafeSlow(10);
507+
// copy the data into the new allocation
508+
data.copy(sb, 0, 0, 10);
509+
store.push(sb);
510+
});
511+
```
512+
513+
Use of `Buffer.allocUnsafeSlow()` should be used only as a last resort *after*
514+
a developer has observed undue memory retention in their applications.
515+
516+
A `TypeError` will be thrown if `size` is not a number.
517+
469518
### Class Method: Buffer.byteLength(string[, encoding])
470519

471520
* `string` {String | Buffer | TypedArray | DataView | ArrayBuffer}
@@ -1805,7 +1854,8 @@ console.log(buf);
18051854
[buffer_from_buffer]: #buffer_class_method_buffer_from_buffer
18061855
[buffer_from_arraybuf]: #buffer_class_method_buffer_from_arraybuffer
18071856
[buffer_from_string]: #buffer_class_method_buffer_from_str_encoding
1808-
[buffer_allocunsafe]: #buffer_class_method_buffer_allocraw_size
1857+
[buffer_allocunsafe]: #buffer_class_method_buffer_allocunsafe_size
1858+
[buffer_allocunsafeslow]: #buffer_class_method_buffer_allocunsafeslow_size
18091859
[buffer_alloc]: #buffer_class_method_buffer_alloc_size_fill_encoding
18101860
[`TypedArray.from()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/from
18111861
[`DataView`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView

lib/buffer.js

+25-4
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,23 @@ Buffer.from = function(value, encodingOrOffset, length) {
133133
Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype);
134134
Object.setPrototypeOf(Buffer, Uint8Array);
135135

136+
function assertSize(size) {
137+
if (typeof size !== 'number') {
138+
const err = new TypeError('"size" argument must be a number');
139+
// The following hides the 'assertSize' method from the
140+
// callstack. This is done simply to hide the internal
141+
// details of the implementation from bleeding out to users.
142+
Error.captureStackTrace(err, assertSize);
143+
throw err;
144+
}
145+
}
146+
136147
/**
137148
* Creates a new filled Buffer instance.
138149
* alloc(size[, fill[, encoding]])
139150
**/
140151
Buffer.alloc = function(size, fill, encoding) {
141-
if (typeof size !== 'number')
142-
throw new TypeError('"size" argument must be a number');
152+
assertSize(size);
143153
if (size <= 0)
144154
return createBuffer(size);
145155
if (fill !== undefined) {
@@ -161,11 +171,22 @@ Buffer.alloc = function(size, fill, encoding) {
161171
* instance. If `--zero-fill-buffers` is set, will zero-fill the buffer.
162172
**/
163173
Buffer.allocUnsafe = function(size) {
164-
if (typeof size !== 'number')
165-
throw new TypeError('"size" argument must be a number');
174+
assertSize(size);
166175
return allocate(size);
167176
};
168177

178+
/**
179+
* Equivalent to SlowBuffer(num), by default creates a non-zero-filled
180+
* Buffer instance that is not allocated off the pre-initialized pool.
181+
* If `--zero-fill-buffers` is set, will zero-fill the buffer.
182+
**/
183+
Buffer.allocUnsafeSlow = function(size) {
184+
assertSize(size);
185+
if (size > 0)
186+
flags[kNoZeroFill] = 1;
187+
return createBuffer(size);
188+
};
189+
169190
// If --zero-fill-buffers command line argument is set, a zero-filled
170191
// buffer is returned.
171192
function SlowBuffer(length) {

lib/fs.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
'use strict';
55

6-
const SlowBuffer = require('buffer').SlowBuffer;
76
const util = require('util');
87
const pathModule = require('path');
98

@@ -321,7 +320,7 @@ ReadFileContext.prototype.read = function() {
321320
var length;
322321

323322
if (this.size === 0) {
324-
buffer = this.buffer = SlowBuffer(kReadFileBufferLength);
323+
buffer = this.buffer = Buffer.allocUnsafeSlow(kReadFileBufferLength);
325324
offset = 0;
326325
length = kReadFileBufferLength;
327326
} else {
@@ -389,7 +388,7 @@ function readFileAfterStat(err, st) {
389388
return context.close(err);
390389
}
391390

392-
context.buffer = SlowBuffer(size);
391+
context.buffer = Buffer.allocUnsafeSlow(size);
393392
context.read();
394393
}
395394

test/parallel/test-buffer-alloc.js

+9-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ var common = require('../common');
33
var assert = require('assert');
44

55
var Buffer = require('buffer').Buffer;
6-
var SlowBuffer = require('buffer').SlowBuffer;
76

87
// counter to ensure unique value is always copied
98
var cntr = 0;
@@ -428,7 +427,7 @@ for (let i = 0; i < Buffer.byteLength(utf8String); i++) {
428427

429428
{
430429
// also from a non-pooled instance
431-
const b = new SlowBuffer(5);
430+
const b = Buffer.allocUnsafeSlow(5);
432431
const c = b.slice(0, 4);
433432
const d = c.slice(0, 2);
434433
assert.equal(c.parent, d.parent);
@@ -1305,7 +1304,7 @@ assert.throws(function() {
13051304

13061305
// try to slice a zero length Buffer
13071306
// see https://github.com/joyent/node/issues/5881
1308-
SlowBuffer(0).slice(0, 1);
1307+
Buffer.alloc(0).slice(0, 1);
13091308
})();
13101309

13111310
// Regression test for #5482: should throw but not assert in C++ land.
@@ -1336,7 +1335,7 @@ assert.throws(function() {
13361335
}, RangeError);
13371336

13381337
assert.throws(function() {
1339-
SlowBuffer((-1 >>> 0) + 1);
1338+
Buffer.allocUnsafeSlow((-1 >>> 0) + 1);
13401339
}, RangeError);
13411340

13421341
if (common.hasCrypto) {
@@ -1435,14 +1434,13 @@ assert.throws(function() {
14351434
}, regErrorMsg);
14361435

14371436

1438-
// Test prototype getters don't throw
1439-
assert.equal(Buffer.prototype.parent, undefined);
1440-
assert.equal(Buffer.prototype.offset, undefined);
1441-
assert.equal(SlowBuffer.prototype.parent, undefined);
1442-
assert.equal(SlowBuffer.prototype.offset, undefined);
1443-
1444-
14451437
// Test that ParseArrayIndex handles full uint32
14461438
assert.throws(function() {
14471439
Buffer.from(new ArrayBuffer(0), -1 >>> 0);
14481440
}, /RangeError: 'offset' is out of bounds/);
1441+
1442+
// Unpooled buffer (replaces SlowBuffer)
1443+
const ubuf = Buffer.allocUnsafeSlow(10);
1444+
assert(ubuf);
1445+
assert(ubuf.buffer);
1446+
assert.equal(ubuf.buffer.byteLength, 10);

0 commit comments

Comments
 (0)