From 3a2f273bd73bc94a6e93f342d629106a9f022f2d Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 17 Apr 2013 16:26:15 -0700 Subject: [PATCH] buffer: use smalloc as backing data store Memory allocations are now done through smalloc. The Buffer cc class has been removed completely, but for backwards compatibility have left the namespace as Buffer. The .parent attribute is only set if the Buffer is a slice of an allocation. Which is then set to the alloc object (not a Buffer). The .offset attribute is now a ReadOnly set to 0, for backwards compatibility. I'd like to remove it in the future (pre v1.0). A few alterations have been made to how arguments are either coerced or thrown. All primitives will now be coerced to their respective values, and (most) all out of range index requests will throw. The indexes that are coerced were left for backwards compatibility. For example: Buffer slice operates more like Array slice, and coerces instead of throwing out of range indexes. This may change in the future. The reason for wanting to throw for out of range indexes is because giving js access to raw memory has high potential risk. To mitigate that it's easier to make sure the developer is always quickly alerted to the fact that their code is attempting to access beyond memory bounds. Because SlowBuffer will be deprecated, and simply returns a new Buffer instance, all tests on SlowBuffer have been removed. Heapdumps will now show usage under "smalloc" instead of "Buffer". ParseArrayIndex was added to node_internals to support proper uint argument checking/coercion for external array data indexes. SlabAllocator had to be updated since handle_ no longer exists. --- doc/api/buffer.markdown | 133 ++-- lib/buffer.js | 654 ++++++------------- src/node.js | 1 - src/node_buffer.cc | 723 +++++++++------------ src/node_buffer.h | 165 +---- src/node_crypto.cc | 28 +- src/node_internals.h | 24 + src/slab_allocator.cc | 5 +- src/string_bytes.cc | 2 +- src/tls_wrap.cc | 11 +- test/simple/test-buffer-regress-GH-2659.js | 30 - test/simple/test-buffer.js | 181 ++---- test/simple/test-readdouble.js | 2 - test/simple/test-readfloat.js | 2 - test/simple/test-readint.js | 4 - test/simple/test-readuint.js | 4 - test/simple/test-writedouble.js | 2 - test/simple/test-writefloat.js | 2 - test/simple/test-writeint.js | 4 - test/simple/test-writeuint.js | 4 - 20 files changed, 695 insertions(+), 1286 deletions(-) delete mode 100644 test/simple/test-buffer-regress-GH-2659.js diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index f0c1c1c16ba1de..335ac4dcc31f69 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -80,6 +80,69 @@ Allocates a new buffer containing the given `str`. Returns true if the `encoding` is a valid encoding argument, or false otherwise. +### Class Method: Buffer.isBuffer(obj) + +* `obj` Object +* Return: Boolean + +Tests if `obj` is a `Buffer`. + +### Class Method: Buffer.byteLength(string, [encoding]) + +* `string` String +* `encoding` String, Optional, Default: 'utf8' +* Return: Number + +Gives the actual byte length of a string. `encoding` defaults to `'utf8'`. +This is not the same as `String.prototype.length` since that returns the +number of *characters* in a string. + +Example: + + str = '\u00bd + \u00bc = \u00be'; + + console.log(str + ": " + str.length + " characters, " + + Buffer.byteLength(str, 'utf8') + " bytes"); + + // ½ + ¼ = ¾: 9 characters, 12 bytes + +### Class Method: Buffer.concat(list, [totalLength]) + +* `list` {Array} List of Buffer objects to concat +* `totalLength` {Number} Total length of the buffers when concatenated + +Returns a buffer which is the result of concatenating all the buffers in +the list together. + +If the list has no items, or if the totalLength is 0, then it returns a +zero-length buffer. + +If the list has exactly one item, then the first item of the list is +returned. + +If the list has more than one item, then a new Buffer is created. + +If totalLength is not provided, it is read from the buffers in the list. +However, this adds an additional loop to the function, so it is faster +to provide the length explicitly. + +### buf.length + +* Number + +The size of the buffer in bytes. Note that this is not necessarily the size +of the contents. `length` refers to the amount of memory allocated for the +buffer object. It does not change when the contents of the buffer are changed. + + buf = new Buffer(1234); + + console.log(buf.length); + buf.write("some string", 0, "ascii"); + console.log(buf.length); + + // 1234 + // 1234 + ### buf.write(string, [offset], [length], [encoding]) * `string` String - data to be written to buffer @@ -155,69 +218,6 @@ Example: copy an ASCII string into a buffer, one byte at a time: // node.js -### Class Method: Buffer.isBuffer(obj) - -* `obj` Object -* Return: Boolean - -Tests if `obj` is a `Buffer`. - -### Class Method: Buffer.byteLength(string, [encoding]) - -* `string` String -* `encoding` String, Optional, Default: 'utf8' -* Return: Number - -Gives the actual byte length of a string. `encoding` defaults to `'utf8'`. -This is not the same as `String.prototype.length` since that returns the -number of *characters* in a string. - -Example: - - str = '\u00bd + \u00bc = \u00be'; - - console.log(str + ": " + str.length + " characters, " + - Buffer.byteLength(str, 'utf8') + " bytes"); - - // ½ + ¼ = ¾: 9 characters, 12 bytes - -### Class Method: Buffer.concat(list, [totalLength]) - -* `list` {Array} List of Buffer objects to concat -* `totalLength` {Number} Total length of the buffers when concatenated - -Returns a buffer which is the result of concatenating all the buffers in -the list together. - -If the list has no items, or if the totalLength is 0, then it returns a -zero-length buffer. - -If the list has exactly one item, then the first item of the list is -returned. - -If the list has more than one item, then a new Buffer is created. - -If totalLength is not provided, it is read from the buffers in the list. -However, this adds an additional loop to the function, so it is faster -to provide the length explicitly. - -### buf.length - -* Number - -The size of the buffer in bytes. Note that this is not necessarily the size -of the contents. `length` refers to the amount of memory allocated for the -buffer object. It does not change when the contents of the buffer are changed. - - buf = new Buffer(1234); - - console.log(buf.length); - buf.write("some string", 0, "ascii"); - console.log(buf.length); - - // 1234 - // 1234 - ### buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd]) * `targetBuffer` Buffer object - Buffer to copy into @@ -694,11 +694,8 @@ Note that this is a property on the buffer module returned by ## Class: SlowBuffer -This class is primarily for internal use. JavaScript programs should -use Buffer instead of using SlowBuffer. +Deprecated. SlowBuffer now returns an instance of Buffer. In order to avoid the overhead of allocating many C++ Buffer objects for small blocks of memory in the lifetime of a server, Node allocates memory -in 8Kb (8192 byte) chunks. If a buffer is smaller than this size, then it -will be backed by a parent SlowBuffer object. If it is larger than this, -then Node will allocate a SlowBuffer slab for it directly. +in 8Kb (8192 byte) chunks. This is now handled by Smalloc. diff --git a/lib/buffer.js b/lib/buffer.js index 8d93384d429274..a5abf368af6d03 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -19,299 +19,202 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -var SlowBuffer = process.binding('buffer').SlowBuffer; +var smalloc = process.binding('smalloc'); +var buffer = process.binding('buffer'); var assert = require('assert'); +var alloc = smalloc.alloc; +var sliceOnto = smalloc.sliceOnto; +var kMaxLength = smalloc.kMaxLength; +exports.Buffer = Buffer; +// backwards compatibility (DEPRECATE) +exports.SlowBuffer = Buffer; exports.INSPECT_MAX_BYTES = 50; -// Make SlowBuffer inherit from Buffer. -// This is an exception to the rule that __proto__ is not allowed in core. -SlowBuffer.prototype.__proto__ = Buffer.prototype; +// add methods to Buffer prototype +buffer.setupBufferJS(Buffer); +function Buffer(subject, encoding) { + if (!(this instanceof Buffer)) + return new Buffer(subject, encoding); -function clamp(index, len, defaultValue) { - if (typeof index !== 'number') return defaultValue; - index = ~~index; // Coerce to integer. - if (index >= len) return len; - if (index >= 0) return index; - index += len; - if (index >= 0) return index; - return 0; -} + var type = typeof subject; + switch (type) { + case 'number': + this.length = subject > 0 ? Math.floor(subject) : 0; + break; -function toHex(n) { - if (n < 16) return '0' + n.toString(16); - return n.toString(16); -} + case 'string': + this.length = Buffer.byteLength(subject, encoding = encoding || 'utf8'); + break; + case 'object': + this.length = +subject.length > 0 ? Math.floor(+subject.length) : 0; + break; -SlowBuffer.prototype.toString = function(encoding, start, end) { - encoding = String(encoding || 'utf8').toLowerCase(); - start = +start || 0; - if (typeof end !== 'number') end = this.length; + // undef first arg returns unallocated buffer, also assumes length passed. + // this is a stop-gap for now while look for better architecture. + // for internal use only. + case 'undefined': + this.length = encoding; + return; - // Fastpath empty strings - if (+end == start) { - return ''; + default: + throw new TypeError('must start with number, buffer, array or string'); } - switch (encoding) { - case 'hex': - return this.hexSlice(start, end); - - case 'utf8': - case 'utf-8': - return this.utf8Slice(start, end); + if (this.length > kMaxLength) + throw new RangeError('length > kMaxLength'); - case 'ascii': - return this.asciiSlice(start, end); + alloc(this, this.length); - case 'binary': - return this.binarySlice(start, end); + if (type !== 'number') { + if (type === 'string') { + // FIXME: the number of bytes hasn't changed, so why change the length? + this.length = this.write(subject, 0, encoding); + } else { + if (subject instanceof Buffer) + this.copy(subject, 0, 0, this.length); + else if (typeof subject.length === 'number' || Array.isArray(subject)) + for (var i = 0; i < this.length; i++) + this[i] = subject[i]; + } + } +} - case 'base64': - return this.base64Slice(start, end); - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return this.ucs2Slice(start, end); +// Static methods - default: - throw new TypeError('Unknown encoding: ' + encoding); - } +Buffer.isBuffer = function isBuffer(b) { + return b instanceof Buffer; }; -SlowBuffer.prototype.write = function(string, offset, length, encoding) { - // Support both (string, offset, length, encoding) - // and the legacy (string, encoding, offset, length) - if (isFinite(offset)) { - if (!isFinite(length)) { - encoding = length; - length = undefined; - } - } else { // legacy - var swap = encoding; - encoding = offset; - offset = length; - length = swap; - } - - offset = +offset || 0; - var remaining = this.length - offset; - if (!length) { - length = remaining; - } else { - length = +length; - if (length > remaining) { - length = remaining; - } - } - encoding = String(encoding || 'utf8').toLowerCase(); - - switch (encoding) { +Buffer.isEncoding = function(encoding) { + switch ((encoding + '').toLowerCase()) { case 'hex': - return this.hexWrite(string, offset, length); - case 'utf8': case 'utf-8': - return this.utf8Write(string, offset, length); - case 'ascii': - return this.asciiWrite(string, offset, length); - case 'binary': - return this.binaryWrite(string, offset, length); - case 'base64': - return this.base64Write(string, offset, length); - case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': - return this.ucs2Write(string, offset, length); + case 'raw': + return true; default: - throw new TypeError('Unknown encoding: ' + encoding); + return false; } }; -// slice(start, end) -SlowBuffer.prototype.slice = function(start, end) { - var len = this.length; - start = clamp(start, len, 0); - end = clamp(end, len, len); - return new Buffer(this, end - start, start); -}; - - -var zeroBuffer = new SlowBuffer(0); - -// Buffer -function Buffer(subject, encoding, offset) { - if (!(this instanceof Buffer)) { - return new Buffer(subject, encoding, offset); - } - - var type; - - // Are we slicing? - if (typeof offset === 'number') { - if (!Buffer.isBuffer(subject)) { - throw new TypeError('First argument must be a Buffer when slicing'); - } +Buffer.concat = function(list, length) { + if (!Array.isArray(list)) + throw new TypeError('Usage: Buffer.concat(list[, length])'); - this.length = +encoding > 0 ? Math.ceil(encoding) : 0; - this.parent = subject.parent ? subject.parent : subject; - this.offset = offset; + if (typeof length === 'undefined') { + length = 0; + for (var i = 0; i < list.length; i++) + length += list[i].length; } else { - // Find the length - switch (type = typeof subject) { - case 'number': - this.length = +subject > 0 ? Math.ceil(subject) : 0; - break; - - case 'string': - this.length = Buffer.byteLength(subject, encoding); - break; - - case 'object': // Assume object is array-ish - this.length = +subject.length > 0 ? Math.ceil(subject.length) : 0; - break; - - default: - throw new TypeError('First argument needs to be a number, ' + - 'array or string.'); - } + length = ~~length; + } - if (this.length > Buffer.poolSize) { - // Big buffer, just alloc one. - this.parent = new SlowBuffer(this.length); - this.offset = 0; + if (length < 0) length = 0; - } else if (this.length > 0) { - // Small buffer. - if (!pool || pool.length - pool.used < this.length) allocPool(); - this.parent = pool; - this.offset = pool.used; - pool.used += this.length; - if (pool.used & 7) pool.used = (pool.used + 8) & ~7; + if (list.length === 0) + return new Buffer(0); + else if (list.length === 1) + return list[0]; - } else { - // Zero-length buffer - this.parent = zeroBuffer; - this.offset = 0; - } + if (length < 0) + throw new RangeError('length is not a positive number'); - // optimize by branching logic for new allocations - if (typeof subject !== 'number') { - if (type === 'string') { - // We are a string - this.length = this.write(subject, 0, encoding); - // if subject is buffer then use built-in copy method - } else if (Buffer.isBuffer(subject)) { - if (subject.parent) - subject.parent.copy(this.parent, - this.offset, - subject.offset, - this.length + subject.offset); - else - subject.copy(this.parent, this.offset, 0, this.length); - } else if (isArrayIsh(subject)) { - for (var i = 0; i < this.length; i++) - this.parent[i + this.offset] = subject[i]; - } - } + var buffer = new Buffer(length); + var pos = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + buf.copy(buffer, pos); + pos += buf.length; } - SlowBuffer.makeFastBuffer(this.parent, this, this.offset, this.length); -} + return buffer; +}; -function isArrayIsh(subject) { - return Array.isArray(subject) || - subject && typeof subject === 'object' && - typeof subject.length === 'number'; -} -exports.SlowBuffer = SlowBuffer; -exports.Buffer = Buffer; +// toString(encoding, start=0, end=buffer.length) +Buffer.prototype.toString = function(encoding, start, end) { + encoding = !!encoding ? (encoding + '').toLowerCase() : 'utf8'; + start = ~~start; + end = typeof end === 'undefined' ? this.length : ~~end; -Buffer.isEncoding = function(encoding) { - switch (encoding && encoding.toLowerCase()) { + if (start < 0) start = 0; + if (end > this.length) end = this.length; + if (end <= start) return ''; + + switch (encoding) { case 'hex': + return this.hexSlice(start, end); + case 'utf8': case 'utf-8': + return this.utf8Slice(start, end); + case 'ascii': + return this.asciiSlice(start, end); + case 'binary': + return this.binarySlice(start, end); + case 'base64': + return this.base64Slice(start, end); + case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': - case 'raw': - return true; + return this.ucs2Slice(start, end); default: - return false; + throw new TypeError('Unknown encoding: ' + encoding); } }; - -Buffer.poolSize = 8 * 1024; -var pool; - -function allocPool() { - pool = new SlowBuffer(Buffer.poolSize); - pool.used = 0; -} - - -// Static methods -Buffer.isBuffer = function isBuffer(b) { - return b instanceof Buffer; -}; - - // Inspect Buffer.prototype.inspect = function inspect() { - var out = [], - len = this.length, - name = this.constructor.name; - - for (var i = 0; i < len; i++) { - out[i] = toHex(this[i]); - if (i == exports.INSPECT_MAX_BYTES) { - out[i + 1] = '...'; - break; - } - } - - return '<' + name + ' ' + out.join(' ') + '>'; + var str = ''; + if (this.length > 0) + str = this.hexSlice(0, this.length).match(/.{2}/g).join(' '); + return '<' + this.constructor.name + ' ' + str + '>'; }; +// TODO(trevnorris): DEPRECATE Buffer.prototype.get = function get(offset) { + offset = ~~offset; if (offset < 0 || offset >= this.length) - throw new RangeError('offset is out of bounds'); - return this.parent[this.offset + offset]; + throw new RangeError('index out of range'); + return this[offset]; }; +// TODO(trevnorris): DEPRECATE Buffer.prototype.set = function set(offset, v) { + offset = ~~offset; if (offset < 0 || offset >= this.length) - throw new RangeError('offset is out of bounds'); - return this.parent[this.offset + offset] = v; + throw new RangeError('index out of range'); + return this[offset] = v; }; -// write(string, offset = 0, length = buffer.length-offset, encoding = 'utf8') +// TODO(trevnorris): fix these checks to follow new standard +// write(string, offset = 0, length = buffer.length, encoding = 'utf8') Buffer.prototype.write = function(string, offset, length, encoding) { // Support both (string, offset, length, encoding) // and the legacy (string, encoding, offset, length) @@ -320,6 +223,7 @@ Buffer.prototype.write = function(string, offset, length, encoding) { encoding = length; length = undefined; } + // TODO(trevnorris): DEPRECATE } else { // legacy var swap = encoding; encoding = offset; @@ -337,7 +241,11 @@ Buffer.prototype.write = function(string, offset, length, encoding) { length = remaining; } } - encoding = String(encoding || 'utf8').toLowerCase(); + + if (typeof encoding === 'undefined') + encoding = 'utf8'; + else + encoding = (encoding + '').toLowerCase(); if (string.length > 0 && (length < 0 || offset < 0)) throw new RangeError('attempt to write beyond buffer bounds'); @@ -345,32 +253,32 @@ Buffer.prototype.write = function(string, offset, length, encoding) { var ret; switch (encoding) { case 'hex': - ret = this.parent.hexWrite(string, this.offset + offset, length); + ret = this.hexWrite(string, offset, length); break; case 'utf8': case 'utf-8': - ret = this.parent.utf8Write(string, this.offset + offset, length); + ret = this.utf8Write(string, offset, length); break; case 'ascii': - ret = this.parent.asciiWrite(string, this.offset + offset, length); + ret = this.asciiWrite(string, offset, length); break; case 'binary': - ret = this.parent.binaryWrite(string, this.offset + offset, length); + ret = this.binaryWrite(string, offset, length); break; case 'base64': // Warning: maxLength not taken into account in base64Write - ret = this.parent.base64Write(string, this.offset + offset, length); + ret = this.base64Write(string, offset, length); break; case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': - ret = this.parent.ucs2Write(string, this.offset + offset, length); + ret = this.ucs2Write(string, offset, length); break; default: @@ -389,203 +297,48 @@ Buffer.prototype.toJSON = function() { }; -// toString(encoding, start=0, end=buffer.length) -Buffer.prototype.toString = function(encoding, start, end) { - encoding = String(encoding || 'utf8').toLowerCase(); - - if (typeof start !== 'number' || start < 0) { - start = 0; - } else if (start > this.length) { - start = this.length; - } - - if (typeof end !== 'number' || end > this.length) { - end = this.length; - } else if (end < 0) { - end = 0; - } - - start = start + this.offset; - end = end + this.offset; - - switch (encoding) { - case 'hex': - return this.parent.hexSlice(start, end); - - case 'utf8': - case 'utf-8': - return this.parent.utf8Slice(start, end); - - case 'ascii': - return this.parent.asciiSlice(start, end); - - case 'binary': - return this.parent.binarySlice(start, end); - - case 'base64': - return this.parent.base64Slice(start, end); - - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return this.parent.ucs2Slice(start, end); - - default: - throw new TypeError('Unknown encoding: ' + encoding); - } -}; - - -// byteLength -Buffer.byteLength = SlowBuffer.byteLength; - - -// fill(value, start=0, end=buffer.length) -Buffer.prototype.fill = function fill(value, start, end) { - value || (value = 0); - start || (start = 0); - end || (end = this.length); - - if (typeof value === 'string') { - value = value.charCodeAt(0); - } - if (typeof value !== 'number' || isNaN(value)) { - throw new TypeError('value is not a number'); - } - - if (end < start) throw new RangeError('end < start'); - - // Fill 0 bytes; we're done - if (end === start) return 0; - if (this.length == 0) return 0; - - if (start < 0 || start >= this.length) { - throw new RangeError('start out of bounds'); - } - - if (end < 0 || end > this.length) { - throw new RangeError('end out of bounds'); - } - - this.parent.fill(value, - start + this.offset, - end + this.offset); - return this; -}; - - -Buffer.concat = function(list, length) { - if (!Array.isArray(list)) { - throw new TypeError('Usage: Buffer.concat(list, [length])'); - } - - if (list.length === 0) { - return new Buffer(0); - } else if (list.length === 1) { - return list[0]; +// TODO(trevnorris): currently works like Array.prototype.slice(), which +// doesn't follow the new standard for throwing on out of range indexes. +Buffer.prototype.slice = function(start, end) { + var len = this.length; + start = ~~start; + end = typeof end === 'undefined' ? len : ~~end; + + if (start < 0) { + start += len; + if (start < 0) + start = 0; + } else if (start > len) { + start = len; } - if (typeof length !== 'number') { - length = 0; - for (var i = 0; i < list.length; i++) { - var buf = list[i]; - length += buf.length; - } + if (end < 0) { + end += len; + if (end < 0) + end = 0; + } else if (end > len) { + end = len; } - var buffer = new Buffer(length); - var pos = 0; - for (var i = 0; i < list.length; i++) { - var buf = list[i]; - buf.copy(buffer, pos); - pos += buf.length; - } - return buffer; -}; - - - - -// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) -Buffer.prototype.copy = function(target, target_start, start, end) { - // set undefined/NaN or out of bounds values equal to their default - if (!(target_start >= 0)) target_start = 0; - if (!(start >= 0)) start = 0; - if (!(end < this.length)) end = this.length; - - // Copy 0 bytes; we're done - if (end === start || - target.length === 0 || - this.length === 0 || - start > this.length) - return 0; - if (end < start) - throw new RangeError('sourceEnd < sourceStart'); - - if (target_start >= target.length) - throw new RangeError('targetStart out of bounds'); - - if (target.length - target_start < end - start) - end = target.length - target_start + start; - - return this.parent.copy(target.parent || target, - target_start + (target.offset || 0), - start + this.offset, - end + this.offset); -}; - - -// slice(start, end) -Buffer.prototype.slice = function(start, end) { - var len = this.length; - start = clamp(start, len, 0); - end = clamp(end, len, len); - return new Buffer(this.parent, end - start, start + this.offset); -}; - - -// Legacy methods for backwards compatibility. - -Buffer.prototype.utf8Slice = function(start, end) { - return this.toString('utf8', start, end); -}; + end = start; -Buffer.prototype.binarySlice = function(start, end) { - return this.toString('binary', start, end); -}; + var buf = new Buffer(); + buf.parent = sliceOnto(this, buf, start, end); + buf.length = end - start; -Buffer.prototype.asciiSlice = function(start, end) { - return this.toString('ascii', start, end); + return buf; }; -Buffer.prototype.utf8Write = function(string, offset) { - return this.write(string, offset, 'utf8'); -}; - -Buffer.prototype.binaryWrite = function(string, offset) { - return this.write(string, offset, 'binary'); -}; -Buffer.prototype.asciiWrite = function(string, offset) { - return this.write(string, offset, 'ascii'); -}; - - -/* - * Need to make sure that buffer isn't trying to write out of bounds. - * This check is far too slow internally for fast buffers. - */ function checkOffset(offset, ext, length) { - if ((offset % 1) !== 0 || offset < 0) - throw new RangeError('offset is not uint'); - if (offset + ext > length) - throw new RangeError('Trying to access beyond buffer length'); + if (offset < 0 || offset + ext > length) + throw new RangeError('index out of range'); } Buffer.prototype.readUInt8 = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 1, this.length); return this[offset]; @@ -601,12 +354,12 @@ function readUInt16(buffer, offset, isBigEndian) { val = buffer[offset]; val |= buffer[offset + 1] << 8; } - return val; } Buffer.prototype.readUInt16LE = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 2, this.length); return readUInt16(this, offset, false, noAssert); @@ -614,6 +367,7 @@ Buffer.prototype.readUInt16LE = function(offset, noAssert) { Buffer.prototype.readUInt16BE = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 2, this.length); return readUInt16(this, offset, true, noAssert); @@ -622,7 +376,6 @@ Buffer.prototype.readUInt16BE = function(offset, noAssert) { function readUInt32(buffer, offset, isBigEndian, noAssert) { var val = 0; - if (isBigEndian) { val = buffer[offset + 1] << 16; val |= buffer[offset + 2] << 8; @@ -634,12 +387,12 @@ function readUInt32(buffer, offset, isBigEndian, noAssert) { val |= buffer[offset]; val = val + (buffer[offset + 3] << 24 >>> 0); } - return val; } Buffer.prototype.readUInt32LE = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 4, this.length); return readUInt32(this, offset, false, noAssert); @@ -647,6 +400,7 @@ Buffer.prototype.readUInt32LE = function(offset, noAssert) { Buffer.prototype.readUInt32BE = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 4, this.length); return readUInt32(this, offset, true, noAssert); @@ -700,6 +454,7 @@ Buffer.prototype.readUInt32BE = function(offset, noAssert) { */ Buffer.prototype.readInt8 = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 1, this.length); if (!(this[offset] & 0x80)) @@ -710,7 +465,6 @@ Buffer.prototype.readInt8 = function(offset, noAssert) { function readInt16(buffer, offset, isBigEndian) { var val = readUInt16(buffer, offset, isBigEndian); - if (!(val & 0x8000)) return val; return (0xffff - val + 1) * -1; @@ -718,6 +472,7 @@ function readInt16(buffer, offset, isBigEndian) { Buffer.prototype.readInt16LE = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 2, this.length); return readInt16(this, offset, false); @@ -725,6 +480,7 @@ Buffer.prototype.readInt16LE = function(offset, noAssert) { Buffer.prototype.readInt16BE = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 2, this.length); return readInt16(this, offset, true); @@ -733,7 +489,6 @@ Buffer.prototype.readInt16BE = function(offset, noAssert) { function readInt32(buffer, offset, isBigEndian) { var val = readUInt32(buffer, offset, isBigEndian); - if (!(val & 0x80000000)) return (val); return (0xffffffff - val + 1) * -1; @@ -741,6 +496,7 @@ function readInt32(buffer, offset, isBigEndian) { Buffer.prototype.readInt32LE = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 4, this.length); return readInt32(this, offset, false); @@ -748,50 +504,24 @@ Buffer.prototype.readInt32LE = function(offset, noAssert) { Buffer.prototype.readInt32BE = function(offset, noAssert) { + offset = ~~offset; if (!noAssert) checkOffset(offset, 4, this.length); return readInt32(this, offset, true); }; -Buffer.prototype.readFloatLE = function(offset, noAssert) { - if (!noAssert) - checkOffset(offset, 4, this.length); - return this.parent.readFloatLE(this.offset + offset, !!noAssert); -}; - - -Buffer.prototype.readFloatBE = function(offset, noAssert) { - if (!noAssert) - checkOffset(offset, 4, this.length); - return this.parent.readFloatBE(this.offset + offset, !!noAssert); -}; - - -Buffer.prototype.readDoubleLE = function(offset, noAssert) { - if (!noAssert) - checkOffset(offset, 8, this.length); - return this.parent.readDoubleLE(this.offset + offset, !!noAssert); -}; - - -Buffer.prototype.readDoubleBE = function(offset, noAssert) { - if (!noAssert) - checkOffset(offset, 8, this.length); - return this.parent.readDoubleBE(this.offset + offset, !!noAssert); -}; - function checkInt(buffer, value, offset, ext, max, min) { - if ((value % 1) !== 0 || value > max || value < min) + if (value > max || value < min) throw TypeError('value is out of bounds'); - if ((offset % 1) !== 0 || offset < 0) - throw TypeError('offset is not uint'); - if (offset + ext > buffer.length || buffer.length + offset < 0) - throw RangeError('Trying to write outside buffer length'); + if (offset < 0 || offset + ext > buffer.length || buffer.length + offset < 0) + throw RangeError('index out of range'); } Buffer.prototype.writeUInt8 = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0); this[offset] = value; @@ -810,6 +540,8 @@ function writeUInt16(buffer, value, offset, isBigEndian) { Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0); writeUInt16(this, value, offset, false); @@ -817,6 +549,8 @@ Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) { Buffer.prototype.writeUInt16BE = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0); writeUInt16(this, value, offset, true); @@ -839,6 +573,8 @@ function writeUInt32(buffer, value, offset, isBigEndian) { Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0); writeUInt32(this, value, offset, false); @@ -846,6 +582,8 @@ Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) { Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0); writeUInt32(this, value, offset, true); @@ -890,6 +628,8 @@ Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) { */ Buffer.prototype.writeInt8 = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80); if (value < 0) value = 0xff + value + 1; @@ -898,6 +638,8 @@ Buffer.prototype.writeInt8 = function(value, offset, noAssert) { Buffer.prototype.writeInt16LE = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000); if (value < 0) value = 0xffff + value + 1; @@ -906,6 +648,8 @@ Buffer.prototype.writeInt16LE = function(value, offset, noAssert) { Buffer.prototype.writeInt16BE = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000); if (value < 0) value = 0xffff + value + 1; @@ -914,6 +658,8 @@ Buffer.prototype.writeInt16BE = function(value, offset, noAssert) { Buffer.prototype.writeInt32LE = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000); if (value < 0) value = 0xffffffff + value + 1; @@ -922,36 +668,10 @@ Buffer.prototype.writeInt32LE = function(value, offset, noAssert) { Buffer.prototype.writeInt32BE = function(value, offset, noAssert) { + value = +value; + offset = ~~offset; if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000); if (value < 0) value = 0xffffffff + value + 1; writeUInt32(this, value, offset, true); }; - - -Buffer.prototype.writeFloatLE = function(value, offset, noAssert) { - if (!noAssert) - checkOffset(offset, 4, this.length); - this.parent.writeFloatLE(value, this.offset + offset, !!noAssert); -}; - - -Buffer.prototype.writeFloatBE = function(value, offset, noAssert) { - if (!noAssert) - checkOffset(offset, 4, this.length); - this.parent.writeFloatBE(value, this.offset + offset, !!noAssert); -}; - - -Buffer.prototype.writeDoubleLE = function(value, offset, noAssert) { - if (!noAssert) - checkOffset(offset, 8, this.length); - this.parent.writeDoubleLE(value, this.offset + offset, !!noAssert); -}; - - -Buffer.prototype.writeDoubleBE = function(value, offset, noAssert) { - if (!noAssert) - checkOffset(offset, 8, this.length); - this.parent.writeDoubleBE(value, this.offset + offset, !!noAssert); -}; diff --git a/src/node.js b/src/node.js index 8d2767e7c0902c..66c8cbbe54f099 100644 --- a/src/node.js +++ b/src/node.js @@ -163,7 +163,6 @@ global.GLOBAL = global; global.root = global; global.Buffer = NativeModule.require('buffer').Buffer; - process.binding('buffer').setFastBufferConstructor(global.Buffer); process.domain = null; process._exiting = false; }; diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 687545a2052a01..4c59a055c02ca9 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -20,367 +20,369 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. -#include "node_buffer.h" - #include "node.h" +#include "node_internals.h" +#include "node_buffer.h" +#include "smalloc.h" #include "string_bytes.h" #include "v8.h" #include "v8-profiler.h" #include -#include // memcpy +#include #include -#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define BUFFER_CLASS_ID (0xBABE) +#define CHECK_NOT_OOB(r) \ + do { if (!(r)) return ThrowRangeError("out of range index"); } while (0) -namespace node { +#define ARGS_THIS(argT) \ + Local obj = argT; \ + size_t obj_length = obj->GetIndexedPropertiesExternalArrayDataLength(); \ + char* obj_data = static_cast( \ + obj->GetIndexedPropertiesExternalArrayData()); -using namespace v8; - -#define SLICE_ARGS(start_arg, end_arg) \ - if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \ - return ThrowException(Exception::TypeError( \ - String::New("Bad argument."))); \ - } \ - int32_t start = start_arg->Int32Value(); \ - int32_t end = end_arg->Int32Value(); \ - if (start < 0 || end < 0) { \ - return ThrowException(Exception::TypeError( \ - String::New("Bad argument."))); \ - } \ - if (!(start <= end)) { \ - return ThrowException(Exception::Error( \ - String::New("Must have start <= end"))); \ - } \ - if ((size_t)end > parent->length_) { \ - return ThrowException(Exception::Error( \ - String::New("end cannot be longer than parent.length"))); \ - } +#define SLICE_START_END(start_arg, end_arg, end_max) \ + size_t start; \ + size_t end; \ + CHECK_NOT_OOB(ParseArrayIndex(start_arg, 0, &start)); \ + CHECK_NOT_OOB(ParseArrayIndex(end_arg, end_max, &end)); \ + if (end < start) end = start; \ + CHECK_NOT_OOB(end <= end_max); \ + size_t length = end - start; +namespace node { -static Persistent length_symbol; -static Persistent write_sym; -static Persistent fast_buffer_constructor; -Persistent Buffer::constructor_template; +namespace Buffer { -Handle Buffer::New(Handle string) { - HandleScope scope(node_isolate); +using v8::Arguments; +using v8::Function; +using v8::FunctionTemplate; +using v8::Handle; +using v8::HandleScope; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::Persistent; +using v8::String; +using v8::Uint32; +using v8::Undefined; +using v8::Value; - // get Buffer from global scope. - Local global = v8::Context::GetCurrent()->Global(); - Local bv = global->Get(String::NewSymbol("Buffer")); - assert(bv->IsFunction()); - Local b = Local::Cast(bv); - Local argv[1] = { Local::New(node_isolate, string) }; - Local instance = b->NewInstance(1, argv); +Persistent p_buffer_fn; - return scope.Close(instance); + +bool HasInstance(Handle val) { + return val->IsObject() && HasInstance(val.As()); } -Buffer* Buffer::New(size_t length) { - HandleScope scope(node_isolate); +bool HasInstance(Handle obj) { + if (!obj->HasIndexedPropertiesInExternalArrayData()) + return false; + v8::ExternalArrayType type = obj->GetIndexedPropertiesExternalArrayDataType(); + return type == v8::kExternalUnsignedByteArray; +} - Local arg = Integer::NewFromUnsigned(length, node_isolate); - Local b = constructor_template->GetFunction()->NewInstance(1, &arg); - if (b.IsEmpty()) return NULL; - return ObjectWrap::Unwrap(b); +char* Data(Handle val) { + assert(val->IsObject()); + return Data(val.As()); } -Buffer* Buffer::New(const char* data, size_t length) { - HandleScope scope(node_isolate); +char* Data(Handle obj) { + assert(obj->HasIndexedPropertiesInExternalArrayData()); + return static_cast(obj->GetIndexedPropertiesExternalArrayData()); +} - Local arg = Integer::NewFromUnsigned(0, node_isolate); - Local obj = constructor_template->GetFunction()->NewInstance(1, &arg); - Buffer *buffer = ObjectWrap::Unwrap(obj); - buffer->Replace(const_cast(data), length, NULL, NULL); +size_t Length(Handle val) { + assert(val->IsObject()); + return Length(val.As()); +} + - return buffer; +size_t Length(Handle obj) { + assert(obj->HasIndexedPropertiesInExternalArrayData()); + return obj->GetIndexedPropertiesExternalArrayDataLength(); } -Buffer* Buffer::New(char *data, size_t length, - free_callback callback, void *hint) { +// TODO(trevnorris): would be more efficient to use StringBytes to calculate the +// length and write out the data beforehand then do the same as New(). +Local New(Handle string) { HandleScope scope(node_isolate); - Local arg = Integer::NewFromUnsigned(0, node_isolate); - Local obj = constructor_template->GetFunction()->NewInstance(1, &arg); + Handle argv[1] = { string }; + Local obj = p_buffer_fn->NewInstance(1, argv); - Buffer *buffer = ObjectWrap::Unwrap(obj); - buffer->Replace(data, length, callback, hint); - - return buffer; + return scope.Close(obj); } -Handle Buffer::New(const Arguments& args) { - if (!args.IsConstructCall()) { - return FromConstructorTemplate(constructor_template, args); - } - +// TODO(trevnorris): these have a flaw by needing to call the Buffer inst then +// Alloc. continue to look for a better architecture. +Local New(size_t length) { HandleScope scope(node_isolate); - if (!args[0]->IsUint32()) return ThrowTypeError("Bad argument"); + assert(length <= kMaxLength); - size_t length = args[0]->Uint32Value(); - if (length > Buffer::kMaxLength) { - return ThrowRangeError("length > kMaxLength"); - } - new Buffer(args.This(), length); + Handle argv[2]; + // this is safe b/c Undefined and length fits in an SMI, so there's no risk + // of GC reclaiming the values prematurely. + argv[0] = Undefined(node_isolate); + argv[1] = Uint32::New(length, node_isolate); + Local obj = p_buffer_fn->NewInstance(2, argv); - return args.This(); + smalloc::Alloc(obj, new char[length], length); + + return scope.Close(obj); } -Buffer::Buffer(Handle wrapper, size_t length) : ObjectWrap() { - Wrap(wrapper); +// TODO(trevnorris): for backwards compatibility this is left to copy the data, +// but for consistency w/ the other should use data. And a copy version renamed +// to something else. +Local New(const char* data, size_t length) { + HandleScope scope(node_isolate); - length_ = 0; - callback_ = NULL; - handle_.SetWrapperClassId(node_isolate, BUFFER_CLASS_ID); + assert(length <= kMaxLength); - Replace(NULL, length, NULL, NULL); -} + Handle argv[2]; + // this is safe b/c Undefined and length fits in an SMI, so there's no risk + // of GC reclaiming the values prematurely. + argv[0] = Undefined(node_isolate); + argv[1] = Uint32::New(length, node_isolate); + Local obj = p_buffer_fn->NewInstance(2, argv); + + char* new_data = new char[length]; + memcpy(new_data, data, length); + smalloc::Alloc(obj, new_data, length); -Buffer::~Buffer() { - Replace(NULL, 0, NULL, NULL); + return scope.Close(obj); } -// if replace doesn't have a callback, data must be copied -// const_cast in Buffer::New requires this -void Buffer::Replace(char *data, size_t length, - free_callback callback, void *hint) { +Local New(char* data, + size_t length, + smalloc::FreeCallback callback, + void* hint) { HandleScope scope(node_isolate); - if (callback_) { - callback_(data_, callback_hint_); - } else if (length_) { - delete [] data_; - node_isolate->AdjustAmountOfExternalAllocatedMemory( - -static_cast(sizeof(Buffer) + length_)); - } + assert(length <= kMaxLength); - length_ = length; - callback_ = callback; - callback_hint_ = hint; - - if (callback_) { - data_ = data; - } else if (length_) { - data_ = new char[length_]; - if (data) - memcpy(data_, data, length_); - node_isolate->AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer) + - length_); - } else { - data_ = NULL; - } + Handle argv[2]; + // this is safe b/c Undefined and length fits in an SMI, so there's no risk + // of GC reclaiming the values prematurely. + argv[0] = Undefined(node_isolate); + argv[1] = Uint32::New(length, node_isolate); + Local obj = p_buffer_fn->NewInstance(2, argv); + + smalloc::Alloc(obj, data, length, callback, hint); - handle_->SetIndexedPropertiesToExternalArrayData(data_, - kExternalUnsignedByteArray, - length_); - handle_->Set(length_symbol, Integer::NewFromUnsigned(length_, node_isolate)); + return scope.Close(obj); } -template -Handle Buffer::StringSlice(const Arguments& args) { +Local Use(char* data, uint32_t length) { HandleScope scope(node_isolate); - Buffer *parent = ObjectWrap::Unwrap(args.This()); - SLICE_ARGS(args[0], args[1]) - const char* src = parent->data_ + start; - size_t slen = (end - start); - return scope.Close(StringBytes::Encode(src, slen, encoding)); -} + assert(length <= kMaxLength); + Handle argv[2]; + // this is safe b/c Undefined and length fits in an SMI, so there's no risk + // of GC reclaiming the values prematurely. + argv[0] = Undefined(node_isolate); + argv[1] = Uint32::New(length, node_isolate); + Local obj = p_buffer_fn->NewInstance(2, argv); -Handle Buffer::BinarySlice(const Arguments& args) { - return Buffer::StringSlice(args); + smalloc::Alloc(obj, data, length); + + return scope.Close(obj); } -Handle Buffer::AsciiSlice(const Arguments& args) { - return Buffer::StringSlice(args); -} +template +Handle StringSlice(const Arguments& args) { + HandleScope scope(node_isolate); + ARGS_THIS(args.This()) + SLICE_START_END(args[0], args[1], obj_length) -Handle Buffer::Utf8Slice(const Arguments& args) { - return Buffer::StringSlice(args); + return scope.Close(StringBytes::Encode(obj_data + start, length, encoding)); } -Handle Buffer::Ucs2Slice(const Arguments& args) { - return Buffer::StringSlice(args); +Handle BinarySlice(const Arguments& args) { + return StringSlice(args); } - -Handle Buffer::HexSlice(const Arguments& args) { - return Buffer::StringSlice(args); +Handle AsciiSlice(const Arguments& args) { + return StringSlice(args); } -Handle Buffer::Base64Slice(const Arguments& args) { - return Buffer::StringSlice(args); +Handle Utf8Slice(const Arguments& args) { + return StringSlice(args); } -// buffer.fill(value, start, end); -Handle Buffer::Fill(const Arguments &args) { - HandleScope scope(node_isolate); +Handle Ucs2Slice(const Arguments& args) { + return StringSlice(args); +} - if (!args[0]->IsInt32()) { - return ThrowException(Exception::Error(String::New( - "value is not a number"))); - } - int value = (char)args[0]->Int32Value(); - Buffer *parent = ObjectWrap::Unwrap(args.This()); - SLICE_ARGS(args[1], args[2]) - memset( (void*)(parent->data_ + start), - value, - end - start); +Handle HexSlice(const Arguments& args) { + return StringSlice(args); +} - return Undefined(node_isolate); + +Handle Base64Slice(const Arguments& args) { + return StringSlice(args); } -// var bytesCopied = buffer.copy(target, targetStart, sourceStart, sourceEnd); -Handle Buffer::Copy(const Arguments &args) { +// bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]); +Handle Copy(const Arguments &args) { HandleScope scope(node_isolate); - Buffer *source = ObjectWrap::Unwrap(args.This()); + Local target = args[0]->ToObject(); - if (!Buffer::HasInstance(args[0])) { - return ThrowTypeError("First arg should be a Buffer"); - } + if (!HasInstance(target)) + return ThrowTypeError("first arg should be a Buffer"); - Local target = args[0]; - char* target_data = Buffer::Data(target); - size_t target_length = Buffer::Length(target); - size_t target_start = args[1]->IsUndefined() ? 0 : args[1]->Uint32Value(); - size_t source_start = args[2]->IsUndefined() ? 0 : args[2]->Uint32Value(); - size_t source_end = args[3]->IsUndefined() ? source->length_ - : args[3]->Uint32Value(); + ARGS_THIS(args.This()) + size_t target_length = target->GetIndexedPropertiesExternalArrayDataLength(); + char* target_data = static_cast( + target->GetIndexedPropertiesExternalArrayData()); + size_t target_start; + size_t source_start; + size_t source_end; - if (source_end < source_start) { - return ThrowRangeError("sourceEnd < sourceStart"); - } + CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &target_start)); + CHECK_NOT_OOB(ParseArrayIndex(args[2], 0, &source_start)); + CHECK_NOT_OOB(ParseArrayIndex(args[3], obj_length, &source_end)); // Copy 0 bytes; we're done - if (source_end == source_start) { - return scope.Close(Integer::New(0, node_isolate)); - } - - if (target_start >= target_length) { - return ThrowRangeError("targetStart out of bounds"); - } + if (target_start >= target_length || source_start >= source_end) + return scope.Close(Uint32::New(0, node_isolate)); - if (source_start >= source->length_) { - return ThrowRangeError("sourceStart out of bounds"); - } + if (source_start > obj_length) + return ThrowRangeError("out of range index"); - if (source_end > source->length_) { - return ThrowRangeError("sourceEnd out of bounds"); - } + if (source_end - source_start > target_length - target_start) + source_end = source_start + target_length - target_start; size_t to_copy = MIN(MIN(source_end - source_start, target_length - target_start), - source->length_ - source_start); + obj_length - source_start); - // need to use slightly slower memmove is the ranges might overlap memmove((void *)(target_data + target_start), - (const void*)(source->data_ + source_start), + (const void*)(obj_data + source_start), to_copy); - return scope.Close(Integer::New(to_copy, node_isolate)); + return scope.Close(Uint32::New(to_copy, node_isolate)); } -Handle Buffer::Base64Write(const Arguments& args) { - return Buffer::StringWrite(args); -} +// buffer.fill(value[, start][, end]); +Handle Fill(const Arguments &args) { + HandleScope scope(node_isolate); -Handle Buffer::BinaryWrite(const Arguments& args) { - return Buffer::StringWrite(args); -} + ARGS_THIS(args.This()) + int value; -Handle Buffer::Utf8Write(const Arguments& args) { - return Buffer::StringWrite(args); -} + if (args[0]->IsString()) { + String::AsciiValue at(args[0]); + value = (*at)[0]; + } else { + value = (char)args[0]->Int32Value(); + } -Handle Buffer::Ucs2Write(const Arguments& args) { - return Buffer::StringWrite(args); -} + SLICE_START_END(args[1], args[2], obj_length) -Handle Buffer::HexWrite(const Arguments& args) { - return Buffer::StringWrite(args); -} + memset((void*)(obj_data + start), value, length); -Handle Buffer::AsciiWrite(const Arguments& args) { - return Buffer::StringWrite(args); + return args.This(); } + template -Handle Buffer::StringWrite(const Arguments& args) { +Handle StringWrite(const Arguments& args) { HandleScope scope(node_isolate); - Buffer* buffer = ObjectWrap::Unwrap(args.This()); + ARGS_THIS(args.This()) - if (!args[0]->IsString()) { + if (!args[0]->IsString()) return ThrowTypeError("Argument must be a string"); - } - - Local str = args[0].As(); - int length = str->Length(); + Local str = args[0]->ToString(); - if (length == 0) { - return scope.Close(Integer::New(0)); - } - - if (encoding == HEX && length % 2 != 0) + if (encoding == HEX && str->Length() % 2 != 0) return ThrowTypeError("Invalid hex string"); + size_t offset; + size_t max_length; - size_t offset = args[1]->Int32Value(); - size_t max_length = args[2]->IsUndefined() ? buffer->length_ - offset - : args[2]->Uint32Value(); - max_length = MIN(buffer->length_ - offset, max_length); + CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset)); + CHECK_NOT_OOB(ParseArrayIndex(args[2], obj_length - offset, &max_length)); - if (max_length == 0) { - // shortcut: nothing to write anyway - Local val = Integer::New(0); - return scope.Close(val); - } + max_length = MIN(obj_length - offset, max_length); + + if (max_length == 0) + return scope.Close(Uint32::New(0, node_isolate)); if (encoding == UCS2) max_length = max_length / 2; - if (offset >= buffer->length_) { - return ThrowTypeError("Offset is out of bounds"); - } + if (offset >= obj_length) + return ThrowRangeError("Offset is out of bounds"); - char* start = buffer->data_ + offset; - size_t written = StringBytes::Write(start, + size_t written = StringBytes::Write(obj_data + offset, max_length, str, encoding, NULL); - return scope.Close(Integer::New(written, node_isolate)); + return scope.Close(Uint32::New(written, node_isolate)); +} + + +Handle Base64Write(const Arguments& args) { + return StringWrite(args); +} + + +Handle BinaryWrite(const Arguments& args) { + return StringWrite(args); +} + + +Handle Utf8Write(const Arguments& args) { + return StringWrite(args); +} + + +Handle Ucs2Write(const Arguments& args) { + return StringWrite(args); +} + + +Handle HexWrite(const Arguments& args) { + return StringWrite(args); +} + + +Handle AsciiWrite(const Arguments& args) { + return StringWrite(args); } @@ -396,14 +398,13 @@ static inline void Swizzle(char* start, unsigned int len) { template Handle ReadFloatGeneric(const Arguments& args) { - size_t offset = args[0]->Uint32Value(); bool doAssert = !args[1]->BooleanValue(); + size_t offset; + + CHECK_NOT_OOB(ParseArrayIndex(args[0], 0, &offset)); if (doAssert) { - if (!args[0]->IsUint32()) - return ThrowTypeError("offset is not uint"); - size_t len = static_cast( - args.This()->GetIndexedPropertiesExternalArrayDataLength()); + size_t len = Length(args.This()); if (offset + sizeof(T) > len || offset + sizeof(T) < offset) return ThrowRangeError("Trying to read beyond buffer length"); } @@ -419,27 +420,26 @@ Handle ReadFloatGeneric(const Arguments& args) { memcpy(na.bytes, ptr, sizeof(na.bytes)); if (endianness != GetEndianness()) Swizzle(na.bytes, sizeof(na.bytes)); - // TODO: when Number::New is updated to accept an Isolate, make the change return Number::New(na.val); } -Handle Buffer::ReadFloatLE(const Arguments& args) { +Handle ReadFloatLE(const Arguments& args) { return ReadFloatGeneric(args); } -Handle Buffer::ReadFloatBE(const Arguments& args) { +Handle ReadFloatBE(const Arguments& args) { return ReadFloatGeneric(args); } -Handle Buffer::ReadDoubleLE(const Arguments& args) { +Handle ReadDoubleLE(const Arguments& args) { return ReadFloatGeneric(args); } -Handle Buffer::ReadDoubleBE(const Arguments& args) { +Handle ReadDoubleBE(const Arguments& args) { return ReadFloatGeneric(args); } @@ -448,19 +448,13 @@ template Handle WriteFloatGeneric(const Arguments& args) { bool doAssert = !args[2]->BooleanValue(); - if (doAssert) { - if (!args[0]->IsNumber()) - return ThrowTypeError("value not a number"); - if (!args[1]->IsUint32()) - return ThrowTypeError("offset is not uint"); - } - T val = static_cast(args[0]->NumberValue()); - size_t offset = args[1]->Uint32Value(); + size_t offset; + + CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset)); if (doAssert) { - size_t len = static_cast( - args.This()->GetIndexedPropertiesExternalArrayDataLength()); + size_t len = Length(args.This()); if (offset + sizeof(T) > len || offset + sizeof(T) < offset) return ThrowRangeError("Trying to write beyond buffer length"); } @@ -480,205 +474,122 @@ Handle WriteFloatGeneric(const Arguments& args) { } -Handle Buffer::WriteFloatLE(const Arguments& args) { +Handle WriteFloatLE(const Arguments& args) { return WriteFloatGeneric(args); } -Handle Buffer::WriteFloatBE(const Arguments& args) { +Handle WriteFloatBE(const Arguments& args) { return WriteFloatGeneric(args); } -Handle Buffer::WriteDoubleLE(const Arguments& args) { +Handle WriteDoubleLE(const Arguments& args) { return WriteFloatGeneric(args); } -Handle Buffer::WriteDoubleBE(const Arguments& args) { +Handle WriteDoubleBE(const Arguments& args) { return WriteFloatGeneric(args); } -// var nbytes = Buffer.byteLength("string", "utf8") -Handle Buffer::ByteLength(const Arguments &args) { +Handle ByteLength(const Arguments &args) { HandleScope scope(node_isolate); - if (!args[0]->IsString()) { + if (!args[0]->IsString()) return ThrowTypeError("Argument must be a string"); - } Local s = args[0]->ToString(); enum encoding e = ParseEncoding(args[1], UTF8); - return scope.Close(Integer::New(StringBytes::Size(s, e), node_isolate)); + return scope.Close(Uint32::New(StringBytes::Size(s, e), node_isolate)); } -Handle Buffer::MakeFastBuffer(const Arguments &args) { +// pass Buffer object to load prototype methods +Handle SetupBufferJS(const Arguments& args) { HandleScope scope(node_isolate); - if (!Buffer::HasInstance(args[0])) { - return ThrowTypeError("First argument must be a Buffer"); - } - - Buffer *buffer = ObjectWrap::Unwrap(args[0]->ToObject()); - Local fast_buffer = args[1]->ToObject(); - uint32_t offset = args[2]->Uint32Value(); - uint32_t length = args[3]->Uint32Value(); - - if (offset > buffer->length_) { - return ThrowRangeError("offset out of range"); - } - - if (offset + length > buffer->length_) { - return ThrowRangeError("length out of range"); - } - - // Check for wraparound. Safe because offset and length are unsigned. - if (offset + length < offset) { - return ThrowRangeError("offset or length out of range"); - } - - fast_buffer->SetIndexedPropertiesToExternalArrayData(buffer->data_ + offset, - kExternalUnsignedByteArray, - length); - - return Undefined(node_isolate); -} - - -bool Buffer::HasInstance(Handle val) { - if (!val->IsObject()) return false; - Local obj = val->ToObject(); - - ExternalArrayType type = obj->GetIndexedPropertiesExternalArrayDataType(); - if (type != kExternalUnsignedByteArray) - return false; - - // Also check for SlowBuffers that are empty. - if (constructor_template->HasInstance(obj)) - return true; - - assert(!fast_buffer_constructor.IsEmpty()); - return obj->GetConstructor()->StrictEquals(fast_buffer_constructor); -} - - -Handle SetFastBufferConstructor(const Arguments& args) { assert(args[0]->IsFunction()); - fast_buffer_constructor = Persistent::New(node_isolate, - args[0].As()); - return Undefined(node_isolate); -} - - -class RetainedBufferInfo: public RetainedObjectInfo { -public: - RetainedBufferInfo(Buffer* buffer); - virtual void Dispose(); - virtual bool IsEquivalent(RetainedObjectInfo* other); - virtual intptr_t GetHash(); - virtual const char* GetLabel(); - virtual intptr_t GetSizeInBytes(); -private: - Buffer* buffer_; - static const char label[]; -}; - -const char RetainedBufferInfo::label[] = "Buffer"; + Local bv = args[0].As(); + p_buffer_fn = Persistent::New(node_isolate, bv); + Local proto_v = bv->Get(String::New("prototype")); + + assert(proto_v->IsObject()); + + Local proto = proto_v.As(); + + bv->Set(String::New("byteLength"), + FunctionTemplate::New(ByteLength)->GetFunction()); + + proto->Set(String::New("asciiSlice"), + FunctionTemplate::New(AsciiSlice)->GetFunction()); + proto->Set(String::New("base64Slice"), + FunctionTemplate::New(Base64Slice)->GetFunction()); + proto->Set(String::New("binarySlice"), + FunctionTemplate::New(BinarySlice)->GetFunction()); + proto->Set(String::New("hexSlice"), + FunctionTemplate::New(HexSlice)->GetFunction()); + proto->Set(String::New("ucs2Slice"), + FunctionTemplate::New(Ucs2Slice)->GetFunction()); + proto->Set(String::New("utf8Slice"), + FunctionTemplate::New(Utf8Slice)->GetFunction()); + + proto->Set(String::New("asciiWrite"), + FunctionTemplate::New(AsciiWrite)->GetFunction()); + proto->Set(String::New("base64Write"), + FunctionTemplate::New(Base64Write)->GetFunction()); + proto->Set(String::New("binaryWrite"), + FunctionTemplate::New(BinaryWrite)->GetFunction()); + proto->Set(String::New("hexWrite"), + FunctionTemplate::New(HexWrite)->GetFunction()); + proto->Set(String::New("ucs2Write"), + FunctionTemplate::New(Ucs2Write)->GetFunction()); + proto->Set(String::New("utf8Write"), + FunctionTemplate::New(Utf8Write)->GetFunction()); + + proto->Set(String::New("readDoubleBE"), + FunctionTemplate::New(ReadDoubleBE)->GetFunction()); + proto->Set(String::New("readDoubleLE"), + FunctionTemplate::New(ReadDoubleLE)->GetFunction()); + proto->Set(String::New("readFloatBE"), + FunctionTemplate::New(ReadFloatBE)->GetFunction()); + proto->Set(String::New("readFloatLE"), + FunctionTemplate::New(ReadFloatLE)->GetFunction()); + + proto->Set(String::New("writeDoubleBE"), + FunctionTemplate::New(WriteDoubleBE)->GetFunction()); + proto->Set(String::New("writeDoubleLE"), + FunctionTemplate::New(WriteDoubleLE)->GetFunction()); + proto->Set(String::New("writeFloatBE"), + FunctionTemplate::New(WriteFloatBE)->GetFunction()); + proto->Set(String::New("writeFloatLE"), + FunctionTemplate::New(WriteFloatLE)->GetFunction()); + + proto->Set(String::New("copy"), + FunctionTemplate::New(Copy)->GetFunction()); + proto->Set(String::New("fill"), + FunctionTemplate::New(Fill)->GetFunction()); + + // for backwards compatibility + proto->Set(String::New("offset"), Uint32::New(0, node_isolate), v8::ReadOnly); -RetainedBufferInfo::RetainedBufferInfo(Buffer* buffer): buffer_(buffer) { -} - - -void RetainedBufferInfo::Dispose() { - buffer_ = NULL; - delete this; -} - - -bool RetainedBufferInfo::IsEquivalent(RetainedObjectInfo* other) { - return label == other->GetLabel() && - buffer_ == static_cast(other)->buffer_; -} - - -intptr_t RetainedBufferInfo::GetHash() { - return reinterpret_cast(buffer_); -} - - -const char* RetainedBufferInfo::GetLabel() { - return label; -} - - -intptr_t RetainedBufferInfo::GetSizeInBytes() { - return Buffer::Length(buffer_); -} - - -RetainedObjectInfo* WrapperInfo(uint16_t class_id, Handle wrapper) { - assert(class_id == BUFFER_CLASS_ID); - assert(Buffer::HasInstance(wrapper)); - Buffer* buffer = Buffer::Unwrap(wrapper.As()); - return new RetainedBufferInfo(buffer); + return Undefined(node_isolate); } -void Buffer::Initialize(Handle target) { +void Initialize(Handle target) { HandleScope scope(node_isolate); - length_symbol = NODE_PSYMBOL("length"); - - Local t = FunctionTemplate::New(Buffer::New); - constructor_template = Persistent::New(node_isolate, t); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - constructor_template->SetClassName(String::NewSymbol("SlowBuffer")); - - NODE_SET_PROTOTYPE_METHOD(constructor_template, "binarySlice", Buffer::BinarySlice); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", Buffer::AsciiSlice); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "base64Slice", Buffer::Base64Slice); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "ucs2Slice", Buffer::Ucs2Slice); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "hexSlice", Buffer::HexSlice); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Buffer::Utf8Slice); - // TODO NODE_SET_PROTOTYPE_METHOD(t, "utf16Slice", Utf16Slice); - - NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Buffer::Utf8Write); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", Buffer::AsciiWrite); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "binaryWrite", Buffer::BinaryWrite); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "base64Write", Buffer::Base64Write); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "ucs2Write", Buffer::Ucs2Write); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "hexWrite", Buffer::HexWrite); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "readFloatLE", Buffer::ReadFloatLE); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "readFloatBE", Buffer::ReadFloatBE); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "readDoubleLE", Buffer::ReadDoubleLE); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "readDoubleBE", Buffer::ReadDoubleBE); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "writeFloatLE", Buffer::WriteFloatLE); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "writeFloatBE", Buffer::WriteFloatBE); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "writeDoubleLE", Buffer::WriteDoubleLE); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "writeDoubleBE", Buffer::WriteDoubleBE); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "fill", Buffer::Fill); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "copy", Buffer::Copy); - - NODE_SET_METHOD(constructor_template->GetFunction(), - "byteLength", - Buffer::ByteLength); - NODE_SET_METHOD(constructor_template->GetFunction(), - "makeFastBuffer", - Buffer::MakeFastBuffer); - - target->Set(String::NewSymbol("SlowBuffer"), constructor_template->GetFunction()); - target->Set(String::NewSymbol("setFastBufferConstructor"), - FunctionTemplate::New(SetFastBufferConstructor)->GetFunction()); - - v8::HeapProfiler* heap_profiler = node_isolate->GetHeapProfiler(); - heap_profiler->SetWrapperClassInfoProvider(BUFFER_CLASS_ID, WrapperInfo); + target->Set(String::New("setupBufferJS"), + FunctionTemplate::New(SetupBufferJS)->GetFunction()); } +} // namespace Buffer + } // namespace node NODE_MODULE(node_buffer, node::Buffer::Initialize) diff --git a/src/node_buffer.h b/src/node_buffer.h index 7b7cf8e586e199..b00badb35f5bd4 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -19,141 +19,44 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +#include "smalloc.h" +#include "v8.h" + #ifndef NODE_BUFFER_H_ #define NODE_BUFFER_H_ -#include "node.h" -#include "node_object_wrap.h" -#include "v8.h" -#include - namespace node { -/* A buffer is a chunk of memory stored outside the V8 heap, mirrored by an - * object in javascript. The object is not totally opaque, one can access - * individual bytes with [] and slice it into substrings or sub-buffers - * without copying memory. - */ - -/* - The C++ API for Buffer changed radically between v0.2 and v0.3, in fact - it was the reason for bumping the version. In v0.2 JavaScript Buffers and - C++ Buffers were in one-to-one correspondence via ObjectWrap. We found - that it was faster to expose the C++ Buffers to JavaScript as a - "SlowBuffer" which is used as a private backend to pure JavaScript - "Buffer" objects - a 'Buffer' in v0.3 might look like this: - - { _parent: s, - _offset: 520, - length: 5 } - - Migrating code C++ Buffer code from v0.2 to v0.3 is difficult. Here are - some tips: - - buffer->data() calls should become Buffer::Data(buffer) calls. - - buffer->length() calls should become Buffer::Length(buffer) calls. - - There should not be any ObjectWrap::Unwrap() calls. You should - not be storing pointers to Buffer objects at all - as they are - now considered internal structures. Instead consider making a - JavaScript reference to the buffer. - - See the source code node-png as an example of a module which successfully - compiles on both v0.2 and v0.3 while making heavy use of the C++ Buffer - API. - - */ - - -class NODE_EXTERN Buffer: public ObjectWrap { - public: - // mirrors deps/v8/src/objects.h - static const unsigned int kMaxLength = 0x3fffffff; - - static v8::Persistent constructor_template; - - static bool HasInstance(v8::Handle val); - - static inline char* Data(v8::Handle val) { - assert(val->IsObject()); - void* data = val.As()->GetIndexedPropertiesExternalArrayData(); - return static_cast(data); - } - - static inline char* Data(Buffer *b) { - return Buffer::Data(b->handle_); - } - - static inline size_t Length(v8::Handle val) { - assert(val->IsObject()); - int len = val.As() - ->GetIndexedPropertiesExternalArrayDataLength(); - return static_cast(len); - } - - static inline size_t Length(Buffer *b) { - return Buffer::Length(b->handle_); - } - - - ~Buffer(); - - typedef void (*free_callback)(char *data, void *hint); - - // C++ API for constructing fast buffer - static v8::Handle New(v8::Handle string); - - static void Initialize(v8::Handle target); - - // public constructor - static Buffer* New(size_t length); - // public constructor - data is copied - static Buffer* New(const char *data, size_t len); - // public constructor - static Buffer* New(char *data, size_t length, - free_callback callback, void *hint); - - private: - static v8::Handle New(const v8::Arguments &args); - - template - static v8::Handle StringSlice(const v8::Arguments &args); - static v8::Handle BinarySlice(const v8::Arguments &args); - static v8::Handle AsciiSlice(const v8::Arguments &args); - static v8::Handle Base64Slice(const v8::Arguments &args); - static v8::Handle Utf8Slice(const v8::Arguments &args); - static v8::Handle Ucs2Slice(const v8::Arguments &args); - static v8::Handle HexSlice(const v8::Arguments &args); - - template - static v8::Handle StringWrite(const v8::Arguments &args); - static v8::Handle BinaryWrite(const v8::Arguments &args); - static v8::Handle Base64Write(const v8::Arguments &args); - static v8::Handle AsciiWrite(const v8::Arguments &args); - static v8::Handle Utf8Write(const v8::Arguments &args); - static v8::Handle Ucs2Write(const v8::Arguments &args); - static v8::Handle HexWrite(const v8::Arguments &args); - static v8::Handle ReadFloatLE(const v8::Arguments &args); - static v8::Handle ReadFloatBE(const v8::Arguments &args); - static v8::Handle ReadDoubleLE(const v8::Arguments &args); - static v8::Handle ReadDoubleBE(const v8::Arguments &args); - static v8::Handle WriteFloatLE(const v8::Arguments &args); - static v8::Handle WriteFloatBE(const v8::Arguments &args); - static v8::Handle WriteDoubleLE(const v8::Arguments &args); - static v8::Handle WriteDoubleBE(const v8::Arguments &args); - static v8::Handle ByteLength(const v8::Arguments &args); - static v8::Handle MakeFastBuffer(const v8::Arguments &args); - static v8::Handle Fill(const v8::Arguments &args); - static v8::Handle Copy(const v8::Arguments &args); - - Buffer(v8::Handle wrapper, size_t length); - void Replace(char *data, size_t length, free_callback callback, void *hint); - - size_t length_; - char* data_; - free_callback callback_; - void* callback_hint_; -}; - - -} // namespace node buffer +namespace Buffer { + +static const unsigned int kMaxLength = smalloc::kMaxLength; + +bool HasInstance(v8::Handle val); +bool HasInstance(v8::Handle val); +char* Data(v8::Handle val); +char* Data(v8::Handle val); +size_t Length(v8::Handle val); +size_t Length(v8::Handle val); + +// public constructor +v8::Local New(size_t length); +// public constructor from string +v8::Local New(v8::Handle string); +// public constructor - data is copied +// TODO(trevnorris): should be something like Copy() +v8::Local New(const char* data, size_t len); +// public constructor - data is used, callback is passed data on object gc +v8::Local New(char* data, + size_t length, + smalloc::FreeCallback callback, + void* hint); + +// public constructor - data is used. +// TODO(trevnorris): should be New() for consistency +v8::Local Use(char* data, uint32_t len); + +} // namespace Buffer + +} // namespace node #endif // NODE_BUFFER_H_ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 0805956b1d9887..fffe6b6438938c 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -275,11 +275,6 @@ SSL_SESSION* SecureContext::GetSessionCallback(SSL* s, } -void SessionDataFree(char* data, void* hint) { - delete[] data; -} - - int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) { HandleScope scope(node_isolate); @@ -297,8 +292,8 @@ int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) { Handle argv[2] = { Buffer::New(reinterpret_cast(sess->session_id), - sess->session_id_length)->handle_, - Buffer::New(serialized, size, SessionDataFree, NULL)->handle_ + sess->session_id_length), + Buffer::Use(serialized, size) }; if (onnewsession_sym.IsEmpty()) { @@ -877,7 +872,7 @@ size_t ClientHelloParser::Write(const uint8_t* data, size_t len) { hello = Object::New(); hello->Set(sessionid_sym, Buffer::New(reinterpret_cast(session_id), - session_size)->handle_); + session_size)); argv[0] = hello; MakeCallback(conn_->handle_, onclienthello_sym, 1, argv); @@ -2292,11 +2287,11 @@ Handle CipherBase::Update(const Arguments& args) { return ThrowCryptoTypeError(ERR_get_error()); } - Buffer* buf = Buffer::New(reinterpret_cast(out), out_len); + Local buf = Buffer::New(reinterpret_cast(out), out_len); if (out) delete[] out; - return scope.Close(buf->handle_); + return scope.Close(buf); } @@ -2347,9 +2342,7 @@ Handle CipherBase::Final(const Arguments& args) { if (!r) return ThrowCryptoTypeError(ERR_get_error()); } - Buffer* buf = Buffer::New(reinterpret_cast(out_value), out_len); - - return scope.Close(buf->handle_); + return scope.Close(Buffer::New(reinterpret_cast(out_value), out_len)); } @@ -3547,11 +3540,6 @@ RandomBytesRequest::~RandomBytesRequest() { } -void RandomBytesFree(char* data, void* hint) { - delete[] data; -} - - template void RandomBytesWork(uv_work_t* work_req) { RandomBytesRequest* req = container_of(work_req, @@ -3588,10 +3576,8 @@ void RandomBytesCheck(RandomBytesRequest* req, Local argv[2]) { argv[1] = Local::New(node_isolate, Null(node_isolate)); } else { - // avoids the malloc + memcpy - Buffer* buffer = Buffer::New(req->data_, req->size_, RandomBytesFree, NULL); argv[0] = Local::New(node_isolate, Null(node_isolate)); - argv[1] = Local::New(node_isolate, buffer->handle_); + argv[1] = Buffer::Use(req->data_, req->size_); } } diff --git a/src/node_internals.h b/src/node_internals.h index 6055147b6c1f7c..9e2491a200f5be 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -74,6 +74,12 @@ inline static int snprintf(char* buf, unsigned int len, const char* fmt, ...) { # define ROUND_UP(a, b) ((a) % (b) ? ((a) + (b)) - ((a) % (b)) : (a)) #endif +#if defined(__GNUC__) && __GNUC__ >= 4 +# define MUST_USE_RESULT __attribute__((warn_unused_result)) +#else +# define MUST_USE_RESULT +#endif + // this would have been a template function were it not for the fact that g++ // sometimes fails to resolve it... #define THROW_ERROR(fun) \ @@ -137,6 +143,24 @@ inline bool IsBigEndian() { return GetEndianness() == kBigEndian; } +// parse index for external array data +inline MUST_USE_RESULT bool ParseArrayIndex(v8::Handle arg, + size_t def, + size_t* ret) { + if (arg->IsUndefined()) { + *ret = def; + return true; + } + + int32_t tmp_i = arg->Int32Value(); + + if (tmp_i < 0) + return false; + + *ret = static_cast(tmp_i); + return true; +} + } // namespace node #endif // SRC_NODE_INTERNALS_H_ diff --git a/src/slab_allocator.cc b/src/slab_allocator.cc index 476124cb6e48f3..1a98c8ecdb8269 100644 --- a/src/slab_allocator.cc +++ b/src/slab_allocator.cc @@ -71,10 +71,7 @@ void SlabAllocator::Initialize() { static Local NewSlab(unsigned int size) { HandleScope scope(node_isolate); - Local arg = Integer::NewFromUnsigned(ROUND_UP(size, 16), node_isolate); - Local buf = Buffer::constructor_template - ->GetFunction() - ->NewInstance(1, &arg); + Local buf = Buffer::New(ROUND_UP(size, 16)); return scope.Close(buf); } diff --git a/src/string_bytes.cc b/src/string_bytes.cc index db6bb8e5e61fd8..db810037d9ff03 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -624,7 +624,7 @@ Local StringBytes::Encode(const char* buf, Local val; switch (encoding) { case BUFFER: - return scope.Close(Buffer::New(buf, buflen)->handle_); + return scope.Close(Buffer::New(buf, buflen)); case ASCII: if (contains_non_ascii(buf, buflen)) { diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index c42898de88a6b8..75ece9b1da5f78 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -128,15 +128,14 @@ int TLSCallbacks::NewSessionCallback(SSL* s, SSL_SESSION* sess) { return 0; // Serialize session - Local buff = Local::New(Buffer::New(size)->handle_); + Local buff = Buffer::New(size); unsigned char* serialized = reinterpret_cast( Buffer::Data(buff)); memset(serialized, 0, size); i2d_SSL_SESSION(sess, &serialized); - Local session = Local::New( - Buffer::New(reinterpret_cast(sess->session_id), - sess->session_id_length)->handle_); + Local session = Buffer::New(reinterpret_cast(sess->session_id), + sess->session_id_length); Handle argv[2] = { session, buff }; MakeCallback(c->handle_, onnewsession_sym, ARRAY_SIZE(argv), argv); @@ -479,7 +478,7 @@ void TLSCallbacks::ClearOut() { do { read = SSL_read(ssl_, out, sizeof(out)); if (read > 0) { - Local buff = Local::New(Buffer::New(out, read)->handle_); + Local buff = Buffer::New(out, read); Handle argv[3] = { buff, Integer::New(0, node_isolate), @@ -775,7 +774,7 @@ void TLSCallbacks::ParseClientHello() { hello_obj = Object::New(); hello_obj->Set(sessionid_sym, Buffer::New(reinterpret_cast(session_id), - session_size)->handle_); + session_size)); argv[0] = hello_obj; MakeCallback(handle_, onclienthello_sym, 1, argv); diff --git a/test/simple/test-buffer-regress-GH-2659.js b/test/simple/test-buffer-regress-GH-2659.js deleted file mode 100644 index 19a1362fd77a2b..00000000000000 --- a/test/simple/test-buffer-regress-GH-2659.js +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -var common = require('../common'); -var assert = require('assert'); - -// This is not a great test. It depends on a Node internal, namely the slab -// size. Maybe we should expose that in some way. Then again, maybe not... -for (var n = 1; n <= 8192; ++n) { - Buffer(n); - Buffer(0).write('', 'base64'); -} diff --git a/test/simple/test-buffer.js b/test/simple/test-buffer.js index a465df8cc0e1b8..b5a76d90f8ee2e 100644 --- a/test/simple/test-buffer.js +++ b/test/simple/test-buffer.js @@ -22,18 +22,11 @@ var common = require('../common'); var assert = require('assert'); -var SlowBuffer = require('buffer').SlowBuffer; var Buffer = require('buffer').Buffer; // counter to ensure unique value is always copied var cntr = 0; -// Regression test for segfault introduced in commit e501ce4. -['base64','binary','ucs2','utf8','ascii'].forEach(function(encoding) { - var buf = new SlowBuffer(0); - buf.write('', encoding); -}); - var b = Buffer(1024); // safe constructor console.log('b.length == %d', b.length); @@ -128,14 +121,6 @@ for (var i = 0; i < b.length; i++) { } -// copy from fast to slow buffer -var sb = new SlowBuffer(b.length); -var copied = b.copy(sb); -console.log('copied %d bytes from b into sb'); -for (var i = 0; i < sb.length; i++) { - assert.strictEqual(sb[i], b[i]); -} - var caught_error = null; // try to copy from before the beginning of b @@ -146,15 +131,10 @@ try { caught_error = err; } -// copy from b to c with negative sourceStart -b.fill(++cntr); -c.fill(++cntr); -var copied = b.copy(c, 0, -1); -assert.strictEqual(c.length, copied); -console.log('copied %d bytes from b into c w/ negative sourceStart', copied); -for (var i = 0; i < c.length; i++) { - assert.strictEqual(b[i], c[i]); -} +// copy throws at negative sourceStart +assert.throws(function() { + Buffer(5).copy(Buffer(5), 0, -1); +}, RangeError); // check sourceEnd resets to targetEnd if former is greater than the latter b.fill(++cntr); @@ -165,31 +145,17 @@ for (var i = 0; i < c.length; i++) { assert.strictEqual(b[i], c[i]); } -// copy from fast buffer to slow buffer without parameters -var sb = new SlowBuffer(b.length); -sb.fill(++cntr, 0, sb.length); -b.fill(++cntr); -var copied = b.copy(sb); -console.log('copied %d bytes from fast buffer to slow buffer', copied); -for (var i = 0 ; i < b.length; i++) { - assert.strictEqual(b[i], sb[i]); -} - // throw with negative sourceEnd console.log('test copy at negative sourceEnd'); assert.throws(function() { b.copy(c, 0, 0, -1); }, RangeError); -// throw when sourceStart is greater than sourceEnd -assert.throws(function() { - b.copy(c, 0, 100, 10); -}, RangeError); +// when sourceStart is greater than sourceEnd, zero copied +assert.equal(b.copy(c, 0, 100, 10), 0); -// throw attempting to copy after end of c -assert.throws(function() { - b.copy(c, 512, 0, 10); -}, RangeError); +// when targetStart > targetLength, zero copied +assert.equal(b.copy(c, 512, 0, 10), 0); var caught_error; @@ -218,8 +184,14 @@ new Buffer('', 'binary'); new Buffer(0); // try to write a 0-length string beyond the end of b -b.write('', 1024); -b.write('', 2048); +assert.throws(function() { + b.write('', 2048); +}, RangeError); + +// throw when writing to negative offset +assert.throws(function() { + b.write('a', -1); +}, RangeError); // throw when writing past bounds from the pool assert.throws(function() { @@ -591,12 +563,9 @@ assert.equal(b2, b3); assert.equal(b2, b4); -// Test slice on SlowBuffer GH-843 -var SlowBuffer = process.binding('buffer').SlowBuffer; - -function buildSlowBuffer(data) { +function buildBuffer(data) { if (Array.isArray(data)) { - var buffer = new SlowBuffer(data.length); + var buffer = new Buffer(data.length); data.forEach(function(v, k) { buffer[k] = v; }); @@ -605,10 +574,10 @@ function buildSlowBuffer(data) { return null; } -var x = buildSlowBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); +var x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); console.log(x.inspect()); -assert.equal('', x.inspect()); +assert.equal('', x.inspect()); var z = x.slice(4); console.log(z.inspect()); @@ -669,7 +638,7 @@ for (; i < 32; i++) assert.equal(1, b[i]); for (; i < b.length; i++) assert.equal(0, b[i]); ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach(function(encoding) { - var b = new SlowBuffer(10); + var b = new Buffer(10); b.write('あいうえお', encoding); assert.equal(b.toString(encoding), 'あいうえお'); }); @@ -689,7 +658,7 @@ assert.equal(0xad, b[1]); assert.equal(0xbe, b[2]); assert.equal(0xef, b[3]); -// testing invalid encoding on SlowBuffer.toString +// testing invalid encoding on Buffer.toString caught_error = null; try { var copied = b.toString('invalid'); @@ -698,7 +667,7 @@ try { } assert.strictEqual('Unknown encoding: invalid', caught_error.message); -// testing invalid encoding on SlowBuffer.write +// testing invalid encoding on Buffer.write caught_error = null; try { var copied = b.write('some string', 0, 5, 'invalid'); @@ -708,11 +677,6 @@ try { assert.strictEqual('Unknown encoding: invalid', caught_error.message); -// This should not segfault the program. -assert.throws(function() { - new Buffer('"pong"', 0, 6, 8031, '127.0.0.1'); -}); - // #1210 Test UTF-8 string includes null character var buf = new Buffer('\0'); assert.equal(buf.length, 1); @@ -800,8 +764,8 @@ assert.equal(buf[4], 0); Buffer(3.3).toString(); // throws bad argument error in commit 43cb4ec assert.equal(Buffer(-1).length, 0); assert.equal(Buffer(NaN).length, 0); -assert.equal(Buffer(3.3).length, 4); -assert.equal(Buffer({length: 3.3}).length, 4); +assert.equal(Buffer(3.3).length, 3); +assert.equal(Buffer({length: 3.3}).length, 3); assert.equal(Buffer({length: 'BAM'}).length, 0); // Make sure that strings are not coerced to numbers. @@ -863,109 +827,79 @@ assert.throws(function() { }, RangeError); assert.throws(function() { new Buffer(0xFFFFFFFFF); -}, TypeError); +}, RangeError); // attempt to overflow buffers, similar to previous bug in array buffers assert.throws(function() { var buf = new Buffer(8); buf.readFloatLE(0xffffffff); -}, /Trying to access beyond buffer length/); +}, RangeError); assert.throws(function() { var buf = new Buffer(8); buf.writeFloatLE(0.0, 0xffffffff); -}, /Trying to access beyond buffer length/); +}, RangeError); assert.throws(function() { - var buf = new SlowBuffer(8); + var buf = new Buffer(8); buf.readFloatLE(0xffffffff); -}, /Trying to read beyond buffer length/); +}, RangeError); assert.throws(function() { - var buf = new SlowBuffer(8); + var buf = new Buffer(8); buf.writeFloatLE(0.0, 0xffffffff); -}, /Trying to write beyond buffer length/); +}, RangeError); // ensure negative values can't get past offset assert.throws(function() { var buf = new Buffer(8); buf.readFloatLE(-1); -}, /offset is not uint/); +}, RangeError); assert.throws(function() { var buf = new Buffer(8); buf.writeFloatLE(0.0, -1); -}, /offset is not uint/); +}, RangeError); assert.throws(function() { - var buf = new SlowBuffer(8); + var buf = new Buffer(8); buf.readFloatLE(-1); -}, /offset is not uint/); +}, RangeError); assert.throws(function() { - var buf = new SlowBuffer(8); + var buf = new Buffer(8); buf.writeFloatLE(0.0, -1); -}, /offset is not uint/); +}, RangeError); // offset checks var buf = new Buffer(0); -assert.throws(function() { buf.readUInt8(0); }, /beyond buffer length/); -assert.throws(function() { buf.readInt8(0); }, /beyond buffer length/); +assert.throws(function() { buf.readUInt8(0); }, RangeError); +assert.throws(function() { buf.readInt8(0); }, RangeError); [16, 32].forEach(function(bits) { var buf = new Buffer(bits / 8 - 1); - assert.throws( - function() { buf['readUInt' + bits + 'BE'](0); }, - /beyond buffer length/, - 'readUInt' + bits + 'BE' - ); - - assert.throws( - function() { buf['readUInt' + bits + 'LE'](0); }, - /beyond buffer length/, - 'readUInt' + bits + 'LE' - ); - - assert.throws( - function() { buf['readInt' + bits + 'BE'](0); }, - /beyond buffer length/, - 'readInt' + bits + 'BE()' - ); - - assert.throws( - function() { buf['readInt' + bits + 'LE'](0); }, - /beyond buffer length/, - 'readInt' + bits + 'LE()' - ); -}); + assert.throws(function() { buf['readUInt' + bits + 'BE'](0); }, + RangeError, + 'readUInt' + bits + 'BE'); -// SlowBuffer sanity checks. -assert.throws(function() { - var len = 0xfffff; - var sbuf = new SlowBuffer(len); - var buf = new Buffer(sbuf, len, 0); - SlowBuffer.makeFastBuffer(sbuf, buf, -len, len); // Should throw. - for (var i = 0; i < len; ++i) buf[i] = 0x42; // Try to force segfault. -}, RangeError); + assert.throws(function() { buf['readUInt' + bits + 'LE'](0); }, + RangeError, + 'readUInt' + bits + 'LE'); -assert.throws(function() { - var len = 0xfffff; - var sbuf = new SlowBuffer(len); - var buf = new Buffer(sbuf, len, -len); // Should throw. - for (var i = 0; i < len; ++i) buf[i] = 0x42; // Try to force segfault. -}, RangeError); + assert.throws(function() { buf['readInt' + bits + 'BE'](0); }, + RangeError, + 'readInt' + bits + 'BE()'); -assert.throws(function() { - var sbuf = new SlowBuffer(1); - var buf = new Buffer(sbuf, 1, 0); - buf.length = 0xffffffff; - buf.slice(0xffffff0, 0xffffffe); // Should throw. -}, Error); + assert.throws(function() { buf['readInt' + bits + 'LE'](0); }, + RangeError, + 'readInt' + bits + 'LE()'); +}); +// test Buffer slice (function() { var buf = new Buffer('0123456789'); assert.equal(buf.slice(-10, 10), '0123456789'); @@ -989,10 +923,3 @@ assert.equal(Buffer.byteLength('aaaa==', 'base64'), 3); assert.throws(function() { Buffer('', 'buffer'); }, TypeError); - -assert.doesNotThrow(function () { - var slow = new SlowBuffer(1); - assert(slow.write('', Buffer.poolSize * 10) === 0); - var fast = new Buffer(1); - assert(fast.write('', Buffer.poolSize * 10) === 0); -}); diff --git a/test/simple/test-readdouble.js b/test/simple/test-readdouble.js index 9fa23e090e4c4a..6749e5b2022746 100644 --- a/test/simple/test-readdouble.js +++ b/test/simple/test-readdouble.js @@ -22,7 +22,6 @@ /* * Tests to verify we're reading in doubles correctly */ -var SlowBuffer = process.binding('buffer').SlowBuffer; var common = require('../common'); var ASSERT = require('assert'); @@ -128,4 +127,3 @@ function test(clazz) { test(Buffer); -test(SlowBuffer); diff --git a/test/simple/test-readfloat.js b/test/simple/test-readfloat.js index 373fa209780cbf..586b992c5b8213 100644 --- a/test/simple/test-readfloat.js +++ b/test/simple/test-readfloat.js @@ -22,7 +22,6 @@ /* * Tests to verify we're reading in floats correctly */ -var SlowBuffer = process.binding('buffer').SlowBuffer; var common = require('../common'); var ASSERT = require('assert'); @@ -90,4 +89,3 @@ function test(clazz) { test(Buffer); -test(SlowBuffer); diff --git a/test/simple/test-readint.js b/test/simple/test-readint.js index f1bae2d130b5d2..f1b79e816a3ff8 100644 --- a/test/simple/test-readint.js +++ b/test/simple/test-readint.js @@ -22,7 +22,6 @@ /* * Tests to verify we're reading in signed integers correctly */ -var SlowBuffer = process.binding('buffer').SlowBuffer; var common = require('../common'); var ASSERT = require('assert'); @@ -115,8 +114,5 @@ function test32(clazz) { test8(Buffer); -test8(SlowBuffer); test16(Buffer); -test16(SlowBuffer); test32(Buffer); -test32(SlowBuffer); diff --git a/test/simple/test-readuint.js b/test/simple/test-readuint.js index e63bba70058ccd..2b11a61c929d22 100644 --- a/test/simple/test-readuint.js +++ b/test/simple/test-readuint.js @@ -23,7 +23,6 @@ * A battery of tests to help us read a series of uints */ -var SlowBuffer = process.binding('buffer').SlowBuffer; var common = require('../common'); var ASSERT = require('assert'); @@ -106,8 +105,5 @@ function test32(clazz) { test8(Buffer); -test8(SlowBuffer); test16(Buffer); -test16(SlowBuffer); test32(Buffer); -test32(SlowBuffer); diff --git a/test/simple/test-writedouble.js b/test/simple/test-writedouble.js index c4bdc1b10d2bfe..10b87d9b7f52f1 100644 --- a/test/simple/test-writedouble.js +++ b/test/simple/test-writedouble.js @@ -22,7 +22,6 @@ /* * Tests to verify we're writing doubles correctly */ -var SlowBuffer = process.binding('buffer').SlowBuffer; var common = require('../common'); var ASSERT = require('assert'); @@ -194,4 +193,3 @@ function test(clazz) { test(Buffer); -test(SlowBuffer); diff --git a/test/simple/test-writefloat.js b/test/simple/test-writefloat.js index f72cedeee96c95..4c736b73afef79 100644 --- a/test/simple/test-writefloat.js +++ b/test/simple/test-writefloat.js @@ -22,7 +22,6 @@ /* * Tests to verify we're writing floats correctly */ -var SlowBuffer = process.binding('buffer').SlowBuffer; var common = require('../common'); var ASSERT = require('assert'); @@ -132,4 +131,3 @@ function test(clazz) { test(Buffer); -test(SlowBuffer); diff --git a/test/simple/test-writeint.js b/test/simple/test-writeint.js index b7ebd6964f02f0..10540b6bd75312 100644 --- a/test/simple/test-writeint.js +++ b/test/simple/test-writeint.js @@ -22,7 +22,6 @@ /* * Tests to verify we're writing signed integers correctly */ -var SlowBuffer = process.binding('buffer').SlowBuffer; var common = require('../common'); var ASSERT = require('assert'); @@ -185,8 +184,5 @@ function test32(clazz) { test8(Buffer); -test8(SlowBuffer); test16(Buffer); -test16(SlowBuffer); test32(Buffer); -test32(SlowBuffer); diff --git a/test/simple/test-writeuint.js b/test/simple/test-writeuint.js index 91b5c6c9c64342..da0d7952812604 100644 --- a/test/simple/test-writeuint.js +++ b/test/simple/test-writeuint.js @@ -22,7 +22,6 @@ /* * A battery of tests to help us read a series of uints */ -var SlowBuffer = process.binding('buffer').SlowBuffer; var common = require('../common'); var ASSERT = require('assert'); @@ -144,8 +143,5 @@ function test32(clazz) { test8(Buffer); -test8(SlowBuffer); test16(Buffer); -test16(SlowBuffer); test32(Buffer); -test32(SlowBuffer);