diff --git a/doc/api/errors.md b/doc/api/errors.md index 6e37ef50c35f81..936b1b0cd5d53c 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1521,6 +1521,11 @@ A Transform stream finished while it was still transforming. A Transform stream finished with data still in the write buffer. + +### ERR_TTY_INIT_FAILED + +The initialization of a TTY failed due to a system error. + ### ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET diff --git a/lib/dgram.js b/lib/dgram.js index fcf9f00a105101..0a72834873e8bd 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -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; } diff --git a/lib/internal/errors.js b/lib/internal/errors.js index f8073d15fe8d49..17b6f966d42357 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -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); } }; } @@ -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); @@ -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 = ''; @@ -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); @@ -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); @@ -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', @@ -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); diff --git a/lib/os.js b/lib/os.js index eb13139dba9a9c..5c83dbfab7b6e3 100644 --- a/lib/os.js +++ b/lib/os.js @@ -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, @@ -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; } diff --git a/lib/tty.js b/lib/tty.js index dfb76bbe53da54..4e9023b0eb6114 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -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'); @@ -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({ @@ -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, { diff --git a/test/parallel/test-dgram-socket-buffer-size.js b/test/parallel/test-dgram-socket-buffer-size.js index 1fe72690d23aa9..834ca30c57d926 100644 --- a/test/parallel/test-dgram-socket-buffer-size.js +++ b/test/parallel/test-dgram-socket-buffer-size.js @@ -1,33 +1,61 @@ 'use strict'; +// Flags: --expose-internals const common = require('../common'); const assert = require('assert'); const dgram = require('dgram'); +const { SystemError } = require('internal/errors'); +const uv = process.binding('uv'); + +function getExpectedError(type) { + const code = common.isWindows ? 'ENOTSOCK' : 'EBADF'; + const message = common.isWindows ? + 'socket operation on non-socket' : 'bad file descriptor'; + const errno = common.isWindows ? uv.UV_ENOTSOCK : uv.UV_EBADF; + const syscall = `uv_${type}_buffer_size`; + const suffix = common.isWindows ? + 'ENOTSOCK (socket operation on non-socket)' : 'EBADF (bad file descriptor)'; + const error = { + code: 'ERR_SOCKET_BUFFER_SIZE', + type: SystemError, + message: `Could not get or set buffer size: ${syscall} returned ${suffix}`, + info: { + code, + message, + errno, + syscall + } + }; + return error; +} { // Should throw error if the socket is never bound. - const errorObj = { - code: 'ERR_SOCKET_BUFFER_SIZE', - type: Error, - message: /^Could not get or set buffer size:.*$/ - }; + const errorObj = getExpectedError('send'); const socket = dgram.createSocket('udp4'); common.expectsError(() => { - socket.setRecvBufferSize(8192); + socket.setSendBufferSize(8192); }, errorObj); common.expectsError(() => { - socket.setSendBufferSize(8192); + socket.getSendBufferSize(); }, errorObj); +} + +{ + const socket = dgram.createSocket('udp4'); + + // Should throw error if the socket is never bound. + const errorObj = getExpectedError('recv'); common.expectsError(() => { - socket.getRecvBufferSize(); + socket.setRecvBufferSize(8192); }, errorObj); common.expectsError(() => { - socket.getSendBufferSize(); + socket.getRecvBufferSize(); }, errorObj); } @@ -73,22 +101,48 @@ const dgram = require('dgram'); })); } -function checkBufferSizeError(type, size) { +{ + const info = { + code: 'EINVAL', + message: 'invalid argument', + errno: uv.UV_EINVAL, + syscall: 'uv_recv_buffer_size' + }; const errorObj = { code: 'ERR_SOCKET_BUFFER_SIZE', - type: Error, - message: /^Could not get or set buffer size:.*$/ + type: SystemError, + message: 'Could not get or set buffer size: uv_recv_buffer_size ' + + 'returned EINVAL (invalid argument)', + info }; - const functionName = `set${type.charAt(0).toUpperCase()}${type.slice(1)}` + - 'BufferSize'; const socket = dgram.createSocket('udp4'); socket.bind(common.mustCall(() => { common.expectsError(() => { - socket[functionName](size); + socket.setRecvBufferSize(2147483648); }, errorObj); socket.close(); })); } -checkBufferSizeError('recv', 2147483648); -checkBufferSizeError('send', 2147483648); +{ + const info = { + code: 'EINVAL', + message: 'invalid argument', + errno: uv.UV_EINVAL, + syscall: 'uv_send_buffer_size' + }; + const errorObj = { + code: 'ERR_SOCKET_BUFFER_SIZE', + type: SystemError, + message: 'Could not get or set buffer size: uv_send_buffer_size ' + + 'returned EINVAL (invalid argument)', + info + }; + const socket = dgram.createSocket('udp4'); + socket.bind(common.mustCall(() => { + common.expectsError(() => { + socket.setSendBufferSize(2147483648); + }, errorObj); + socket.close(); + })); +} diff --git a/test/parallel/test-errors-systemerror.js b/test/parallel/test-errors-systemerror.js index 45ac7341752512..285c89b5d25c2a 100644 --- a/test/parallel/test-errors-systemerror.js +++ b/test/parallel/test-errors-systemerror.js @@ -4,149 +4,79 @@ const common = require('../common'); const assert = require('assert'); const errors = require('internal/errors'); +const { AssertionError } = require('assert'); -common.expectsError( - () => { throw new errors.SystemError(); }, - { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred' - } -); - -common.expectsError( - () => { throw new errors.SystemError({}); }, - { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred' - } -); +const { E, SystemError } = errors; common.expectsError( - () => { throw new errors.SystemError(null); }, + () => { throw new errors.SystemError(); }, { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred' + code: 'ERR_ASSERTION', + type: AssertionError, + message: 'An invalid error message key was used: undefined.' } ); -common.expectsError( - () => { throw new errors.SystemError({ code: 'ERR' }); }, - { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred: ERR' - } -); +E('ERR_TEST', 'custom message', SystemError); +const { ERR_TEST } = errors.codes; { const ctx = { - code: 'ERR', - syscall: 'foo' + code: 'ETEST', + message: 'code message', + syscall: 'syscall_test', + path: '/str', + dest: '/str2' }; - common.expectsError( - () => { throw new errors.SystemError(ctx); }, - { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred: ERR [foo]' - } - ); -} -{ - const ctx = { - code: 'ERR', - syscall: 'foo', - path: Buffer.from('a') - }; common.expectsError( - () => { throw new errors.SystemError(ctx); }, + () => { throw new ERR_TEST(ctx); }, { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred: ERR [foo]: a' + code: 'ERR_TEST', + type: SystemError, + message: 'custom message: syscall_test returned ETEST (code message)' + + ' /str => /str2', + info: ctx } ); } { const ctx = { - code: 'ERR', - syscall: 'foo', - path: Buffer.from('a'), - dest: Buffer.from('b') - }; - common.expectsError( - () => { throw new errors.SystemError(ctx); }, - { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred: ERR [foo]: a => b' - } - ); -} - -{ - const ctx = { - syscall: 'foo', - path: Buffer.from('a'), - dest: Buffer.from('b') - }; - common.expectsError( - () => { throw new errors.SystemError(ctx); }, - { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred: [foo]: a => b' - } - ); -} - -{ - const ctx = { - path: Buffer.from('a'), - dest: Buffer.from('b') - }; - common.expectsError( - () => { throw new errors.SystemError(ctx); }, - { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'A system error occurred: a => b' - } - ); -} - -{ - const ctx = { - code: 'ERR', - message: 'something happened', - syscall: 'foo', - path: Buffer.from('a'), - dest: Buffer.from('b') + code: 'ETEST', + message: 'code message', + syscall: 'syscall_test', + path: Buffer.from('/buf'), + dest: '/str2' }; common.expectsError( - () => { throw new errors.SystemError(ctx); }, + () => { throw new ERR_TEST(ctx); }, { - code: 'ERR_SYSTEM_ERROR', - type: errors.SystemError, - message: 'something happened: ERR [foo]: a => b' + code: 'ERR_TEST', + type: SystemError, + message: 'custom message: syscall_test returned ETEST (code message)' + + ' /buf => /str2', + info: ctx } ); } { const ctx = { - code: 'ERR_ASSERTION' + code: 'ETEST', + message: 'code message', + syscall: 'syscall_test', + path: Buffer.from('/buf'), + dest: Buffer.from('/buf2') }; common.expectsError( - () => { throw new errors.SystemError(ctx); }, + () => { throw new ERR_TEST(ctx); }, { - code: 'ERR_ASSERTION', - type: errors.SystemError + code: 'ERR_TEST', + type: SystemError, + message: 'custom message: syscall_test returned ETEST (code message)' + + ' /buf => /buf2', + info: ctx } ); } @@ -156,20 +86,20 @@ common.expectsError( code: 'ERR', errno: 123, message: 'something happened', - syscall: 'foo', + syscall: 'syscall_test', path: Buffer.from('a'), dest: Buffer.from('b') }; - const err = new errors.SystemError(ctx); + const err = new ERR_TEST(ctx); assert.strictEqual(err.info, ctx); - assert.strictEqual(err.code, 'ERR_SYSTEM_ERROR'); + assert.strictEqual(err.code, 'ERR_TEST'); err.code = 'test'; assert.strictEqual(err.code, 'test'); // Test legacy properties. These shouldn't be used anymore // but let us make sure they still work assert.strictEqual(err.errno, 123); - assert.strictEqual(err.syscall, 'foo'); + assert.strictEqual(err.syscall, 'syscall_test'); assert.strictEqual(err.path, 'a'); assert.strictEqual(err.dest, 'b'); diff --git a/test/parallel/test-ttywrap-invalid-fd.js b/test/parallel/test-ttywrap-invalid-fd.js index adf88cbde659ce..c360489cb33529 100644 --- a/test/parallel/test-ttywrap-invalid-fd.js +++ b/test/parallel/test-ttywrap-invalid-fd.js @@ -4,6 +4,7 @@ const common = require('../common'); const tty = require('tty'); const { SystemError } = require('internal/errors'); +const uv = process.binding('uv'); common.expectsError( () => new tty.WriteStream(-1), @@ -15,9 +16,16 @@ common.expectsError( ); { - const message = common.isWindows ? - 'bad file descriptor: EBADF [uv_tty_init]' : - 'invalid argument: EINVAL [uv_tty_init]'; + const info = { + code: common.isWindows ? 'EBADF' : 'EINVAL', + message: common.isWindows ? 'bad file descriptor' : 'invalid argument', + errno: common.isWindows ? uv.UV_EBADF : uv.UV_EINVAL, + syscall: 'uv_tty_init' + }; + + const suffix = common.isWindows ? + 'EBADF (bad file descriptor)' : 'EINVAL (invalid argument)'; + const message = `TTY initialization failed: uv_tty_init returned ${suffix}`; common.expectsError( () => { @@ -25,9 +33,10 @@ common.expectsError( new tty.WriteStream(fd); }); }, { - code: 'ERR_SYSTEM_ERROR', + code: 'ERR_TTY_INIT_FAILED', type: SystemError, - message + message, + info } ); @@ -37,9 +46,10 @@ common.expectsError( new tty.ReadStream(fd); }); }, { - code: 'ERR_SYSTEM_ERROR', + code: 'ERR_TTY_INIT_FAILED', type: SystemError, - message + message, + info }); }