src: refactor uncaught exception handling
The C++ land `node::FatalException()` is not in fact fatal anymore.
It gives the user a chance to handle the uncaught exception
globally by listening to the `uncaughtException` event. This patch
renames it to `TriggerUncaughtException` in C++ to avoid the confusion.
In addition rename the JS land handler to `onGlobalUncaughtException`
to reflect its purpose - we have to keep the alias
`process._fatalException` and use that for now since it has been
monkey-patchable in the user land.
This patch also
- Adds more comments to the global uncaught exception handling routine
- Puts a few other C++ error handling functions into the `errors`
namespace
- Moves error-handling-related bindings to the `errors` binding.
Refs: 2b252acea4
PR-URL: https://github.com/nodejs/node/pull/28257
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
This commit is contained in:
parent
1c23b6f2be
commit
a33c3c6d33
@ -254,12 +254,18 @@ Object.defineProperty(process, 'features', {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
fatalException,
|
onGlobalUncaughtException,
|
||||||
setUncaughtExceptionCaptureCallback,
|
setUncaughtExceptionCaptureCallback,
|
||||||
hasUncaughtExceptionCaptureCallback
|
hasUncaughtExceptionCaptureCallback
|
||||||
} = require('internal/process/execution');
|
} = require('internal/process/execution');
|
||||||
|
|
||||||
process._fatalException = fatalException;
|
// For legacy reasons this is still called `_fatalException`, even
|
||||||
|
// though it is now a global uncaught exception handler.
|
||||||
|
// The C++ land node::errors::TriggerUncaughtException grabs it
|
||||||
|
// from the process object because it has been monkey-patchable.
|
||||||
|
// TODO(joyeecheung): investigate whether process._fatalException
|
||||||
|
// can be deprecated.
|
||||||
|
process._fatalException = onGlobalUncaughtException;
|
||||||
process.setUncaughtExceptionCaptureCallback =
|
process.setUncaughtExceptionCaptureCallback =
|
||||||
setUncaughtExceptionCaptureCallback;
|
setUncaughtExceptionCaptureCallback;
|
||||||
process.hasUncaughtExceptionCaptureCallback =
|
process.hasUncaughtExceptionCaptureCallback =
|
||||||
|
@ -42,7 +42,7 @@ const {
|
|||||||
} = workerIo;
|
} = workerIo;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fatalException: originalFatalException
|
onGlobalUncaughtException
|
||||||
} = require('internal/process/execution');
|
} = require('internal/process/execution');
|
||||||
|
|
||||||
const publicWorker = require('worker_threads');
|
const publicWorker = require('worker_threads');
|
||||||
@ -156,24 +156,23 @@ port.on('message', (message) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Overwrite fatalException
|
function workerOnGlobalUncaughtException(error, fromPromise) {
|
||||||
process._fatalException = (error, fromPromise) => {
|
debug(`[${threadId}] gets uncaught exception`);
|
||||||
debug(`[${threadId}] gets fatal exception`);
|
let handled = false;
|
||||||
let caught = false;
|
|
||||||
try {
|
try {
|
||||||
caught = originalFatalException.call(this, error, fromPromise);
|
handled = onGlobalUncaughtException(error, fromPromise);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
debug(`[${threadId}] fatal exception caught = ${caught}`);
|
debug(`[${threadId}] uncaught exception handled = ${handled}`);
|
||||||
|
|
||||||
if (!caught) {
|
if (!handled) {
|
||||||
let serialized;
|
let serialized;
|
||||||
try {
|
try {
|
||||||
const { serializeError } = require('internal/error-serdes');
|
const { serializeError } = require('internal/error-serdes');
|
||||||
serialized = serializeError(error);
|
serialized = serializeError(error);
|
||||||
} catch {}
|
} catch {}
|
||||||
debug(`[${threadId}] fatal exception serialized = ${!!serialized}`);
|
debug(`[${threadId}] uncaught exception serialized = ${!!serialized}`);
|
||||||
if (serialized)
|
if (serialized)
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
type: ERROR_MESSAGE,
|
type: ERROR_MESSAGE,
|
||||||
@ -187,7 +186,11 @@ process._fatalException = (error, fromPromise) => {
|
|||||||
|
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// Patch the global uncaught exception handler so it gets picked up by
|
||||||
|
// node::errors::TriggerUncaughtException().
|
||||||
|
process._fatalException = workerOnGlobalUncaughtException;
|
||||||
|
|
||||||
markBootstrapComplete();
|
markBootstrapComplete();
|
||||||
|
|
||||||
|
@ -832,7 +832,7 @@ Module.runMain = function() {
|
|||||||
return loader.import(pathToFileURL(process.argv[1]).pathname);
|
return loader.import(pathToFileURL(process.argv[1]).pathname);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
internalBinding('task_queue').triggerFatalException(
|
internalBinding('errors').triggerUncaughtException(
|
||||||
e,
|
e,
|
||||||
true /* fromPromise */
|
true /* fromPromise */
|
||||||
);
|
);
|
||||||
|
@ -115,10 +115,14 @@ function noop() {}
|
|||||||
// and exported to be written to process._fatalException, it has to be
|
// and exported to be written to process._fatalException, it has to be
|
||||||
// returned as an *anonymous function* wrapped inside a factory function,
|
// returned as an *anonymous function* wrapped inside a factory function,
|
||||||
// otherwise it breaks the test-timers.setInterval async hooks test -
|
// otherwise it breaks the test-timers.setInterval async hooks test -
|
||||||
// this may indicate that node::FatalException should fix up the callback scope
|
// this may indicate that node::errors::TriggerUncaughtException() should
|
||||||
// before calling into process._fatalException, or this function should
|
// fix up the callback scope before calling into process._fatalException,
|
||||||
// take extra care of the async hooks before it schedules a setImmediate.
|
// or this function should take extra care of the async hooks before it
|
||||||
function createFatalException() {
|
// schedules a setImmediate.
|
||||||
|
function createOnGlobalUncaughtException() {
|
||||||
|
// The C++ land node::errors::TriggerUncaughtException() will
|
||||||
|
// exit the process if it returns false, and continue execution if it
|
||||||
|
// returns true (which indicates that the exception is handled by the user).
|
||||||
return (er, fromPromise) => {
|
return (er, fromPromise) => {
|
||||||
// It's possible that defaultTriggerAsyncId was set for a constructor
|
// It's possible that defaultTriggerAsyncId was set for a constructor
|
||||||
// call that threw and was never cleared. So clear it now.
|
// call that threw and was never cleared. So clear it now.
|
||||||
@ -206,7 +210,7 @@ module.exports = {
|
|||||||
tryGetCwd,
|
tryGetCwd,
|
||||||
evalModule,
|
evalModule,
|
||||||
evalScript,
|
evalScript,
|
||||||
fatalException: createFatalException(),
|
onGlobalUncaughtException: createOnGlobalUncaughtException(),
|
||||||
setUncaughtExceptionCaptureCallback,
|
setUncaughtExceptionCaptureCallback,
|
||||||
hasUncaughtExceptionCaptureCallback
|
hasUncaughtExceptionCaptureCallback
|
||||||
};
|
};
|
||||||
|
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
const { Object } = primordials;
|
const { Object } = primordials;
|
||||||
|
|
||||||
const {
|
|
||||||
safeToString
|
|
||||||
} = internalBinding('util');
|
|
||||||
const {
|
const {
|
||||||
tickInfo,
|
tickInfo,
|
||||||
promiseRejectEvents: {
|
promiseRejectEvents: {
|
||||||
@ -13,10 +10,14 @@ const {
|
|||||||
kPromiseResolveAfterResolved,
|
kPromiseResolveAfterResolved,
|
||||||
kPromiseRejectAfterResolved
|
kPromiseRejectAfterResolved
|
||||||
},
|
},
|
||||||
setPromiseRejectCallback,
|
setPromiseRejectCallback
|
||||||
triggerFatalException
|
|
||||||
} = internalBinding('task_queue');
|
} = internalBinding('task_queue');
|
||||||
|
|
||||||
|
const {
|
||||||
|
noSideEffectsToString,
|
||||||
|
triggerUncaughtException
|
||||||
|
} = internalBinding('errors');
|
||||||
|
|
||||||
// *Must* match Environment::TickInfo::Fields in src/env.h.
|
// *Must* match Environment::TickInfo::Fields in src/env.h.
|
||||||
const kHasRejectionToWarn = 1;
|
const kHasRejectionToWarn = 1;
|
||||||
|
|
||||||
@ -127,7 +128,7 @@ function handledRejection(promise) {
|
|||||||
|
|
||||||
const unhandledRejectionErrName = 'UnhandledPromiseRejectionWarning';
|
const unhandledRejectionErrName = 'UnhandledPromiseRejectionWarning';
|
||||||
function emitUnhandledRejectionWarning(uid, reason) {
|
function emitUnhandledRejectionWarning(uid, reason) {
|
||||||
const warning = getError(
|
const warning = getErrorWithoutStack(
|
||||||
unhandledRejectionErrName,
|
unhandledRejectionErrName,
|
||||||
'Unhandled promise rejection. This error originated either by ' +
|
'Unhandled promise rejection. This error originated either by ' +
|
||||||
'throwing inside of an async function without a catch block, ' +
|
'throwing inside of an async function without a catch block, ' +
|
||||||
@ -139,7 +140,8 @@ function emitUnhandledRejectionWarning(uid, reason) {
|
|||||||
warning.stack = reason.stack;
|
warning.stack = reason.stack;
|
||||||
process.emitWarning(reason.stack, unhandledRejectionErrName);
|
process.emitWarning(reason.stack, unhandledRejectionErrName);
|
||||||
} else {
|
} else {
|
||||||
process.emitWarning(safeToString(reason), unhandledRejectionErrName);
|
process.emitWarning(
|
||||||
|
noSideEffectsToString(reason), unhandledRejectionErrName);
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
@ -179,7 +181,9 @@ function processPromiseRejections() {
|
|||||||
const { reason, uid } = promiseInfo;
|
const { reason, uid } = promiseInfo;
|
||||||
switch (unhandledRejectionsMode) {
|
switch (unhandledRejectionsMode) {
|
||||||
case kThrowUnhandledRejections: {
|
case kThrowUnhandledRejections: {
|
||||||
fatalException(reason);
|
const err = reason instanceof Error ?
|
||||||
|
reason : generateUnhandledRejectionError(reason);
|
||||||
|
triggerUncaughtException(err, true /* fromPromise */);
|
||||||
const handled = process.emit('unhandledRejection', reason, promise);
|
const handled = process.emit('unhandledRejection', reason, promise);
|
||||||
if (!handled) emitUnhandledRejectionWarning(uid, reason);
|
if (!handled) emitUnhandledRejectionWarning(uid, reason);
|
||||||
break;
|
break;
|
||||||
@ -209,7 +213,7 @@ function processPromiseRejections() {
|
|||||||
pendingUnhandledRejections.length !== 0;
|
pendingUnhandledRejections.length !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getError(name, message) {
|
function getErrorWithoutStack(name, message) {
|
||||||
// Reset the stack to prevent any overhead.
|
// Reset the stack to prevent any overhead.
|
||||||
const tmp = Error.stackTraceLimit;
|
const tmp = Error.stackTraceLimit;
|
||||||
Error.stackTraceLimit = 0;
|
Error.stackTraceLimit = 0;
|
||||||
@ -225,21 +229,17 @@ function getError(name, message) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fatalException(reason) {
|
function generateUnhandledRejectionError(reason) {
|
||||||
let err;
|
const message =
|
||||||
if (reason instanceof Error) {
|
'This error originated either by ' +
|
||||||
err = reason;
|
'throwing inside of an async function without a catch block, ' +
|
||||||
} else {
|
'or by rejecting a promise which was not handled with .catch().' +
|
||||||
err = getError(
|
' The promise rejected with the reason ' +
|
||||||
'UnhandledPromiseRejection',
|
`"${noSideEffectsToString(reason)}".`;
|
||||||
'This error originated either by ' +
|
|
||||||
'throwing inside of an async function without a catch block, ' +
|
const err = getErrorWithoutStack('UnhandledPromiseRejection', message);
|
||||||
'or by rejecting a promise which was not handled with .catch().' +
|
err.code = 'ERR_UNHANDLED_REJECTION';
|
||||||
` The promise rejected with the reason "${safeToString(reason)}".`
|
return err;
|
||||||
);
|
|
||||||
err.code = 'ERR_UNHANDLED_REJECTION';
|
|
||||||
}
|
|
||||||
triggerFatalException(err, true /* fromPromise */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function listenForRejections() {
|
function listenForRejections() {
|
||||||
|
@ -9,10 +9,13 @@ const {
|
|||||||
// Used to run V8's micro task queue.
|
// Used to run V8's micro task queue.
|
||||||
runMicrotasks,
|
runMicrotasks,
|
||||||
setTickCallback,
|
setTickCallback,
|
||||||
enqueueMicrotask,
|
enqueueMicrotask
|
||||||
triggerFatalException
|
|
||||||
} = internalBinding('task_queue');
|
} = internalBinding('task_queue');
|
||||||
|
|
||||||
|
const {
|
||||||
|
triggerUncaughtException
|
||||||
|
} = internalBinding('errors');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setHasRejectionToWarn,
|
setHasRejectionToWarn,
|
||||||
hasRejectionToWarn,
|
hasRejectionToWarn,
|
||||||
@ -143,11 +146,9 @@ function runMicrotask() {
|
|||||||
try {
|
try {
|
||||||
callback();
|
callback();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// TODO(devsnek): Remove this if
|
// runInAsyncScope() swallows the error so we need to catch
|
||||||
// https://bugs.chromium.org/p/v8/issues/detail?id=8326
|
// it and handle it here.
|
||||||
// is resolved such that V8 triggers the fatal exception
|
triggerUncaughtException(error, false /* fromPromise */);
|
||||||
// handler for microtasks.
|
|
||||||
triggerFatalException(error, false /* fromPromise */);
|
|
||||||
} finally {
|
} finally {
|
||||||
this.emitDestroy();
|
this.emitDestroy();
|
||||||
}
|
}
|
||||||
|
@ -244,17 +244,12 @@ Local<Value> WinapiErrnoException(Isolate* isolate,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Implement the legacy name exposed in node.h. This has not been in fact
|
||||||
|
// fatal any more, as the user can handle the exception in the
|
||||||
|
// TryCatch by listening to `uncaughtException`.
|
||||||
|
// TODO(joyeecheung): deprecate it in favor of a more accurate name.
|
||||||
void FatalException(Isolate* isolate, const v8::TryCatch& try_catch) {
|
void FatalException(Isolate* isolate, const v8::TryCatch& try_catch) {
|
||||||
// If we try to print out a termination exception, we'd just get 'null',
|
errors::TriggerUncaughtException(isolate, try_catch);
|
||||||
// so just crashing here with that information seems like a better idea,
|
|
||||||
// and in particular it seems like we should handle terminations at the call
|
|
||||||
// site for this function rather than by printing them out somewhere.
|
|
||||||
CHECK(!try_catch.HasTerminated());
|
|
||||||
|
|
||||||
HandleScope scope(isolate);
|
|
||||||
if (!try_catch.IsVerbose()) {
|
|
||||||
FatalException(isolate, try_catch.Exception(), try_catch.Message());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
@ -666,7 +666,7 @@ void Environment::RunAndClearNativeImmediates() {
|
|||||||
ref_count++;
|
ref_count++;
|
||||||
if (UNLIKELY(try_catch.HasCaught())) {
|
if (UNLIKELY(try_catch.HasCaught())) {
|
||||||
if (!try_catch.HasTerminated())
|
if (!try_catch.HasTerminated())
|
||||||
FatalException(isolate(), try_catch);
|
errors::TriggerUncaughtException(isolate(), try_catch);
|
||||||
|
|
||||||
// We are done with the current callback. Increase the counter so that
|
// We are done with the current callback. Increase the counter so that
|
||||||
// the steps below make everything *after* the current item part of
|
// the steps below make everything *after* the current item part of
|
||||||
|
@ -49,7 +49,7 @@ bool JSStream::IsClosing() {
|
|||||||
Local<Value> value;
|
Local<Value> value;
|
||||||
if (!MakeCallback(env()->isclosing_string(), 0, nullptr).ToLocal(&value)) {
|
if (!MakeCallback(env()->isclosing_string(), 0, nullptr).ToLocal(&value)) {
|
||||||
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
||||||
FatalException(env()->isolate(), try_catch);
|
errors::TriggerUncaughtException(env()->isolate(), try_catch);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return value->IsTrue();
|
return value->IsTrue();
|
||||||
@ -65,7 +65,7 @@ int JSStream::ReadStart() {
|
|||||||
if (!MakeCallback(env()->onreadstart_string(), 0, nullptr).ToLocal(&value) ||
|
if (!MakeCallback(env()->onreadstart_string(), 0, nullptr).ToLocal(&value) ||
|
||||||
!value->Int32Value(env()->context()).To(&value_int)) {
|
!value->Int32Value(env()->context()).To(&value_int)) {
|
||||||
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
||||||
FatalException(env()->isolate(), try_catch);
|
errors::TriggerUncaughtException(env()->isolate(), try_catch);
|
||||||
}
|
}
|
||||||
return value_int;
|
return value_int;
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ int JSStream::ReadStop() {
|
|||||||
if (!MakeCallback(env()->onreadstop_string(), 0, nullptr).ToLocal(&value) ||
|
if (!MakeCallback(env()->onreadstop_string(), 0, nullptr).ToLocal(&value) ||
|
||||||
!value->Int32Value(env()->context()).To(&value_int)) {
|
!value->Int32Value(env()->context()).To(&value_int)) {
|
||||||
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
||||||
FatalException(env()->isolate(), try_catch);
|
errors::TriggerUncaughtException(env()->isolate(), try_catch);
|
||||||
}
|
}
|
||||||
return value_int;
|
return value_int;
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ int JSStream::DoShutdown(ShutdownWrap* req_wrap) {
|
|||||||
argv).ToLocal(&value) ||
|
argv).ToLocal(&value) ||
|
||||||
!value->Int32Value(env()->context()).To(&value_int)) {
|
!value->Int32Value(env()->context()).To(&value_int)) {
|
||||||
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
||||||
FatalException(env()->isolate(), try_catch);
|
errors::TriggerUncaughtException(env()->isolate(), try_catch);
|
||||||
}
|
}
|
||||||
return value_int;
|
return value_int;
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ int JSStream::DoWrite(WriteWrap* w,
|
|||||||
argv).ToLocal(&value) ||
|
argv).ToLocal(&value) ||
|
||||||
!value->Int32Value(env()->context()).To(&value_int)) {
|
!value->Int32Value(env()->context()).To(&value_int)) {
|
||||||
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
if (try_catch.HasCaught() && !try_catch.HasTerminated())
|
||||||
FatalException(env()->isolate(), try_catch);
|
errors::TriggerUncaughtException(env()->isolate(), try_catch);
|
||||||
}
|
}
|
||||||
return value_int;
|
return value_int;
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,6 @@ using options_parser::kDisallowedInEnvironment;
|
|||||||
|
|
||||||
using v8::Boolean;
|
using v8::Boolean;
|
||||||
using v8::EscapableHandleScope;
|
using v8::EscapableHandleScope;
|
||||||
using v8::Exception;
|
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
using v8::FunctionCallbackInfo;
|
using v8::FunctionCallbackInfo;
|
||||||
using v8::HandleScope;
|
using v8::HandleScope;
|
||||||
@ -218,10 +217,9 @@ MaybeLocal<Value> ExecuteBootstrapper(Environment* env,
|
|||||||
arguments->size(),
|
arguments->size(),
|
||||||
arguments->data());
|
arguments->data());
|
||||||
|
|
||||||
// If there was an error during bootstrap then it was either handled by the
|
// If there was an error during bootstrap, it must be unrecoverable
|
||||||
// FatalException handler or it's unrecoverable (e.g. max call stack
|
// (e.g. max call stack exceeded). Clear the stack so that the
|
||||||
// exceeded). Either way, clear the stack so that the AsyncCallbackScope
|
// AsyncCallbackScope destructor doesn't fail on the id check.
|
||||||
// destructor doesn't fail on the id check.
|
|
||||||
// There are only two ways to have a stack size > 1: 1) the user manually
|
// There are only two ways to have a stack size > 1: 1) the user manually
|
||||||
// called MakeCallback or 2) user awaited during bootstrap, which triggered
|
// called MakeCallback or 2) user awaited during bootstrap, which triggered
|
||||||
// _tickCallback().
|
// _tickCallback().
|
||||||
|
@ -115,7 +115,7 @@ static inline void trigger_fatal_exception(
|
|||||||
napi_env env, v8::Local<v8::Value> local_err) {
|
napi_env env, v8::Local<v8::Value> local_err) {
|
||||||
v8::Local<v8::Message> local_msg =
|
v8::Local<v8::Message> local_msg =
|
||||||
v8::Exception::CreateMessage(env->isolate, local_err);
|
v8::Exception::CreateMessage(env->isolate, local_err);
|
||||||
node::FatalException(env->isolate, local_err, local_msg);
|
node::errors::TriggerUncaughtException(env->isolate, local_err, local_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThreadSafeFunction : public node::AsyncResource {
|
class ThreadSafeFunction : public node::AsyncResource {
|
||||||
|
@ -734,7 +734,7 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
|
|||||||
compile_options);
|
compile_options);
|
||||||
|
|
||||||
if (v8_script.IsEmpty()) {
|
if (v8_script.IsEmpty()) {
|
||||||
DecorateErrorStack(env, try_catch);
|
errors::DecorateErrorStack(env, try_catch);
|
||||||
no_abort_scope.Close();
|
no_abort_scope.Close();
|
||||||
if (!try_catch.HasTerminated())
|
if (!try_catch.HasTerminated())
|
||||||
try_catch.ReThrow();
|
try_catch.ReThrow();
|
||||||
@ -946,7 +946,7 @@ bool ContextifyScript::EvalMachine(Environment* env,
|
|||||||
if (try_catch.HasCaught()) {
|
if (try_catch.HasCaught()) {
|
||||||
if (!timed_out && !received_signal && display_errors) {
|
if (!timed_out && !received_signal && display_errors) {
|
||||||
// We should decorate non-termination exceptions
|
// We should decorate non-termination exceptions
|
||||||
DecorateErrorStack(env, try_catch);
|
errors::DecorateErrorStack(env, try_catch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there was an exception thrown during script execution, re-throw it.
|
// If there was an exception thrown during script execution, re-throw it.
|
||||||
@ -1110,7 +1110,7 @@ void ContextifyContext::CompileFunction(
|
|||||||
|
|
||||||
if (maybe_fn.IsEmpty()) {
|
if (maybe_fn.IsEmpty()) {
|
||||||
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
|
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
|
||||||
DecorateErrorStack(env, try_catch);
|
errors::DecorateErrorStack(env, try_catch);
|
||||||
try_catch.ReThrow();
|
try_catch.ReThrow();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -343,10 +343,6 @@ void ReportException(Environment* env,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReportException(Environment* env, const v8::TryCatch& try_catch) {
|
|
||||||
ReportException(env, try_catch.Exception(), try_catch.Message());
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintErrorString(const char* format, ...) {
|
void PrintErrorString(const char* format, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, format);
|
va_start(ap, format);
|
||||||
@ -764,7 +760,7 @@ void PerIsolateMessageListener(Local<Message> message, Local<Value> error) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Isolate::MessageErrorLevel::kMessageError:
|
case Isolate::MessageErrorLevel::kMessageError:
|
||||||
FatalException(isolate, error, message);
|
TriggerUncaughtException(isolate, error, message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -775,6 +771,27 @@ void SetPrepareStackTraceCallback(const FunctionCallbackInfo<Value>& args) {
|
|||||||
env->set_prepare_stack_trace_callback(args[0].As<Function>());
|
env->set_prepare_stack_trace_callback(args[0].As<Function>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Side effect-free stringification that will never throw exceptions.
|
||||||
|
static void NoSideEffectsToString(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Local<Context> context = args.GetIsolate()->GetCurrentContext();
|
||||||
|
Local<String> detail_string;
|
||||||
|
if (args[0]->ToDetailString(context).ToLocal(&detail_string))
|
||||||
|
args.GetReturnValue().Set(detail_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TriggerUncaughtException(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
Environment* env = Environment::GetCurrent(isolate);
|
||||||
|
Local<Value> exception = args[0];
|
||||||
|
Local<Message> message = Exception::CreateMessage(isolate, exception);
|
||||||
|
if (env != nullptr && env->abort_on_uncaught_exception()) {
|
||||||
|
ReportException(env, exception, message);
|
||||||
|
Abort();
|
||||||
|
}
|
||||||
|
bool from_promise = args[1]->IsTrue();
|
||||||
|
errors::TriggerUncaughtException(isolate, exception, message, from_promise);
|
||||||
|
}
|
||||||
|
|
||||||
void Initialize(Local<Object> target,
|
void Initialize(Local<Object> target,
|
||||||
Local<Value> unused,
|
Local<Value> unused,
|
||||||
Local<Context> context,
|
Local<Context> context,
|
||||||
@ -782,10 +799,11 @@ void Initialize(Local<Object> target,
|
|||||||
Environment* env = Environment::GetCurrent(context);
|
Environment* env = Environment::GetCurrent(context);
|
||||||
env->SetMethod(
|
env->SetMethod(
|
||||||
target, "setPrepareStackTraceCallback", SetPrepareStackTraceCallback);
|
target, "setPrepareStackTraceCallback", SetPrepareStackTraceCallback);
|
||||||
|
env->SetMethodNoSideEffect(
|
||||||
|
target, "noSideEffectsToString", NoSideEffectsToString);
|
||||||
|
env->SetMethod(target, "triggerUncaughtException", TriggerUncaughtException);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace errors
|
|
||||||
|
|
||||||
void DecorateErrorStack(Environment* env,
|
void DecorateErrorStack(Environment* env,
|
||||||
const errors::TryCatchScope& try_catch) {
|
const errors::TryCatchScope& try_catch) {
|
||||||
Local<Value> exception = try_catch.Exception();
|
Local<Value> exception = try_catch.Exception();
|
||||||
@ -822,10 +840,10 @@ void DecorateErrorStack(Environment* env,
|
|||||||
env->context(), env->decorated_private_symbol(), True(env->isolate()));
|
env->context(), env->decorated_private_symbol(), True(env->isolate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FatalException(Isolate* isolate,
|
void TriggerUncaughtException(Isolate* isolate,
|
||||||
Local<Value> error,
|
Local<Value> error,
|
||||||
Local<Message> message,
|
Local<Message> message,
|
||||||
bool from_promise) {
|
bool from_promise) {
|
||||||
CHECK(!error.IsEmpty());
|
CHECK(!error.IsEmpty());
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
|
|
||||||
@ -843,59 +861,95 @@ void FatalException(Isolate* isolate,
|
|||||||
Abort();
|
Abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoke process._fatalException() to give user a chance to handle it.
|
||||||
|
// We have to grab it from the process object since this has been
|
||||||
|
// monkey-patchable.
|
||||||
Local<Object> process_object = env->process_object();
|
Local<Object> process_object = env->process_object();
|
||||||
Local<String> fatal_exception_string = env->fatal_exception_string();
|
Local<String> fatal_exception_string = env->fatal_exception_string();
|
||||||
Local<Value> fatal_exception_function =
|
Local<Value> fatal_exception_function =
|
||||||
process_object->Get(env->context(),
|
process_object->Get(env->context(),
|
||||||
fatal_exception_string).ToLocalChecked();
|
fatal_exception_string).ToLocalChecked();
|
||||||
|
// If the exception happens before process._fatalException is attached
|
||||||
|
// during bootstrap, or if the user has patched it incorrectly, exit
|
||||||
|
// the current Node.js instance.
|
||||||
if (!fatal_exception_function->IsFunction()) {
|
if (!fatal_exception_function->IsFunction()) {
|
||||||
// Failed before the process._fatalException function was added!
|
|
||||||
// this is probably pretty bad. Nothing to do but report and exit.
|
|
||||||
ReportException(env, error, message);
|
ReportException(env, error, message);
|
||||||
env->Exit(6);
|
env->Exit(6);
|
||||||
} else {
|
return;
|
||||||
errors::TryCatchScope fatal_try_catch(env);
|
}
|
||||||
|
|
||||||
// Do not call FatalException when _fatalException handler throws
|
|
||||||
fatal_try_catch.SetVerbose(false);
|
|
||||||
|
|
||||||
|
MaybeLocal<Value> handled;
|
||||||
|
{
|
||||||
|
// We do not expect the global uncaught exception itself to throw any more
|
||||||
|
// exceptions. If it does, exit the current Node.js instance.
|
||||||
|
errors::TryCatchScope try_catch(env,
|
||||||
|
errors::TryCatchScope::CatchMode::kFatal);
|
||||||
|
// Explicitly disable verbose exception reporting -
|
||||||
|
// if process._fatalException() throws an error, we don't want it to
|
||||||
|
// trigger the per-isolate message listener which will call this
|
||||||
|
// function and recurse.
|
||||||
|
try_catch.SetVerbose(false);
|
||||||
Local<Value> argv[2] = { error,
|
Local<Value> argv[2] = { error,
|
||||||
Boolean::New(env->isolate(), from_promise) };
|
Boolean::New(env->isolate(), from_promise) };
|
||||||
|
|
||||||
// This will return true if the JS layer handled it, false otherwise
|
handled = fatal_exception_function.As<Function>()->Call(
|
||||||
MaybeLocal<Value> caught = fatal_exception_function.As<Function>()->Call(
|
|
||||||
env->context(), process_object, arraysize(argv), argv);
|
env->context(), process_object, arraysize(argv), argv);
|
||||||
|
}
|
||||||
|
|
||||||
if (fatal_try_catch.HasTerminated()) return;
|
// If process._fatalException() throws, we are now exiting the Node.js
|
||||||
|
// instance so return to continue the exit routine.
|
||||||
|
// TODO(joyeecheung): return a Maybe here to prevent the caller from
|
||||||
|
// stepping on the exit.
|
||||||
|
if (handled.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (fatal_try_catch.HasCaught()) {
|
// The global uncaught exception handler returns true if the user handles it
|
||||||
// The fatal exception function threw, so we must exit
|
// by e.g. listening to `uncaughtException`. In that case, continue program
|
||||||
ReportException(env, fatal_try_catch);
|
// execution.
|
||||||
env->Exit(7);
|
// TODO(joyeecheung): This has been only checking that the return value is
|
||||||
|
// exactly false. Investigate whether this can be turned to an "if true"
|
||||||
|
// similar to how the worker global uncaught exception handler handles it.
|
||||||
|
if (!handled.ToLocalChecked()->IsFalse()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (caught.ToLocalChecked()->IsFalse()) {
|
ReportException(env, error, message);
|
||||||
ReportException(env, error, message);
|
// If the global uncaught exception handler sets process.exitCode,
|
||||||
|
// exit with that code. Otherwise, exit with 1.
|
||||||
// fatal_exception_function call before may have set a new exit code ->
|
Local<String> exit_code = env->exit_code_string();
|
||||||
// read it again, otherwise use default for uncaughtException 1
|
Local<Value> code;
|
||||||
Local<String> exit_code = env->exit_code_string();
|
if (process_object->Get(env->context(), exit_code).ToLocal(&code) &&
|
||||||
Local<Value> code;
|
code->IsInt32()) {
|
||||||
if (!process_object->Get(env->context(), exit_code).ToLocal(&code) ||
|
env->Exit(code.As<Int32>()->Value());
|
||||||
!code->IsInt32()) {
|
} else {
|
||||||
env->Exit(1);
|
env->Exit(1);
|
||||||
}
|
|
||||||
env->Exit(code.As<Int32>()->Value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FatalException(Isolate* isolate,
|
void TriggerUncaughtException(Isolate* isolate, const v8::TryCatch& try_catch) {
|
||||||
Local<Value> error,
|
// If the try_catch is verbose, the per-isolate message listener is going to
|
||||||
Local<Message> message) {
|
// handle it (which is going to call into another overload of
|
||||||
FatalException(isolate, error, message, false /* from_promise */);
|
// TriggerUncaughtException()).
|
||||||
|
if (try_catch.IsVerbose()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user calls TryCatch::TerminateExecution() on this TryCatch
|
||||||
|
// they must call CancelTerminateExecution() again before invoking
|
||||||
|
// TriggerUncaughtException() because it will invoke
|
||||||
|
// process._fatalException() in the JS land.
|
||||||
|
CHECK(!try_catch.HasTerminated());
|
||||||
|
CHECK(try_catch.HasCaught());
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
TriggerUncaughtException(isolate,
|
||||||
|
try_catch.Exception(),
|
||||||
|
try_catch.Message(),
|
||||||
|
false /* from_promise */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace errors
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
NODE_MODULE_CONTEXT_AWARE_INTERNAL(errors, node::errors::Initialize)
|
NODE_MODULE_CONTEXT_AWARE_INTERNAL(errors, node::errors::Initialize)
|
||||||
|
@ -29,21 +29,6 @@ void OnFatalError(const char* location, const char* message);
|
|||||||
|
|
||||||
void PrintErrorString(const char* format, ...);
|
void PrintErrorString(const char* format, ...);
|
||||||
|
|
||||||
void ReportException(Environment* env, const v8::TryCatch& try_catch);
|
|
||||||
|
|
||||||
void ReportException(Environment* env,
|
|
||||||
Local<Value> er,
|
|
||||||
Local<Message> message);
|
|
||||||
|
|
||||||
void FatalException(v8::Isolate* isolate,
|
|
||||||
Local<Value> error,
|
|
||||||
Local<Message> message);
|
|
||||||
|
|
||||||
void FatalException(v8::Isolate* isolate,
|
|
||||||
Local<Value> error,
|
|
||||||
Local<Message> message,
|
|
||||||
bool from_promise);
|
|
||||||
|
|
||||||
// Helpers to construct errors similar to the ones provided by
|
// Helpers to construct errors similar to the ones provided by
|
||||||
// lib/internal/errors.js.
|
// lib/internal/errors.js.
|
||||||
// Example: with `V(ERR_INVALID_ARG_TYPE, TypeError)`, there will be
|
// Example: with `V(ERR_INVALID_ARG_TYPE, TypeError)`, there will be
|
||||||
@ -190,14 +175,24 @@ class TryCatchScope : public v8::TryCatch {
|
|||||||
CatchMode mode_;
|
CatchMode mode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Trigger the global uncaught exception handler `process._fatalException`
|
||||||
|
// in JS land (which emits the 'uncaughtException' event). If that returns
|
||||||
|
// true, continue program execution, otherwise exit the process.
|
||||||
|
void TriggerUncaughtException(v8::Isolate* isolate,
|
||||||
|
const v8::TryCatch& try_catch);
|
||||||
|
void TriggerUncaughtException(v8::Isolate* isolate,
|
||||||
|
Local<Value> error,
|
||||||
|
Local<Message> message,
|
||||||
|
bool from_promise = false);
|
||||||
|
|
||||||
const char* errno_string(int errorno);
|
const char* errno_string(int errorno);
|
||||||
void PerIsolateMessageListener(v8::Local<v8::Message> message,
|
void PerIsolateMessageListener(v8::Local<v8::Message> message,
|
||||||
v8::Local<v8::Value> error);
|
v8::Local<v8::Value> error);
|
||||||
|
|
||||||
} // namespace errors
|
|
||||||
|
|
||||||
void DecorateErrorStack(Environment* env,
|
void DecorateErrorStack(Environment* env,
|
||||||
const errors::TryCatchScope& try_catch);
|
const errors::TryCatchScope& try_catch);
|
||||||
|
} // namespace errors
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
@ -12,7 +12,6 @@ namespace node {
|
|||||||
|
|
||||||
using v8::Array;
|
using v8::Array;
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
using v8::Exception;
|
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
using v8::FunctionCallbackInfo;
|
using v8::FunctionCallbackInfo;
|
||||||
using v8::Isolate;
|
using v8::Isolate;
|
||||||
@ -123,19 +122,6 @@ static void SetPromiseRejectCallback(
|
|||||||
env->set_promise_reject_callback(args[0].As<Function>());
|
env->set_promise_reject_callback(args[0].As<Function>());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void TriggerFatalException(const FunctionCallbackInfo<Value>& args) {
|
|
||||||
Isolate* isolate = args.GetIsolate();
|
|
||||||
Environment* env = Environment::GetCurrent(isolate);
|
|
||||||
Local<Value> exception = args[0];
|
|
||||||
Local<Message> message = Exception::CreateMessage(isolate, exception);
|
|
||||||
if (env != nullptr && env->abort_on_uncaught_exception()) {
|
|
||||||
ReportException(env, exception, message);
|
|
||||||
Abort();
|
|
||||||
}
|
|
||||||
bool from_promise = args[1]->IsTrue();
|
|
||||||
FatalException(isolate, exception, message, from_promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Initialize(Local<Object> target,
|
static void Initialize(Local<Object> target,
|
||||||
Local<Value> unused,
|
Local<Value> unused,
|
||||||
Local<Context> context,
|
Local<Context> context,
|
||||||
@ -143,7 +129,6 @@ static void Initialize(Local<Object> target,
|
|||||||
Environment* env = Environment::GetCurrent(context);
|
Environment* env = Environment::GetCurrent(context);
|
||||||
Isolate* isolate = env->isolate();
|
Isolate* isolate = env->isolate();
|
||||||
|
|
||||||
env->SetMethod(target, "triggerFatalException", TriggerFatalException);
|
|
||||||
env->SetMethod(target, "enqueueMicrotask", EnqueueMicrotask);
|
env->SetMethod(target, "enqueueMicrotask", EnqueueMicrotask);
|
||||||
env->SetMethod(target, "setTickCallback", SetTickCallback);
|
env->SetMethod(target, "setTickCallback", SetTickCallback);
|
||||||
env->SetMethod(target, "runMicrotasks", RunMicrotasks);
|
env->SetMethod(target, "runMicrotasks", RunMicrotasks);
|
||||||
|
@ -123,14 +123,6 @@ static void PreviewEntries(const FunctionCallbackInfo<Value>& args) {
|
|||||||
Array::New(env->isolate(), ret, arraysize(ret)));
|
Array::New(env->isolate(), ret, arraysize(ret)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Side effect-free stringification that will never throw exceptions.
|
|
||||||
static void SafeToString(const FunctionCallbackInfo<Value>& args) {
|
|
||||||
Local<Context> context = args.GetIsolate()->GetCurrentContext();
|
|
||||||
Local<String> detail_string;
|
|
||||||
if (args[0]->ToDetailString(context).ToLocal(&detail_string))
|
|
||||||
args.GetReturnValue().Set(detail_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Local<Private> IndexToPrivateSymbol(Environment* env, uint32_t index) {
|
inline Local<Private> IndexToPrivateSymbol(Environment* env, uint32_t index) {
|
||||||
#define V(name, _) &Environment::name,
|
#define V(name, _) &Environment::name,
|
||||||
static Local<Private> (Environment::*const methods[])() const = {
|
static Local<Private> (Environment::*const methods[])() const = {
|
||||||
@ -270,7 +262,6 @@ void Initialize(Local<Object> target,
|
|||||||
env->SetMethod(target, "setHiddenValue", SetHiddenValue);
|
env->SetMethod(target, "setHiddenValue", SetHiddenValue);
|
||||||
env->SetMethodNoSideEffect(target, "getPromiseDetails", GetPromiseDetails);
|
env->SetMethodNoSideEffect(target, "getPromiseDetails", GetPromiseDetails);
|
||||||
env->SetMethodNoSideEffect(target, "getProxyDetails", GetProxyDetails);
|
env->SetMethodNoSideEffect(target, "getProxyDetails", GetProxyDetails);
|
||||||
env->SetMethodNoSideEffect(target, "safeToString", SafeToString);
|
|
||||||
env->SetMethodNoSideEffect(target, "previewEntries", PreviewEntries);
|
env->SetMethodNoSideEffect(target, "previewEntries", PreviewEntries);
|
||||||
env->SetMethodNoSideEffect(target, "getOwnNonIndexProperties",
|
env->SetMethodNoSideEffect(target, "getOwnNonIndexProperties",
|
||||||
GetOwnNonIndexProperties);
|
GetOwnNonIndexProperties);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user