Skip to content

Commit

Permalink
src: add CollectExceptionInfo & errors.SystemError
Browse files Browse the repository at this point in the history
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.

PR-URL: #16567
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
  • Loading branch information
jasnell committed Nov 2, 2017
1 parent f1f0eb2 commit c3dc0e0
Show file tree
Hide file tree
Showing 7 changed files with 720 additions and 329 deletions.
28 changes: 26 additions & 2 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -1379,6 +1396,13 @@ instance.setEncoding('utf8');
Used when an attempt is made to call [`stream.write()`][] after
`stream.end()` has been called.

<a id="ERR_SYSTEM_ERROR"></a>
### 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.

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

Expand Down
90 changes: 90 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -128,6 +191,7 @@ module.exports = exports = {
RangeError: makeNodeError(RangeError),
URIError: makeNodeError(URIError),
AssertionError,
SystemError,
E // This is exported only to facilitate testing.
};

Expand All @@ -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`);
Expand Down Expand Up @@ -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) =>
Expand Down Expand Up @@ -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');

Expand Down
78 changes: 78 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -228,4 +229,81 @@ void Environment::EnvPromiseHook(v8::PromiseHookType type,
}
}

void CollectExceptionInfo(Environment* env,
v8::Local<v8::Object> 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<v8::Value> 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<v8::Value> 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<v8::Value> object,
int errorno,
const char* syscall,
const char* message,
const char* path) {
if (!object->IsObject() || errorno == 0)
return;

v8::Local<v8::Object> obj = object.As<v8::Object>();
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<v8::Value> object,
int errorno,
const char* syscall,
const char* message,
const char* path,
const char* dest) {
if (!object->IsObject() || errorno == 0)
return;

v8::Local<v8::Object> obj = object.As<v8::Object>();
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
13 changes: 13 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,19 @@ class Environment {
inline performance::performance_state* performance_state();
inline std::map<std::string, uint64_t>* performance_marks();

void CollectExceptionInfo(v8::Local<v8::Value> context,
int errorno,
const char* syscall = nullptr,
const char* message = nullptr,
const char* path = nullptr);

void CollectUVExceptionInfo(v8::Local<v8::Value> 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);
Expand Down
Loading

0 comments on commit c3dc0e0

Please sign in to comment.