From 7462524674e9231daadac0186095d01f46e6e264 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 27 Oct 2017 16:25:14 -0700 Subject: [PATCH 1/4] src: add CollectExceptionInfo & errors.SystemError Preparing for the migration of existing UVException and ErrnoExceptions from the native layer, add new `errors.SystemError` to internal/errors and new `env->CollectExceptionInfo()` / `env->CollectUVExceptionInfo()` methods. --- doc/api/errors.md | 28 +- lib/internal/errors.js | 90 +++++++ src/env.cc | 78 ++++++ src/env.h | 13 + src/node.cc | 327 ----------------------- src/node_internals.h | 326 ++++++++++++++++++++++ test/parallel/test-errors-systemerror.js | 187 +++++++++++++ 7 files changed, 720 insertions(+), 329 deletions(-) create mode 100644 test/parallel/test-errors-systemerror.js diff --git a/doc/api/errors.md b/doc/api/errors.md index 23817ef3d1924f..e7b629781e6d37 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -458,7 +458,7 @@ checks or `abort()` calls in the C++ layer. ## System Errors -System errors are generated when exceptions occur within the program's +System errors are generated when exceptions occur within the Node.js runtime environment. Typically, these are operational errors that occur when an application violates an operating system constraint such as attempting to read a file that does not exist or when the user does not have sufficient @@ -471,7 +471,24 @@ of error codes and their meanings is available by running `man 2 intro` or In Node.js, system errors are represented as augmented `Error` objects with added properties. -### Class: System Error +### Class: SystemError + +### error.info + +`SystemError` instances may have an additional `info` property whose +value is an object with additional details about the error conditions. + +The following properties are provided: + +* `code` {string} The string error code +* `errno` {number} The system-provided error number +* `message` {string} A system-provided human readable description of the error +* `syscall` {string} The name of the system call that triggered the error +* `path` {Buffer} When reporting a file system error, the `path` will identify + the file path. +* `dest` {Buffer} When reporting a file system error, the `dest` will identify + the file path destination (if any). + #### error.code @@ -1379,6 +1396,13 @@ instance.setEncoding('utf8'); Used when an attempt is made to call [`stream.write()`][] after `stream.end()` has been called. + +### ERR_SYSTEM_ERROR + +The `ERR_SYSTEM_ERROR` code is used when an unspecified or non-specific system +error has occurred within the Node.js process. The error object will have an +`err.info` object property with additional details. + ### ERR_TLS_CERT_ALTNAME_INVALID diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 7148279672c2cc..d6a77b88757283 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -10,6 +10,7 @@ // message may change, the code should not. const kCode = Symbol('code'); +const kInfo = Symbol('info'); const messages = new Map(); const { kMaxLength } = process.binding('buffer'); @@ -58,6 +59,68 @@ function makeNodeError(Base) { }; } +// 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 ? Buffer.from(val.toString()) : undefined; + } + + get dest() { + return this[kInfo].path !== undefined ? + this[kInfo].dest.toString() : undefined; + } + + set dest(val) { + this[kInfo].dest = val ? Buffer.from(val.toString()) : undefined; + } +} + class AssertionError extends Error { constructor(options) { if (typeof options !== 'object' || options === null) { @@ -128,6 +191,7 @@ module.exports = exports = { RangeError: makeNodeError(RangeError), URIError: makeNodeError(URIError), AssertionError, + SystemError, E // This is exported only to facilitate testing. }; @@ -144,6 +208,9 @@ module.exports = exports = { // Any error code added here should also be added to the documentation // // Note: Please try to keep these in alphabetical order +// +// Note: Node.js specific errors must begin with the prefix ERR_ + E('ERR_ARG_NOT_ITERABLE', '%s must be iterable'); E('ERR_ASSERTION', '%s'); E('ERR_ASYNC_CALLBACK', (name) => `${name} must be a function`); @@ -334,6 +401,7 @@ E('ERR_STREAM_READ_NOT_IMPLEMENTED', '_read() is not implemented'); E('ERR_STREAM_UNSHIFT_AFTER_END_EVENT', 'stream.unshift() after end event'); E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode'); E('ERR_STREAM_WRITE_AFTER_END', 'write after end'); +E('ERR_SYSTEM_ERROR', sysError('A system error occurred')); E('ERR_TLS_CERT_ALTNAME_INVALID', 'Hostname/IP does not match certificate\'s altnames: %s'); E('ERR_TLS_DH_PARAM_SIZE', (size) => @@ -371,6 +439,28 @@ E('ERR_VALUE_OUT_OF_RANGE', (start, end, value) => { E('ERR_ZLIB_BINDING_CLOSED', 'zlib binding closed'); E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed'); +function sysError(defaultMessage) { + return function(code, + syscall, + path, + dest, + message = defaultMessage) { + 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; + }; +} + function invalidArgType(name, expected, actual) { internalAssert(name, 'name is required'); diff --git a/src/env.cc b/src/env.cc index a0d82986c9e231..9bed08cdb09175 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1,6 +1,7 @@ #include "node_internals.h" #include "async-wrap.h" #include "v8-profiler.h" +#include "node_buffer.h" #if defined(_MSC_VER) #define getpid GetCurrentProcessId @@ -228,4 +229,81 @@ void Environment::EnvPromiseHook(v8::PromiseHookType type, } } +void CollectExceptionInfo(Environment* env, + v8::Local obj, + int errorno, + const char* err_string, + const char* syscall, + const char* message, + const char* path, + const char* dest) { + obj->Set(env->errno_string(), v8::Integer::New(env->isolate(), errorno)); + + obj->Set(env->context(), env->code_string(), + OneByteString(env->isolate(), err_string)).FromJust(); + + if (message != nullptr) { + obj->Set(env->context(), env->message_string(), + OneByteString(env->isolate(), message)).FromJust(); + } + + v8::Local path_buffer; + if (path != nullptr) { + path_buffer = + Buffer::Copy(env->isolate(), path, strlen(path)).ToLocalChecked(); + obj->Set(env->context(), env->path_string(), path_buffer).FromJust(); + } + + v8::Local dest_buffer; + if (dest != nullptr) { + dest_buffer = + Buffer::Copy(env->isolate(), dest, strlen(dest)).ToLocalChecked(); + obj->Set(env->context(), env->dest_string(), dest_buffer).FromJust(); + } + + if (syscall != nullptr) { + obj->Set(env->context(), env->syscall_string(), + OneByteString(env->isolate(), syscall)); + } +} + +void Environment::CollectExceptionInfo(v8::Local object, + int errorno, + const char* syscall, + const char* message, + const char* path) { + if (!object->IsObject() || errorno == 0) + return; + + v8::Local obj = object.As(); + const char* err_string = node::errno_string(errorno); + + if (message == nullptr || message[0] == '\0') { + message = strerror(errorno); + } + + node::CollectExceptionInfo(this, obj, errorno, err_string, + syscall, message, path, nullptr); +} + +void Environment::CollectUVExceptionInfo(v8::Local object, + int errorno, + const char* syscall, + const char* message, + const char* path, + const char* dest) { + if (!object->IsObject() || errorno == 0) + return; + + v8::Local obj = object.As(); + const char* err_string = uv_err_name(errorno); + + if (message == nullptr || message[0] == '\0') { + message = uv_strerror(errorno); + } + + node::CollectExceptionInfo(this, obj, errorno, err_string, + syscall, message, path, dest); +} + } // namespace node diff --git a/src/env.h b/src/env.h index c8bb168df4d845..787cc7eb19d064 100644 --- a/src/env.h +++ b/src/env.h @@ -617,6 +617,19 @@ class Environment { inline performance::performance_state* performance_state(); inline std::map* performance_marks(); + void CollectExceptionInfo(v8::Local context, + int errorno, + const char* syscall = nullptr, + const char* message = nullptr, + const char* path = nullptr); + + void CollectUVExceptionInfo(v8::Local context, + int errorno, + const char* syscall = nullptr, + const char* message = nullptr, + const char* path = nullptr, + const char* dest = nullptr); + inline void ThrowError(const char* errmsg); inline void ThrowTypeError(const char* errmsg); inline void ThrowRangeError(const char* errmsg); diff --git a/src/node.cc b/src/node.cc index bfe36f218adf4b..b0457e530eedc9 100644 --- a/src/node.cc +++ b/src/node.cc @@ -390,333 +390,6 @@ static void IdleImmediateDummy(uv_idle_t* handle) { // TODO(bnoordhuis) Maybe make libuv accept nullptr idle callbacks. } - -static inline const char *errno_string(int errorno) { -#define ERRNO_CASE(e) case e: return #e; - switch (errorno) { -#ifdef EACCES - ERRNO_CASE(EACCES); -#endif - -#ifdef EADDRINUSE - ERRNO_CASE(EADDRINUSE); -#endif - -#ifdef EADDRNOTAVAIL - ERRNO_CASE(EADDRNOTAVAIL); -#endif - -#ifdef EAFNOSUPPORT - ERRNO_CASE(EAFNOSUPPORT); -#endif - -#ifdef EAGAIN - ERRNO_CASE(EAGAIN); -#endif - -#ifdef EWOULDBLOCK -# if EAGAIN != EWOULDBLOCK - ERRNO_CASE(EWOULDBLOCK); -# endif -#endif - -#ifdef EALREADY - ERRNO_CASE(EALREADY); -#endif - -#ifdef EBADF - ERRNO_CASE(EBADF); -#endif - -#ifdef EBADMSG - ERRNO_CASE(EBADMSG); -#endif - -#ifdef EBUSY - ERRNO_CASE(EBUSY); -#endif - -#ifdef ECANCELED - ERRNO_CASE(ECANCELED); -#endif - -#ifdef ECHILD - ERRNO_CASE(ECHILD); -#endif - -#ifdef ECONNABORTED - ERRNO_CASE(ECONNABORTED); -#endif - -#ifdef ECONNREFUSED - ERRNO_CASE(ECONNREFUSED); -#endif - -#ifdef ECONNRESET - ERRNO_CASE(ECONNRESET); -#endif - -#ifdef EDEADLK - ERRNO_CASE(EDEADLK); -#endif - -#ifdef EDESTADDRREQ - ERRNO_CASE(EDESTADDRREQ); -#endif - -#ifdef EDOM - ERRNO_CASE(EDOM); -#endif - -#ifdef EDQUOT - ERRNO_CASE(EDQUOT); -#endif - -#ifdef EEXIST - ERRNO_CASE(EEXIST); -#endif - -#ifdef EFAULT - ERRNO_CASE(EFAULT); -#endif - -#ifdef EFBIG - ERRNO_CASE(EFBIG); -#endif - -#ifdef EHOSTUNREACH - ERRNO_CASE(EHOSTUNREACH); -#endif - -#ifdef EIDRM - ERRNO_CASE(EIDRM); -#endif - -#ifdef EILSEQ - ERRNO_CASE(EILSEQ); -#endif - -#ifdef EINPROGRESS - ERRNO_CASE(EINPROGRESS); -#endif - -#ifdef EINTR - ERRNO_CASE(EINTR); -#endif - -#ifdef EINVAL - ERRNO_CASE(EINVAL); -#endif - -#ifdef EIO - ERRNO_CASE(EIO); -#endif - -#ifdef EISCONN - ERRNO_CASE(EISCONN); -#endif - -#ifdef EISDIR - ERRNO_CASE(EISDIR); -#endif - -#ifdef ELOOP - ERRNO_CASE(ELOOP); -#endif - -#ifdef EMFILE - ERRNO_CASE(EMFILE); -#endif - -#ifdef EMLINK - ERRNO_CASE(EMLINK); -#endif - -#ifdef EMSGSIZE - ERRNO_CASE(EMSGSIZE); -#endif - -#ifdef EMULTIHOP - ERRNO_CASE(EMULTIHOP); -#endif - -#ifdef ENAMETOOLONG - ERRNO_CASE(ENAMETOOLONG); -#endif - -#ifdef ENETDOWN - ERRNO_CASE(ENETDOWN); -#endif - -#ifdef ENETRESET - ERRNO_CASE(ENETRESET); -#endif - -#ifdef ENETUNREACH - ERRNO_CASE(ENETUNREACH); -#endif - -#ifdef ENFILE - ERRNO_CASE(ENFILE); -#endif - -#ifdef ENOBUFS - ERRNO_CASE(ENOBUFS); -#endif - -#ifdef ENODATA - ERRNO_CASE(ENODATA); -#endif - -#ifdef ENODEV - ERRNO_CASE(ENODEV); -#endif - -#ifdef ENOENT - ERRNO_CASE(ENOENT); -#endif - -#ifdef ENOEXEC - ERRNO_CASE(ENOEXEC); -#endif - -#ifdef ENOLINK - ERRNO_CASE(ENOLINK); -#endif - -#ifdef ENOLCK -# if ENOLINK != ENOLCK - ERRNO_CASE(ENOLCK); -# endif -#endif - -#ifdef ENOMEM - ERRNO_CASE(ENOMEM); -#endif - -#ifdef ENOMSG - ERRNO_CASE(ENOMSG); -#endif - -#ifdef ENOPROTOOPT - ERRNO_CASE(ENOPROTOOPT); -#endif - -#ifdef ENOSPC - ERRNO_CASE(ENOSPC); -#endif - -#ifdef ENOSR - ERRNO_CASE(ENOSR); -#endif - -#ifdef ENOSTR - ERRNO_CASE(ENOSTR); -#endif - -#ifdef ENOSYS - ERRNO_CASE(ENOSYS); -#endif - -#ifdef ENOTCONN - ERRNO_CASE(ENOTCONN); -#endif - -#ifdef ENOTDIR - ERRNO_CASE(ENOTDIR); -#endif - -#ifdef ENOTEMPTY -# if ENOTEMPTY != EEXIST - ERRNO_CASE(ENOTEMPTY); -# endif -#endif - -#ifdef ENOTSOCK - ERRNO_CASE(ENOTSOCK); -#endif - -#ifdef ENOTSUP - ERRNO_CASE(ENOTSUP); -#else -# ifdef EOPNOTSUPP - ERRNO_CASE(EOPNOTSUPP); -# endif -#endif - -#ifdef ENOTTY - ERRNO_CASE(ENOTTY); -#endif - -#ifdef ENXIO - ERRNO_CASE(ENXIO); -#endif - - -#ifdef EOVERFLOW - ERRNO_CASE(EOVERFLOW); -#endif - -#ifdef EPERM - ERRNO_CASE(EPERM); -#endif - -#ifdef EPIPE - ERRNO_CASE(EPIPE); -#endif - -#ifdef EPROTO - ERRNO_CASE(EPROTO); -#endif - -#ifdef EPROTONOSUPPORT - ERRNO_CASE(EPROTONOSUPPORT); -#endif - -#ifdef EPROTOTYPE - ERRNO_CASE(EPROTOTYPE); -#endif - -#ifdef ERANGE - ERRNO_CASE(ERANGE); -#endif - -#ifdef EROFS - ERRNO_CASE(EROFS); -#endif - -#ifdef ESPIPE - ERRNO_CASE(ESPIPE); -#endif - -#ifdef ESRCH - ERRNO_CASE(ESRCH); -#endif - -#ifdef ESTALE - ERRNO_CASE(ESTALE); -#endif - -#ifdef ETIME - ERRNO_CASE(ETIME); -#endif - -#ifdef ETIMEDOUT - ERRNO_CASE(ETIMEDOUT); -#endif - -#ifdef ETXTBSY - ERRNO_CASE(ETXTBSY); -#endif - -#ifdef EXDEV - ERRNO_CASE(EXDEV); -#endif - - default: return ""; - } -} - const char *signo_string(int signo) { #define SIGNO_CASE(e) case e: return #e; switch (signo) { diff --git a/src/node_internals.h b/src/node_internals.h index 5d2f996cc8c1c5..579221b85c3478 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -324,6 +324,332 @@ class InternalCallbackScope { bool closed_ = false; }; +static inline const char *errno_string(int errorno) { +#define ERRNO_CASE(e) case e: return #e; + switch (errorno) { +#ifdef EACCES + ERRNO_CASE(EACCES); +#endif + +#ifdef EADDRINUSE + ERRNO_CASE(EADDRINUSE); +#endif + +#ifdef EADDRNOTAVAIL + ERRNO_CASE(EADDRNOTAVAIL); +#endif + +#ifdef EAFNOSUPPORT + ERRNO_CASE(EAFNOSUPPORT); +#endif + +#ifdef EAGAIN + ERRNO_CASE(EAGAIN); +#endif + +#ifdef EWOULDBLOCK +# if EAGAIN != EWOULDBLOCK + ERRNO_CASE(EWOULDBLOCK); +# endif +#endif + +#ifdef EALREADY + ERRNO_CASE(EALREADY); +#endif + +#ifdef EBADF + ERRNO_CASE(EBADF); +#endif + +#ifdef EBADMSG + ERRNO_CASE(EBADMSG); +#endif + +#ifdef EBUSY + ERRNO_CASE(EBUSY); +#endif + +#ifdef ECANCELED + ERRNO_CASE(ECANCELED); +#endif + +#ifdef ECHILD + ERRNO_CASE(ECHILD); +#endif + +#ifdef ECONNABORTED + ERRNO_CASE(ECONNABORTED); +#endif + +#ifdef ECONNREFUSED + ERRNO_CASE(ECONNREFUSED); +#endif + +#ifdef ECONNRESET + ERRNO_CASE(ECONNRESET); +#endif + +#ifdef EDEADLK + ERRNO_CASE(EDEADLK); +#endif + +#ifdef EDESTADDRREQ + ERRNO_CASE(EDESTADDRREQ); +#endif + +#ifdef EDOM + ERRNO_CASE(EDOM); +#endif + +#ifdef EDQUOT + ERRNO_CASE(EDQUOT); +#endif + +#ifdef EEXIST + ERRNO_CASE(EEXIST); +#endif + +#ifdef EFAULT + ERRNO_CASE(EFAULT); +#endif + +#ifdef EFBIG + ERRNO_CASE(EFBIG); +#endif + +#ifdef EHOSTUNREACH + ERRNO_CASE(EHOSTUNREACH); +#endif + +#ifdef EIDRM + ERRNO_CASE(EIDRM); +#endif + +#ifdef EILSEQ + ERRNO_CASE(EILSEQ); +#endif + +#ifdef EINPROGRESS + ERRNO_CASE(EINPROGRESS); +#endif + +#ifdef EINTR + ERRNO_CASE(EINTR); +#endif + +#ifdef EINVAL + ERRNO_CASE(EINVAL); +#endif + +#ifdef EIO + ERRNO_CASE(EIO); +#endif + +#ifdef EISCONN + ERRNO_CASE(EISCONN); +#endif + +#ifdef EISDIR + ERRNO_CASE(EISDIR); +#endif + +#ifdef ELOOP + ERRNO_CASE(ELOOP); +#endif + +#ifdef EMFILE + ERRNO_CASE(EMFILE); +#endif + +#ifdef EMLINK + ERRNO_CASE(EMLINK); +#endif + +#ifdef EMSGSIZE + ERRNO_CASE(EMSGSIZE); +#endif + +#ifdef EMULTIHOP + ERRNO_CASE(EMULTIHOP); +#endif + +#ifdef ENAMETOOLONG + ERRNO_CASE(ENAMETOOLONG); +#endif + +#ifdef ENETDOWN + ERRNO_CASE(ENETDOWN); +#endif + +#ifdef ENETRESET + ERRNO_CASE(ENETRESET); +#endif + +#ifdef ENETUNREACH + ERRNO_CASE(ENETUNREACH); +#endif + +#ifdef ENFILE + ERRNO_CASE(ENFILE); +#endif + +#ifdef ENOBUFS + ERRNO_CASE(ENOBUFS); +#endif + +#ifdef ENODATA + ERRNO_CASE(ENODATA); +#endif + +#ifdef ENODEV + ERRNO_CASE(ENODEV); +#endif + +#ifdef ENOENT + ERRNO_CASE(ENOENT); +#endif + +#ifdef ENOEXEC + ERRNO_CASE(ENOEXEC); +#endif + +#ifdef ENOLINK + ERRNO_CASE(ENOLINK); +#endif + +#ifdef ENOLCK +# if ENOLINK != ENOLCK + ERRNO_CASE(ENOLCK); +# endif +#endif + +#ifdef ENOMEM + ERRNO_CASE(ENOMEM); +#endif + +#ifdef ENOMSG + ERRNO_CASE(ENOMSG); +#endif + +#ifdef ENOPROTOOPT + ERRNO_CASE(ENOPROTOOPT); +#endif + +#ifdef ENOSPC + ERRNO_CASE(ENOSPC); +#endif + +#ifdef ENOSR + ERRNO_CASE(ENOSR); +#endif + +#ifdef ENOSTR + ERRNO_CASE(ENOSTR); +#endif + +#ifdef ENOSYS + ERRNO_CASE(ENOSYS); +#endif + +#ifdef ENOTCONN + ERRNO_CASE(ENOTCONN); +#endif + +#ifdef ENOTDIR + ERRNO_CASE(ENOTDIR); +#endif + +#ifdef ENOTEMPTY +# if ENOTEMPTY != EEXIST + ERRNO_CASE(ENOTEMPTY); +# endif +#endif + +#ifdef ENOTSOCK + ERRNO_CASE(ENOTSOCK); +#endif + +#ifdef ENOTSUP + ERRNO_CASE(ENOTSUP); +#else +# ifdef EOPNOTSUPP + ERRNO_CASE(EOPNOTSUPP); +# endif +#endif + +#ifdef ENOTTY + ERRNO_CASE(ENOTTY); +#endif + +#ifdef ENXIO + ERRNO_CASE(ENXIO); +#endif + + +#ifdef EOVERFLOW + ERRNO_CASE(EOVERFLOW); +#endif + +#ifdef EPERM + ERRNO_CASE(EPERM); +#endif + +#ifdef EPIPE + ERRNO_CASE(EPIPE); +#endif + +#ifdef EPROTO + ERRNO_CASE(EPROTO); +#endif + +#ifdef EPROTONOSUPPORT + ERRNO_CASE(EPROTONOSUPPORT); +#endif + +#ifdef EPROTOTYPE + ERRNO_CASE(EPROTOTYPE); +#endif + +#ifdef ERANGE + ERRNO_CASE(ERANGE); +#endif + +#ifdef EROFS + ERRNO_CASE(EROFS); +#endif + +#ifdef ESPIPE + ERRNO_CASE(ESPIPE); +#endif + +#ifdef ESRCH + ERRNO_CASE(ESRCH); +#endif + +#ifdef ESTALE + ERRNO_CASE(ESTALE); +#endif + +#ifdef ETIME + ERRNO_CASE(ETIME); +#endif + +#ifdef ETIMEDOUT + ERRNO_CASE(ETIMEDOUT); +#endif + +#ifdef ETXTBSY + ERRNO_CASE(ETXTBSY); +#endif + +#ifdef EXDEV + ERRNO_CASE(EXDEV); +#endif + + default: return ""; + } +} + #define NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc) \ NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, NM_F_INTERNAL) \ diff --git a/test/parallel/test-errors-systemerror.js b/test/parallel/test-errors-systemerror.js new file mode 100644 index 00000000000000..45ac7341752512 --- /dev/null +++ b/test/parallel/test-errors-systemerror.js @@ -0,0 +1,187 @@ +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const errors = require('internal/errors'); + +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' + } +); + +common.expectsError( + () => { throw new errors.SystemError(null); }, + { + code: 'ERR_SYSTEM_ERROR', + type: errors.SystemError, + message: 'A system error occurred' + } +); + +common.expectsError( + () => { throw new errors.SystemError({ code: 'ERR' }); }, + { + code: 'ERR_SYSTEM_ERROR', + type: errors.SystemError, + message: 'A system error occurred: ERR' + } +); + +{ + const ctx = { + code: 'ERR', + syscall: 'foo' + }; + 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); }, + { + code: 'ERR_SYSTEM_ERROR', + type: errors.SystemError, + message: 'A system error occurred: ERR [foo]: a' + } + ); +} + +{ + 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') + }; + common.expectsError( + () => { throw new errors.SystemError(ctx); }, + { + code: 'ERR_SYSTEM_ERROR', + type: errors.SystemError, + message: 'something happened: ERR [foo]: a => b' + } + ); +} + +{ + const ctx = { + code: 'ERR_ASSERTION' + }; + common.expectsError( + () => { throw new errors.SystemError(ctx); }, + { + code: 'ERR_ASSERTION', + type: errors.SystemError + } + ); +} + +{ + const ctx = { + code: 'ERR', + errno: 123, + message: 'something happened', + syscall: 'foo', + path: Buffer.from('a'), + dest: Buffer.from('b') + }; + const err = new errors.SystemError(ctx); + assert.strictEqual(err.info, ctx); + assert.strictEqual(err.code, 'ERR_SYSTEM_ERROR'); + 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.path, 'a'); + assert.strictEqual(err.dest, 'b'); + + // Make sure it's mutable + err.code = 'test'; + err.errno = 321; + err.syscall = 'test'; + err.path = 'path'; + err.dest = 'path'; + + assert.strictEqual(err.errno, 321); + assert.strictEqual(err.syscall, 'test'); + assert.strictEqual(err.path, 'path'); + assert.strictEqual(err.dest, 'path'); +} From 5698a33cf3e11e1225a19e29cba887f34e49a37d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 27 Oct 2017 16:27:16 -0700 Subject: [PATCH 2/4] os: migrate node_os.cc to internal/errors --- lib/os.js | 34 ++++++++++++++++++++++++++++------ src/node_os.cc | 26 ++++++++++++++++++++------ 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/lib/os.js b/lib/os.js index 6af5d15cbc43cf..7c07a5b0d385c5 100644 --- a/lib/os.js +++ b/lib/os.js @@ -27,21 +27,43 @@ const { deprecate } = require('internal/util'); const { getCIDRSuffix } = require('internal/os'); const isWindows = process.platform === 'win32'; +const errors = require('internal/errors'); + const { getCPUs, getFreeMem, - getHomeDirectory, - getHostname, - getInterfaceAddresses, + getHomeDirectory: _getHomeDirectory, + getHostname: _getHostname, + getInterfaceAddresses: _getInterfaceAddresses, getLoadAvg, - getOSRelease, - getOSType, + getOSRelease: _getOSRelease, + getOSType: _getOSType, getTotalMem, - getUserInfo, + getUserInfo: _getUserInfo, getUptime, isBigEndian } = process.binding('os'); +function getCheckedFunction(fn) { + return function checkError(...args) { + const ctx = {}; + const ret = fn(...args, ctx); + if (ret === undefined) { + const err = new errors.SystemError(ctx); + Error.captureStackTrace(err, checkError); + throw err; + } + return ret; + }; +} + +const getHomeDirectory = getCheckedFunction(_getHomeDirectory); +const getHostname = getCheckedFunction(_getHostname); +const getInterfaceAddresses = getCheckedFunction(_getInterfaceAddresses); +const getOSRelease = getCheckedFunction(_getOSRelease); +const getOSType = getCheckedFunction(_getOSType); +const getUserInfo = getCheckedFunction(_getUserInfo); + getFreeMem[Symbol.toPrimitive] = () => getFreeMem(); getHostname[Symbol.toPrimitive] = () => getHostname(); getHomeDirectory[Symbol.toPrimitive] = () => getHomeDirectory(); diff --git a/src/node_os.cc b/src/node_os.cc index f09cd6fa5a03ff..c57841ca9ec3d3 100644 --- a/src/node_os.cc +++ b/src/node_os.cc @@ -72,7 +72,9 @@ static void GetHostname(const FunctionCallbackInfo& args) { #else // __MINGW32__ int errorno = WSAGetLastError(); #endif // __POSIX__ - return env->ThrowErrnoException(errorno, "gethostname"); + CHECK_GE(args.Length(), 1); + env->CollectExceptionInfo(args[args.Length() - 1], errorno, "gethostname"); + return args.GetReturnValue().SetUndefined(); } buf[sizeof(buf) - 1] = '\0'; @@ -87,7 +89,9 @@ static void GetOSType(const FunctionCallbackInfo& args) { #ifdef __POSIX__ struct utsname info; if (uname(&info) < 0) { - return env->ThrowErrnoException(errno, "uname"); + CHECK_GE(args.Length(), 1); + env->CollectExceptionInfo(args[args.Length() - 1], errno, "uname"); + return args.GetReturnValue().SetUndefined(); } rval = info.sysname; #else // __MINGW32__ @@ -105,7 +109,9 @@ static void GetOSRelease(const FunctionCallbackInfo& args) { #ifdef __POSIX__ struct utsname info; if (uname(&info) < 0) { - return env->ThrowErrnoException(errno, "uname"); + CHECK_GE(args.Length(), 1); + env->CollectExceptionInfo(args[args.Length() - 1], errno, "uname"); + return args.GetReturnValue().SetUndefined(); } # ifdef _AIX char release[256]; @@ -242,7 +248,10 @@ static void GetInterfaceAddresses(const FunctionCallbackInfo& args) { if (err == UV_ENOSYS) { return args.GetReturnValue().Set(ret); } else if (err) { - return env->ThrowUVException(err, "uv_interface_addresses"); + CHECK_GE(args.Length(), 1); + env->CollectUVExceptionInfo(args[args.Length() - 1], errno, + "uv_interface_addresses"); + return args.GetReturnValue().SetUndefined(); } for (i = 0; i < count; i++) { @@ -319,7 +328,9 @@ static void GetHomeDirectory(const FunctionCallbackInfo& args) { const int err = uv_os_homedir(buf, &len); if (err) { - return env->ThrowUVException(err, "uv_os_homedir"); + CHECK_GE(args.Length(), 1); + env->CollectUVExceptionInfo(args[args.Length() - 1], err, "uv_os_homedir"); + return args.GetReturnValue().SetUndefined(); } Local home = String::NewFromUtf8(env->isolate(), @@ -351,7 +362,10 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { const int err = uv_os_get_passwd(&pwd); if (err) { - return env->ThrowUVException(err, "uv_os_get_passwd"); + CHECK_GE(args.Length(), 2); + env->CollectUVExceptionInfo(args[args.Length() - 1], err, + "uv_os_get_passwd"); + return args.GetReturnValue().SetUndefined(); } Local error; From 1863099839176549cf294a7e184aa1890a65e1d1 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 27 Oct 2017 17:20:54 -0700 Subject: [PATCH 3/4] tty: convert to internal/errors using SystemError --- lib/internal/errors.js | 13 +++- lib/tty.js | 16 ++++- src/tty_wrap.cc | 7 +- test/parallel/test-ttywrap-invalid-fd.js | 83 ++++++++++++++---------- 4 files changed, 77 insertions(+), 42 deletions(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index d6a77b88757283..c871f8e8d32e80 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -18,6 +18,7 @@ const { defineProperty } = Object; // Lazily loaded var util = null; +var buffer; function makeNodeError(Base) { return class NodeError extends Base { @@ -59,6 +60,12 @@ function makeNodeError(Base) { }; } +function lazyBuffer() { + if (buffer === undefined) + buffer = require('buffer').Buffer; + 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 @@ -108,7 +115,8 @@ class SystemError extends makeNodeError(Error) { } set path(val) { - this[kInfo].path = val ? Buffer.from(val.toString()) : undefined; + this[kInfo].path = val ? + lazyBuffer().from(val.toString()) : undefined; } get dest() { @@ -117,7 +125,8 @@ class SystemError extends makeNodeError(Error) { } set dest(val) { - this[kInfo].dest = val ? Buffer.from(val.toString()) : undefined; + this[kInfo].dest = val ? + lazyBuffer().from(val.toString()) : undefined; } } diff --git a/lib/tty.js b/lib/tty.js index cf020f529dae71..144fc86b8e8205 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -40,11 +40,17 @@ function ReadStream(fd, options) { if (fd >> 0 !== fd || fd < 0) throw new errors.RangeError('ERR_INVALID_FD', fd); + const ctx = {}; + const tty = new TTY(fd, true, ctx); + if (ctx.code !== undefined) { + throw new errors.SystemError(ctx); + } + options = util._extend({ highWaterMark: 0, readable: true, writable: false, - handle: new TTY(fd, true) + handle: tty }, options); net.Socket.call(this, options); @@ -69,8 +75,14 @@ function WriteStream(fd) { if (fd >> 0 !== fd || fd < 0) throw new errors.RangeError('ERR_INVALID_FD', fd); + const ctx = {}; + const tty = new TTY(fd, false, ctx); + if (ctx.code !== undefined) { + throw new errors.SystemError(ctx); + } + net.Socket.call(this, { - handle: new TTY(fd, false), + handle: tty, readable: false, writable: true }); diff --git a/src/tty_wrap.cc b/src/tty_wrap.cc index 872a126c6d4ee4..4d49e1a65c8c36 100644 --- a/src/tty_wrap.cc +++ b/src/tty_wrap.cc @@ -154,9 +154,10 @@ void TTYWrap::New(const FunctionCallbackInfo& args) { int err = 0; TTYWrap* wrap = new TTYWrap(env, args.This(), fd, args[1]->IsTrue(), &err); - if (err != 0) - return env->ThrowUVException(err, "uv_tty_init"); - + if (err != 0) { + env->CollectUVExceptionInfo(args[2], err, "uv_tty_init"); + args.GetReturnValue().SetUndefined(); + } wrap->UpdateWriteQueueSize(); } diff --git a/test/parallel/test-ttywrap-invalid-fd.js b/test/parallel/test-ttywrap-invalid-fd.js index 44647590ccb413..ad7a98da3460dd 100644 --- a/test/parallel/test-ttywrap-invalid-fd.js +++ b/test/parallel/test-ttywrap-invalid-fd.js @@ -1,44 +1,57 @@ 'use strict'; const common = require('../common'); -const assert = require('assert'); const fs = require('fs'); const tty = require('tty'); -assert.throws(() => { - new tty.WriteStream(-1); -}, common.expectsError({ - code: 'ERR_INVALID_FD', - type: RangeError, - message: '"fd" must be a positive integer: -1' -}) +common.expectsError( + () => new tty.WriteStream(-1), + { + code: 'ERR_INVALID_FD', + type: RangeError, + message: '"fd" must be a positive integer: -1' + } ); -const err_regex = common.isWindows ? - /^Error: EBADF: bad file descriptor, uv_tty_init$/ : - /^Error: EINVAL: invalid argument, uv_tty_init$/; -assert.throws(() => { - let fd = 2; - // Get first known bad file descriptor. - try { - while (fs.fstatSync(++fd)); - } catch (e) { } - new tty.WriteStream(fd); -}, err_regex); +{ + const message = common.isWindows ? + 'bad file descriptor: EBADF [uv_tty_init]' : + 'invalid argument: EINVAL [uv_tty_init]'; -assert.throws(() => { - new tty.ReadStream(-1); -}, common.expectsError({ - code: 'ERR_INVALID_FD', - type: RangeError, - message: '"fd" must be a positive integer: -1' -}) -); + common.expectsError( + () => { + let fd = 2; + // Get first known bad file descriptor. + try { + while (fs.fstatSync(++fd)); + } catch (e) { } + new tty.WriteStream(fd); + }, { + code: 'ERR_SYSTEM_ERROR', + type: Error, + message + } + ); + + common.expectsError( + () => { + let fd = 2; + // Get first known bad file descriptor. + try { + while (fs.fstatSync(++fd)); + } catch (e) { } + new tty.ReadStream(fd); + }, { + code: 'ERR_SYSTEM_ERROR', + type: Error, + message + }); +} -assert.throws(() => { - let fd = 2; - // Get first known bad file descriptor. - try { - while (fs.fstatSync(++fd)); - } catch (e) { } - new tty.ReadStream(fd); -}, err_regex); +common.expectsError( + () => new tty.ReadStream(-1), + { + code: 'ERR_INVALID_FD', + type: RangeError, + message: '"fd" must be a positive integer: -1' + } +); From ee9e7a66f6ec836f5e19e8fed953876e6a87f779 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 27 Oct 2017 23:24:15 -0700 Subject: [PATCH 4/4] dgram: migrate bufferSize to use internal/errors --- lib/dgram.js | 10 ++++++---- src/udp_wrap.cc | 12 ++++++++---- test/parallel/test-dgram-socket-buffer-size.js | 3 +-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/dgram.js b/lib/dgram.js index 4d5ea7c3b2afbc..cc2d11b81ecfa6 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -173,11 +173,13 @@ function bufferSize(self, size, buffer) { if (size >>> 0 !== size) throw new errors.TypeError('ERR_SOCKET_BAD_BUFFER_SIZE'); - try { - return self._handle.bufferSize(size, buffer); - } catch (e) { - throw new errors.Error('ERR_SOCKET_BUFFER_SIZE', e); + const ctx = {}; + const ret = self._handle.bufferSize(size, buffer, ctx); + if (ret === undefined) { + throw new errors.Error('ERR_SOCKET_BUFFER_SIZE', + new errors.SystemError(ctx)); } + return ret; } Socket.prototype.bind = function(port_, address_ /*, callback*/) { diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index b3702ea6e3feb2..05682a3e1f4753 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -234,8 +234,10 @@ void UDPWrap::BufferSize(const FunctionCallbackInfo& args) { const char* uv_func_name = is_recv ? "uv_recv_buffer_size" : "uv_send_buffer_size"; - if (!args[0]->IsInt32()) - return env->ThrowUVException(UV_EINVAL, uv_func_name); + if (!args[0]->IsInt32()) { + env->CollectUVExceptionInfo(args[2], UV_EINVAL, uv_func_name); + return args.GetReturnValue().SetUndefined(); + } uv_handle_t* handle = reinterpret_cast(&wrap->handle_); int size = static_cast(args[0].As()->Value()); @@ -246,8 +248,10 @@ void UDPWrap::BufferSize(const FunctionCallbackInfo& args) { else err = uv_send_buffer_size(handle, &size); - if (err != 0) - return env->ThrowUVException(err, uv_func_name); + if (err != 0) { + env->CollectUVExceptionInfo(args[2], err, uv_func_name); + return args.GetReturnValue().SetUndefined(); + } args.GetReturnValue().Set(size); } diff --git a/test/parallel/test-dgram-socket-buffer-size.js b/test/parallel/test-dgram-socket-buffer-size.js index 7d6e2bfcc564ca..2ef09f98fe13a6 100644 --- a/test/parallel/test-dgram-socket-buffer-size.js +++ b/test/parallel/test-dgram-socket-buffer-size.js @@ -77,8 +77,7 @@ function checkBufferSizeError(type, size) { const errorObj = { code: 'ERR_SOCKET_BUFFER_SIZE', type: Error, - message: 'Could not get or set buffer size: Error: EINVAL: ' + - `invalid argument, uv_${type}_buffer_size` + message: /^Could not get or set buffer size:.*$/ }; const functionName = `set${type.charAt(0).toUpperCase()}${type.slice(1)}` + 'BufferSize';