Node.js contains a plethora of possible errors it can throw during execution, so today we'll continue our in-depth Node.js Error Handling series by looking at one of the many System Errors
Node produces: the ERR_BUFFER_OUT_OF_BOUNDS. Node throws a System Error
when an exception occurs within the program's runtime environment, and such errors are typically an indication that there was an operational problem within the application. An ERR_BUFFER_OUT_OF_BOUNDS
error indicates that an operation was made outside the bounds of a Buffer
object.
Throughout this article, we'll explore the ERR_BUFFER_OUT_OF_BOUNDS
system error by looking at where it sits in the overall Node.js Error Class Hierarchy. We'll also explore the basic process of using Buffers
in Node.js, which will illustrate how passing invalid bounds arguments (offsets or lengths, in particular) to some Buffer
class methods can throw ERR_BUFFER_OUT_OF_BOUNDS
errors. Let's get into it!
Most Node.js errors inherit from the Error
base class, or extend from an inherited class therein. The full error hierarchy of this error is:
Error
SystemError
ERR_BUFFER_OUT_OF_BOUNDS
Below is the full code sample we'll be using in this article. It can be copied and pasted if you'd like to play with the code yourself and see how everything works.
/**
* index.js
*/
const logging = require('logging');function executeTests () {
// Create int8 array populated with first 5 numeric key values.
const data = Uint8Array.from(new Uint8Array(5).keys());logging.lineSeparator('bufferFromValue(data.buffer)', 60);
bufferFromValue(data.buffer);logging.lineSeparator('bufferFromValue(data.buffer, 3)', 60);
bufferFromValue(data.buffer, 3);logging.lineSeparator('bufferFromValue(data.buffer, 6)', 60);
bufferFromValue(data.buffer, 6);logging.lineSeparator('bufferFromValue(data.buffer, 2, 7)', 60);
bufferFromValue(data.buffer, 2, 7);
}/**
* Retrieves a buffer from passed value, using optional encoding/offset and length.
*
* @param value String, buffer, array-like Object to get buffer from.
* @param encodingOrOffset Encoding (for Strings) or offset (for array-like Objects) at which to offset returned buffer.
* @param length Length of buffer to retrieve.
* @returns {Buffer2} Generated buffer.
*/
function bufferFromValue (value, encodingOrOffset, length) {
try {
// Invokes Buffer.from method with passed parameters.
let buffer = Buffer.from(value, encodingOrOffset, length);
// Log generated buffer.
logging.log(buffer);
// Return generated buffer.
return buffer;
} catch (e) {
if (e instanceof RangeError && e.code === 'ERR_BUFFER_OUT_OF_BOUNDS') {
// Output expected ERR_BUFFER_OUT_OF_BOUNDS RangeErrors.
logging.log(e);
} else {
// Output unexpected Errors.
logging.log(e, false);
}
}
}
executeTests();
// logging module - index.js
const fs = require("fs");
const SeparatorCharacterDefault = '-';
const SeparatorLengthDefault = 40;module.exports = {
/**
* Outputs a line separator via console.log, with optional first argument text centered in the middle.
*/
lineSeparator: function () {
// Check if at least one argument of string type is passed.
if (arguments.length >= 1 && typeof(arguments[0]) === 'string') {
lineSeparatorWithInsert(arguments[0], arguments[1], arguments[2]);
} else {
// Otherwise, assume default separator without insertion.
lineSeparator(arguments[0], arguments[1]);
}
},/**
* Log the passed object or value.
*
* @param value Value to be logged to the console.
*/
log: function (value) {
if (value instanceof Error) {
logError(value, arguments[1]);
} else {
logValue(value);
}
},/**
* Synchronously log the passed object or value.
*
* @param value Value to be logged to the console.
*/
logSync: function (value) {
if (value instanceof Error) {
logError(value, arguments[1], true);
} else {
logValue(value, true);
}
}
};/**
* Outputs a line separator via console.log.
*
* @param length Total separator length.
* @param char Separator character.
*/
function lineSeparator (length = SeparatorLengthDefault, char = SeparatorCharacterDefault) {
// Default output to insertion.
logValue(Array(length).join(char));
}/**
* Outputs a line separator via console.log with inserted text centered in the middle.
*
* @param insert Inserted text to be centered.
* @param length Total separator length.
* @param char Separator character.
*/
function lineSeparatorWithInsert (insert, length = SeparatorLengthDefault, char = SeparatorCharacterDefault) {
// Default output to insertion.
let output = insert;if (insert.length < length) {
// Update length based on insert length, less a space for margin.
length -= insert.length + 2;
// Halve the length and floor left side.
let left = Math.floor(length / 2);
let right = left;
// If odd number, add dropped remainder to right side.
if ((length % 2) !== 0) {
right += 1;
}// Surround insert with separators.
output = `${Array(left).join(char)} ${insert} ${Array(right).join(char)}`;
}logValue(output);
}/**
* Logs an Error with explicit/inexplicit tag, error name, and message.
*
* @param error Error to be logged.
* @param explicit Determines if passed Error was explicit (intended) or not.
* @param synchronous Determines if output must be synchronous.
*/
function logError(error, explicit = true, synchronous = false) {
let message = `[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`;
// To avoid duplication get stack without initial error message line.
let stack = error.stack.slice(error.stack.indexOf("\n") + 1);
// Use fs.writeSync for synchronous output, otherwise console.log.
if (synchronous) {
fs.writeSync(1, message + '\n');
fs.writeSync(1, stack);
} else {
console.log(message);
console.log(stack);
}
}
/**
* Logs a value (string, object, number, etc).
*
* @param value Value to be logged.
* @param synchronous Determines if output must be synchronous.
*/
function logValue(value, synchronous = false) {
// Use fs.writeSync for synchronous output, otherwise console.log.
if (synchronous) {
fs.writeSync(1, value + '\n');
} else {
console.log(value);
}
}
The Buffer
class was introduced into early versions of Node.js to give developers a means of reading and manipulating streams of binary data. Since then, however, JavaScript ES6 (ECMAScript 2015) introduced the TypedArray
object to handle binary data buffers. Therefore, modern Node.js versions have adapted the Buffer
class to focus on implementing a more optimized Uint8Array
API.
It's also worth briefly noting that the Buffer
class constructor has been deprecated, so the process of actually creating buffers should use the Buffer.from()
, buffer.allocUnsafe()
, and buffer.alloc()
methods. For our example code we'll be using the simplest of these, Buffer.from(value, encodingOrOffset, length)
, which retrieves a binary buffer representation of the first value
argument passed to it. We can also specify additional arguments to alter the offset or length of the returned buffer.
To test this stuff out we have a simple bufferFromValue (value, encodingOrOffset, length)
helper method:
/**
* Retrieves a buffer from passed value, using optional encoding/offset and length.
*
* @param value String, buffer, array-like Object to get buffer from.
* @param encodingOrOffset Encoding (for Strings) or offset (for array-like Objects) at which to offset returned buffer.
* @param length Length of buffer to retrieve.
* @returns {Buffer2} Generated buffer.
*/
function bufferFromValue (value, encodingOrOffset, length) {
try {
// Invokes Buffer.from method with passed parameters.
let buffer = Buffer.from(value, encodingOrOffset, length);
// Log generated buffer.
logging.log(buffer);
// Return generated buffer.
return buffer;
} catch (e) {
if (e instanceof RangeError && e.code === 'ERR_BUFFER_OUT_OF_BOUNDS') {
// Output expected ERR_BUFFER_OUT_OF_BOUNDS RangeErrors.
logging.log(e);
} else {
// Output unexpected Errors.
logging.log(e, false);
}
}
}
As you can see, this method is merely a wrapper for the aforementioned Buffer.from(...)
method, but it allows us to perform some simple, self-contained tests by passing different arguments. As such, we begin our tests by creating a simple data
Uint8Array
that is populated with the first five numeric values as keys (i.e. 0
, 1
, 2
, 3
, and 4
):
// Create int8 array populated with first 5 keys.
const data = Uint8Array.from(new Uint8Array(5).keys());
logging.lineSeparator('bufferFromValue(data.buffer)', 60);
bufferFromValue(data.buffer);
We then pass the data.buffer
value to bufferFromValue(...)
to ensure everything works. Sure enough, the output we get is our Buffer
object with the five expected values:
-------------- bufferFromValue(data.buffer) --------------
<Buffer 00 01 02 03 04>
Next, let's try passing an offset
value of 3
:
logging.lineSeparator('bufferFromValue(data.buffer, 3)', 60);
bufferFromValue(data.buffer, 3);
This also works just fine and outputs the same Buffer
object as before, except we've offset the result by three values:
------------ bufferFromValue(data.buffer, 3) -------------
<Buffer 03 04>
Cool! However, what happens if we pass an offset
value that exceeds the bounds of the original array-like object?
logging.lineSeparator('bufferFromValue(data.buffer, 6)', 60);
bufferFromValue(data.buffer, 6);
As you can certainly guess, this throws a RangeError
with a code
property of ERR_BUFFER_OUT_OF_BOUNDS
:
------------ bufferFromValue(data.buffer, 6) -------------
[EXPLICIT] RangeError [ERR_BUFFER_OUT_OF_BOUNDS]: "offset" is outside of buffer bounds
at fromArrayBuffer (buffer.js:375:11)
at Function.from (buffer.js:192:12)
at bufferFromValue (D:\work\Airbrake.io\Exceptions\NodeJS\Error\SystemError\ERR_BUFFER_OUT_OF_BOUNDS\index.js:34:25)
at executeTests (D:\work\Airbrake.io\Exceptions\NodeJS\Error\SystemError\ERR_BUFFER_OUT_OF_BOUNDS\index.js:17:3)
at Object.<anonymous> (D:\work\Airbrake.io\Exceptions\NodeJS\Error\SystemError\ERR_BUFFER_OUT_OF_BOUNDS\index.js:50:1)
at Module._compile (module.js:657:14)
at Object.Module._extensions..js (module.js:671:10)
at Module.load (module.js:573:32)
at tryModuleLoad (module.js:513:12)
at Function.Module._load (module.js:505:3)
Since our buffer bounds only go up to 5
, an offset
of 6
or higher is invalid. Similarly, let's try passing an in-bounds offset
with an invalid length
argument:
logging.lineSeparator('bufferFromValue(data.buffer, 2, 7)', 60);
bufferFromValue(data.buffer, 2, 7);
Once again, this throws an ERR_BUFFER_OUT_OF_BOUNDS
RangeError
, this time with the indication that length
is outside the buffer bounds:
----------- bufferFromValue(data.buffer, 2, 7) -----------
[EXPLICIT] RangeError [ERR_BUFFER_OUT_OF_BOUNDS]: "length" is outside of buffer bounds
at fromArrayBuffer (buffer.js:387:15)
at Function.from (buffer.js:192:12)
at bufferFromValue (D:\work\Airbrake.io\Exceptions\NodeJS\Error\SystemError\ERR_BUFFER_OUT_OF_BOUNDS\index.js:34:25)
at executeTests (D:\work\Airbrake.io\Exceptions\NodeJS\Error\SystemError\ERR_BUFFER_OUT_OF_BOUNDS\index.js:20:3)
at Object.<anonymous> (D:\work\Airbrake.io\Exceptions\NodeJS\Error\SystemError\ERR_BUFFER_OUT_OF_BOUNDS\index.js:50:1)
at Module._compile (module.js:657:14)
at Object.Module._extensions..js (module.js:671:10)
at Module.load (module.js:573:32)
at tryModuleLoad (module.js:513:12)
at Function.Module._load (module.js:505:3)
If we dig into the Node.js Buffer
class source code we can quickly find exactly where these errors originate from. In this case, they are both thrown within the fromArrayBuffer(obj, byteOffset, length)
internal function:
// Source: https://github.com/nodejs/node/blob/master/lib/buffer.js#L362-L392
function fromArrayBuffer(obj, byteOffset, length) {
// convert byteOffset to integer
if (byteOffset === undefined) {
byteOffset = 0;
} else {
byteOffset = +byteOffset;
// check for NaN
if (byteOffset !== byteOffset)
byteOffset = 0;
}const maxLength = obj.byteLength - byteOffset;
if (maxLength < 0)
throw new errors.RangeError('ERR_BUFFER_OUT_OF_BOUNDS', 'offset');
if (length === undefined) {
length = maxLength;
} else {
// convert length to non-negative integer
length = +length;
// Check for NaN
if (length !== length) {
length = 0;
} else if (length > 0) {
if (length > maxLength)
throw new errors.RangeError('ERR_BUFFER_OUT_OF_BOUNDS', 'length');
} else {
length = 0;
}
}
Airbrake's robust error monitoring software provides real-time error monitoring and automatic error reporting for all your development projects. Airbrake's state of the art web dashboard ensures you receive round-the-clock status updates on your application's health and error rates. No matter what you're working on, Airbrake easily integrates with all the most popular languages and frameworks. Plus, Airbrake makes it easy to customize error parameters, while giving you complete control of the active error filter system, so you only gather the errors that matter most.
Check out Airbrake's error monitoring software today and try Airbrake free for 30 days. See for yourself why so many of the world's best engineering teams use Airbrake to revolutionize their exception handling practices!