Skip to content

Commit

Permalink
errors: improve SystemError messages
Browse files Browse the repository at this point in the history
This commit improves the SystemError messages by allowing user
to combine a custom message and the libuv error message. Also
since we now prefer use subclasses to construct the errors instead
of using `new errors.SystemError()` directly, this removes
the behavior of assigning a default error code `ERR_SYSTEM_ERROR`
to SystemError and requires the user to directly use the
`ERR_SYSTEM_ERROR` class to construct errors instead.

Also merges `makeNodeError` into the SystemError class definition
since that's the only place the function gets used and it seems
unnecessary to introduce another level of inheritance. SystemError
now directly inherits from Error instead of an intermmediate Error
class that inherits from Error.

Class hierarchy before this patch:

ERR_SOCKET_BUFFER_SIZE -> Error (use message formatted by SystemError)
ERR_SYSTEM_ERROR -> NodeError (temp) -> Error

After:

ERR_SOCKET_BUFFER_SIZE -> SystemError -> Error
ERR_TTY_INIT_FAILED -> SystemError -> Error
ERR_SYSTEM_ERROR -> SystemError -> Error

Error messages before this patch:

```
const dgram = require('dgram');
const socket = dgram.createSocket('udp4');
socket.setRecvBufferSize(8192);

// Error [ERR_SOCKET_BUFFER_SIZE]: Could not get or set buffer
// size: Error [ERR_SYSTEM_ERROR]: bad file descriptor:
// EBADF [uv_recv_buffer_size]
//    at bufferSize (dgram.js:191:11)
//    at Socket.setRecvBufferSize (dgram.js:689:3)

const tty = require('tty');
new tty.WriteStream(1 << 30);
// Error [ERR_SYSTEM_ERROR]: invalid argument: EINVAL [uv_tty_init]
//     at new WriteStream (tty.js:84:11)
```

After:

```
const dgram = require('dgram');
const socket = dgram.createSocket('udp4');
socket.setRecvBufferSize(8192);

// SystemError [ERR_SOCKET_BUFFER_SIZE]: Could not get or set buffer
// size: uv_recv_buffer_size returned EBADF (bad file descriptor)
//     at bufferSize (dgram.js:191:11)
//     at Socket.setRecvBufferSize (dgram.js:689:3)

const tty = require('tty');
new tty.WriteStream(1 << 30);
// SystemError [ERR_TTY_INIT_FAILED]: TTY initialization failed:
// uv_tty_init returned EINVAL (invalid argument)
//     at new WriteStream (tty.js:84:11)
```

PR-URL: #19514
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
joyeecheung committed Apr 4, 2018
1 parent 3e0d40d commit 7d06761
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 261 deletions.
5 changes: 5 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,11 @@ A Transform stream finished while it was still transforming.

A Transform stream finished with data still in the write buffer.

<a id="ERR_TTY_INIT_FAILED"></a>
### ERR_TTY_INIT_FAILED

The initialization of a TTY failed due to a system error.

<a id="ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET"></a>
### ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET

Expand Down
2 changes: 1 addition & 1 deletion lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ function bufferSize(self, size, buffer) {
const ctx = {};
const ret = self._handle.bufferSize(size, buffer, ctx);
if (ret === undefined) {
throw new ERR_SOCKET_BUFFER_SIZE(new errors.SystemError(ctx));
throw new ERR_SOCKET_BUFFER_SIZE(ctx);
}
return ret;
}
Expand Down
231 changes: 115 additions & 116 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,42 +47,112 @@ function inspectValue(val) {
).split('\n');
}

function makeNodeError(Base) {
return class NodeError extends Base {
constructor(key, ...args) {
super(message(key, args));
defineProperty(this, kCode, {
configurable: true,
enumerable: false,
value: key,
writable: true
});
}
function sysErrorMessage(prefix, ctx) {
let message = `${prefix}: ${ctx.syscall} returned ` +
`${ctx.code} (${ctx.message})`;
if (ctx.path !== undefined)
message += ` ${ctx.path}`;
if (ctx.dest !== undefined)
message += ` => ${ctx.dest}`;
return message;
}

get name() {
return `${super.name} [${this[kCode]}]`;
}
// A specialized Error that includes an additional info property with
// additional information about the error condition.
// It has the properties present in a UVException but with a custom error
// message followed by the uv error code and uv error message.
// It also has its own error code with the original uv error context put into
// `err.info`.
// The context passed into this error must have .code, .syscall and .message,
// and may have .path and .dest.
class SystemError extends Error {
constructor(key, context = {}) {
context = context || {};
super(sysErrorMessage(message(key), context));
Object.defineProperty(this, kInfo, {
configurable: false,
enumerable: false,
value: context
});
Object.defineProperty(this, kCode, {
configurable: true,
enumerable: false,
value: key,
writable: true
});
}

set name(value) {
defineProperty(this, 'name', {
configurable: true,
enumerable: true,
value,
writable: true
});
}
get name() {
return `SystemError [${this[kCode]}]`;
}

get code() {
return this[kCode];
}
set name(value) {
defineProperty(this, 'name', {
configurable: true,
enumerable: true,
value,
writable: true
});
}

set code(value) {
defineProperty(this, 'code', {
configurable: true,
enumerable: true,
value,
writable: true
});
get code() {
return this[kCode];
}

set code(value) {
defineProperty(this, 'code', {
configurable: true,
enumerable: true,
value,
writable: true
});
}

get info() {
return this[kInfo];
}

get errno() {
return this[kInfo].errno;
}

set errno(val) {
this[kInfo].errno = val;
}

get syscall() {
return this[kInfo].syscall;
}

set syscall(val) {
this[kInfo].syscall = val;
}

get path() {
return this[kInfo].path !== undefined ?
this[kInfo].path.toString() : undefined;
}

set path(val) {
this[kInfo].path = val ?
lazyBuffer().from(val.toString()) : undefined;
}

get dest() {
return this[kInfo].path !== undefined ?
this[kInfo].dest.toString() : undefined;
}

set dest(val) {
this[kInfo].dest = val ?
lazyBuffer().from(val.toString()) : undefined;
}
}

function makeSystemErrorWithCode(key) {
return class NodeError extends SystemError {
constructor(...args) {
super(key, ...args);
}
};
}
Expand Down Expand Up @@ -124,8 +194,15 @@ function makeNodeErrorWithCode(Base, key) {
// Utility function for registering the error codes. Only used here. Exported
// *only* to allow for testing.
function E(sym, val, def, ...otherClasses) {
// Special case for SystemError that formats the error message differently
// The SystemErrors only have SystemError as their base classes.
messages.set(sym, val);
def = makeNodeErrorWithCode(def, sym);
if (def === SystemError) {
def = makeSystemErrorWithCode(sym);
} else {
def = makeNodeErrorWithCode(def, sym);
}

if (otherClasses.length !== 0) {
otherClasses.forEach((clazz) => {
def[clazz.name] = makeNodeErrorWithCode(clazz, sym);
Expand All @@ -140,70 +217,6 @@ function lazyBuffer() {
return buffer;
}

// A specialized Error that includes an additional info property with
// additional information about the error condition. The code key will
// be extracted from the context object or the ERR_SYSTEM_ERROR default
// will be used.
class SystemError extends makeNodeError(Error) {
constructor(context) {
context = context || {};
let code = 'ERR_SYSTEM_ERROR';
if (messages.has(context.code))
code = context.code;
super(code,
context.code,
context.syscall,
context.path,
context.dest,
context.message);
Object.defineProperty(this, kInfo, {
configurable: false,
enumerable: false,
value: context
});
}

get info() {
return this[kInfo];
}

get errno() {
return this[kInfo].errno;
}

set errno(val) {
this[kInfo].errno = val;
}

get syscall() {
return this[kInfo].syscall;
}

set syscall(val) {
this[kInfo].syscall = val;
}

get path() {
return this[kInfo].path !== undefined ?
this[kInfo].path.toString() : undefined;
}

set path(val) {
this[kInfo].path = val ?
lazyBuffer().from(val.toString()) : undefined;
}

get dest() {
return this[kInfo].path !== undefined ?
this[kInfo].dest.toString() : undefined;
}

set dest(val) {
this[kInfo].dest = val ?
lazyBuffer().from(val.toString()) : undefined;
}
}

function createErrDiff(actual, expected, operator) {
var other = '';
var res = '';
Expand Down Expand Up @@ -872,7 +885,9 @@ E('ERR_SOCKET_BAD_PORT',
'Port should be > 0 and < 65536. Received %s.', RangeError);
E('ERR_SOCKET_BAD_TYPE',
'Bad socket type specified. Valid types are: udp4, udp6', TypeError);
E('ERR_SOCKET_BUFFER_SIZE', 'Could not get or set buffer size: %s', Error);
E('ERR_SOCKET_BUFFER_SIZE',
'Could not get or set buffer size',
SystemError);
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data', Error);
E('ERR_SOCKET_CLOSED', 'Socket is closed', Error);
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running', Error);
Expand All @@ -886,6 +901,7 @@ E('ERR_STREAM_UNSHIFT_AFTER_END_EVENT',
'stream.unshift() after end event', Error);
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode', Error);
E('ERR_STREAM_WRITE_AFTER_END', 'write after end', Error);
E('ERR_SYSTEM_ERROR', 'A system error occurred', SystemError);
E('ERR_TLS_CERT_ALTNAME_INVALID',
'Hostname/IP does not match certificate\'s altnames: %s', Error);
E('ERR_TLS_DH_PARAM_SIZE', 'DH parameter size %s is less than 2048', Error);
Expand All @@ -905,6 +921,7 @@ E('ERR_TRANSFORM_ALREADY_TRANSFORMING',
// This should probably be a `RangeError`.
E('ERR_TRANSFORM_WITH_LENGTH_0',
'Calling transform done when writableState.length != 0', Error);
E('ERR_TTY_INIT_FAILED', 'TTY initialization failed', SystemError);
E('ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET',
'`process.setupUncaughtExceptionCapture()` was called while a capture ' +
'callback was already active',
Expand Down Expand Up @@ -945,24 +962,6 @@ E('ERR_VM_MODULE_NOT_MODULE',
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed', Error);

function sysError(code, syscall, path, dest,
message = 'A system error occurred') {
if (code !== undefined)
message += `: ${code}`;
if (syscall !== undefined) {
if (code === undefined)
message += ':';
message += ` [${syscall}]`;
}
if (path !== undefined) {
message += `: ${path}`;
if (dest !== undefined)
message += ` => ${dest}`;
}
return message;
}
messages.set('ERR_SYSTEM_ERROR', sysError);

function invalidArgType(name, expected, actual) {
internalAssert(typeof name === 'string');
internalAssert(arguments.length === 3);
Expand Down
4 changes: 2 additions & 2 deletions lib/os.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const { deprecate } = require('internal/util');
const { getCIDRSuffix } = require('internal/os');
const isWindows = process.platform === 'win32';

const errors = require('internal/errors');
const { ERR_SYSTEM_ERROR } = require('internal/errors');

const {
getCPUs,
Expand All @@ -49,7 +49,7 @@ function getCheckedFunction(fn) {
const ctx = {};
const ret = fn(...args, ctx);
if (ret === undefined) {
const err = new errors.SystemError(ctx);
const err = new ERR_SYSTEM_ERROR(ctx);
Error.captureStackTrace(err, checkError);
throw err;
}
Expand Down
6 changes: 3 additions & 3 deletions lib/tty.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const { inherits, _extend } = require('util');
const net = require('net');
const { TTY, isTTY } = process.binding('tty_wrap');
const errors = require('internal/errors');
const { ERR_INVALID_FD } = errors.codes;
const { ERR_INVALID_FD, ERR_TTY_INIT_FAILED } = errors.codes;
const readline = require('readline');
const { getColorDepth } = require('internal/tty');

Expand All @@ -42,7 +42,7 @@ function ReadStream(fd, options) {
const ctx = {};
const tty = new TTY(fd, true, ctx);
if (ctx.code !== undefined) {
throw new errors.SystemError(ctx);
throw new ERR_TTY_INIT_FAILED(ctx);
}

options = _extend({
Expand Down Expand Up @@ -74,7 +74,7 @@ function WriteStream(fd) {
const ctx = {};
const tty = new TTY(fd, false, ctx);
if (ctx.code !== undefined) {
throw new errors.SystemError(ctx);
throw new ERR_TTY_INIT_FAILED(ctx);
}

net.Socket.call(this, {
Expand Down
Loading

0 comments on commit 7d06761

Please sign in to comment.