From 37ed81610ddb5004e59d2827effd9f226d2f8ac3 Mon Sep 17 00:00:00 2001 From: Appurva Murawat Date: Thu, 29 Aug 2024 18:43:02 +0530 Subject: [PATCH] Use native Buffer whenever available --- CHANGELOG.yaml | 2 + lib/environment.js | 7 +- lib/sandbox/index.js | 7 ++ lib/vendor/buffer/buffer.js | 57 +++++++++ lib/vendor/buffer/index.browser.js | 9 ++ lib/vendor/buffer/index.js | 9 ++ test/unit/sandbox-libraries/buffer.test.js | 127 ++++++++++++++++++++- 7 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 lib/vendor/buffer/buffer.js create mode 100644 lib/vendor/buffer/index.browser.js create mode 100644 lib/vendor/buffer/index.js diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index b471bb2c..04bbb424 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -1,6 +1,8 @@ unreleased: chores: - Add support for configuring module resolver based on environment + new features: + - Enhanced performance when operating on buffers in Node environment 5.1.1: date: 2024-08-01 diff --git a/lib/environment.js b/lib/environment.js index add774ff..756937ed 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -11,7 +11,12 @@ module.exports = { util: { preferBuiltin: true, glob: true }, stream: { preferBuiltin: true, glob: true }, string_decoder: { preferBuiltin: true, glob: true }, - buffer: { resolve: 'buffer/index.js', expose: 'buffer', glob: true }, + buffer: { + resolve: '../vendor/buffer/index.js', + resolveBrowser: '../vendor/buffer/index.browser.js', + expose: 'buffer', + glob: true + }, url: { preferBuiltin: true, glob: true }, punycode: { preferBuiltin: true, glob: true }, querystring: { preferBuiltin: true, glob: true }, diff --git a/lib/sandbox/index.js b/lib/sandbox/index.js index cde7c047..fa152b21 100644 --- a/lib/sandbox/index.js +++ b/lib/sandbox/index.js @@ -22,6 +22,13 @@ // Setup Timerz before we delete the global timers require('./timers'); +// Require buffer to make sure it's available in the sandbox +// Browserify statically analyses the usage of buffers and only +// injects Buffer into the scope if it's used. `Buffer` injected +// by browserify is part of the functional scope and does not get +// deleted when we mutate the global scope below. +require('buffer'); + // Although we execute the user code in a well-defined scope using the uniscope // module but still to cutoff the reference to the globally available properties // we sanitize the global scope by deleting the forbidden properties in this UVM diff --git a/lib/vendor/buffer/buffer.js b/lib/vendor/buffer/buffer.js new file mode 100644 index 00000000..bbe05078 --- /dev/null +++ b/lib/vendor/buffer/buffer.js @@ -0,0 +1,57 @@ +function SpecificBuffer (_Buffer) { + const Buffer = function () { + if (typeof arguments[0] === 'number') { + return _Buffer.alloc(...arguments); + } + + return _Buffer.from(...arguments); + } + + Buffer.poolSize = _Buffer.poolSize; + + Object.defineProperty(Buffer, Symbol.hasInstance, { + value: function (instance) { + return instance instanceof _Buffer; + } + }); + + Buffer.isBuffer = function () { + return _Buffer.isBuffer(...arguments); + } + + Buffer.alloc = function () { + return _Buffer.alloc(...arguments); + } + + Buffer.allocUnsafe = function () { + return _Buffer.allocUnsafe(...arguments); + } + + Buffer.allocUnsafeSlow = function () { + return _Buffer.allocUnsafeSlow(...arguments); + } + + Buffer.from = function () { + return _Buffer.from(...arguments); + } + + Buffer.compare = function () { + return _Buffer.compare(...arguments); + } + + Buffer.isEncoding = function () { + return _Buffer.isEncoding(...arguments); + } + + Buffer.concat = function () { + return _Buffer.concat(...arguments); + } + + Buffer.byteLength = function () { + return _Buffer.byteLength(...arguments); + } + + return Buffer; +} + +module.exports = SpecificBuffer; diff --git a/lib/vendor/buffer/index.browser.js b/lib/vendor/buffer/index.browser.js new file mode 100644 index 00000000..96f052b5 --- /dev/null +++ b/lib/vendor/buffer/index.browser.js @@ -0,0 +1,9 @@ +const SpecificBuffer = require('./buffer'); +const buffer = require('buffer/'); + +module.exports = { + Buffer: SpecificBuffer(buffer.Buffer), + SlowBuffer: buffer.SlowBuffer, + INSPECT_MAX_BYTES: buffer.INSPECT_MAX_BYTES, + kMaxLength: buffer.kMaxLength +} diff --git a/lib/vendor/buffer/index.js b/lib/vendor/buffer/index.js new file mode 100644 index 00000000..eed3ce6e --- /dev/null +++ b/lib/vendor/buffer/index.js @@ -0,0 +1,9 @@ +const SpecificBuffer = require('./buffer'); +const buffer = globalThis._nodeRequires.buffer; + +module.exports = { + Buffer: SpecificBuffer(globalThis.Buffer), + SlowBuffer: buffer.SlowBuffer, + INSPECT_MAX_BYTES: buffer.INSPECT_MAX_BYTES, + kMaxLength: buffer.kMaxLength +} diff --git a/test/unit/sandbox-libraries/buffer.test.js b/test/unit/sandbox-libraries/buffer.test.js index 6ee0baa7..07599d47 100644 --- a/test/unit/sandbox-libraries/buffer.test.js +++ b/test/unit/sandbox-libraries/buffer.test.js @@ -114,12 +114,137 @@ describe('sandbox library - buffer', function () { context.execute(` var assert = require('assert'), buf1 = new Buffer('buffer'), - buf2 = new Buffer(buf1); + buf2 = new Buffer(buf1), + buf3 = Buffer(1); buf1[0] = 0x61; + buf3[0] = 0x61; assert.strictEqual(buf1.toString(), 'auffer'); assert.strictEqual(buf2.toString(), 'buffer'); + assert.strictEqual(buf3.toString(), 'a'); `, done); }); + + it('should be able to detect Buffer instances using isBuffer', function (done) { + context.execute(` + const assert = require('assert'), + + bufUsingFrom = Buffer.from('test'), + bufUsingNew = new Buffer('test'), + buf = Buffer(1); + + assert.strictEqual(Buffer.isBuffer(bufUsingFrom), true); + assert.strictEqual(Buffer.isBuffer(bufUsingNew), true); + assert.strictEqual(Buffer.isBuffer(buf), true); + `, done); + }); + + it('should be able to detect Buffer instances using Symbol.hasInstance', function (done) { + context.execute(` + const assert = require('assert'), + + bufUsingFrom = Buffer.from('test'), + bufUsingNew = new Buffer('test'); + buf = Buffer(1); + + assert.strictEqual(bufUsingFrom instanceof Buffer, true); + assert.strictEqual(bufUsingNew instanceof Buffer, true); + assert.strictEqual(buf instanceof Buffer, true); + `, done); + }); + + it('should be able to convert large buffer to string', function (done) { + // For native buffer, the max string length is ~512MB + // For browser buffer, the max string length is ~100MB + const SIZE = (typeof window === 'undefined' ? 511 : 100) * 1024 * 1024; + + context.execute(` + const assert = require('assert'), + buf = Buffer.alloc(${SIZE}, 'a'); + + assert.strictEqual(buf.toString().length, ${SIZE}); + `, done); + }); + + it('should implement Buffer.compare', function (done) { + context.execute(` + const assert = require('assert'), + + buf1 = Buffer.from('abc'), + buf2 = Buffer.from('abc'), + buf3 = Buffer.from('abd'); + + assert.strictEqual(Buffer.compare(buf1, buf2), 0); + assert.strictEqual(Buffer.compare(buf1, buf3), -1); + assert.strictEqual(Buffer.compare(buf3, buf1), 1); + `, done); + }); + + it('should implement Buffer.byteLength', function (done) { + context.execute(` + const assert = require('assert'), + buf = Buffer.from('abc'); + + assert.strictEqual(Buffer.byteLength(buf), 3); + `, done); + }); + + it('should implement Buffer.concat', function (done) { + context.execute(` + const assert = require('assert'), + + buf1 = Buffer.from('abc'), + buf2 = Buffer.from('def'); + + assert.strictEqual(Buffer.concat([buf1, buf2]).toString(), 'abcdef'); + `, done); + }); + + it('should implement Buffer.isEncoding', function (done) { + context.execute(` + const assert = require('assert'); + + assert.strictEqual(Buffer.isEncoding('utf8'), true); + assert.strictEqual(Buffer.isEncoding('hex'), true); + assert.strictEqual(Buffer.isEncoding('ascii'), true); + assert.strictEqual(Buffer.isEncoding('utf16le'), true); + assert.strictEqual(Buffer.isEncoding('ucs2'), true); + assert.strictEqual(Buffer.isEncoding('base64'), true); + assert.strictEqual(Buffer.isEncoding('binary'), true); + + assert.strictEqual(Buffer.isEncoding('utf-8'), true); + assert.strictEqual(Buffer.isEncoding('utf/8'), false); + assert.strictEqual(Buffer.isEncoding(''), false); + `, done); + }); + + it('should expose Buffer.poolSize', function (done) { + context.execute(` + const assert = require('assert'); + + assert.strictEqual(typeof Buffer.poolSize, 'number'); + `, done); + }); + + it('should expose SlowBuffer', function (done) { + context.execute(` + const assert = require('assert'), + buffer = require('buffer'); + + const buf = new buffer.SlowBuffer(10); + assert.strictEqual(buf.length, 10); + `, done); + }); + + it('should expose constants', function (done) { + context.execute(` + const assert = require('assert'), + buffer = require('buffer'); + + assert.strictEqual(typeof buffer.kMaxLength, 'number'); + assert.strictEqual(typeof buffer.INSPECT_MAX_BYTES, 'number'); + + `, done); + }); });