Browse Source

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.
v0.11.3-release
Trevor Norris 12 years ago
parent
commit
3a2f273bd7
  1. 133
      doc/api/buffer.markdown
  2. 654
      lib/buffer.js
  3. 1
      src/node.js
  4. 723
      src/node_buffer.cc
  5. 165
      src/node_buffer.h
  6. 28
      src/node_crypto.cc
  7. 24
      src/node_internals.h
  8. 5
      src/slab_allocator.cc
  9. 2
      src/string_bytes.cc
  10. 11
      src/tls_wrap.cc
  11. 30
      test/simple/test-buffer-regress-GH-2659.js
  12. 181
      test/simple/test-buffer.js
  13. 2
      test/simple/test-readdouble.js
  14. 2
      test/simple/test-readfloat.js
  15. 4
      test/simple/test-readint.js
  16. 4
      test/simple/test-readuint.js
  17. 2
      test/simple/test-writedouble.js
  18. 2
      test/simple/test-writefloat.js
  19. 4
      test/simple/test-writeint.js
  20. 4
      test/simple/test-writeuint.js

133
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 Returns true if the `encoding` is a valid encoding argument, or false
otherwise. 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]) ### buf.write(string, [offset], [length], [encoding])
* `string` String - data to be written to buffer * `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 // 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]) ### buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
* `targetBuffer` Buffer object - Buffer to copy into * `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 ## Class: SlowBuffer
This class is primarily for internal use. JavaScript programs should Deprecated. SlowBuffer now returns an instance of Buffer.
use Buffer instead of using SlowBuffer.
In order to avoid the overhead of allocating many C++ Buffer objects for 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 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 in 8Kb (8192 byte) chunks. This is now handled by Smalloc.
will be backed by a parent SlowBuffer object. If it is larger than this,
then Node will allocate a SlowBuffer slab for it directly.

654
lib/buffer.js

@ -19,299 +19,202 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE. // 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 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; exports.INSPECT_MAX_BYTES = 50;
// Make SlowBuffer inherit from Buffer. // add methods to Buffer prototype
// This is an exception to the rule that __proto__ is not allowed in core. buffer.setupBufferJS(Buffer);
SlowBuffer.prototype.__proto__ = Buffer.prototype;
function Buffer(subject, encoding) {
if (!(this instanceof Buffer))
return new Buffer(subject, encoding);
function clamp(index, len, defaultValue) { var type = typeof subject;
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;
}
switch (type) {
case 'number':
this.length = subject > 0 ? Math.floor(subject) : 0;
break;
function toHex(n) { case 'string':
if (n < 16) return '0' + n.toString(16); this.length = Buffer.byteLength(subject, encoding = encoding || 'utf8');
return n.toString(16); break;
}
case 'object':
this.length = +subject.length > 0 ? Math.floor(+subject.length) : 0;
break;
SlowBuffer.prototype.toString = function(encoding, start, end) { // undef first arg returns unallocated buffer, also assumes length passed.
encoding = String(encoding || 'utf8').toLowerCase(); // this is a stop-gap for now while look for better architecture.
start = +start || 0; // for internal use only.
if (typeof end !== 'number') end = this.length; case 'undefined':
this.length = encoding;
return;
// Fastpath empty strings default:
if (+end == start) { throw new TypeError('must start with number, buffer, array or string');
return '';
} }
switch (encoding) { if (this.length > kMaxLength)
case 'hex': throw new RangeError('length > kMaxLength');
return this.hexSlice(start, end);
case 'utf8':
case 'utf-8':
return this.utf8Slice(start, end);
case 'ascii': alloc(this, this.length);
return this.asciiSlice(start, end);
case 'binary': if (type !== 'number') {
return this.binarySlice(start, end); 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': // Static methods
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return this.ucs2Slice(start, end);
default: Buffer.isBuffer = function isBuffer(b) {
throw new TypeError('Unknown encoding: ' + encoding); return b instanceof Buffer;
}
}; };
SlowBuffer.prototype.write = function(string, offset, length, encoding) { Buffer.isEncoding = function(encoding) {
// Support both (string, offset, length, encoding) switch ((encoding + '').toLowerCase()) {
// 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) {
case 'hex': case 'hex':
return this.hexWrite(string, offset, length);
case 'utf8': case 'utf8':
case 'utf-8': case 'utf-8':
return this.utf8Write(string, offset, length);
case 'ascii': case 'ascii':
return this.asciiWrite(string, offset, length);
case 'binary': case 'binary':
return this.binaryWrite(string, offset, length);
case 'base64': case 'base64':
return this.base64Write(string, offset, length);
case 'ucs2': case 'ucs2':
case 'ucs-2': case 'ucs-2':
case 'utf16le': case 'utf16le':
case 'utf-16le': case 'utf-16le':
return this.ucs2Write(string, offset, length); case 'raw':
return true;
default: default:
throw new TypeError('Unknown encoding: ' + encoding); return false;
} }
}; };
// slice(start, end) Buffer.concat = function(list, length) {
SlowBuffer.prototype.slice = function(start, end) { if (!Array.isArray(list))
var len = this.length; throw new TypeError('Usage: Buffer.concat(list[, 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');
}
this.length = +encoding > 0 ? Math.ceil(encoding) : 0; if (typeof length === 'undefined') {
this.parent = subject.parent ? subject.parent : subject; length = 0;
this.offset = offset; for (var i = 0; i < list.length; i++)
length += list[i].length;
} else { } else {
// Find the length length = ~~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.');
}
if (this.length > Buffer.poolSize) { if (length < 0) length = 0;
// Big buffer, just alloc one.
this.parent = new SlowBuffer(this.length);
this.offset = 0;
} else if (this.length > 0) { if (list.length === 0)
// Small buffer. return new Buffer(0);
if (!pool || pool.length - pool.used < this.length) allocPool(); else if (list.length === 1)
this.parent = pool; return list[0];
this.offset = pool.used;
pool.used += this.length;
if (pool.used & 7) pool.used = (pool.used + 8) & ~7;
} else { if (length < 0)
// Zero-length buffer throw new RangeError('length is not a positive number');
this.parent = zeroBuffer;
this.offset = 0;
}
// optimize by branching logic for new allocations var buffer = new Buffer(length);
if (typeof subject !== 'number') { var pos = 0;
if (type === 'string') { for (var i = 0; i < list.length; i++) {
// We are a string var buf = list[i];
this.length = this.write(subject, 0, encoding); buf.copy(buffer, pos);
// if subject is buffer then use built-in copy method pos += buf.length;
} 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];
}
}
} }
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; // toString(encoding, start=0, end=buffer.length)
exports.Buffer = Buffer; 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) { if (start < 0) start = 0;
switch (encoding && encoding.toLowerCase()) { if (end > this.length) end = this.length;
if (end <= start) return '';
switch (encoding) {
case 'hex': case 'hex':
return this.hexSlice(start, end);
case 'utf8': case 'utf8':
case 'utf-8': case 'utf-8':
return this.utf8Slice(start, end);
case 'ascii': case 'ascii':
return this.asciiSlice(start, end);
case 'binary': case 'binary':
return this.binarySlice(start, end);
case 'base64': case 'base64':
return this.base64Slice(start, end);
case 'ucs2': case 'ucs2':
case 'ucs-2': case 'ucs-2':
case 'utf16le': case 'utf16le':
case 'utf-16le': case 'utf-16le':
case 'raw': return this.ucs2Slice(start, end);
return true;
default: 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 // Inspect
Buffer.prototype.inspect = function inspect() { Buffer.prototype.inspect = function inspect() {
var out = [], var str = '';
len = this.length, if (this.length > 0)
name = this.constructor.name; str = this.hexSlice(0, this.length).match(/.{2}/g).join(' ');
return '<' + this.constructor.name + ' ' + str + '>';
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(' ') + '>';
}; };
// TODO(trevnorris): DEPRECATE
Buffer.prototype.get = function get(offset) { Buffer.prototype.get = function get(offset) {
offset = ~~offset;
if (offset < 0 || offset >= this.length) if (offset < 0 || offset >= this.length)
throw new RangeError('offset is out of bounds'); throw new RangeError('index out of range');
return this.parent[this.offset + offset]; return this[offset];
}; };
// TODO(trevnorris): DEPRECATE
Buffer.prototype.set = function set(offset, v) { Buffer.prototype.set = function set(offset, v) {
offset = ~~offset;
if (offset < 0 || offset >= this.length) if (offset < 0 || offset >= this.length)
throw new RangeError('offset is out of bounds'); throw new RangeError('index out of range');
return this.parent[this.offset + offset] = v; 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) { Buffer.prototype.write = function(string, offset, length, encoding) {
// Support both (string, offset, length, encoding) // Support both (string, offset, length, encoding)
// and the legacy (string, encoding, offset, length) // and the legacy (string, encoding, offset, length)
@ -320,6 +223,7 @@ Buffer.prototype.write = function(string, offset, length, encoding) {
encoding = length; encoding = length;
length = undefined; length = undefined;
} }
// TODO(trevnorris): DEPRECATE
} else { // legacy } else { // legacy
var swap = encoding; var swap = encoding;
encoding = offset; encoding = offset;
@ -337,7 +241,11 @@ Buffer.prototype.write = function(string, offset, length, encoding) {
length = remaining; 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)) if (string.length > 0 && (length < 0 || offset < 0))
throw new RangeError('attempt to write beyond buffer bounds'); throw new RangeError('attempt to write beyond buffer bounds');
@ -345,32 +253,32 @@ Buffer.prototype.write = function(string, offset, length, encoding) {
var ret; var ret;
switch (encoding) { switch (encoding) {
case 'hex': case 'hex':
ret = this.parent.hexWrite(string, this.offset + offset, length); ret = this.hexWrite(string, offset, length);
break; break;
case 'utf8': case 'utf8':
case 'utf-8': case 'utf-8':
ret = this.parent.utf8Write(string, this.offset + offset, length); ret = this.utf8Write(string, offset, length);
break; break;
case 'ascii': case 'ascii':
ret = this.parent.asciiWrite(string, this.offset + offset, length); ret = this.asciiWrite(string, offset, length);
break; break;
case 'binary': case 'binary':
ret = this.parent.binaryWrite(string, this.offset + offset, length); ret = this.binaryWrite(string, offset, length);
break; break;
case 'base64': case 'base64':
// Warning: maxLength not taken into account in base64Write // Warning: maxLength not taken into account in base64Write
ret = this.parent.base64Write(string, this.offset + offset, length); ret = this.base64Write(string, offset, length);
break; break;
case 'ucs2': case 'ucs2':
case 'ucs-2': case 'ucs-2':
case 'utf16le': case 'utf16le':
case 'utf-16le': case 'utf-16le':
ret = this.parent.ucs2Write(string, this.offset + offset, length); ret = this.ucs2Write(string, offset, length);
break; break;
default: default:
@ -389,203 +297,48 @@ Buffer.prototype.toJSON = function() {
}; };
// toString(encoding, start=0, end=buffer.length) // TODO(trevnorris): currently works like Array.prototype.slice(), which
Buffer.prototype.toString = function(encoding, start, end) { // doesn't follow the new standard for throwing on out of range indexes.
encoding = String(encoding || 'utf8').toLowerCase(); Buffer.prototype.slice = function(start, end) {
var len = this.length;
if (typeof start !== 'number' || start < 0) { start = ~~start;
start = 0; end = typeof end === 'undefined' ? len : ~~end;
} else if (start > this.length) {
start = this.length; if (start < 0) {
} start += len;
if (start < 0)
if (typeof end !== 'number' || end > this.length) { start = 0;
end = this.length; } else if (start > len) {
} else if (end < 0) { start = len;
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];
} }
if (typeof length !== 'number') { if (end < 0) {
length = 0; end += len;
for (var i = 0; i < list.length; i++) { if (end < 0)
var buf = list[i]; end = 0;
length += buf.length; } 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) if (end < start)
throw new RangeError('sourceEnd < sourceStart'); end = start;
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);
};
Buffer.prototype.binarySlice = function(start, end) { var buf = new Buffer();
return this.toString('binary', start, end); buf.parent = sliceOnto(this, buf, start, end);
}; buf.length = end - start;
Buffer.prototype.asciiSlice = function(start, end) { return buf;
return this.toString('ascii', start, end);
}; };
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) { function checkOffset(offset, ext, length) {
if ((offset % 1) !== 0 || offset < 0) if (offset < 0 || offset + ext > length)
throw new RangeError('offset is not uint'); throw new RangeError('index out of range');
if (offset + ext > length)
throw new RangeError('Trying to access beyond buffer length');
} }
Buffer.prototype.readUInt8 = function(offset, noAssert) { Buffer.prototype.readUInt8 = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 1, this.length); checkOffset(offset, 1, this.length);
return this[offset]; return this[offset];
@ -601,12 +354,12 @@ function readUInt16(buffer, offset, isBigEndian) {
val = buffer[offset]; val = buffer[offset];
val |= buffer[offset + 1] << 8; val |= buffer[offset + 1] << 8;
} }
return val; return val;
} }
Buffer.prototype.readUInt16LE = function(offset, noAssert) { Buffer.prototype.readUInt16LE = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 2, this.length); checkOffset(offset, 2, this.length);
return readUInt16(this, offset, false, noAssert); return readUInt16(this, offset, false, noAssert);
@ -614,6 +367,7 @@ Buffer.prototype.readUInt16LE = function(offset, noAssert) {
Buffer.prototype.readUInt16BE = function(offset, noAssert) { Buffer.prototype.readUInt16BE = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 2, this.length); checkOffset(offset, 2, this.length);
return readUInt16(this, offset, true, noAssert); return readUInt16(this, offset, true, noAssert);
@ -622,7 +376,6 @@ Buffer.prototype.readUInt16BE = function(offset, noAssert) {
function readUInt32(buffer, offset, isBigEndian, noAssert) { function readUInt32(buffer, offset, isBigEndian, noAssert) {
var val = 0; var val = 0;
if (isBigEndian) { if (isBigEndian) {
val = buffer[offset + 1] << 16; val = buffer[offset + 1] << 16;
val |= buffer[offset + 2] << 8; val |= buffer[offset + 2] << 8;
@ -634,12 +387,12 @@ function readUInt32(buffer, offset, isBigEndian, noAssert) {
val |= buffer[offset]; val |= buffer[offset];
val = val + (buffer[offset + 3] << 24 >>> 0); val = val + (buffer[offset + 3] << 24 >>> 0);
} }
return val; return val;
} }
Buffer.prototype.readUInt32LE = function(offset, noAssert) { Buffer.prototype.readUInt32LE = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 4, this.length); checkOffset(offset, 4, this.length);
return readUInt32(this, offset, false, noAssert); return readUInt32(this, offset, false, noAssert);
@ -647,6 +400,7 @@ Buffer.prototype.readUInt32LE = function(offset, noAssert) {
Buffer.prototype.readUInt32BE = function(offset, noAssert) { Buffer.prototype.readUInt32BE = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 4, this.length); checkOffset(offset, 4, this.length);
return readUInt32(this, offset, true, noAssert); return readUInt32(this, offset, true, noAssert);
@ -700,6 +454,7 @@ Buffer.prototype.readUInt32BE = function(offset, noAssert) {
*/ */
Buffer.prototype.readInt8 = function(offset, noAssert) { Buffer.prototype.readInt8 = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 1, this.length); checkOffset(offset, 1, this.length);
if (!(this[offset] & 0x80)) if (!(this[offset] & 0x80))
@ -710,7 +465,6 @@ Buffer.prototype.readInt8 = function(offset, noAssert) {
function readInt16(buffer, offset, isBigEndian) { function readInt16(buffer, offset, isBigEndian) {
var val = readUInt16(buffer, offset, isBigEndian); var val = readUInt16(buffer, offset, isBigEndian);
if (!(val & 0x8000)) if (!(val & 0x8000))
return val; return val;
return (0xffff - val + 1) * -1; return (0xffff - val + 1) * -1;
@ -718,6 +472,7 @@ function readInt16(buffer, offset, isBigEndian) {
Buffer.prototype.readInt16LE = function(offset, noAssert) { Buffer.prototype.readInt16LE = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 2, this.length); checkOffset(offset, 2, this.length);
return readInt16(this, offset, false); return readInt16(this, offset, false);
@ -725,6 +480,7 @@ Buffer.prototype.readInt16LE = function(offset, noAssert) {
Buffer.prototype.readInt16BE = function(offset, noAssert) { Buffer.prototype.readInt16BE = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 2, this.length); checkOffset(offset, 2, this.length);
return readInt16(this, offset, true); return readInt16(this, offset, true);
@ -733,7 +489,6 @@ Buffer.prototype.readInt16BE = function(offset, noAssert) {
function readInt32(buffer, offset, isBigEndian) { function readInt32(buffer, offset, isBigEndian) {
var val = readUInt32(buffer, offset, isBigEndian); var val = readUInt32(buffer, offset, isBigEndian);
if (!(val & 0x80000000)) if (!(val & 0x80000000))
return (val); return (val);
return (0xffffffff - val + 1) * -1; return (0xffffffff - val + 1) * -1;
@ -741,6 +496,7 @@ function readInt32(buffer, offset, isBigEndian) {
Buffer.prototype.readInt32LE = function(offset, noAssert) { Buffer.prototype.readInt32LE = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 4, this.length); checkOffset(offset, 4, this.length);
return readInt32(this, offset, false); return readInt32(this, offset, false);
@ -748,50 +504,24 @@ Buffer.prototype.readInt32LE = function(offset, noAssert) {
Buffer.prototype.readInt32BE = function(offset, noAssert) { Buffer.prototype.readInt32BE = function(offset, noAssert) {
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkOffset(offset, 4, this.length); checkOffset(offset, 4, this.length);
return readInt32(this, offset, true); 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) { 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'); throw TypeError('value is out of bounds');
if ((offset % 1) !== 0 || offset < 0) if (offset < 0 || offset + ext > buffer.length || buffer.length + offset < 0)
throw TypeError('offset is not uint'); throw RangeError('index out of range');
if (offset + ext > buffer.length || buffer.length + offset < 0)
throw RangeError('Trying to write outside buffer length');
} }
Buffer.prototype.writeUInt8 = function(value, offset, noAssert) { Buffer.prototype.writeUInt8 = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 1, 0xff, 0); checkInt(this, value, offset, 1, 0xff, 0);
this[offset] = value; this[offset] = value;
@ -810,6 +540,8 @@ function writeUInt16(buffer, value, offset, isBigEndian) {
Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) { Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 2, 0xffff, 0); checkInt(this, value, offset, 2, 0xffff, 0);
writeUInt16(this, value, offset, false); writeUInt16(this, value, offset, false);
@ -817,6 +549,8 @@ Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) {
Buffer.prototype.writeUInt16BE = function(value, offset, noAssert) { Buffer.prototype.writeUInt16BE = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 2, 0xffff, 0); checkInt(this, value, offset, 2, 0xffff, 0);
writeUInt16(this, value, offset, true); writeUInt16(this, value, offset, true);
@ -839,6 +573,8 @@ function writeUInt32(buffer, value, offset, isBigEndian) {
Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) { Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 4, 0xffffffff, 0); checkInt(this, value, offset, 4, 0xffffffff, 0);
writeUInt32(this, value, offset, false); writeUInt32(this, value, offset, false);
@ -846,6 +582,8 @@ Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) {
Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) { Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 4, 0xffffffff, 0); checkInt(this, value, offset, 4, 0xffffffff, 0);
writeUInt32(this, value, offset, true); writeUInt32(this, value, offset, true);
@ -890,6 +628,8 @@ Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) {
*/ */
Buffer.prototype.writeInt8 = function(value, offset, noAssert) { Buffer.prototype.writeInt8 = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 1, 0x7f, -0x80); checkInt(this, value, offset, 1, 0x7f, -0x80);
if (value < 0) value = 0xff + value + 1; 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) { Buffer.prototype.writeInt16LE = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 2, 0x7fff, -0x8000); checkInt(this, value, offset, 2, 0x7fff, -0x8000);
if (value < 0) value = 0xffff + value + 1; 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) { Buffer.prototype.writeInt16BE = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 2, 0x7fff, -0x8000); checkInt(this, value, offset, 2, 0x7fff, -0x8000);
if (value < 0) value = 0xffff + value + 1; 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) { Buffer.prototype.writeInt32LE = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000); checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000);
if (value < 0) value = 0xffffffff + value + 1; 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) { Buffer.prototype.writeInt32BE = function(value, offset, noAssert) {
value = +value;
offset = ~~offset;
if (!noAssert) if (!noAssert)
checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000); checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000);
if (value < 0) value = 0xffffffff + value + 1; if (value < 0) value = 0xffffffff + value + 1;
writeUInt32(this, value, offset, true); 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);
};

1
src/node.js

@ -163,7 +163,6 @@
global.GLOBAL = global; global.GLOBAL = global;
global.root = global; global.root = global;
global.Buffer = NativeModule.require('buffer').Buffer; global.Buffer = NativeModule.require('buffer').Buffer;
process.binding('buffer').setFastBufferConstructor(global.Buffer);
process.domain = null; process.domain = null;
process._exiting = false; process._exiting = false;
}; };

723
src/node_buffer.cc

@ -20,367 +20,369 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "node_buffer.h"
#include "node.h" #include "node.h"
#include "node_internals.h"
#include "node_buffer.h"
#include "smalloc.h"
#include "string_bytes.h" #include "string_bytes.h"
#include "v8.h" #include "v8.h"
#include "v8-profiler.h" #include "v8-profiler.h"
#include <assert.h> #include <assert.h>
#include <string.h> // memcpy #include <string.h>
#include <limits.h> #include <limits.h>
#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<Object> obj = argT; \
size_t obj_length = obj->GetIndexedPropertiesExternalArrayDataLength(); \
char* obj_data = static_cast<char*>( \
obj->GetIndexedPropertiesExternalArrayData());
using namespace v8; #define SLICE_START_END(start_arg, end_arg, end_max) \
size_t start; \
#define SLICE_ARGS(start_arg, end_arg) \ size_t end; \
if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \ CHECK_NOT_OOB(ParseArrayIndex(start_arg, 0, &start)); \
return ThrowException(Exception::TypeError( \ CHECK_NOT_OOB(ParseArrayIndex(end_arg, end_max, &end)); \
String::New("Bad argument."))); \ if (end < start) end = start; \
} \ CHECK_NOT_OOB(end <= end_max); \
int32_t start = start_arg->Int32Value(); \ size_t length = end - start;
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"))); \
}
namespace node {
static Persistent<String> length_symbol; namespace Buffer {
static Persistent<String> write_sym;
static Persistent<Function> fast_buffer_constructor;
Persistent<FunctionTemplate> Buffer::constructor_template;
Handle<Object> Buffer::New(Handle<String> string) { using v8::Arguments;
HandleScope scope(node_isolate); 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<Object> global = v8::Context::GetCurrent()->Global();
Local<Value> bv = global->Get(String::NewSymbol("Buffer"));
assert(bv->IsFunction());
Local<Function> b = Local<Function>::Cast(bv);
Local<Value> argv[1] = { Local<Value>::New(node_isolate, string) }; Persistent<Function> p_buffer_fn;
Local<Object> instance = b->NewInstance(1, argv);
return scope.Close(instance);
bool HasInstance(Handle<Value> val) {
return val->IsObject() && HasInstance(val.As<Object>());
} }
Buffer* Buffer::New(size_t length) { bool HasInstance(Handle<Object> obj) {
HandleScope scope(node_isolate); if (!obj->HasIndexedPropertiesInExternalArrayData())
return false;
v8::ExternalArrayType type = obj->GetIndexedPropertiesExternalArrayDataType();
return type == v8::kExternalUnsignedByteArray;
}
Local<Value> arg = Integer::NewFromUnsigned(length, node_isolate);
Local<Object> b = constructor_template->GetFunction()->NewInstance(1, &arg);
if (b.IsEmpty()) return NULL;
return ObjectWrap::Unwrap<Buffer>(b); char* Data(Handle<Value> val) {
assert(val->IsObject());
return Data(val.As<Object>());
} }
Buffer* Buffer::New(const char* data, size_t length) { char* Data(Handle<Object> obj) {
HandleScope scope(node_isolate); assert(obj->HasIndexedPropertiesInExternalArrayData());
return static_cast<char*>(obj->GetIndexedPropertiesExternalArrayData());
}
Local<Value> arg = Integer::NewFromUnsigned(0, node_isolate);
Local<Object> obj = constructor_template->GetFunction()->NewInstance(1, &arg);
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(obj); size_t Length(Handle<Value> val) {
buffer->Replace(const_cast<char*>(data), length, NULL, NULL); assert(val->IsObject());
return Length(val.As<Object>());
}
return buffer; size_t Length(Handle<Object> obj) {
assert(obj->HasIndexedPropertiesInExternalArrayData());
return obj->GetIndexedPropertiesExternalArrayDataLength();
} }
Buffer* Buffer::New(char *data, size_t length, // TODO(trevnorris): would be more efficient to use StringBytes to calculate the
free_callback callback, void *hint) { // length and write out the data beforehand then do the same as New().
Local<Object> New(Handle<String> string) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
Local<Value> arg = Integer::NewFromUnsigned(0, node_isolate); Handle<Value> argv[1] = { string };
Local<Object> obj = constructor_template->GetFunction()->NewInstance(1, &arg); Local<Object> obj = p_buffer_fn->NewInstance(1, argv);
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(obj); return scope.Close(obj);
buffer->Replace(data, length, callback, hint);
return buffer;
} }
Handle<Value> Buffer::New(const Arguments& args) { // TODO(trevnorris): these have a flaw by needing to call the Buffer inst then
if (!args.IsConstructCall()) { // Alloc. continue to look for a better architecture.
return FromConstructorTemplate(constructor_template, args); Local<Object> New(size_t length) {
}
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
if (!args[0]->IsUint32()) return ThrowTypeError("Bad argument"); assert(length <= kMaxLength);
size_t length = args[0]->Uint32Value(); Handle<Value> argv[2];
if (length > Buffer::kMaxLength) { // this is safe b/c Undefined and length fits in an SMI, so there's no risk
return ThrowRangeError("length > kMaxLength"); // of GC reclaiming the values prematurely.
} argv[0] = Undefined(node_isolate);
new Buffer(args.This(), length); argv[1] = Uint32::New(length, node_isolate);
Local<Object> obj = p_buffer_fn->NewInstance(2, argv);
return args.This(); smalloc::Alloc(obj, new char[length], length);
return scope.Close(obj);
} }
Buffer::Buffer(Handle<Object> wrapper, size_t length) : ObjectWrap() { // TODO(trevnorris): for backwards compatibility this is left to copy the data,
Wrap(wrapper); // but for consistency w/ the other should use data. And a copy version renamed
// to something else.
Local<Object> New(const char* data, size_t length) {
HandleScope scope(node_isolate);
length_ = 0; assert(length <= kMaxLength);
callback_ = NULL;
handle_.SetWrapperClassId(node_isolate, BUFFER_CLASS_ID);
Replace(NULL, length, NULL, NULL); Handle<Value> 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<Object> 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() { return scope.Close(obj);
Replace(NULL, 0, NULL, NULL);
} }
// if replace doesn't have a callback, data must be copied Local<Object> New(char* data,
// const_cast in Buffer::New requires this size_t length,
void Buffer::Replace(char *data, size_t length, smalloc::FreeCallback callback,
free_callback callback, void *hint) { void* hint) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
if (callback_) { assert(length <= kMaxLength);
callback_(data_, callback_hint_);
} else if (length_) {
delete [] data_;
node_isolate->AdjustAmountOfExternalAllocatedMemory(
-static_cast<intptr_t>(sizeof(Buffer) + length_));
}
length_ = length; Handle<Value> argv[2];
callback_ = callback; // this is safe b/c Undefined and length fits in an SMI, so there's no risk
callback_hint_ = hint; // of GC reclaiming the values prematurely.
argv[0] = Undefined(node_isolate);
if (callback_) { argv[1] = Uint32::New(length, node_isolate);
data_ = data; Local<Object> obj = p_buffer_fn->NewInstance(2, argv);
} else if (length_) {
data_ = new char[length_]; smalloc::Alloc(obj, data, length, callback, hint);
if (data)
memcpy(data_, data, length_);
node_isolate->AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer) +
length_);
} else {
data_ = NULL;
}
handle_->SetIndexedPropertiesToExternalArrayData(data_, return scope.Close(obj);
kExternalUnsignedByteArray,
length_);
handle_->Set(length_symbol, Integer::NewFromUnsigned(length_, node_isolate));
} }
template <encoding encoding> Local<Object> Use(char* data, uint32_t length) {
Handle<Value> Buffer::StringSlice(const Arguments& args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
Buffer *parent = ObjectWrap::Unwrap<Buffer>(args.This());
SLICE_ARGS(args[0], args[1])
const char* src = parent->data_ + start; assert(length <= kMaxLength);
size_t slen = (end - start);
return scope.Close(StringBytes::Encode(src, slen, encoding));
}
Handle<Value> 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<Object> obj = p_buffer_fn->NewInstance(2, argv);
Handle<Value> Buffer::BinarySlice(const Arguments& args) { smalloc::Alloc(obj, data, length);
return Buffer::StringSlice<BINARY>(args);
return scope.Close(obj);
} }
Handle<Value> Buffer::AsciiSlice(const Arguments& args) { template <encoding encoding>
return Buffer::StringSlice<ASCII>(args); Handle<Value> StringSlice(const Arguments& args) {
} HandleScope scope(node_isolate);
ARGS_THIS(args.This())
SLICE_START_END(args[0], args[1], obj_length)
Handle<Value> Buffer::Utf8Slice(const Arguments& args) { return scope.Close(StringBytes::Encode(obj_data + start, length, encoding));
return Buffer::StringSlice<UTF8>(args);
} }
Handle<Value> Buffer::Ucs2Slice(const Arguments& args) { Handle<Value> BinarySlice(const Arguments& args) {
return Buffer::StringSlice<UCS2>(args); return StringSlice<BINARY>(args);
} }
Handle<Value> AsciiSlice(const Arguments& args) {
Handle<Value> Buffer::HexSlice(const Arguments& args) { return StringSlice<ASCII>(args);
return Buffer::StringSlice<HEX>(args);
} }
Handle<Value> Buffer::Base64Slice(const Arguments& args) { Handle<Value> Utf8Slice(const Arguments& args) {
return Buffer::StringSlice<BASE64>(args); return StringSlice<UTF8>(args);
} }
// buffer.fill(value, start, end); Handle<Value> Ucs2Slice(const Arguments& args) {
Handle<Value> Buffer::Fill(const Arguments &args) { return StringSlice<UCS2>(args);
HandleScope scope(node_isolate); }
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<Buffer>(args.This());
SLICE_ARGS(args[1], args[2])
memset( (void*)(parent->data_ + start), Handle<Value> HexSlice(const Arguments& args) {
value, return StringSlice<HEX>(args);
end - start); }
return Undefined(node_isolate);
Handle<Value> Base64Slice(const Arguments& args) {
return StringSlice<BASE64>(args);
} }
// var bytesCopied = buffer.copy(target, targetStart, sourceStart, sourceEnd); // bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]);
Handle<Value> Buffer::Copy(const Arguments &args) { Handle<Value> Copy(const Arguments &args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
Buffer *source = ObjectWrap::Unwrap<Buffer>(args.This()); Local<Object> target = args[0]->ToObject();
if (!Buffer::HasInstance(args[0])) { if (!HasInstance(target))
return ThrowTypeError("First arg should be a Buffer"); return ThrowTypeError("first arg should be a Buffer");
}
Local<Value> target = args[0]; ARGS_THIS(args.This())
char* target_data = Buffer::Data(target); size_t target_length = target->GetIndexedPropertiesExternalArrayDataLength();
size_t target_length = Buffer::Length(target); char* target_data = static_cast<char*>(
size_t target_start = args[1]->IsUndefined() ? 0 : args[1]->Uint32Value(); target->GetIndexedPropertiesExternalArrayData());
size_t source_start = args[2]->IsUndefined() ? 0 : args[2]->Uint32Value(); size_t target_start;
size_t source_end = args[3]->IsUndefined() ? source->length_ size_t source_start;
: args[3]->Uint32Value(); size_t source_end;
if (source_end < source_start) { CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &target_start));
return ThrowRangeError("sourceEnd < sourceStart"); 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 // Copy 0 bytes; we're done
if (source_end == source_start) { if (target_start >= target_length || source_start >= source_end)
return scope.Close(Integer::New(0, node_isolate)); return scope.Close(Uint32::New(0, node_isolate));
}
if (target_start >= target_length) {
return ThrowRangeError("targetStart out of bounds");
}
if (source_start >= source->length_) { if (source_start > obj_length)
return ThrowRangeError("sourceStart out of bounds"); return ThrowRangeError("out of range index");
}
if (source_end > source->length_) { if (source_end - source_start > target_length - target_start)
return ThrowRangeError("sourceEnd out of bounds"); source_end = source_start + target_length - target_start;
}
size_t to_copy = MIN(MIN(source_end - source_start, size_t to_copy = MIN(MIN(source_end - source_start,
target_length - target_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), memmove((void *)(target_data + target_start),
(const void*)(source->data_ + source_start), (const void*)(obj_data + source_start),
to_copy); to_copy);
return scope.Close(Integer::New(to_copy, node_isolate)); return scope.Close(Uint32::New(to_copy, node_isolate));
} }
Handle<Value> Buffer::Base64Write(const Arguments& args) { // buffer.fill(value[, start][, end]);
return Buffer::StringWrite<BASE64>(args); Handle<Value> Fill(const Arguments &args) {
} HandleScope scope(node_isolate);
Handle<Value> Buffer::BinaryWrite(const Arguments& args) { ARGS_THIS(args.This())
return Buffer::StringWrite<BINARY>(args); int value;
}
Handle<Value> Buffer::Utf8Write(const Arguments& args) { if (args[0]->IsString()) {
return Buffer::StringWrite<UTF8>(args); String::AsciiValue at(args[0]);
} value = (*at)[0];
} else {
value = (char)args[0]->Int32Value();
}
Handle<Value> Buffer::Ucs2Write(const Arguments& args) { SLICE_START_END(args[1], args[2], obj_length)
return Buffer::StringWrite<UCS2>(args);
}
Handle<Value> Buffer::HexWrite(const Arguments& args) { memset((void*)(obj_data + start), value, length);
return Buffer::StringWrite<HEX>(args);
}
Handle<Value> Buffer::AsciiWrite(const Arguments& args) { return args.This();
return Buffer::StringWrite<ASCII>(args);
} }
template <encoding encoding> template <encoding encoding>
Handle<Value> Buffer::StringWrite(const Arguments& args) { Handle<Value> StringWrite(const Arguments& args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
Buffer* buffer = ObjectWrap::Unwrap<Buffer>(args.This()); ARGS_THIS(args.This())
if (!args[0]->IsString()) { if (!args[0]->IsString())
return ThrowTypeError("Argument must be a string"); return ThrowTypeError("Argument must be a string");
}
Local<String> str = args[0].As<String>();
int length = str->Length(); Local<String> str = args[0]->ToString();
if (length == 0) { if (encoding == HEX && str->Length() % 2 != 0)
return scope.Close(Integer::New(0));
}
if (encoding == HEX && length % 2 != 0)
return ThrowTypeError("Invalid hex string"); return ThrowTypeError("Invalid hex string");
size_t offset;
size_t max_length;
size_t offset = args[1]->Int32Value(); CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset));
size_t max_length = args[2]->IsUndefined() ? buffer->length_ - offset CHECK_NOT_OOB(ParseArrayIndex(args[2], obj_length - offset, &max_length));
: args[2]->Uint32Value();
max_length = MIN(buffer->length_ - offset, max_length);
if (max_length == 0) { max_length = MIN(obj_length - offset, max_length);
// shortcut: nothing to write anyway
Local<Integer> val = Integer::New(0); if (max_length == 0)
return scope.Close(val); return scope.Close(Uint32::New(0, node_isolate));
}
if (encoding == UCS2) if (encoding == UCS2)
max_length = max_length / 2; max_length = max_length / 2;
if (offset >= buffer->length_) { if (offset >= obj_length)
return ThrowTypeError("Offset is out of bounds"); return ThrowRangeError("Offset is out of bounds");
}
char* start = buffer->data_ + offset; size_t written = StringBytes::Write(obj_data + offset,
size_t written = StringBytes::Write(start,
max_length, max_length,
str, str,
encoding, encoding,
NULL); NULL);
return scope.Close(Integer::New(written, node_isolate)); return scope.Close(Uint32::New(written, node_isolate));
}
Handle<Value> Base64Write(const Arguments& args) {
return StringWrite<BASE64>(args);
}
Handle<Value> BinaryWrite(const Arguments& args) {
return StringWrite<BINARY>(args);
}
Handle<Value> Utf8Write(const Arguments& args) {
return StringWrite<UTF8>(args);
}
Handle<Value> Ucs2Write(const Arguments& args) {
return StringWrite<UCS2>(args);
}
Handle<Value> HexWrite(const Arguments& args) {
return StringWrite<HEX>(args);
}
Handle<Value> AsciiWrite(const Arguments& args) {
return StringWrite<ASCII>(args);
} }
@ -396,14 +398,13 @@ static inline void Swizzle(char* start, unsigned int len) {
template <typename T, enum Endianness endianness> template <typename T, enum Endianness endianness>
Handle<Value> ReadFloatGeneric(const Arguments& args) { Handle<Value> ReadFloatGeneric(const Arguments& args) {
size_t offset = args[0]->Uint32Value();
bool doAssert = !args[1]->BooleanValue(); bool doAssert = !args[1]->BooleanValue();
size_t offset;
CHECK_NOT_OOB(ParseArrayIndex(args[0], 0, &offset));
if (doAssert) { if (doAssert) {
if (!args[0]->IsUint32()) size_t len = Length(args.This());
return ThrowTypeError("offset is not uint");
size_t len = static_cast<size_t>(
args.This()->GetIndexedPropertiesExternalArrayDataLength());
if (offset + sizeof(T) > len || offset + sizeof(T) < offset) if (offset + sizeof(T) > len || offset + sizeof(T) < offset)
return ThrowRangeError("Trying to read beyond buffer length"); return ThrowRangeError("Trying to read beyond buffer length");
} }
@ -419,27 +420,26 @@ Handle<Value> ReadFloatGeneric(const Arguments& args) {
memcpy(na.bytes, ptr, sizeof(na.bytes)); memcpy(na.bytes, ptr, sizeof(na.bytes));
if (endianness != GetEndianness()) Swizzle(na.bytes, 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); return Number::New(na.val);
} }
Handle<Value> Buffer::ReadFloatLE(const Arguments& args) { Handle<Value> ReadFloatLE(const Arguments& args) {
return ReadFloatGeneric<float, kLittleEndian>(args); return ReadFloatGeneric<float, kLittleEndian>(args);
} }
Handle<Value> Buffer::ReadFloatBE(const Arguments& args) { Handle<Value> ReadFloatBE(const Arguments& args) {
return ReadFloatGeneric<float, kBigEndian>(args); return ReadFloatGeneric<float, kBigEndian>(args);
} }
Handle<Value> Buffer::ReadDoubleLE(const Arguments& args) { Handle<Value> ReadDoubleLE(const Arguments& args) {
return ReadFloatGeneric<double, kLittleEndian>(args); return ReadFloatGeneric<double, kLittleEndian>(args);
} }
Handle<Value> Buffer::ReadDoubleBE(const Arguments& args) { Handle<Value> ReadDoubleBE(const Arguments& args) {
return ReadFloatGeneric<double, kBigEndian>(args); return ReadFloatGeneric<double, kBigEndian>(args);
} }
@ -448,19 +448,13 @@ template <typename T, enum Endianness endianness>
Handle<Value> WriteFloatGeneric(const Arguments& args) { Handle<Value> WriteFloatGeneric(const Arguments& args) {
bool doAssert = !args[2]->BooleanValue(); 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<T>(args[0]->NumberValue()); T val = static_cast<T>(args[0]->NumberValue());
size_t offset = args[1]->Uint32Value(); size_t offset;
CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset));
if (doAssert) { if (doAssert) {
size_t len = static_cast<size_t>( size_t len = Length(args.This());
args.This()->GetIndexedPropertiesExternalArrayDataLength());
if (offset + sizeof(T) > len || offset + sizeof(T) < offset) if (offset + sizeof(T) > len || offset + sizeof(T) < offset)
return ThrowRangeError("Trying to write beyond buffer length"); return ThrowRangeError("Trying to write beyond buffer length");
} }
@ -480,205 +474,122 @@ Handle<Value> WriteFloatGeneric(const Arguments& args) {
} }
Handle<Value> Buffer::WriteFloatLE(const Arguments& args) { Handle<Value> WriteFloatLE(const Arguments& args) {
return WriteFloatGeneric<float, kLittleEndian>(args); return WriteFloatGeneric<float, kLittleEndian>(args);
} }
Handle<Value> Buffer::WriteFloatBE(const Arguments& args) { Handle<Value> WriteFloatBE(const Arguments& args) {
return WriteFloatGeneric<float, kBigEndian>(args); return WriteFloatGeneric<float, kBigEndian>(args);
} }
Handle<Value> Buffer::WriteDoubleLE(const Arguments& args) { Handle<Value> WriteDoubleLE(const Arguments& args) {
return WriteFloatGeneric<double, kLittleEndian>(args); return WriteFloatGeneric<double, kLittleEndian>(args);
} }
Handle<Value> Buffer::WriteDoubleBE(const Arguments& args) { Handle<Value> WriteDoubleBE(const Arguments& args) {
return WriteFloatGeneric<double, kBigEndian>(args); return WriteFloatGeneric<double, kBigEndian>(args);
} }
// var nbytes = Buffer.byteLength("string", "utf8") Handle<Value> ByteLength(const Arguments &args) {
Handle<Value> Buffer::ByteLength(const Arguments &args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
if (!args[0]->IsString()) { if (!args[0]->IsString())
return ThrowTypeError("Argument must be a string"); return ThrowTypeError("Argument must be a string");
}
Local<String> s = args[0]->ToString(); Local<String> s = args[0]->ToString();
enum encoding e = ParseEncoding(args[1], UTF8); 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<Value> Buffer::MakeFastBuffer(const Arguments &args) { // pass Buffer object to load prototype methods
Handle<Value> SetupBufferJS(const Arguments& args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
if (!Buffer::HasInstance(args[0])) {
return ThrowTypeError("First argument must be a Buffer");
}
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(args[0]->ToObject());
Local<Object> 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<Value> val) {
if (!val->IsObject()) return false;
Local<Object> 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<Value> SetFastBufferConstructor(const Arguments& args) {
assert(args[0]->IsFunction()); assert(args[0]->IsFunction());
fast_buffer_constructor = Persistent<Function>::New(node_isolate,
args[0].As<Function>());
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<Function> bv = args[0].As<Function>();
p_buffer_fn = Persistent<Function>::New(node_isolate, bv);
Local<Value> proto_v = bv->Get(String::New("prototype"));
assert(proto_v->IsObject());
Local<Object> proto = proto_v.As<Object>();
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) { return Undefined(node_isolate);
}
void RetainedBufferInfo::Dispose() {
buffer_ = NULL;
delete this;
}
bool RetainedBufferInfo::IsEquivalent(RetainedObjectInfo* other) {
return label == other->GetLabel() &&
buffer_ == static_cast<RetainedBufferInfo*>(other)->buffer_;
}
intptr_t RetainedBufferInfo::GetHash() {
return reinterpret_cast<intptr_t>(buffer_);
}
const char* RetainedBufferInfo::GetLabel() {
return label;
}
intptr_t RetainedBufferInfo::GetSizeInBytes() {
return Buffer::Length(buffer_);
}
RetainedObjectInfo* WrapperInfo(uint16_t class_id, Handle<Value> wrapper) {
assert(class_id == BUFFER_CLASS_ID);
assert(Buffer::HasInstance(wrapper));
Buffer* buffer = Buffer::Unwrap<Buffer>(wrapper.As<Object>());
return new RetainedBufferInfo(buffer);
} }
void Buffer::Initialize(Handle<Object> target) { void Initialize(Handle<Object> target) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
length_symbol = NODE_PSYMBOL("length"); target->Set(String::New("setupBufferJS"),
FunctionTemplate::New(SetupBufferJS)->GetFunction());
Local<FunctionTemplate> t = FunctionTemplate::New(Buffer::New);
constructor_template = Persistent<FunctionTemplate>::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);
} }
} // namespace Buffer
} // namespace node } // namespace node
NODE_MODULE(node_buffer, node::Buffer::Initialize) NODE_MODULE(node_buffer, node::Buffer::Initialize)

165
src/node_buffer.h

@ -19,141 +19,44 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "smalloc.h"
#include "v8.h"
#ifndef NODE_BUFFER_H_ #ifndef NODE_BUFFER_H_
#define NODE_BUFFER_H_ #define NODE_BUFFER_H_
#include "node.h"
#include "node_object_wrap.h"
#include "v8.h"
#include <assert.h>
namespace node { namespace node {
/* A buffer is a chunk of memory stored outside the V8 heap, mirrored by an namespace Buffer {
* object in javascript. The object is not totally opaque, one can access
* individual bytes with [] and slice it into substrings or sub-buffers static const unsigned int kMaxLength = smalloc::kMaxLength;
* without copying memory.
*/ bool HasInstance(v8::Handle<v8::Value> val);
bool HasInstance(v8::Handle<v8::Object> val);
/* char* Data(v8::Handle<v8::Value> val);
The C++ API for Buffer changed radically between v0.2 and v0.3, in fact char* Data(v8::Handle<v8::Object> val);
it was the reason for bumping the version. In v0.2 JavaScript Buffers and size_t Length(v8::Handle<v8::Value> val);
C++ Buffers were in one-to-one correspondence via ObjectWrap. We found size_t Length(v8::Handle<v8::Object> val);
that it was faster to expose the C++ Buffers to JavaScript as a
"SlowBuffer" which is used as a private backend to pure JavaScript // public constructor
"Buffer" objects - a 'Buffer' in v0.3 might look like this: v8::Local<v8::Object> New(size_t length);
// public constructor from string
{ _parent: s, v8::Local<v8::Object> New(v8::Handle<v8::String> string);
_offset: 520, // public constructor - data is copied
length: 5 } // TODO(trevnorris): should be something like Copy()
v8::Local<v8::Object> New(const char* data, size_t len);
Migrating code C++ Buffer code from v0.2 to v0.3 is difficult. Here are // public constructor - data is used, callback is passed data on object gc
some tips: v8::Local<v8::Object> New(char* data,
- buffer->data() calls should become Buffer::Data(buffer) calls. size_t length,
- buffer->length() calls should become Buffer::Length(buffer) calls. smalloc::FreeCallback callback,
- There should not be any ObjectWrap::Unwrap<Buffer>() calls. You should void* hint);
not be storing pointers to Buffer objects at all - as they are
now considered internal structures. Instead consider making a // public constructor - data is used.
JavaScript reference to the buffer. // TODO(trevnorris): should be New() for consistency
v8::Local<v8::Object> Use(char* data, uint32_t len);
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 } // namespace Buffer
API.
} // namespace node
*/
class NODE_EXTERN Buffer: public ObjectWrap {
public:
// mirrors deps/v8/src/objects.h
static const unsigned int kMaxLength = 0x3fffffff;
static v8::Persistent<v8::FunctionTemplate> constructor_template;
static bool HasInstance(v8::Handle<v8::Value> val);
static inline char* Data(v8::Handle<v8::Value> val) {
assert(val->IsObject());
void* data = val.As<v8::Object>()->GetIndexedPropertiesExternalArrayData();
return static_cast<char*>(data);
}
static inline char* Data(Buffer *b) {
return Buffer::Data(b->handle_);
}
static inline size_t Length(v8::Handle<v8::Value> val) {
assert(val->IsObject());
int len = val.As<v8::Object>()
->GetIndexedPropertiesExternalArrayDataLength();
return static_cast<size_t>(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<v8::Object> New(v8::Handle<v8::String> string);
static void Initialize(v8::Handle<v8::Object> 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<v8::Value> New(const v8::Arguments &args);
template <encoding encoding>
static v8::Handle<v8::Value> StringSlice(const v8::Arguments &args);
static v8::Handle<v8::Value> BinarySlice(const v8::Arguments &args);
static v8::Handle<v8::Value> AsciiSlice(const v8::Arguments &args);
static v8::Handle<v8::Value> Base64Slice(const v8::Arguments &args);
static v8::Handle<v8::Value> Utf8Slice(const v8::Arguments &args);
static v8::Handle<v8::Value> Ucs2Slice(const v8::Arguments &args);
static v8::Handle<v8::Value> HexSlice(const v8::Arguments &args);
template <encoding encoding>
static v8::Handle<v8::Value> StringWrite(const v8::Arguments &args);
static v8::Handle<v8::Value> BinaryWrite(const v8::Arguments &args);
static v8::Handle<v8::Value> Base64Write(const v8::Arguments &args);
static v8::Handle<v8::Value> AsciiWrite(const v8::Arguments &args);
static v8::Handle<v8::Value> Utf8Write(const v8::Arguments &args);
static v8::Handle<v8::Value> Ucs2Write(const v8::Arguments &args);
static v8::Handle<v8::Value> HexWrite(const v8::Arguments &args);
static v8::Handle<v8::Value> ReadFloatLE(const v8::Arguments &args);
static v8::Handle<v8::Value> ReadFloatBE(const v8::Arguments &args);
static v8::Handle<v8::Value> ReadDoubleLE(const v8::Arguments &args);
static v8::Handle<v8::Value> ReadDoubleBE(const v8::Arguments &args);
static v8::Handle<v8::Value> WriteFloatLE(const v8::Arguments &args);
static v8::Handle<v8::Value> WriteFloatBE(const v8::Arguments &args);
static v8::Handle<v8::Value> WriteDoubleLE(const v8::Arguments &args);
static v8::Handle<v8::Value> WriteDoubleBE(const v8::Arguments &args);
static v8::Handle<v8::Value> ByteLength(const v8::Arguments &args);
static v8::Handle<v8::Value> MakeFastBuffer(const v8::Arguments &args);
static v8::Handle<v8::Value> Fill(const v8::Arguments &args);
static v8::Handle<v8::Value> Copy(const v8::Arguments &args);
Buffer(v8::Handle<v8::Object> 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
#endif // NODE_BUFFER_H_ #endif // NODE_BUFFER_H_

28
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) { int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
@ -297,8 +292,8 @@ int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
Handle<Value> argv[2] = { Handle<Value> argv[2] = {
Buffer::New(reinterpret_cast<char*>(sess->session_id), Buffer::New(reinterpret_cast<char*>(sess->session_id),
sess->session_id_length)->handle_, sess->session_id_length),
Buffer::New(serialized, size, SessionDataFree, NULL)->handle_ Buffer::Use(serialized, size)
}; };
if (onnewsession_sym.IsEmpty()) { if (onnewsession_sym.IsEmpty()) {
@ -877,7 +872,7 @@ size_t ClientHelloParser::Write(const uint8_t* data, size_t len) {
hello = Object::New(); hello = Object::New();
hello->Set(sessionid_sym, hello->Set(sessionid_sym,
Buffer::New(reinterpret_cast<char*>(session_id), Buffer::New(reinterpret_cast<char*>(session_id),
session_size)->handle_); session_size));
argv[0] = hello; argv[0] = hello;
MakeCallback(conn_->handle_, onclienthello_sym, 1, argv); MakeCallback(conn_->handle_, onclienthello_sym, 1, argv);
@ -2292,11 +2287,11 @@ Handle<Value> CipherBase::Update(const Arguments& args) {
return ThrowCryptoTypeError(ERR_get_error()); return ThrowCryptoTypeError(ERR_get_error());
} }
Buffer* buf = Buffer::New(reinterpret_cast<char*>(out), out_len); Local<Object> buf = Buffer::New(reinterpret_cast<char*>(out), out_len);
if (out) delete[] out; if (out) delete[] out;
return scope.Close(buf->handle_); return scope.Close(buf);
} }
@ -2347,9 +2342,7 @@ Handle<Value> CipherBase::Final(const Arguments& args) {
if (!r) return ThrowCryptoTypeError(ERR_get_error()); if (!r) return ThrowCryptoTypeError(ERR_get_error());
} }
Buffer* buf = Buffer::New(reinterpret_cast<char*>(out_value), out_len); return scope.Close(Buffer::New(reinterpret_cast<char*>(out_value), out_len));
return scope.Close(buf->handle_);
} }
@ -3547,11 +3540,6 @@ RandomBytesRequest::~RandomBytesRequest() {
} }
void RandomBytesFree(char* data, void* hint) {
delete[] data;
}
template <bool pseudoRandom> template <bool pseudoRandom>
void RandomBytesWork(uv_work_t* work_req) { void RandomBytesWork(uv_work_t* work_req) {
RandomBytesRequest* req = container_of(work_req, RandomBytesRequest* req = container_of(work_req,
@ -3588,10 +3576,8 @@ void RandomBytesCheck(RandomBytesRequest* req, Local<Value> argv[2]) {
argv[1] = Local<Value>::New(node_isolate, Null(node_isolate)); argv[1] = Local<Value>::New(node_isolate, Null(node_isolate));
} }
else { else {
// avoids the malloc + memcpy
Buffer* buffer = Buffer::New(req->data_, req->size_, RandomBytesFree, NULL);
argv[0] = Local<Value>::New(node_isolate, Null(node_isolate)); argv[0] = Local<Value>::New(node_isolate, Null(node_isolate));
argv[1] = Local<Object>::New(node_isolate, buffer->handle_); argv[1] = Buffer::Use(req->data_, req->size_);
} }
} }

24
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)) # define ROUND_UP(a, b) ((a) % (b) ? ((a) + (b)) - ((a) % (b)) : (a))
#endif #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++ // this would have been a template function were it not for the fact that g++
// sometimes fails to resolve it... // sometimes fails to resolve it...
#define THROW_ERROR(fun) \ #define THROW_ERROR(fun) \
@ -137,6 +143,24 @@ inline bool IsBigEndian() {
return GetEndianness() == kBigEndian; return GetEndianness() == kBigEndian;
} }
// parse index for external array data
inline MUST_USE_RESULT bool ParseArrayIndex(v8::Handle<v8::Value> 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<size_t>(tmp_i);
return true;
}
} // namespace node } // namespace node
#endif // SRC_NODE_INTERNALS_H_ #endif // SRC_NODE_INTERNALS_H_

5
src/slab_allocator.cc

@ -71,10 +71,7 @@ void SlabAllocator::Initialize() {
static Local<Object> NewSlab(unsigned int size) { static Local<Object> NewSlab(unsigned int size) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
Local<Value> arg = Integer::NewFromUnsigned(ROUND_UP(size, 16), node_isolate); Local<Object> buf = Buffer::New(ROUND_UP(size, 16));
Local<Object> buf = Buffer::constructor_template
->GetFunction()
->NewInstance(1, &arg);
return scope.Close(buf); return scope.Close(buf);
} }

2
src/string_bytes.cc

@ -624,7 +624,7 @@ Local<Value> StringBytes::Encode(const char* buf,
Local<String> val; Local<String> val;
switch (encoding) { switch (encoding) {
case BUFFER: case BUFFER:
return scope.Close(Buffer::New(buf, buflen)->handle_); return scope.Close(Buffer::New(buf, buflen));
case ASCII: case ASCII:
if (contains_non_ascii(buf, buflen)) { if (contains_non_ascii(buf, buflen)) {

11
src/tls_wrap.cc

@ -128,15 +128,14 @@ int TLSCallbacks::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
return 0; return 0;
// Serialize session // Serialize session
Local<Object> buff = Local<Object>::New(Buffer::New(size)->handle_); Local<Object> buff = Buffer::New(size);
unsigned char* serialized = reinterpret_cast<unsigned char*>( unsigned char* serialized = reinterpret_cast<unsigned char*>(
Buffer::Data(buff)); Buffer::Data(buff));
memset(serialized, 0, size); memset(serialized, 0, size);
i2d_SSL_SESSION(sess, &serialized); i2d_SSL_SESSION(sess, &serialized);
Local<Object> session = Local<Object>::New( Local<Object> session = Buffer::New(reinterpret_cast<char*>(sess->session_id),
Buffer::New(reinterpret_cast<char*>(sess->session_id), sess->session_id_length);
sess->session_id_length)->handle_);
Handle<Value> argv[2] = { session, buff }; Handle<Value> argv[2] = { session, buff };
MakeCallback(c->handle_, onnewsession_sym, ARRAY_SIZE(argv), argv); MakeCallback(c->handle_, onnewsession_sym, ARRAY_SIZE(argv), argv);
@ -479,7 +478,7 @@ void TLSCallbacks::ClearOut() {
do { do {
read = SSL_read(ssl_, out, sizeof(out)); read = SSL_read(ssl_, out, sizeof(out));
if (read > 0) { if (read > 0) {
Local<Value> buff = Local<Value>::New(Buffer::New(out, read)->handle_); Local<Value> buff = Buffer::New(out, read);
Handle<Value> argv[3] = { Handle<Value> argv[3] = {
buff, buff,
Integer::New(0, node_isolate), Integer::New(0, node_isolate),
@ -775,7 +774,7 @@ void TLSCallbacks::ParseClientHello() {
hello_obj = Object::New(); hello_obj = Object::New();
hello_obj->Set(sessionid_sym, hello_obj->Set(sessionid_sym,
Buffer::New(reinterpret_cast<char*>(session_id), Buffer::New(reinterpret_cast<char*>(session_id),
session_size)->handle_); session_size));
argv[0] = hello_obj; argv[0] = hello_obj;
MakeCallback(handle_, onclienthello_sym, 1, argv); MakeCallback(handle_, onclienthello_sym, 1, argv);

30
test/simple/test-buffer-regress-GH-2659.js

@ -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');
}

181
test/simple/test-buffer.js

@ -22,18 +22,11 @@
var common = require('../common'); var common = require('../common');
var assert = require('assert'); var assert = require('assert');
var SlowBuffer = require('buffer').SlowBuffer;
var Buffer = require('buffer').Buffer; var Buffer = require('buffer').Buffer;
// counter to ensure unique value is always copied // counter to ensure unique value is always copied
var cntr = 0; 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 var b = Buffer(1024); // safe constructor
console.log('b.length == %d', b.length); 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; var caught_error = null;
// try to copy from before the beginning of b // try to copy from before the beginning of b
@ -146,15 +131,10 @@ try {
caught_error = err; caught_error = err;
} }
// copy from b to c with negative sourceStart // copy throws at negative sourceStart
b.fill(++cntr); assert.throws(function() {
c.fill(++cntr); Buffer(5).copy(Buffer(5), 0, -1);
var copied = b.copy(c, 0, -1); }, RangeError);
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]);
}
// check sourceEnd resets to targetEnd if former is greater than the latter // check sourceEnd resets to targetEnd if former is greater than the latter
b.fill(++cntr); b.fill(++cntr);
@ -165,31 +145,17 @@ for (var i = 0; i < c.length; i++) {
assert.strictEqual(b[i], c[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 // throw with negative sourceEnd
console.log('test copy at negative sourceEnd'); console.log('test copy at negative sourceEnd');
assert.throws(function() { assert.throws(function() {
b.copy(c, 0, 0, -1); b.copy(c, 0, 0, -1);
}, RangeError); }, RangeError);
// throw when sourceStart is greater than sourceEnd // when sourceStart is greater than sourceEnd, zero copied
assert.throws(function() { assert.equal(b.copy(c, 0, 100, 10), 0);
b.copy(c, 0, 100, 10);
}, RangeError);
// throw attempting to copy after end of c // when targetStart > targetLength, zero copied
assert.throws(function() { assert.equal(b.copy(c, 512, 0, 10), 0);
b.copy(c, 512, 0, 10);
}, RangeError);
var caught_error; var caught_error;
@ -218,8 +184,14 @@ new Buffer('', 'binary');
new Buffer(0); new Buffer(0);
// try to write a 0-length string beyond the end of b // try to write a 0-length string beyond the end of b
b.write('', 1024); assert.throws(function() {
b.write('', 2048); 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 // throw when writing past bounds from the pool
assert.throws(function() { assert.throws(function() {
@ -591,12 +563,9 @@ assert.equal(b2, b3);
assert.equal(b2, b4); assert.equal(b2, b4);
// Test slice on SlowBuffer GH-843 function buildBuffer(data) {
var SlowBuffer = process.binding('buffer').SlowBuffer;
function buildSlowBuffer(data) {
if (Array.isArray(data)) { if (Array.isArray(data)) {
var buffer = new SlowBuffer(data.length); var buffer = new Buffer(data.length);
data.forEach(function(v, k) { data.forEach(function(v, k) {
buffer[k] = v; buffer[k] = v;
}); });
@ -605,10 +574,10 @@ function buildSlowBuffer(data) {
return null; 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()); console.log(x.inspect());
assert.equal('<SlowBuffer 81 a3 66 6f 6f a3 62 61 72>', x.inspect()); assert.equal('<Buffer 81 a3 66 6f 6f a3 62 61 72>', x.inspect());
var z = x.slice(4); var z = x.slice(4);
console.log(z.inspect()); 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]); for (; i < b.length; i++) assert.equal(0, b[i]);
['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach(function(encoding) { ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach(function(encoding) {
var b = new SlowBuffer(10); var b = new Buffer(10);
b.write('あいうえお', encoding); b.write('あいうえお', encoding);
assert.equal(b.toString(encoding), 'あいうえお'); assert.equal(b.toString(encoding), 'あいうえお');
}); });
@ -689,7 +658,7 @@ assert.equal(0xad, b[1]);
assert.equal(0xbe, b[2]); assert.equal(0xbe, b[2]);
assert.equal(0xef, b[3]); assert.equal(0xef, b[3]);
// testing invalid encoding on SlowBuffer.toString // testing invalid encoding on Buffer.toString
caught_error = null; caught_error = null;
try { try {
var copied = b.toString('invalid'); var copied = b.toString('invalid');
@ -698,7 +667,7 @@ try {
} }
assert.strictEqual('Unknown encoding: invalid', caught_error.message); assert.strictEqual('Unknown encoding: invalid', caught_error.message);
// testing invalid encoding on SlowBuffer.write // testing invalid encoding on Buffer.write
caught_error = null; caught_error = null;
try { try {
var copied = b.write('some string', 0, 5, 'invalid'); var copied = b.write('some string', 0, 5, 'invalid');
@ -708,11 +677,6 @@ try {
assert.strictEqual('Unknown encoding: invalid', caught_error.message); 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 // #1210 Test UTF-8 string includes null character
var buf = new Buffer('\0'); var buf = new Buffer('\0');
assert.equal(buf.length, 1); 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 Buffer(3.3).toString(); // throws bad argument error in commit 43cb4ec
assert.equal(Buffer(-1).length, 0); assert.equal(Buffer(-1).length, 0);
assert.equal(Buffer(NaN).length, 0); assert.equal(Buffer(NaN).length, 0);
assert.equal(Buffer(3.3).length, 4); assert.equal(Buffer(3.3).length, 3);
assert.equal(Buffer({length: 3.3}).length, 4); assert.equal(Buffer({length: 3.3}).length, 3);
assert.equal(Buffer({length: 'BAM'}).length, 0); assert.equal(Buffer({length: 'BAM'}).length, 0);
// Make sure that strings are not coerced to numbers. // Make sure that strings are not coerced to numbers.
@ -863,109 +827,79 @@ assert.throws(function() {
}, RangeError); }, RangeError);
assert.throws(function() { assert.throws(function() {
new Buffer(0xFFFFFFFFF); new Buffer(0xFFFFFFFFF);
}, TypeError); }, RangeError);
// attempt to overflow buffers, similar to previous bug in array buffers // attempt to overflow buffers, similar to previous bug in array buffers
assert.throws(function() { assert.throws(function() {
var buf = new Buffer(8); var buf = new Buffer(8);
buf.readFloatLE(0xffffffff); buf.readFloatLE(0xffffffff);
}, /Trying to access beyond buffer length/); }, RangeError);
assert.throws(function() { assert.throws(function() {
var buf = new Buffer(8); var buf = new Buffer(8);
buf.writeFloatLE(0.0, 0xffffffff); buf.writeFloatLE(0.0, 0xffffffff);
}, /Trying to access beyond buffer length/); }, RangeError);
assert.throws(function() { assert.throws(function() {
var buf = new SlowBuffer(8); var buf = new Buffer(8);
buf.readFloatLE(0xffffffff); buf.readFloatLE(0xffffffff);
}, /Trying to read beyond buffer length/); }, RangeError);
assert.throws(function() { assert.throws(function() {
var buf = new SlowBuffer(8); var buf = new Buffer(8);
buf.writeFloatLE(0.0, 0xffffffff); buf.writeFloatLE(0.0, 0xffffffff);
}, /Trying to write beyond buffer length/); }, RangeError);
// ensure negative values can't get past offset // ensure negative values can't get past offset
assert.throws(function() { assert.throws(function() {
var buf = new Buffer(8); var buf = new Buffer(8);
buf.readFloatLE(-1); buf.readFloatLE(-1);
}, /offset is not uint/); }, RangeError);
assert.throws(function() { assert.throws(function() {
var buf = new Buffer(8); var buf = new Buffer(8);
buf.writeFloatLE(0.0, -1); buf.writeFloatLE(0.0, -1);
}, /offset is not uint/); }, RangeError);
assert.throws(function() { assert.throws(function() {
var buf = new SlowBuffer(8); var buf = new Buffer(8);
buf.readFloatLE(-1); buf.readFloatLE(-1);
}, /offset is not uint/); }, RangeError);
assert.throws(function() { assert.throws(function() {
var buf = new SlowBuffer(8); var buf = new Buffer(8);
buf.writeFloatLE(0.0, -1); buf.writeFloatLE(0.0, -1);
}, /offset is not uint/); }, RangeError);
// offset checks // offset checks
var buf = new Buffer(0); var buf = new Buffer(0);
assert.throws(function() { buf.readUInt8(0); }, /beyond buffer length/); assert.throws(function() { buf.readUInt8(0); }, RangeError);
assert.throws(function() { buf.readInt8(0); }, /beyond buffer length/); assert.throws(function() { buf.readInt8(0); }, RangeError);
[16, 32].forEach(function(bits) { [16, 32].forEach(function(bits) {
var buf = new Buffer(bits / 8 - 1); var buf = new Buffer(bits / 8 - 1);
assert.throws( assert.throws(function() { buf['readUInt' + bits + 'BE'](0); },
function() { buf['readUInt' + bits + 'BE'](0); }, RangeError,
/beyond buffer length/, 'readUInt' + bits + 'BE');
'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()'
);
});
// SlowBuffer sanity checks. assert.throws(function() { buf['readUInt' + bits + 'LE'](0); },
assert.throws(function() { RangeError,
var len = 0xfffff; 'readUInt' + bits + 'LE');
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() { assert.throws(function() { buf['readInt' + bits + 'BE'](0); },
var len = 0xfffff; RangeError,
var sbuf = new SlowBuffer(len); 'readInt' + bits + 'BE()');
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() { assert.throws(function() { buf['readInt' + bits + 'LE'](0); },
var sbuf = new SlowBuffer(1); RangeError,
var buf = new Buffer(sbuf, 1, 0); 'readInt' + bits + 'LE()');
buf.length = 0xffffffff; });
buf.slice(0xffffff0, 0xffffffe); // Should throw.
}, Error);
// test Buffer slice
(function() { (function() {
var buf = new Buffer('0123456789'); var buf = new Buffer('0123456789');
assert.equal(buf.slice(-10, 10), '0123456789'); assert.equal(buf.slice(-10, 10), '0123456789');
@ -989,10 +923,3 @@ assert.equal(Buffer.byteLength('aaaa==', 'base64'), 3);
assert.throws(function() { assert.throws(function() {
Buffer('', 'buffer'); Buffer('', 'buffer');
}, TypeError); }, 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);
});

2
test/simple/test-readdouble.js

@ -22,7 +22,6 @@
/* /*
* Tests to verify we're reading in doubles correctly * Tests to verify we're reading in doubles correctly
*/ */
var SlowBuffer = process.binding('buffer').SlowBuffer;
var common = require('../common'); var common = require('../common');
var ASSERT = require('assert'); var ASSERT = require('assert');
@ -128,4 +127,3 @@ function test(clazz) {
test(Buffer); test(Buffer);
test(SlowBuffer);

2
test/simple/test-readfloat.js

@ -22,7 +22,6 @@
/* /*
* Tests to verify we're reading in floats correctly * Tests to verify we're reading in floats correctly
*/ */
var SlowBuffer = process.binding('buffer').SlowBuffer;
var common = require('../common'); var common = require('../common');
var ASSERT = require('assert'); var ASSERT = require('assert');
@ -90,4 +89,3 @@ function test(clazz) {
test(Buffer); test(Buffer);
test(SlowBuffer);

4
test/simple/test-readint.js

@ -22,7 +22,6 @@
/* /*
* Tests to verify we're reading in signed integers correctly * Tests to verify we're reading in signed integers correctly
*/ */
var SlowBuffer = process.binding('buffer').SlowBuffer;
var common = require('../common'); var common = require('../common');
var ASSERT = require('assert'); var ASSERT = require('assert');
@ -115,8 +114,5 @@ function test32(clazz) {
test8(Buffer); test8(Buffer);
test8(SlowBuffer);
test16(Buffer); test16(Buffer);
test16(SlowBuffer);
test32(Buffer); test32(Buffer);
test32(SlowBuffer);

4
test/simple/test-readuint.js

@ -23,7 +23,6 @@
* A battery of tests to help us read a series of uints * A battery of tests to help us read a series of uints
*/ */
var SlowBuffer = process.binding('buffer').SlowBuffer;
var common = require('../common'); var common = require('../common');
var ASSERT = require('assert'); var ASSERT = require('assert');
@ -106,8 +105,5 @@ function test32(clazz) {
test8(Buffer); test8(Buffer);
test8(SlowBuffer);
test16(Buffer); test16(Buffer);
test16(SlowBuffer);
test32(Buffer); test32(Buffer);
test32(SlowBuffer);

2
test/simple/test-writedouble.js

@ -22,7 +22,6 @@
/* /*
* Tests to verify we're writing doubles correctly * Tests to verify we're writing doubles correctly
*/ */
var SlowBuffer = process.binding('buffer').SlowBuffer;
var common = require('../common'); var common = require('../common');
var ASSERT = require('assert'); var ASSERT = require('assert');
@ -194,4 +193,3 @@ function test(clazz) {
test(Buffer); test(Buffer);
test(SlowBuffer);

2
test/simple/test-writefloat.js

@ -22,7 +22,6 @@
/* /*
* Tests to verify we're writing floats correctly * Tests to verify we're writing floats correctly
*/ */
var SlowBuffer = process.binding('buffer').SlowBuffer;
var common = require('../common'); var common = require('../common');
var ASSERT = require('assert'); var ASSERT = require('assert');
@ -132,4 +131,3 @@ function test(clazz) {
test(Buffer); test(Buffer);
test(SlowBuffer);

4
test/simple/test-writeint.js

@ -22,7 +22,6 @@
/* /*
* Tests to verify we're writing signed integers correctly * Tests to verify we're writing signed integers correctly
*/ */
var SlowBuffer = process.binding('buffer').SlowBuffer;
var common = require('../common'); var common = require('../common');
var ASSERT = require('assert'); var ASSERT = require('assert');
@ -185,8 +184,5 @@ function test32(clazz) {
test8(Buffer); test8(Buffer);
test8(SlowBuffer);
test16(Buffer); test16(Buffer);
test16(SlowBuffer);
test32(Buffer); test32(Buffer);
test32(SlowBuffer);

4
test/simple/test-writeuint.js

@ -22,7 +22,6 @@
/* /*
* A battery of tests to help us read a series of uints * A battery of tests to help us read a series of uints
*/ */
var SlowBuffer = process.binding('buffer').SlowBuffer;
var common = require('../common'); var common = require('../common');
var ASSERT = require('assert'); var ASSERT = require('assert');
@ -144,8 +143,5 @@ function test32(clazz) {
test8(Buffer); test8(Buffer);
test8(SlowBuffer);
test16(Buffer); test16(Buffer);
test16(SlowBuffer);
test32(Buffer); test32(Buffer);
test32(SlowBuffer);

Loading…
Cancel
Save