From 85ab4a5f1281c4e1dd06450ac7bd3250326267fa Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 25 Jan 2016 15:00:06 -0800 Subject: buffer: add .from(), .alloc() and .allocUnsafe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several changes: * Soft-Deprecate Buffer() constructors * Add `Buffer.from()`, `Buffer.alloc()`, and `Buffer.allocUnsafe()` * Add `--zero-fill-buffers` command line option * Add byteOffset and length to `new Buffer(arrayBuffer)` constructor * buffer.fill('') previously had no effect, now zero-fills * Update the docs PR-URL: https://github.com/nodejs/node/pull/4682 Reviewed-By: Сковорода Никита Андреевич Reviewed-By: Stephen Belanger --- lib/_debugger.js | 2 +- lib/_http_client.js | 2 +- lib/_http_outgoing.js | 2 +- lib/_stream_readable.js | 4 +- lib/_stream_writable.js | 2 +- lib/_tls_legacy.js | 2 +- lib/_tls_wrap.js | 4 +- lib/assert.js | 2 +- lib/buffer.js | 138 +++++++++++++++++++++++++++++---------- lib/child_process.js | 2 +- lib/crypto.js | 2 +- lib/dgram.js | 6 +- lib/fs.js | 22 +++---- lib/internal/v8_prof_polyfill.js | 2 +- lib/net.js | 2 +- lib/querystring.js | 2 +- lib/string_decoder.js | 2 +- lib/tls.js | 4 +- lib/zlib.js | 10 +-- 19 files changed, 141 insertions(+), 71 deletions(-) (limited to 'lib') diff --git a/lib/_debugger.js b/lib/_debugger.js index 7ec91d1e00..b183f2b586 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -108,7 +108,7 @@ Protocol.prototype.execute = function(d) { var resRawByteLength = Buffer.byteLength(res.raw, 'utf8'); if (resRawByteLength - this.bodyStartByteIndex >= this.contentLength) { - var buf = new Buffer(resRawByteLength); + var buf = Buffer.allocUnsafe(resRawByteLength); buf.write(res.raw, 0, resRawByteLength, 'utf8'); res.body = buf.slice(this.bodyStartByteIndex, diff --git a/lib/_http_client.js b/lib/_http_client.js index 2b37ad1a82..25beb97ba9 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -97,7 +97,7 @@ function ClientRequest(options, cb) { if (options.auth && !this.getHeader('Authorization')) { //basic auth this.setHeader('Authorization', 'Basic ' + - new Buffer(options.auth).toString('base64')); + Buffer.from(options.auth).toString('base64')); } if (method === 'GET' || diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index ec5e078412..77bfe4ba77 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -526,7 +526,7 @@ OutgoingMessage.prototype.addTrailers = function(headers) { }; -const crlf_buf = new Buffer('\r\n'); +const crlf_buf = Buffer.from('\r\n'); OutgoingMessage.prototype.end = function(data, encoding, callback) { diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js index 1ad85dc81c..338bf2a753 100644 --- a/lib/_stream_readable.js +++ b/lib/_stream_readable.js @@ -103,7 +103,7 @@ Readable.prototype.push = function(chunk, encoding) { if (!state.objectMode && typeof chunk === 'string') { encoding = encoding || state.defaultEncoding; if (encoding !== state.encoding) { - chunk = new Buffer(chunk, encoding); + chunk = Buffer.from(chunk, encoding); encoding = ''; } } @@ -866,7 +866,7 @@ function fromList(n, state) { if (stringMode) ret = ''; else - ret = new Buffer(n); + ret = Buffer.allocUnsafe(n); var c = 0; for (var i = 0, l = list.length; i < l && c < n; i++) { diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js index 9efce66a3c..b6e621fd1e 100644 --- a/lib/_stream_writable.js +++ b/lib/_stream_writable.js @@ -252,7 +252,7 @@ function decodeChunk(state, chunk, encoding) { if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { - chunk = new Buffer(chunk, encoding); + chunk = Buffer.from(chunk, encoding); } return chunk; } diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js index 42b7f283bc..bc293f0417 100644 --- a/lib/_tls_legacy.js +++ b/lib/_tls_legacy.js @@ -23,7 +23,7 @@ function SlabBuffer() { SlabBuffer.prototype.create = function create() { this.isFull = false; - this.pool = new Buffer(tls.SLAB_BUFFER_SIZE); + this.pool = Buffer.allocUnsafe(tls.SLAB_BUFFER_SIZE); this.offset = 0; this.remaining = this.pool.length; }; diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 697fbfa3e5..0f52363e10 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -602,7 +602,7 @@ TLSSocket.prototype.setServername = function(name) { TLSSocket.prototype.setSession = function(session) { if (typeof session === 'string') - session = new Buffer(session, 'binary'); + session = Buffer.from(session, 'binary'); this._handle.setSession(session); }; @@ -845,7 +845,7 @@ Server.prototype._getServerData = function() { Server.prototype._setServerData = function(data) { - this.setTicketKeys(new Buffer(data.ticketKeys, 'hex')); + this.setTicketKeys(Buffer.from(data.ticketKeys, 'hex')); }; diff --git a/lib/assert.js b/lib/assert.js index c2b6699c66..61aba557ec 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -173,7 +173,7 @@ function _deepEqual(actual, expected, strict) { // If both values are instances of typed arrays, wrap them in // a Buffer each to increase performance } else if (ArrayBuffer.isView(actual) && ArrayBuffer.isView(expected)) { - return compare(new Buffer(actual), new Buffer(expected)) === 0; + return compare(Buffer.from(actual), Buffer.from(expected)) === 0; // 7.5 For all other Object pairs, including Array objects, equivalence is // determined by having the same number of owned properties (as verified diff --git a/lib/buffer.js b/lib/buffer.js index 766a82c2f2..01a8f303e1 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -9,6 +9,8 @@ exports.SlowBuffer = SlowBuffer; exports.INSPECT_MAX_BYTES = 50; exports.kMaxLength = binding.kMaxLength; +const kFromErrorMsg = 'First argument must be a string, Buffer, ' + + 'ArrayBuffer, Array, or array-like object.'; Buffer.poolSize = 8 * 1024; var poolSize, poolOffset, allocPool; @@ -42,34 +44,90 @@ function alignPool() { } } - -function Buffer(arg, encoding) { +/** + * The Buffer() construtor is "soft deprecated" ... that is, it is deprecated + * in the documentation and should not be used moving forward. Rather, + * developers should use one of the three new factory APIs: Buffer.from(), + * Buffer.allocUnsafe() or Buffer.alloc() based on their specific needs. There + * is no hard deprecation because of the extent to which the Buffer constructor + * is used in the ecosystem currently -- a hard deprecation would introduce too + * much breakage at this time. It's not likely that the Buffer constructors + * would ever actually be removed. + **/ +function Buffer(arg, encodingOrOffset, length) { // Common case. if (typeof arg === 'number') { - if (typeof encoding === 'string') { + if (typeof encodingOrOffset === 'string') { throw new Error( 'If encoding is specified then the first argument must be a string' ); } - // If less than zero, or NaN. - if (arg < 0 || arg !== arg) - arg = 0; - return allocate(arg); - } - - // Slightly less common case. - if (typeof arg === 'string') { - return fromString(arg, encoding); + return Buffer.allocUnsafe(arg); } - - // Unusual. - return fromObject(arg); + return Buffer.from(arg, encodingOrOffset, length); } +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function(value, encodingOrOffset, length) { + if (typeof value === 'number') + throw new TypeError('"value" argument must not be a number'); + + if (value instanceof ArrayBuffer) + return fromArrayBuffer(value, encodingOrOffset, length); + + if (typeof value === 'string') + return fromString(value, encodingOrOffset); + + return fromObject(value); +}; + Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype); Object.setPrototypeOf(Buffer, Uint8Array); +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function(size, fill, encoding) { + if (typeof size !== 'number') + throw new TypeError('"size" argument must be a number'); + if (size <= 0) + return createBuffer(size); + if (fill !== undefined) { + // Since we are filling anyway, don't zero fill initially. + flags[kNoZeroFill] = 1; + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' ? + createBuffer(size).fill(fill, encoding) : + createBuffer(size).fill(fill); + } + flags[kNoZeroFill] = 0; + return createBuffer(size); +}; + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer + * instance. If `--zero-fill-buffers` is set, will zero-fill the buffer. + **/ +Buffer.allocUnsafe = function(size) { + if (typeof size !== 'number') + throw new TypeError('"size" argument must be a number'); + if (size > 0) + flags[kNoZeroFill] = 1; + return allocate(size); +}; +// If --zero-fill-buffers command line argument is set, a zero-filled +// buffer is returned. function SlowBuffer(length) { if (+length != length) length = 0; @@ -108,6 +166,9 @@ function fromString(string, encoding) { if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8'; + if (!Buffer.isEncoding(encoding)) + throw new TypeError('"encoding" must be a valid string encoding'); + var length = byteLength(string, encoding); if (length >= (Buffer.poolSize >>> 1)) return binding.createFromString(string, encoding); @@ -129,6 +190,16 @@ function fromArrayLike(obj) { return b; } +function fromArrayBuffer(obj, byteOffset, length) { + byteOffset >>>= 0; + + if (typeof length === 'undefined') + return binding.createFromArrayBuffer(obj, byteOffset); + + length >>>= 0; + return binding.createFromArrayBuffer(obj, byteOffset, length); +} + function fromObject(obj) { if (obj instanceof Buffer) { const b = allocate(obj.length); @@ -140,26 +211,20 @@ function fromObject(obj) { return b; } - if (obj == null) { - throw new TypeError('Must start with number, buffer, array or string'); - } - - if (obj instanceof ArrayBuffer) { - return binding.createFromArrayBuffer(obj); - } - - if (obj.buffer instanceof ArrayBuffer || 'length' in obj) { - if (typeof obj.length !== 'number' || obj.length !== obj.length) { - return allocate(0); + if (obj) { + if (obj.buffer instanceof ArrayBuffer || 'length' in obj) { + if (typeof obj.length !== 'number' || obj.length !== obj.length) { + return allocate(0); + } + return fromArrayLike(obj); } - return fromArrayLike(obj); - } - if (obj.type === 'Buffer' && Array.isArray(obj.data)) { - return fromArrayLike(obj.data); + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data); + } } - throw new TypeError('Must start with number, buffer, array or string'); + throw new TypeError(kFromErrorMsg); } @@ -215,7 +280,7 @@ Buffer.concat = function(list, length) { throw new TypeError('"list" argument must be an Array of Buffers'); if (list.length === 0) - return new Buffer(0); + return Buffer.alloc(0); if (length === undefined) { length = 0; @@ -225,7 +290,7 @@ Buffer.concat = function(list, length) { length = length >>> 0; } - var buffer = new Buffer(length); + var buffer = Buffer.allocUnsafe(length); var pos = 0; for (let i = 0; i < list.length; i++) { var buf = list[i]; @@ -454,7 +519,7 @@ function slowIndexOf(buffer, val, byteOffset, encoding) { case 'ascii': case 'hex': return binding.indexOfBuffer( - buffer, Buffer(val, encoding), byteOffset, encoding); + buffer, Buffer.from(val, encoding), byteOffset, encoding); default: if (loweredCase) { @@ -518,6 +583,11 @@ Buffer.prototype.fill = function fill(val, start, end, encoding) { if (code < 256) val = code; } + if (val.length === 0) { + // Previously, if val === '', the Buffer would not fill, + // which is rather surprising. + val = 0; + } if (encoding !== undefined && typeof encoding !== 'string') { throw new TypeError('encoding must be a string'); } diff --git a/lib/child_process.js b/lib/child_process.js index e682aed5ae..d6b029b7a5 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -422,7 +422,7 @@ function spawnSync(/*file, args, options*/) { if (Buffer.isBuffer(input)) pipe.input = input; else if (typeof input === 'string') - pipe.input = new Buffer(input, options.encoding); + pipe.input = Buffer.from(input, options.encoding); else throw new TypeError(util.format( 'stdio[%d] should be Buffer or string not %s', diff --git a/lib/crypto.js b/lib/crypto.js index 89bafdfddd..3e440a61fc 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -33,7 +33,7 @@ function toBuf(str, encoding) { if (typeof str === 'string') { if (encoding === 'buffer' || !encoding) encoding = 'utf8'; - return new Buffer(str, encoding); + return Buffer.from(str, encoding); } return str; } diff --git a/lib/dgram.js b/lib/dgram.js index 4b6e7fa6e2..0c92e361c0 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -252,7 +252,7 @@ Socket.prototype.sendto = function(buffer, function sliceBuffer(buffer, offset, length) { if (typeof buffer === 'string') - buffer = new Buffer(buffer); + buffer = Buffer.from(buffer); else if (!(buffer instanceof Buffer)) throw new TypeError('First argument must be a buffer or string'); @@ -267,7 +267,7 @@ function fixBuffer(buffer) { for (var i = 0, l = buffer.length; i < l; i++) { var buf = buffer[i]; if (typeof buf === 'string') - buffer[i] = new Buffer(buf); + buffer[i] = Buffer.from(buf); else if (!(buf instanceof Buffer)) return false; } @@ -318,7 +318,7 @@ Socket.prototype.send = function(buffer, if (!Array.isArray(buffer)) { if (typeof buffer === 'string') { - buffer = [ new Buffer(buffer) ]; + buffer = [ Buffer.from(buffer) ]; } else if (!(buffer instanceof Buffer)) { throw new TypeError('First argument must be a buffer or a string'); } else { diff --git a/lib/fs.js b/lib/fs.js index 9aa4eaa6a5..487f19d305 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -321,7 +321,7 @@ ReadFileContext.prototype.read = function() { var length; if (this.size === 0) { - buffer = this.buffer = new SlowBuffer(kReadFileBufferLength); + buffer = this.buffer = SlowBuffer(kReadFileBufferLength); offset = 0; length = kReadFileBufferLength; } else { @@ -389,7 +389,7 @@ function readFileAfterStat(err, st) { return context.close(err); } - context.buffer = new SlowBuffer(size); + context.buffer = SlowBuffer(size); context.read(); } @@ -486,7 +486,7 @@ fs.readFileSync = function(path, options) { } else { threw = true; try { - buffer = new Buffer(size); + buffer = Buffer.allocUnsafe(size); threw = false; } finally { if (threw && !isUserFd) fs.closeSync(fd); @@ -504,7 +504,7 @@ fs.readFileSync = function(path, options) { } else { // the kernel lies about many files. // Go ahead and try to read some bytes. - buffer = new Buffer(8192); + buffer = Buffer.allocUnsafe(8192); bytesRead = fs.readSync(fd, buffer, 0, 8192); if (bytesRead) { buffers.push(buffer.slice(0, bytesRead)); @@ -635,7 +635,7 @@ fs.read = function(fd, buffer, offset, length, position, callback) { position = arguments[2]; length = arguments[1]; - buffer = new Buffer(length); + buffer = Buffer.allocUnsafe(length); offset = 0; callback = function(err, bytesRead) { @@ -695,7 +695,7 @@ fs.readSync = function(fd, buffer, offset, length, position) { position = arguments[2]; length = arguments[1]; - buffer = new Buffer(length); + buffer = Buffer.allocUnsafe(length); offset = 0; } @@ -1260,8 +1260,8 @@ fs.writeFile = function(path, data, options, callback_) { }); function writeFd(fd, isUserFd) { - var buffer = (data instanceof Buffer) ? data : new Buffer('' + data, - options.encoding || 'utf8'); + var buffer = (data instanceof Buffer) ? + data : Buffer.from('' + data, options.encoding || 'utf8'); var position = /a/.test(flag) ? null : 0; writeAll(fd, isUserFd, buffer, 0, buffer.length, position, callback); @@ -1284,7 +1284,7 @@ fs.writeFileSync = function(path, data, options) { var fd = isUserFd ? path : fs.openSync(path, flag, options.mode); if (!(data instanceof Buffer)) { - data = new Buffer('' + data, options.encoding || 'utf8'); + data = Buffer.from('' + data, options.encoding || 'utf8'); } var offset = 0; var length = data.length; @@ -1738,7 +1738,7 @@ fs.realpath = function realpath(p, cache, cb) { var pool; function allocNewPool(poolSize) { - pool = new Buffer(poolSize); + pool = Buffer.allocUnsafe(poolSize); pool.used = 0; } @@ -2108,7 +2108,7 @@ SyncWriteStream.prototype.write = function(data, arg1, arg2) { // Change strings to buffers. SLOW if (typeof data === 'string') { - data = new Buffer(data, encoding); + data = Buffer.from(data, encoding); } fs.writeSync(this.fd, data, 0, data.length); diff --git a/lib/internal/v8_prof_polyfill.js b/lib/internal/v8_prof_polyfill.js index 755f8f0d65..145b09e345 100644 --- a/lib/internal/v8_prof_polyfill.js +++ b/lib/internal/v8_prof_polyfill.js @@ -60,7 +60,7 @@ try { process.exit(1); } const fd = fs.openSync(logFile, 'r'); -const buf = new Buffer(4096); +const buf = Buffer.allocUnsafe(4096); const dec = new (require('string_decoder').StringDecoder)('utf-8'); var line = ''; versionCheck(); diff --git a/lib/net.js b/lib/net.js index b0436143f1..b3af78e447 100644 --- a/lib/net.js +++ b/lib/net.js @@ -724,7 +724,7 @@ function createWriteReq(req, handle, data, encoding) { return handle.writeUcs2String(req, data); default: - return handle.writeBuffer(req, new Buffer(data, encoding)); + return handle.writeBuffer(req, Buffer.from(data, encoding)); } } diff --git a/lib/querystring.js b/lib/querystring.js index b56ad77012..29fc6552c5 100644 --- a/lib/querystring.js +++ b/lib/querystring.js @@ -8,7 +8,7 @@ const Buffer = require('buffer').Buffer; // a safe fast alternative to decodeURIComponent QueryString.unescapeBuffer = function(s, decodeSpaces) { - var out = new Buffer(s.length); + var out = Buffer.allocUnsafe(s.length); var state = 0; var n, m, hexchar; diff --git a/lib/string_decoder.js b/lib/string_decoder.js index a0bfd535ad..1680392157 100644 --- a/lib/string_decoder.js +++ b/lib/string_decoder.js @@ -44,7 +44,7 @@ const StringDecoder = exports.StringDecoder = function(encoding) { // Enough space to store all bytes of a single character. UTF-8 needs 4 // bytes, but CESU-8 may require up to 6 (3 bytes per surrogate). - this.charBuffer = new Buffer(6); + this.charBuffer = Buffer.allocUnsafe(6); // Number of bytes received for the current incomplete multi-byte character. this.charReceived = 0; // Number of bytes expected for the current incomplete multi-byte character. diff --git a/lib/tls.js b/lib/tls.js index 245afb6bdc..1f7dcf39a1 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -33,7 +33,7 @@ exports.getCiphers = function() { // Convert protocols array into valid OpenSSL protocols list // ("\x06spdy/2\x08http/1.1\x08http/1.0") function convertProtocols(protocols) { - var buff = new Buffer(protocols.reduce(function(p, c) { + var buff = Buffer.allocUnsafe(protocols.reduce(function(p, c) { return p + 1 + Buffer.byteLength(c); }, 0)); @@ -67,7 +67,7 @@ exports.convertALPNProtocols = function(protocols, out) { // If it's already a Buffer - store it if (protocols instanceof Buffer) { // copy new buffer not to be modified by user - out.ALPNProtocols = new Buffer(protocols); + out.ALPNProtocols = Buffer.from(protocols); } }; diff --git a/lib/zlib.js b/lib/zlib.js index 3e6f7b1876..0028c35d79 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -230,7 +230,7 @@ function zlibBuffer(engine, buffer, callback) { function zlibBufferSync(engine, buffer) { if (typeof buffer === 'string') - buffer = new Buffer(buffer); + buffer = Buffer.from(buffer); if (!(buffer instanceof Buffer)) throw new TypeError('Not a string or buffer'); @@ -378,7 +378,7 @@ function Zlib(opts, mode) { strategy, opts.dictionary); - this._buffer = new Buffer(this._chunkSize); + this._buffer = Buffer.allocUnsafe(this._chunkSize); this._offset = 0; this._closed = false; this._level = level; @@ -426,7 +426,7 @@ Zlib.prototype.reset = function() { // This is the _flush function called by the transform class, // internally, when the last chunk has been written. Zlib.prototype._flush = function(callback) { - this._transform(new Buffer(0), '', callback); + this._transform(Buffer.alloc(0), '', callback); }; Zlib.prototype.flush = function(kind, callback) { @@ -449,7 +449,7 @@ Zlib.prototype.flush = function(kind, callback) { } } else { this._flushFlag = kind; - this.write(new Buffer(0), '', callback); + this.write(Buffer.alloc(0), '', callback); } }; @@ -580,7 +580,7 @@ Zlib.prototype._processChunk = function(chunk, flushFlag, cb) { if (availOutAfter === 0 || self._offset >= self._chunkSize) { availOutBefore = self._chunkSize; self._offset = 0; - self._buffer = new Buffer(self._chunkSize); + self._buffer = Buffer.allocUnsafe(self._chunkSize); } if (availOutAfter === 0) { -- cgit v1.2.3