Skip to content

Commit 0ca0827

Browse files
committed
buffer: backport allocUnsafeSlow
This backports the new `Buffer.allocUnsafeSlow()` API for v5. This backport includes the new API, test cases, and docs additions. Already present API and testcases were not changed. PR-URL: #7169 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 27785ae commit 0ca0827

File tree

3 files changed

+87
-17
lines changed

3 files changed

+87
-17
lines changed

doc/api/buffer.md

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,25 +87,27 @@ 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

101-
### What makes `Buffer.allocUnsafe(size)` "unsafe"?
103+
### What makes `Buffer.allocUnsafe(size)` and `Buffer.allocUnsafeSlow(size)` "unsafe"?
102104

103-
When calling `Buffer.allocUnsafe()`, the segment of allocated memory is
104-
*uninitialized* (it is not zeroed-out). While this design makes the allocation
105-
of memory quite fast, the allocated segment of memory might contain old data
106-
that is potentially sensitive. Using a `Buffer` created by
107-
`Buffer.allocUnsafe(size)` without *completely* overwriting the memory can
108-
allow this old data to be leaked when the `Buffer` memory is read.
105+
When calling `Buffer.allocUnsafe()` (and `Buffer.allocUnsafeSlow()`), the
106+
segment of allocated memory is *uninitialized* (it is not zeroed-out). While
107+
this design makes the allocation of memory quite fast, the allocated segment of
108+
memory might contain old data that is potentially sensitive. Using a `Buffer`
109+
created by `Buffer.allocUnsafe()` without *completely* overwriting the memory
110+
can allow this old data to be leaked when the `Buffer` memory is read.
109111

110112
While there are clear performance advantages to using `Buffer.allocUnsafe()`,
111113
extra care *must* be taken in order to avoid introducing security
@@ -240,7 +242,8 @@ Additionally, the [`buf.values()`][], [`buf.keys()`][], and
240242

241243
Node.js can be started using the `--zero-fill-buffers` command line option to
242244
force all newly allocated `Buffer` and `SlowBuffer` instances created using
243-
either `new Buffer(size)` and `new SlowBuffer(size)` to be *automatically
245+
either `new Buffer(size)`, `Buffer.allocUnsafe(size)`,
246+
`Buffer.allocUnsafeSlow(size)` or `new SlowBuffer(size)` to be *automatically
244247
zero-filled* upon creation. Use of this flag *changes the default behavior* of
245248
these methods and *can have a significant impact* on performance. Use of the
246249
`--zero-fill-buffers` option is recommended only when absolutely necessary to
@@ -449,6 +452,52 @@ Buffer pool if `size` is less than or equal to half `Buffer.poolSize`. The
449452
difference is subtle but can be important when an application requires the
450453
additional performance that `Buffer.allocUnsafe(size)` provides.
451454

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

454503
* `string` {String | Buffer | TypedArray | DataView | ArrayBuffer}
@@ -1787,7 +1836,8 @@ console.log(buf);
17871836
[buffer_from_buffer]: #buffer_class_method_buffer_from_buffer
17881837
[buffer_from_arraybuf]: #buffer_class_method_buffer_from_arraybuffer_byteoffset_length
17891838
[buffer_from_string]: #buffer_class_method_buffer_from_str_encoding
1790-
[buffer_allocunsafe]: #buffer_class_method_buffer_allocraw_size
1839+
[buffer_allocunsafe]: #buffer_class_method_buffer_allocunsafe_size
1840+
[buffer_allocunsafeslow]: #buffer_class_method_buffer_allocunsafeslow_size
17911841
[buffer_alloc]: #buffer_class_method_buffer_alloc_size_fill_encoding
17921842
[`TypedArray.from()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/from
17931843
[`DataView`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView

lib/buffer.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,20 @@ Buffer.allocUnsafe = function(size) {
152152
return allocate(size);
153153
};
154154

155+
/**
156+
* Equivalent to SlowBuffer(num), by default creates a non-zero-filled
157+
* Buffer instance that is not allocated off the pre-initialized pool.
158+
* If `--zero-fill-buffers` is set, will zero-fill the buffer.
159+
**/
160+
Buffer.allocUnsafeSlow = function(size) {
161+
if (typeof size !== 'number')
162+
throw new TypeError('"size" argument must be a number');
163+
return createBuffer(size, true);
164+
};
165+
166+
// If --zero-fill-buffers command line argument is set, a zero-filled
167+
// buffer is returned.
168+
155169
function SlowBuffer(length) {
156170
if (+length != length)
157171
length = 0;

test/parallel/test-buffer-alloc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,3 +1435,9 @@ assert.equal(SlowBuffer.prototype.offset, undefined);
14351435
assert.throws(function() {
14361436
Buffer.from(new ArrayBuffer(0), -1 >>> 0);
14371437
}, /RangeError: 'offset' is out of bounds/);
1438+
1439+
// Unpooled buffer (replaces SlowBuffer)
1440+
const ubuf = Buffer.allocUnsafeSlow(10);
1441+
assert(ubuf);
1442+
assert(ubuf.buffer);
1443+
assert.equal(ubuf.buffer.byteLength, 10);

0 commit comments

Comments
 (0)