Skip to content

Commit 7d06761

Browse files
committed
errors: improve SystemError messages
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>
1 parent 3e0d40d commit 7d06761

8 files changed

+259
-261
lines changed

doc/api/errors.md

+5
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,11 @@ A Transform stream finished while it was still transforming.
15211521

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

1524+
<a id="ERR_TTY_INIT_FAILED"></a>
1525+
### ERR_TTY_INIT_FAILED
1526+
1527+
The initialization of a TTY failed due to a system error.
1528+
15241529
<a id="ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET"></a>
15251530
### ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET
15261531

lib/dgram.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ function bufferSize(self, size, buffer) {
188188
const ctx = {};
189189
const ret = self._handle.bufferSize(size, buffer, ctx);
190190
if (ret === undefined) {
191-
throw new ERR_SOCKET_BUFFER_SIZE(new errors.SystemError(ctx));
191+
throw new ERR_SOCKET_BUFFER_SIZE(ctx);
192192
}
193193
return ret;
194194
}

lib/internal/errors.js

+115-116
Original file line numberDiff line numberDiff line change
@@ -47,42 +47,112 @@ function inspectValue(val) {
4747
).split('\n');
4848
}
4949

50-
function makeNodeError(Base) {
51-
return class NodeError extends Base {
52-
constructor(key, ...args) {
53-
super(message(key, args));
54-
defineProperty(this, kCode, {
55-
configurable: true,
56-
enumerable: false,
57-
value: key,
58-
writable: true
59-
});
60-
}
50+
function sysErrorMessage(prefix, ctx) {
51+
let message = `${prefix}: ${ctx.syscall} returned ` +
52+
`${ctx.code} (${ctx.message})`;
53+
if (ctx.path !== undefined)
54+
message += ` ${ctx.path}`;
55+
if (ctx.dest !== undefined)
56+
message += ` => ${ctx.dest}`;
57+
return message;
58+
}
6159

62-
get name() {
63-
return `${super.name} [${this[kCode]}]`;
64-
}
60+
// A specialized Error that includes an additional info property with
61+
// additional information about the error condition.
62+
// It has the properties present in a UVException but with a custom error
63+
// message followed by the uv error code and uv error message.
64+
// It also has its own error code with the original uv error context put into
65+
// `err.info`.
66+
// The context passed into this error must have .code, .syscall and .message,
67+
// and may have .path and .dest.
68+
class SystemError extends Error {
69+
constructor(key, context = {}) {
70+
context = context || {};
71+
super(sysErrorMessage(message(key), context));
72+
Object.defineProperty(this, kInfo, {
73+
configurable: false,
74+
enumerable: false,
75+
value: context
76+
});
77+
Object.defineProperty(this, kCode, {
78+
configurable: true,
79+
enumerable: false,
80+
value: key,
81+
writable: true
82+
});
83+
}
6584

66-
set name(value) {
67-
defineProperty(this, 'name', {
68-
configurable: true,
69-
enumerable: true,
70-
value,
71-
writable: true
72-
});
73-
}
85+
get name() {
86+
return `SystemError [${this[kCode]}]`;
87+
}
7488

75-
get code() {
76-
return this[kCode];
77-
}
89+
set name(value) {
90+
defineProperty(this, 'name', {
91+
configurable: true,
92+
enumerable: true,
93+
value,
94+
writable: true
95+
});
96+
}
7897

79-
set code(value) {
80-
defineProperty(this, 'code', {
81-
configurable: true,
82-
enumerable: true,
83-
value,
84-
writable: true
85-
});
98+
get code() {
99+
return this[kCode];
100+
}
101+
102+
set code(value) {
103+
defineProperty(this, 'code', {
104+
configurable: true,
105+
enumerable: true,
106+
value,
107+
writable: true
108+
});
109+
}
110+
111+
get info() {
112+
return this[kInfo];
113+
}
114+
115+
get errno() {
116+
return this[kInfo].errno;
117+
}
118+
119+
set errno(val) {
120+
this[kInfo].errno = val;
121+
}
122+
123+
get syscall() {
124+
return this[kInfo].syscall;
125+
}
126+
127+
set syscall(val) {
128+
this[kInfo].syscall = val;
129+
}
130+
131+
get path() {
132+
return this[kInfo].path !== undefined ?
133+
this[kInfo].path.toString() : undefined;
134+
}
135+
136+
set path(val) {
137+
this[kInfo].path = val ?
138+
lazyBuffer().from(val.toString()) : undefined;
139+
}
140+
141+
get dest() {
142+
return this[kInfo].path !== undefined ?
143+
this[kInfo].dest.toString() : undefined;
144+
}
145+
146+
set dest(val) {
147+
this[kInfo].dest = val ?
148+
lazyBuffer().from(val.toString()) : undefined;
149+
}
150+
}
151+
152+
function makeSystemErrorWithCode(key) {
153+
return class NodeError extends SystemError {
154+
constructor(...args) {
155+
super(key, ...args);
86156
}
87157
};
88158
}
@@ -124,8 +194,15 @@ function makeNodeErrorWithCode(Base, key) {
124194
// Utility function for registering the error codes. Only used here. Exported
125195
// *only* to allow for testing.
126196
function E(sym, val, def, ...otherClasses) {
197+
// Special case for SystemError that formats the error message differently
198+
// The SystemErrors only have SystemError as their base classes.
127199
messages.set(sym, val);
128-
def = makeNodeErrorWithCode(def, sym);
200+
if (def === SystemError) {
201+
def = makeSystemErrorWithCode(sym);
202+
} else {
203+
def = makeNodeErrorWithCode(def, sym);
204+
}
205+
129206
if (otherClasses.length !== 0) {
130207
otherClasses.forEach((clazz) => {
131208
def[clazz.name] = makeNodeErrorWithCode(clazz, sym);
@@ -140,70 +217,6 @@ function lazyBuffer() {
140217
return buffer;
141218
}
142219

143-
// A specialized Error that includes an additional info property with
144-
// additional information about the error condition. The code key will
145-
// be extracted from the context object or the ERR_SYSTEM_ERROR default
146-
// will be used.
147-
class SystemError extends makeNodeError(Error) {
148-
constructor(context) {
149-
context = context || {};
150-
let code = 'ERR_SYSTEM_ERROR';
151-
if (messages.has(context.code))
152-
code = context.code;
153-
super(code,
154-
context.code,
155-
context.syscall,
156-
context.path,
157-
context.dest,
158-
context.message);
159-
Object.defineProperty(this, kInfo, {
160-
configurable: false,
161-
enumerable: false,
162-
value: context
163-
});
164-
}
165-
166-
get info() {
167-
return this[kInfo];
168-
}
169-
170-
get errno() {
171-
return this[kInfo].errno;
172-
}
173-
174-
set errno(val) {
175-
this[kInfo].errno = val;
176-
}
177-
178-
get syscall() {
179-
return this[kInfo].syscall;
180-
}
181-
182-
set syscall(val) {
183-
this[kInfo].syscall = val;
184-
}
185-
186-
get path() {
187-
return this[kInfo].path !== undefined ?
188-
this[kInfo].path.toString() : undefined;
189-
}
190-
191-
set path(val) {
192-
this[kInfo].path = val ?
193-
lazyBuffer().from(val.toString()) : undefined;
194-
}
195-
196-
get dest() {
197-
return this[kInfo].path !== undefined ?
198-
this[kInfo].dest.toString() : undefined;
199-
}
200-
201-
set dest(val) {
202-
this[kInfo].dest = val ?
203-
lazyBuffer().from(val.toString()) : undefined;
204-
}
205-
}
206-
207220
function createErrDiff(actual, expected, operator) {
208221
var other = '';
209222
var res = '';
@@ -872,7 +885,9 @@ E('ERR_SOCKET_BAD_PORT',
872885
'Port should be > 0 and < 65536. Received %s.', RangeError);
873886
E('ERR_SOCKET_BAD_TYPE',
874887
'Bad socket type specified. Valid types are: udp4, udp6', TypeError);
875-
E('ERR_SOCKET_BUFFER_SIZE', 'Could not get or set buffer size: %s', Error);
888+
E('ERR_SOCKET_BUFFER_SIZE',
889+
'Could not get or set buffer size',
890+
SystemError);
876891
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data', Error);
877892
E('ERR_SOCKET_CLOSED', 'Socket is closed', Error);
878893
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running', Error);
@@ -886,6 +901,7 @@ E('ERR_STREAM_UNSHIFT_AFTER_END_EVENT',
886901
'stream.unshift() after end event', Error);
887902
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode', Error);
888903
E('ERR_STREAM_WRITE_AFTER_END', 'write after end', Error);
904+
E('ERR_SYSTEM_ERROR', 'A system error occurred', SystemError);
889905
E('ERR_TLS_CERT_ALTNAME_INVALID',
890906
'Hostname/IP does not match certificate\'s altnames: %s', Error);
891907
E('ERR_TLS_DH_PARAM_SIZE', 'DH parameter size %s is less than 2048', Error);
@@ -905,6 +921,7 @@ E('ERR_TRANSFORM_ALREADY_TRANSFORMING',
905921
// This should probably be a `RangeError`.
906922
E('ERR_TRANSFORM_WITH_LENGTH_0',
907923
'Calling transform done when writableState.length != 0', Error);
924+
E('ERR_TTY_INIT_FAILED', 'TTY initialization failed', SystemError);
908925
E('ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET',
909926
'`process.setupUncaughtExceptionCapture()` was called while a capture ' +
910927
'callback was already active',
@@ -945,24 +962,6 @@ E('ERR_VM_MODULE_NOT_MODULE',
945962
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
946963
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed', Error);
947964

948-
function sysError(code, syscall, path, dest,
949-
message = 'A system error occurred') {
950-
if (code !== undefined)
951-
message += `: ${code}`;
952-
if (syscall !== undefined) {
953-
if (code === undefined)
954-
message += ':';
955-
message += ` [${syscall}]`;
956-
}
957-
if (path !== undefined) {
958-
message += `: ${path}`;
959-
if (dest !== undefined)
960-
message += ` => ${dest}`;
961-
}
962-
return message;
963-
}
964-
messages.set('ERR_SYSTEM_ERROR', sysError);
965-
966965
function invalidArgType(name, expected, actual) {
967966
internalAssert(typeof name === 'string');
968967
internalAssert(arguments.length === 3);

lib/os.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const { deprecate } = require('internal/util');
2727
const { getCIDRSuffix } = require('internal/os');
2828
const isWindows = process.platform === 'win32';
2929

30-
const errors = require('internal/errors');
30+
const { ERR_SYSTEM_ERROR } = require('internal/errors');
3131

3232
const {
3333
getCPUs,
@@ -49,7 +49,7 @@ function getCheckedFunction(fn) {
4949
const ctx = {};
5050
const ret = fn(...args, ctx);
5151
if (ret === undefined) {
52-
const err = new errors.SystemError(ctx);
52+
const err = new ERR_SYSTEM_ERROR(ctx);
5353
Error.captureStackTrace(err, checkError);
5454
throw err;
5555
}

lib/tty.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const { inherits, _extend } = require('util');
2525
const net = require('net');
2626
const { TTY, isTTY } = process.binding('tty_wrap');
2727
const errors = require('internal/errors');
28-
const { ERR_INVALID_FD } = errors.codes;
28+
const { ERR_INVALID_FD, ERR_TTY_INIT_FAILED } = errors.codes;
2929
const readline = require('readline');
3030
const { getColorDepth } = require('internal/tty');
3131

@@ -42,7 +42,7 @@ function ReadStream(fd, options) {
4242
const ctx = {};
4343
const tty = new TTY(fd, true, ctx);
4444
if (ctx.code !== undefined) {
45-
throw new errors.SystemError(ctx);
45+
throw new ERR_TTY_INIT_FAILED(ctx);
4646
}
4747

4848
options = _extend({
@@ -74,7 +74,7 @@ function WriteStream(fd) {
7474
const ctx = {};
7575
const tty = new TTY(fd, false, ctx);
7676
if (ctx.code !== undefined) {
77-
throw new errors.SystemError(ctx);
77+
throw new ERR_TTY_INIT_FAILED(ctx);
7878
}
7979

8080
net.Socket.call(this, {

0 commit comments

Comments
 (0)