From 22b84e6216b86f9d5178e7d8a4d1a577209b7008 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Sat, 22 Dec 2012 13:06:50 -0800 Subject: [PATCH] buffer: floating point read/write improvements Improvements: * floating point operations are approx 4x's faster * Now write quiet NaN's * all read/write on floating point now done in C, so no more need for lib/buffer_ieee754.js * float values have more accurate min/max value checks * add additional benchmarks for buffers read/write * created benchmark/_bench_timer.js which is a simple library that can be included into any benchmark and provides an intelligent tracker for sync and async tests * add benchmarks for DataView set methods * add checks and tests to make sure offset is greater than 0 --- LICENSE | 32 -------- benchmark/_bench_timer.js | 88 ++++++++++++++++++++ benchmark/buffer_read.js | 97 ++++++++++++++++++++++ benchmark/buffer_write.js | 103 +++++++++++++++++++++++ benchmark/dataview_set.js | 104 +++++++++++++++++++++++ lib/buffer.js | 116 +++++++------------------- lib/buffer_ieee754.js | 116 -------------------------- node.gyp | 1 - src/node_buffer.cc | 141 ++++++++++++++++++++++++++++++++ src/node_buffer.h | 8 ++ test/simple/test-buffer.js | 44 ++++++++++ test/simple/test-writedouble.js | 8 +- test/simple/test-writefloat.js | 30 +++---- 13 files changed, 633 insertions(+), 255 deletions(-) create mode 100644 benchmark/_bench_timer.js create mode 100644 benchmark/buffer_read.js create mode 100644 benchmark/buffer_write.js create mode 100644 benchmark/dataview_set.js delete mode 100644 lib/buffer_ieee754.js diff --git a/LICENSE b/LICENSE index 8c7b54c7bf..494e37a766 100644 --- a/LICENSE +++ b/LICENSE @@ -252,38 +252,6 @@ maintained libraries. The externally maintained libraries used by Node are: # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ -- lib/buffer_ieee754.js. Its license follows: - """ - // Copyright (c) 2008, Fair Oaks Labs, Inc. - // All rights reserved. - // - // Redistribution and use in source and binary forms, with or without - // modification, are permitted provided that the following conditions are met: - // - // * Redistributions of source code must retain the above copyright notice, - // this list of conditions and the following disclaimer. - // - // * Redistributions in binary form must reproduce the above copyright notice, - // this list of conditions and the following disclaimer in the documentation - // and/or other materials provided with the distribution. - // - // * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors - // may be used to endorse or promote products derived from this software - // without specific prior written permission. - // - // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - // POSSIBILITY OF SUCH DAMAGE. - """ - - lib/punycode.js is copyright 2011 Mathias Bynens and released under the MIT license. """ diff --git a/benchmark/_bench_timer.js b/benchmark/_bench_timer.js new file mode 100644 index 0000000000..43460945fb --- /dev/null +++ b/benchmark/_bench_timer.js @@ -0,0 +1,88 @@ +/* + * This is a simple addition to allow for higher resolution timers. + * It can be used to track time for both synchronous or asynchronous + * calls. For synchronous calls pass a callback function like so: + * + * var timer = require('./_bench_timer'); + * + * timer('myTest', function() { + * for (var i = 0; i < 1e6; i++) + * // ... run something here + * } + * }); + * + * For asynchronous timers just pass the name. Then run it again with + * the same name to finish it: + * + * timer('checkAsync'); + * setTimeout(function() { + * timer('checkAsync'); + * }, 300); + * + * When this happens all currently queued benchmarks will be paused + * until the asynchronous benchmark has completed. + * + * If the benchmark has been run with --expose_gc then the garbage + * collector will be run between each test. + * + * The setTimeout delay can also be changed by passing a value to + * timer.delay. + */ + + +var store = {}; +var order = []; +var maxLength = 0; +var processing = false; +var asyncQueue = 0; +var GCd = typeof gc !== 'function' ? false : true; + +function timer(name, fn) { + if (maxLength < name.length) + maxLength = name.length; + if (!fn) { + processing = false; + if (!store[name]) { + asyncQueue++; + store[name] = process.hrtime(); + return; + } + displayTime(name, process.hrtime(store[name])); + asyncQueue--; + } else { + store[name] = fn; + order.push(name); + } + if (!processing && asyncQueue <= 0) { + processing = true; + setTimeout(run, timer.delay); + } +} + +timer.delay = 100; + +function run() { + if (asyncQueue > 0 || order.length <= 0) + return; + if (GCd) gc(); + setTimeout(function() { + var name = order.shift(); + var fn = store[name]; + var ini = process.hrtime(); + fn(); + ini = process.hrtime(ini); + displayTime(name, ini); + run(); + }, timer.delay); +} + +function displayTime(name, ini) { + name += ': '; + while (name.length < maxLength + 2) + name += ' '; + console.log(name + '%s \u00b5s', + (~~((ini[0] * 1e6) + (ini[1] / 1e3))) + .toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")); +} + +module.exports = timer; diff --git a/benchmark/buffer_read.js b/benchmark/buffer_read.js new file mode 100644 index 0000000000..f128bf2226 --- /dev/null +++ b/benchmark/buffer_read.js @@ -0,0 +1,97 @@ +const LEN = 1e7; +const noAssert = process.argv[3] == 'true' ? true + : process.argv[3] == 'false' ? false + : undefined; + +var timer = require('./_bench_timer'); + +var buff = (process.argv[2] == 'slow') ? + (new require('buffer').SlowBuffer(8)) : + (new Buffer(8)); +var i; + +buff.writeDoubleLE(0, 0, noAssert); + +timer('readUInt8', function() { + for (i = 0; i < LEN; i++) { + buff.readUInt8(0, noAssert); + } +}); + +timer('readUInt16LE', function() { + for (i = 0; i < LEN; i++) { + buff.readUInt16LE(0, noAssert); + } +}); + +timer('readUInt16BE', function() { + for (i = 0; i < LEN; i++) { + buff.readUInt16BE(0, noAssert); + } +}); + +timer('readUInt32LE', function() { + for (i = 0; i < LEN; i++) { + buff.readUInt32LE(0, noAssert); + } +}); + +timer('readUInt32BE', function() { + for (i = 0; i < LEN; i++) { + buff.readUInt32BE(0, noAssert); + } +}); + +timer('readInt8', function() { + for (i = 0; i < LEN; i++) { + buff.readInt8(0, noAssert); + } +}); + +timer('readInt16LE', function() { + for (i = 0; i < LEN; i++) { + buff.readInt16LE(0, noAssert); + } +}); + +timer('readInt16BE', function() { + for (i = 0; i < LEN; i++) { + buff.readInt16BE(0, noAssert); + } +}); + +timer('readInt32LE', function() { + for (i = 0; i < LEN; i++) { + buff.readInt32LE(0, noAssert); + } +}); + +timer('readInt32BE', function() { + for (i = 0; i < LEN; i++) { + buff.readInt32BE(0, noAssert); + } +}); + +timer('readFloatLE', function() { + for (i = 0; i < LEN; i++) { + buff.readFloatLE(0, noAssert); + } +}); + +timer('readFloatBE', function() { + for (i = 0; i < LEN; i++) { + buff.readFloatBE(0, noAssert); + } +}); + +timer('readDoubleLE', function() { + for (i = 0; i < LEN; i++) { + buff.readDoubleLE(0, noAssert); + } +}); + +timer('readDoubleBE', function() { + for (i = 0; i < LEN; i++) { + buff.readDoubleBE(0, noAssert); + } +}); diff --git a/benchmark/buffer_write.js b/benchmark/buffer_write.js new file mode 100644 index 0000000000..70f9926563 --- /dev/null +++ b/benchmark/buffer_write.js @@ -0,0 +1,103 @@ +const LEN = 1e7; + +const INT8 = 0x7f; +const INT16 = 0x7fff; +const INT32 = 0x7fffffff; +const UINT8 = INT8 * 2; +const UINT16 = INT16 * 2; +const UINT32 = INT32 * 2; + +const noAssert = process.argv[3] == 'true' ? true + : process.argv[3] == 'false' ? false + : undefined; + +var timer = require('./_bench_timer'); + +var buff = (process.argv[2] == 'slow') ? + (new require('buffer').SlowBuffer(8)) : + (new Buffer(8)); +var i; + +timer('writeUInt8', function() { + for (i = 0; i < LEN; i++) { + buff.writeUInt8(i % UINT8, 0, noAssert); + } +}); + +timer('writeUInt16LE', function() { + for (i = 0; i < LEN; i++) { + buff.writeUInt16LE(i % UINT16, 0, noAssert); + } +}); + +timer('writeUInt16BE', function() { + for (i = 0; i < LEN; i++) { + buff.writeUInt16BE(i % UINT16, 0, noAssert); + } +}); + +timer('writeUInt32LE', function() { + for (i = 0; i < LEN; i++) { + buff.writeUInt32LE(i % UINT32, 0, noAssert); + } +}); + +timer('writeUInt32BE', function() { + for (i = 0; i < LEN; i++) { + buff.writeUInt32BE(i % UINT32, 0, noAssert); + } +}); + +timer('writeInt8', function() { + for (i = 0; i < LEN; i++) { + buff.writeInt8(i % INT8, 0, noAssert); + } +}); + +timer('writeInt16LE', function() { + for (i = 0; i < LEN; i++) { + buff.writeInt16LE(i % INT16, 0, noAssert); + } +}); + +timer('writeInt16BE', function() { + for (i = 0; i < LEN; i++) { + buff.writeInt16BE(i % INT16, 0, noAssert); + } +}); + +timer('writeInt32LE', function() { + for (i = 0; i < LEN; i++) { + buff.writeInt32LE(i % INT32, 0, noAssert); + } +}); + +timer('writeInt32BE', function() { + for (i = 0; i < LEN; i++) { + buff.writeInt32BE(i % INT32, 0, noAssert); + } +}); + +timer('writeFloatLE', function() { + for (i = 0; i < LEN; i++) { + buff.writeFloatLE(i * 0.1, 0, noAssert); + } +}); + +timer('writeFloatBE', function() { + for (i = 0; i < LEN; i++) { + buff.writeFloatBE(i * 0.1, 0, noAssert); + } +}); + +timer('writeDoubleLE', function() { + for (i = 0; i < LEN; i++) { + buff.writeDoubleLE(i * 0.1, 0, noAssert); + } +}); + +timer('writeDoubleBE', function() { + for (i = 0; i < LEN; i++) { + buff.writeDoubleBE(i * 0.1, 0, noAssert); + } +}); diff --git a/benchmark/dataview_set.js b/benchmark/dataview_set.js new file mode 100644 index 0000000000..d0eca61515 --- /dev/null +++ b/benchmark/dataview_set.js @@ -0,0 +1,104 @@ +const LEN = 1e7; + +const INT8 = 0x7f; +const INT16 = 0x7fff; +const INT32 = 0x7fffffff; +const UINT8 = INT8 * 2; +const UINT16 = INT16 * 2; +const UINT32 = INT32 * 2; + +const noAssert = process.argv[3] == 'true' ? true + : process.argv[3] == 'false' ? false + : undefined; + +var timer = require('./_bench_timer'); + +var buff = (process.argv[2] == 'slow') ? + (new require('buffer').SlowBuffer(8)) : + (new Buffer(8)); +var dv = new DataView(buff); +var i; + +timer('setUint8', function() { + for (i = 0; i < LEN; i++) { + dv.setUint8(0, i % UINT8); + } +}); + +timer('setUint16 - LE', function() { + for (i = 0; i < LEN; i++) { + dv.setUint16(0, i % UINT16, true); + } +}); + +timer('setUint16 - BE', function() { + for (i = 0; i < LEN; i++) { + dv.setUint16(0, i % UINT16); + } +}); + +timer('setUint32 - LE', function() { + for (i = 0; i < LEN; i++) { + dv.setUint32(0, i % UINT32, true); + } +}); + +timer('setUint32 - BE', function() { + for (i = 0; i < LEN; i++) { + dv.setUint32(0, i % UINT32); + } +}); + +timer('setInt8', function() { + for (i = 0; i < LEN; i++) { + dv.setInt8(0, i % INT8); + } +}); + +timer('setInt16 - LE', function() { + for (i = 0; i < LEN; i++) { + dv.setInt16(0, i % INT16, true); + } +}); + +timer('setInt16 - BE', function() { + for (i = 0; i < LEN; i++) { + dv.setInt16(0, i % INT16); + } +}); + +timer('setInt32 - LE', function() { + for (i = 0; i < LEN; i++) { + dv.setInt32(0, i % INT32, true); + } +}); + +timer('setInt32 - BE', function() { + for (i = 0; i < LEN; i++) { + dv.setInt32(0, i % INT32); + } +}); + +timer('setFloat32 - LE', function() { + for (i = 0; i < LEN; i++) { + dv.setFloat32(0, i * 0.1, true); + } +}); + +timer('setFloat32 - BE', function() { + for (i = 0; i < LEN; i++) { + dv.setFloat32(0, i * 0.1); + } +}); + +timer('setFloat64 - LE', function() { + for (i = 0; i < LEN; i++) { + dv.setFloat64(0, i * 0.1, true); + } +}); + +timer('setFloat64 - BE', function() { + for (i = 0; i < LEN; i++) { + dv.setFloat64(0, i * 0.1); + } +}); diff --git a/lib/buffer.js b/lib/buffer.js index 4958b31d3a..6fb7a6d993 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -829,46 +829,35 @@ Buffer.prototype.readInt32BE = function(offset, noAssert) { return readInt32(this, offset, true, noAssert); }; -function readFloat(buffer, offset, isBigEndian, noAssert) { - if (!noAssert) { - assert.ok(typeof (isBigEndian) === 'boolean', - 'missing or invalid endian'); - - assert.ok(offset + 3 < buffer.length, - 'Trying to read beyond buffer length'); - } - - return require('buffer_ieee754').readIEEE754(buffer, offset, isBigEndian, - 23, 4); +function checkOffset(offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) + throw new RangeError('offset is not uint'); + if (offset + ext > length) + throw new RangeError('Trying to access beyond buffer length'); } Buffer.prototype.readFloatLE = function(offset, noAssert) { - return readFloat(this, offset, false, noAssert); + if (!noAssert) + checkOffset(offset, 4, this.length); + return this.parent.readFloatLE(this.offset + offset, !!noAssert); }; Buffer.prototype.readFloatBE = function(offset, noAssert) { - return readFloat(this, offset, true, noAssert); + if (!noAssert) + checkOffset(offset, 4, this.length); + return this.parent.readFloatBE(this.offset + offset, !!noAssert); }; -function readDouble(buffer, offset, isBigEndian, noAssert) { - if (!noAssert) { - assert.ok(typeof (isBigEndian) === 'boolean', - 'missing or invalid endian'); - - assert.ok(offset + 7 < buffer.length, - 'Trying to read beyond buffer length'); - } - - return require('buffer_ieee754').readIEEE754(buffer, offset, isBigEndian, - 52, 8); -} - Buffer.prototype.readDoubleLE = function(offset, noAssert) { - return readDouble(this, offset, false, noAssert); + if (!noAssert) + checkOffset(offset, 8, this.length); + return this.parent.readDoubleLE(this.offset + offset, !!noAssert); }; Buffer.prototype.readDoubleBE = function(offset, noAssert) { - return readDouble(this, offset, true, noAssert); + if (!noAssert) + checkOffset(offset, 8, this.length); + return this.parent.readDoubleBE(this.offset + offset, !!noAssert); }; @@ -1036,19 +1025,6 @@ function verifsint(value, max, min) { assert.ok(Math.floor(value) === value, 'value has a fractional component'); } -function verifyIEEE754(value, max, min) { - assert.ok(typeof (value) == 'number', - 'cannot write a non-number as a number'); - - if (isNaN(value) || value === Infinity || value === -Infinity) { - return; - } - - assert.ok(value <= max, 'value larger than maximum allowed value'); - - assert.ok(value >= min, 'value smaller than minimum allowed value'); -} - Buffer.prototype.writeInt8 = function(value, offset, noAssert) { var buffer = this; @@ -1103,60 +1079,26 @@ Buffer.prototype.writeInt32BE = function(value, offset, noAssert) { writeInt32(this, value, offset, true, noAssert); }; -function writeFloat(buffer, value, offset, isBigEndian, noAssert) { - if (!noAssert) { - assert.ok(value !== undefined && value !== null, - 'missing value'); - - assert.ok(typeof (isBigEndian) === 'boolean', - 'missing or invalid endian'); - - assert.ok(offset !== undefined && offset !== null, - 'missing offset'); - - assert.ok(offset + 3 < buffer.length, - 'Trying to write beyond buffer length'); - - verifyIEEE754(value, 3.4028234663852886e+38, -3.4028234663852886e+38); - } - - require('buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian, - 23, 4); -} - Buffer.prototype.writeFloatLE = function(value, offset, noAssert) { - writeFloat(this, value, offset, false, noAssert); + if (!noAssert) + checkOffset(offset, 4, this.length); + this.parent.writeFloatLE(value, this.offset + offset, !!noAssert); }; Buffer.prototype.writeFloatBE = function(value, offset, noAssert) { - writeFloat(this, value, offset, true, noAssert); + if (!noAssert) + checkOffset(offset, 4, this.length); + this.parent.writeFloatBE(value, this.offset + offset, !!noAssert); }; -function writeDouble(buffer, value, offset, isBigEndian, noAssert) { - if (!noAssert) { - assert.ok(value !== undefined && value !== null, - 'missing value'); - - assert.ok(typeof (isBigEndian) === 'boolean', - 'missing or invalid endian'); - - assert.ok(offset !== undefined && offset !== null, - 'missing offset'); - - assert.ok(offset + 7 < buffer.length, - 'Trying to write beyond buffer length'); - - verifyIEEE754(value, 1.7976931348623157E+308, -1.7976931348623157E+308); - } - - require('buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian, - 52, 8); -} - Buffer.prototype.writeDoubleLE = function(value, offset, noAssert) { - writeDouble(this, value, offset, false, noAssert); + if (!noAssert) + checkOffset(offset, 8, this.length); + this.parent.writeDoubleLE(value, this.offset + offset, !!noAssert); }; Buffer.prototype.writeDoubleBE = function(value, offset, noAssert) { - writeDouble(this, value, offset, true, noAssert); + if (!noAssert) + checkOffset(offset, 8, this.length); + this.parent.writeDoubleBE(value, this.offset + offset, !!noAssert); }; diff --git a/lib/buffer_ieee754.js b/lib/buffer_ieee754.js deleted file mode 100644 index 943ce7acdf..0000000000 --- a/lib/buffer_ieee754.js +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2008, Fair Oaks Labs, Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// -// -// Modifications to writeIEEE754 to support negative zeroes made by Brian White - -exports.readIEEE754 = function(buffer, offset, isBE, mLen, nBytes) { - var e, m, - eLen = nBytes * 8 - mLen - 1, - eMax = (1 << eLen) - 1, - eBias = eMax >> 1, - nBits = -7, - i = isBE ? 0 : (nBytes - 1), - d = isBE ? 1 : -1, - s = buffer[offset + i]; - - i += d; - - e = s & ((1 << (-nBits)) - 1); - s >>= (-nBits); - nBits += eLen; - for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); - - m = e & ((1 << (-nBits)) - 1); - e >>= (-nBits); - nBits += mLen; - for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); - - if (e === 0) { - e = 1 - eBias; - } else if (e === eMax) { - return m ? NaN : ((s ? -1 : 1) * Infinity); - } else { - m = m + Math.pow(2, mLen); - e = e - eBias; - } - return (s ? -1 : 1) * m * Math.pow(2, e - mLen); -}; - -exports.writeIEEE754 = function(buffer, value, offset, isBE, mLen, nBytes) { - var e, m, c, - eLen = nBytes * 8 - mLen - 1, - eMax = (1 << eLen) - 1, - eBias = eMax >> 1, - rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), - i = isBE ? (nBytes - 1) : 0, - d = isBE ? -1 : 1, - s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; - - value = Math.abs(value); - - if (isNaN(value) || value === Infinity) { - m = isNaN(value) ? 1 : 0; - e = eMax; - } else { - e = Math.floor(Math.log(value) / Math.LN2); - if (value * (c = Math.pow(2, -e)) < 1) { - e--; - c *= 2; - } - if (e + eBias >= 1) { - value += rt / c; - } else { - value += rt * Math.pow(2, 1 - eBias); - } - if (value * c >= 2) { - e++; - c /= 2; - } - - if (e + eBias >= eMax) { - m = 0; - e = eMax; - } else if (e + eBias >= 1) { - m = (value * c - 1) * Math.pow(2, mLen); - e = e + eBias; - } else { - m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); - e = 0; - } - } - - for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8); - - e = (e << mLen) | m; - eLen += mLen; - for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8); - - buffer[offset + i - d] |= s * 128; -}; diff --git a/node.gyp b/node.gyp index 64a4ab62ff..b07a1e0cab 100644 --- a/node.gyp +++ b/node.gyp @@ -22,7 +22,6 @@ 'lib/_linklist.js', 'lib/assert.js', 'lib/buffer.js', - 'lib/buffer_ieee754.js', 'lib/child_process.js', 'lib/console.js', 'lib/constants.js', diff --git a/src/node_buffer.cc b/src/node_buffer.cc index ff7f451a3e..f49524774c 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -28,6 +28,8 @@ #include #include // memcpy +#include // float limits +#include // infinity #define MIN(a,b) ((a) < (b) ? (a) : (b)) @@ -671,6 +673,137 @@ Handle Buffer::BinaryWrite(const Arguments &args) { } +static bool is_big_endian() { + const union { uint8_t u8[2]; uint16_t u16; } u = {{0, 1}}; + return u.u16 == 1 ? true : false; +} + + +static void swizzle(char* buf, size_t len) { + char t; + for (size_t i = 0; i < len / 2; ++i) { + t = buf[i]; + buf[i] = buf[len - i - 1]; + buf[len - i - 1] = t; + } +} + + +inline bool OutOfRangeCheck(float val, double val_tmp) { + if ((val_tmp > 0 && (val_tmp > FLT_MAX || val_tmp < FLT_MIN) + && val_tmp != INFINITY) || + ((val_tmp < 0 && (val_tmp < -FLT_MAX || val_tmp > -FLT_MIN) + && val_tmp != -INFINITY))) + return true; + return false; +} + + +template +Handle ReadFloatGeneric(const Arguments& args) { + double offset_tmp = args[0]->NumberValue(); + int64_t offset = static_cast(offset_tmp); + bool doAssert = !args[1]->BooleanValue(); + + if (doAssert) { + if (offset_tmp != offset || offset < 0) + return ThrowTypeError("offset is not uint"); + size_t len = static_cast( + args.This()->GetIndexedPropertiesExternalArrayDataLength()); + if (offset + sizeof(T) > len) + return ThrowRangeError("Trying to read beyond buffer length"); + } + + T val; + char* data = static_cast( + args.This()->GetIndexedPropertiesExternalArrayData()); + char* ptr = data + offset; + + memcpy(&val, ptr, sizeof(T)); + if (ENDIANNESS != is_big_endian()) + swizzle(reinterpret_cast(&val), sizeof(T)); + + // TODO: when Number::New is updated to accept an Isolate, make the change + return Number::New(val); +} + + +Handle Buffer::ReadFloatLE(const Arguments& args) { + return ReadFloatGeneric(args); +} + + +Handle Buffer::ReadFloatBE(const Arguments& args) { + return ReadFloatGeneric(args); +} + + +Handle Buffer::ReadDoubleLE(const Arguments& args) { + return ReadFloatGeneric(args); +} + + +Handle Buffer::ReadDoubleBE(const Arguments& args) { + return ReadFloatGeneric(args); +} + + +template +Handle WriteFloatGeneric(const Arguments& args) { + bool doAssert = !args[2]->BooleanValue(); + + if (doAssert) { + if (!args[0]->IsNumber()) + return ThrowTypeError("value not a number"); + } + + double val_tmp = args[0]->NumberValue(); + T val = static_cast(val_tmp); + double offset_tmp = args[1]->NumberValue(); + int64_t offset = static_cast(offset_tmp); + char* data = static_cast( + args.This()->GetIndexedPropertiesExternalArrayData()); + char* ptr = data + offset; + + if (doAssert) { + if (offset_tmp != offset || offset < 0) + return ThrowTypeError("offset is not uint"); + if (sizeof(T) == 4 && OutOfRangeCheck(val, val_tmp)) + return ThrowRangeError("value is out of type range"); + size_t len = static_cast( + args.This()->GetIndexedPropertiesExternalArrayDataLength()); + if (offset + sizeof(T) > len) + return ThrowRangeError("Trying to write beyond buffer length"); + } + + memcpy(ptr, &val, sizeof(T)); + if (ENDIANNESS != is_big_endian()) + swizzle(ptr, sizeof(T)); + + return Undefined(node_isolate); +} + + +Handle Buffer::WriteFloatLE(const Arguments& args) { + return WriteFloatGeneric(args); +} + + +Handle Buffer::WriteFloatBE(const Arguments& args) { + return WriteFloatGeneric(args); +} + + +Handle Buffer::WriteDoubleLE(const Arguments& args) { + return WriteFloatGeneric(args); +} + + +Handle Buffer::WriteDoubleBE(const Arguments& args) { + return WriteFloatGeneric(args); +} + + // var nbytes = Buffer.byteLength("string", "utf8") Handle Buffer::ByteLength(const Arguments &args) { HandleScope scope; @@ -814,6 +947,14 @@ void Buffer::Initialize(Handle target) { 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, "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); diff --git a/src/node_buffer.h b/src/node_buffer.h index f3d9dd4681..48d0dd2727 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -123,6 +123,14 @@ class NODE_EXTERN Buffer: public ObjectWrap { static v8::Handle AsciiWrite(const v8::Arguments &args); static v8::Handle Utf8Write(const v8::Arguments &args); static v8::Handle Ucs2Write(const v8::Arguments &args); + static v8::Handle ReadFloatLE(const v8::Arguments &args); + static v8::Handle ReadFloatBE(const v8::Arguments &args); + static v8::Handle ReadDoubleLE(const v8::Arguments &args); + static v8::Handle ReadDoubleBE(const v8::Arguments &args); + static v8::Handle WriteFloatLE(const v8::Arguments &args); + static v8::Handle WriteFloatBE(const v8::Arguments &args); + static v8::Handle WriteDoubleLE(const v8::Arguments &args); + static v8::Handle WriteDoubleBE(const v8::Arguments &args); static v8::Handle ByteLength(const v8::Arguments &args); static v8::Handle MakeFastBuffer(const v8::Arguments &args); static v8::Handle Fill(const v8::Arguments &args); diff --git a/test/simple/test-buffer.js b/test/simple/test-buffer.js index 7851a002d5..534839918f 100644 --- a/test/simple/test-buffer.js +++ b/test/simple/test-buffer.js @@ -812,3 +812,47 @@ assert.throws(function() { assert.throws(function() { new Buffer(0xFFFFFFFFF); }, TypeError); + + +// attempt to overflow buffers, similar to previous bug in array buffers +assert.throws(function() { + var buf = new Buffer(8); + buf.readFloatLE(0xffffffff); +}, /Trying to access beyond buffer length/); + +assert.throws(function() { + var buf = new Buffer(8); + buf.writeFloatLE(0.0, 0xffffffff); +}, /Trying to access beyond buffer length/); + +assert.throws(function() { + var buf = new SlowBuffer(8); + buf.readFloatLE(0xffffffff); +}, /Trying to read beyond buffer length/); + +assert.throws(function() { + var buf = new SlowBuffer(8); + buf.writeFloatLE(0.0, 0xffffffff); +}, /Trying to write beyond buffer length/); + + +// ensure negative values can't get past offset +assert.throws(function() { + var buf = new Buffer(8); + buf.readFloatLE(-1); +}, /offset is not uint/); + +assert.throws(function() { + var buf = new Buffer(8); + buf.writeFloatLE(0.0, -1); +}, /offset is not uint/); + +assert.throws(function() { + var buf = new SlowBuffer(8); + buf.readFloatLE(-1); +}, /offset is not uint/); + +assert.throws(function() { + var buf = new SlowBuffer(8); + buf.writeFloatLE(0.0, -1); +}, /offset is not uint/); diff --git a/test/simple/test-writedouble.js b/test/simple/test-writedouble.js index 58c88272c7..69bed9249e 100644 --- a/test/simple/test-writedouble.js +++ b/test/simple/test-writedouble.js @@ -169,20 +169,20 @@ function test(clazz) { buffer.writeDoubleBE(NaN, 0); buffer.writeDoubleLE(NaN, 8); ASSERT.equal(0x7F, buffer[0]); - ASSERT.equal(0xF0, buffer[1]); + ASSERT.equal(0xF8, buffer[1]); ASSERT.equal(0x00, buffer[2]); ASSERT.equal(0x00, buffer[3]); ASSERT.equal(0x00, buffer[4]); ASSERT.equal(0x00, buffer[5]); ASSERT.equal(0x00, buffer[6]); - ASSERT.equal(0x01, buffer[7]); - ASSERT.equal(0x01, buffer[8]); + ASSERT.equal(0x00, buffer[7]); + ASSERT.equal(0x00, buffer[8]); ASSERT.equal(0x00, buffer[9]); ASSERT.equal(0x00, buffer[10]); ASSERT.equal(0x00, buffer[11]); ASSERT.equal(0x00, buffer[12]); ASSERT.equal(0x00, buffer[13]); - ASSERT.equal(0xF0, buffer[14]); + ASSERT.equal(0xF8, buffer[14]); ASSERT.equal(0x7F, buffer[15]); ASSERT.ok(isNaN(buffer.readDoubleBE(0))); ASSERT.ok(isNaN(buffer.readDoubleLE(8))); diff --git a/test/simple/test-writefloat.js b/test/simple/test-writefloat.js index b160d1290d..626a11f19a 100644 --- a/test/simple/test-writefloat.js +++ b/test/simple/test-writefloat.js @@ -40,17 +40,6 @@ function test(clazz) { ASSERT.equal(0x80, buffer[6]); ASSERT.equal(0x3f, buffer[7]); - buffer.writeFloatBE(1.793662034335766e-43, 0); - buffer.writeFloatLE(1.793662034335766e-43, 4); - ASSERT.equal(0x00, buffer[0]); - ASSERT.equal(0x00, buffer[1]); - ASSERT.equal(0x00, buffer[2]); - ASSERT.equal(0x80, buffer[3]); - ASSERT.equal(0x80, buffer[4]); - ASSERT.equal(0x00, buffer[5]); - ASSERT.equal(0x00, buffer[6]); - ASSERT.equal(0x00, buffer[7]); - buffer.writeFloatBE(1 / 3, 0); buffer.writeFloatLE(1 / 3, 4); ASSERT.equal(0x3e, buffer[0]); @@ -73,6 +62,17 @@ function test(clazz) { ASSERT.equal(0x7f, buffer[6]); ASSERT.equal(0x7f, buffer[7]); + buffer.writeFloatLE(1.1754943508222875e-38, 0); + buffer.writeFloatBE(1.1754943508222875e-38, 4); + ASSERT.equal(0x00, buffer[0]); + ASSERT.equal(0x00, buffer[1]); + ASSERT.equal(0x80, buffer[2]); + ASSERT.equal(0x00, buffer[3]); + ASSERT.equal(0x00, buffer[4]); + ASSERT.equal(0x80, buffer[5]); + ASSERT.equal(0x00, buffer[6]); + ASSERT.equal(0x00, buffer[7]); + buffer.writeFloatBE(0 * -1, 0); buffer.writeFloatLE(0 * -1, 4); ASSERT.equal(0x80, buffer[0]); @@ -113,12 +113,12 @@ function test(clazz) { buffer.writeFloatBE(NaN, 0); buffer.writeFloatLE(NaN, 4); ASSERT.equal(0x7F, buffer[0]); - ASSERT.equal(0x80, buffer[1]); + ASSERT.equal(0xc0, buffer[1]); ASSERT.equal(0x00, buffer[2]); - ASSERT.equal(0x01, buffer[3]); - ASSERT.equal(0x01, buffer[4]); + ASSERT.equal(0x00, buffer[3]); + ASSERT.equal(0x00, buffer[4]); ASSERT.equal(0x00, buffer[5]); - ASSERT.equal(0x80, buffer[6]); + ASSERT.equal(0xc0, buffer[6]); ASSERT.equal(0x7F, buffer[7]); ASSERT.ok(isNaN(buffer.readFloatBE(0))); ASSERT.ok(isNaN(buffer.readFloatLE(4)));