Files
node/src/node_errors.cc
T
Joyee Cheung be525d7d04 src: consolidate exit codes in the code base
Add an ExitCode enum class and use it throughout the code base
instead of hard-coding the exit codes everywhere. At the moment,
the exit codes used in many places do not actually conform to
what the documentation describes. With the new enums (which
are also available to the JS land as constants in an internal
binding) we could migrate to a more consistent usage of the
codes, and eventually expose the constants to the user land
when they are stable enough.

PR-URL: https://github.com/nodejs/node/pull/44746
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
2022-10-06 12:41:16 +00:00

1192 lines
34 KiB
C++

#include <cerrno>
#include <cstdarg>
#include "debug_utils-inl.h"
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_internals.h"
#include "node_process-inl.h"
#include "node_report.h"
#include "node_v8_platform-inl.h"
#include "util-inl.h"
namespace node {
using errors::TryCatchScope;
using v8::Boolean;
using v8::Context;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Int32;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Message;
using v8::Object;
using v8::ScriptOrigin;
using v8::StackFrame;
using v8::StackTrace;
using v8::String;
using v8::Undefined;
using v8::Value;
bool IsExceptionDecorated(Environment* env, Local<Value> er) {
if (!er.IsEmpty() && er->IsObject()) {
Local<Object> err_obj = er.As<Object>();
auto maybe_value =
err_obj->GetPrivate(env->context(), env->decorated_private_symbol());
Local<Value> decorated;
return maybe_value.ToLocal(&decorated) && decorated->IsTrue();
}
return false;
}
namespace per_process {
static Mutex tty_mutex;
} // namespace per_process
static std::string GetSourceMapErrorSource(Isolate* isolate,
Local<Context> context,
Local<Message> message,
bool* added_exception_line) {
v8::TryCatch try_catch(isolate);
HandleScope handle_scope(isolate);
Environment* env = Environment::GetCurrent(context);
// The ScriptResourceName of the message may be different from the one we use
// to compile the script. V8 replaces it when it detects magic comments in
// the source texts.
Local<Value> script_resource_name = message->GetScriptResourceName();
int linenum = message->GetLineNumber(context).FromJust();
int columnum = message->GetStartColumn(context).FromJust();
Local<Value> argv[] = {script_resource_name,
v8::Int32::New(isolate, linenum),
v8::Int32::New(isolate, columnum)};
MaybeLocal<Value> maybe_ret = env->get_source_map_error_source()->Call(
context, Undefined(isolate), arraysize(argv), argv);
Local<Value> ret;
if (!maybe_ret.ToLocal(&ret)) {
// Ignore the caught exceptions.
DCHECK(try_catch.HasCaught());
return std::string();
}
if (!ret->IsString()) {
return std::string();
}
*added_exception_line = true;
node::Utf8Value error_source_utf8(isolate, ret.As<String>());
return *error_source_utf8;
}
static std::string GetErrorSource(Isolate* isolate,
Local<Context> context,
Local<Message> message,
bool* added_exception_line) {
MaybeLocal<String> source_line_maybe = message->GetSourceLine(context);
node::Utf8Value encoded_source(isolate, source_line_maybe.ToLocalChecked());
std::string sourceline(*encoded_source, encoded_source.length());
*added_exception_line = false;
if (sourceline.find("node-do-not-add-exception-line") != std::string::npos) {
return sourceline;
}
// If source maps have been enabled, the exception line will instead be
// added in the JavaScript context:
Environment* env = Environment::GetCurrent(isolate);
const bool has_source_map_url =
!message->GetScriptOrigin().SourceMapUrl().IsEmpty() &&
!message->GetScriptOrigin().SourceMapUrl()->IsUndefined();
if (has_source_map_url && env != nullptr && env->source_maps_enabled()) {
std::string source = GetSourceMapErrorSource(
isolate, context, message, added_exception_line);
if (*added_exception_line) {
return source;
}
}
// Because of how node modules work, all scripts are wrapped with a
// "function (module, exports, __filename, ...) {"
// to provide script local variables.
//
// When reporting errors on the first line of a script, this wrapper
// function is leaked to the user. There used to be a hack here to
// truncate off the first 62 characters, but it caused numerous other
// problems when vm.runIn*Context() methods were used for non-module
// code.
//
// If we ever decide to re-instate such a hack, the following steps
// must be taken:
//
// 1. Pass a flag around to say "this code was wrapped"
// 2. Update the stack frame output so that it is also correct.
//
// It would probably be simpler to add a line rather than add some
// number of characters to the first line, since V8 truncates the
// sourceline to 78 characters, and we end up not providing very much
// useful debugging info to the user if we remove 62 characters.
// Print (filename):(line number): (message).
ScriptOrigin origin = message->GetScriptOrigin();
node::Utf8Value filename(isolate, message->GetScriptResourceName());
const char* filename_string = *filename;
int linenum = message->GetLineNumber(context).FromJust();
int script_start = (linenum - origin.LineOffset()) == 1
? origin.ColumnOffset()
: 0;
int start = message->GetStartColumn(context).FromMaybe(0);
int end = message->GetEndColumn(context).FromMaybe(0);
if (start >= script_start) {
CHECK_GE(end, start);
start -= script_start;
end -= script_start;
}
std::string buf = SPrintF("%s:%i\n%s\n",
filename_string,
linenum,
sourceline.c_str());
CHECK_GT(buf.size(), 0);
*added_exception_line = true;
if (start > end ||
start < 0 ||
static_cast<size_t>(end) > sourceline.size()) {
return buf;
}
constexpr int kUnderlineBufsize = 1020;
char underline_buf[kUnderlineBufsize + 4];
int off = 0;
// Print wavy underline (GetUnderline is deprecated).
for (int i = 0; i < start; i++) {
if (sourceline[i] == '\0' || off >= kUnderlineBufsize) {
break;
}
CHECK_LT(off, kUnderlineBufsize);
underline_buf[off++] = (sourceline[i] == '\t') ? '\t' : ' ';
}
for (int i = start; i < end; i++) {
if (sourceline[i] == '\0' || off >= kUnderlineBufsize) {
break;
}
CHECK_LT(off, kUnderlineBufsize);
underline_buf[off++] = '^';
}
CHECK_LE(off, kUnderlineBufsize);
underline_buf[off++] = '\n';
return buf + std::string(underline_buf, off);
}
void PrintStackTrace(Isolate* isolate, Local<StackTrace> stack) {
for (int i = 0; i < stack->GetFrameCount(); i++) {
Local<StackFrame> stack_frame = stack->GetFrame(isolate, i);
node::Utf8Value fn_name_s(isolate, stack_frame->GetFunctionName());
node::Utf8Value script_name(isolate, stack_frame->GetScriptName());
const int line_number = stack_frame->GetLineNumber();
const int column = stack_frame->GetColumn();
if (stack_frame->IsEval()) {
if (stack_frame->GetScriptId() == Message::kNoScriptIdInfo) {
FPrintF(stderr, " at [eval]:%i:%i\n", line_number, column);
} else {
FPrintF(stderr,
" at [eval] (%s:%i:%i)\n",
*script_name,
line_number,
column);
}
break;
}
if (fn_name_s.length() == 0) {
FPrintF(stderr, " at %s:%i:%i\n", script_name, line_number, column);
} else {
FPrintF(stderr,
" at %s (%s:%i:%i)\n",
fn_name_s,
script_name,
line_number,
column);
}
}
fflush(stderr);
}
void PrintException(Isolate* isolate,
Local<Context> context,
Local<Value> err,
Local<Message> message) {
node::Utf8Value reason(isolate,
err->ToDetailString(context)
.FromMaybe(Local<String>()));
bool added_exception_line = false;
std::string source =
GetErrorSource(isolate, context, message, &added_exception_line);
FPrintF(stderr, "%s\n", source);
FPrintF(stderr, "%s\n", reason);
Local<v8::StackTrace> stack = message->GetStackTrace();
if (!stack.IsEmpty()) PrintStackTrace(isolate, stack);
}
void PrintCaughtException(Isolate* isolate,
Local<Context> context,
const v8::TryCatch& try_catch) {
CHECK(try_catch.HasCaught());
PrintException(isolate, context, try_catch.Exception(), try_catch.Message());
}
void AppendExceptionLine(Environment* env,
Local<Value> er,
Local<Message> message,
enum ErrorHandlingMode mode) {
if (message.IsEmpty()) return;
HandleScope scope(env->isolate());
Local<Object> err_obj;
if (!er.IsEmpty() && er->IsObject()) {
err_obj = er.As<Object>();
// If arrow_message is already set, skip.
auto maybe_value = err_obj->GetPrivate(env->context(),
env->arrow_message_private_symbol());
Local<Value> lvalue;
if (!maybe_value.ToLocal(&lvalue) || lvalue->IsString())
return;
}
bool added_exception_line = false;
std::string source = GetErrorSource(
env->isolate(), env->context(), message, &added_exception_line);
if (!added_exception_line) {
return;
}
MaybeLocal<Value> arrow_str = ToV8Value(env->context(), source);
const bool can_set_arrow = !arrow_str.IsEmpty() && !err_obj.IsEmpty();
// If allocating arrow_str failed, print it out. There's not much else to do.
// If it's not an error, but something needs to be printed out because
// it's a fatal exception, also print it out from here.
// Otherwise, the arrow property will be attached to the object and handled
// by the caller.
if (!can_set_arrow || (mode == FATAL_ERROR && !err_obj->IsNativeError())) {
if (env->printed_error()) return;
Mutex::ScopedLock lock(per_process::tty_mutex);
env->set_printed_error(true);
ResetStdio();
FPrintF(stderr, "\n%s", source);
return;
}
CHECK(err_obj
->SetPrivate(env->context(),
env->arrow_message_private_symbol(),
arrow_str.ToLocalChecked())
.FromMaybe(false));
}
[[noreturn]] void Abort() {
DumpBacktrace(stderr);
fflush(stderr);
ABORT_NO_BACKTRACE();
}
[[noreturn]] void Assert(const AssertionInfo& info) {
std::string name = GetHumanReadableProcessName();
fprintf(stderr,
"%s: %s:%s%s Assertion `%s' failed.\n",
name.c_str(),
info.file_line,
info.function,
*info.function ? ":" : "",
info.message);
fflush(stderr);
Abort();
}
enum class EnhanceFatalException { kEnhance, kDontEnhance };
/**
* Report the exception to the inspector, then print it to stderr.
* This should only be used when the Node.js instance is about to exit
* (i.e. this should be followed by a env->Exit() or an Abort()).
*
* Use enhance_stack = EnhanceFatalException::kDontEnhance
* when it's unsafe to call into JavaScript.
*/
static void ReportFatalException(Environment* env,
Local<Value> error,
Local<Message> message,
EnhanceFatalException enhance_stack) {
if (!env->can_call_into_js())
enhance_stack = EnhanceFatalException::kDontEnhance;
Isolate* isolate = env->isolate();
CHECK(!error.IsEmpty());
CHECK(!message.IsEmpty());
HandleScope scope(isolate);
AppendExceptionLine(env, error, message, FATAL_ERROR);
auto report_to_inspector = [&]() {
#if HAVE_INSPECTOR
env->inspector_agent()->ReportUncaughtException(error, message);
#endif
};
Local<Value> arrow;
Local<Value> stack_trace;
bool decorated = IsExceptionDecorated(env, error);
if (!error->IsObject()) { // We can only enhance actual errors.
report_to_inspector();
stack_trace = Undefined(isolate);
// If error is not an object, AppendExceptionLine() has already print the
// source line and the arrow to stderr.
// TODO(joyeecheung): move that side effect out of AppendExceptionLine().
// It is done just to preserve the source line as soon as possible.
} else {
Local<Object> err_obj = error.As<Object>();
auto enhance_with = [&](Local<Function> enhancer) {
Local<Value> enhanced;
Local<Value> argv[] = {err_obj};
if (!enhancer.IsEmpty() &&
enhancer
->Call(env->context(), Undefined(isolate), arraysize(argv), argv)
.ToLocal(&enhanced)) {
stack_trace = enhanced;
}
};
switch (enhance_stack) {
case EnhanceFatalException::kEnhance: {
enhance_with(env->enhance_fatal_stack_before_inspector());
report_to_inspector();
enhance_with(env->enhance_fatal_stack_after_inspector());
break;
}
case EnhanceFatalException::kDontEnhance: {
USE(err_obj->Get(env->context(), env->stack_string())
.ToLocal(&stack_trace));
report_to_inspector();
break;
}
default:
UNREACHABLE();
}
arrow =
err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol())
.ToLocalChecked();
}
node::Utf8Value trace(env->isolate(), stack_trace);
std::string report_message = "Exception";
// range errors have a trace member set to undefined
if (trace.length() > 0 && !stack_trace->IsUndefined()) {
if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
FPrintF(stderr, "%s\n", trace);
} else {
node::Utf8Value arrow_string(env->isolate(), arrow);
FPrintF(stderr, "%s\n%s\n", arrow_string, trace);
}
} else {
// this really only happens for RangeErrors, since they're the only
// kind that won't have all this info in the trace, or when non-Error
// objects are thrown manually.
MaybeLocal<Value> message;
MaybeLocal<Value> name;
if (error->IsObject()) {
Local<Object> err_obj = error.As<Object>();
message = err_obj->Get(env->context(), env->message_string());
name = err_obj->Get(env->context(), env->name_string());
}
if (message.IsEmpty() || message.ToLocalChecked()->IsUndefined() ||
name.IsEmpty() || name.ToLocalChecked()->IsUndefined()) {
// Not an error object. Just print as-is.
node::Utf8Value message(env->isolate(), error);
FPrintF(stderr, "%s\n",
*message ? message.ToString() : "<toString() threw exception>");
} else {
node::Utf8Value name_string(env->isolate(), name.ToLocalChecked());
node::Utf8Value message_string(env->isolate(), message.ToLocalChecked());
// Update the report message if it is an object has message property.
report_message = message_string.ToString();
if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
FPrintF(stderr, "%s: %s\n", name_string, message_string);
} else {
node::Utf8Value arrow_string(env->isolate(), arrow);
FPrintF(stderr,
"%s\n%s: %s\n", arrow_string, name_string, message_string);
}
}
if (!env->options()->trace_uncaught) {
std::string argv0;
if (!env->argv().empty()) argv0 = env->argv()[0];
if (argv0.empty()) argv0 = "node";
FPrintF(stderr,
"(Use `%s --trace-uncaught ...` to show where the exception "
"was thrown)\n",
fs::Basename(argv0, ".exe"));
}
}
if (env->isolate_data()->options()->report_uncaught_exception) {
TriggerNodeReport(env, report_message.c_str(), "Exception", "", error);
}
if (env->options()->trace_uncaught) {
Local<StackTrace> trace = message->GetStackTrace();
if (!trace.IsEmpty()) {
FPrintF(stderr, "Thrown at:\n");
PrintStackTrace(env->isolate(), trace);
}
}
if (env->options()->extra_info_on_fatal_exception) {
FPrintF(stderr, "\nNode.js %s\n", NODE_VERSION);
}
fflush(stderr);
}
[[noreturn]] void FatalError(const char* location, const char* message) {
OnFatalError(location, message);
// to suppress compiler warning
ABORT();
}
void OnFatalError(const char* location, const char* message) {
if (location) {
FPrintF(stderr, "FATAL ERROR: %s %s\n", location, message);
} else {
FPrintF(stderr, "FATAL ERROR: %s\n", message);
}
Isolate* isolate = Isolate::TryGetCurrent();
bool report_on_fatalerror;
{
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
report_on_fatalerror = per_process::cli_options->report_on_fatalerror;
}
if (report_on_fatalerror) {
TriggerNodeReport(isolate, message, "FatalError", "", Local<Object>());
}
fflush(stderr);
ABORT();
}
void OOMErrorHandler(const char* location, bool is_heap_oom) {
const char* message =
is_heap_oom ? "Allocation failed - JavaScript heap out of memory"
: "Allocation failed - process out of memory";
if (location) {
FPrintF(stderr, "FATAL ERROR: %s %s\n", location, message);
} else {
FPrintF(stderr, "FATAL ERROR: %s\n", message);
}
Isolate* isolate = Isolate::TryGetCurrent();
bool report_on_fatalerror;
{
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
report_on_fatalerror = per_process::cli_options->report_on_fatalerror;
}
if (report_on_fatalerror) {
// Trigger report with the isolate. Environment::GetCurrent may return
// nullptr here:
// - If the OOM is reported by a young generation space allocation,
// Isolate::GetCurrentContext returns an empty handle.
// - Otherwise, Isolate::GetCurrentContext returns a non-empty handle.
TriggerNodeReport(isolate, message, "OOMError", "", Local<Object>());
}
fflush(stderr);
ABORT();
}
v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings(
v8::Local<v8::Context> context,
v8::Local<v8::Value> source,
bool is_code_like) {
HandleScope scope(context->GetIsolate());
Environment* env = Environment::GetCurrent(context);
if (env->source_maps_enabled()) {
// We do not expect the maybe_cache_generated_source_map to throw any more
// exceptions. If it does, just ignore it.
errors::TryCatchScope try_catch(env);
Local<Function> maybe_cache_source_map =
env->maybe_cache_generated_source_map();
Local<Value> argv[1] = {source};
MaybeLocal<Value> maybe_cached = maybe_cache_source_map->Call(
context, context->Global(), arraysize(argv), argv);
if (maybe_cached.IsEmpty()) {
DCHECK(try_catch.HasCaught());
}
}
Local<Value> allow_code_gen = context->GetEmbedderData(
ContextEmbedderIndex::kAllowCodeGenerationFromStrings);
bool codegen_allowed =
allow_code_gen->IsUndefined() || allow_code_gen->IsTrue();
return {
codegen_allowed,
{},
};
}
namespace errors {
TryCatchScope::~TryCatchScope() {
if (HasCaught() && !HasTerminated() && mode_ == CatchMode::kFatal) {
HandleScope scope(env_->isolate());
Local<v8::Value> exception = Exception();
Local<v8::Message> message = Message();
EnhanceFatalException enhance = CanContinue() ?
EnhanceFatalException::kEnhance : EnhanceFatalException::kDontEnhance;
if (message.IsEmpty())
message = Exception::CreateMessage(env_->isolate(), exception);
ReportFatalException(env_, exception, message, enhance);
env_->Exit(ExitCode::kExceptionInFatalExceptionHandler);
}
}
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 "";
}
}
void PerIsolateMessageListener(Local<Message> message, Local<Value> error) {
Isolate* isolate = message->GetIsolate();
switch (message->ErrorLevel()) {
case Isolate::MessageErrorLevel::kMessageWarning: {
Environment* env = Environment::GetCurrent(isolate);
if (!env) {
break;
}
Utf8Value filename(isolate, message->GetScriptOrigin().ResourceName());
// (filename):(line) (message)
std::stringstream warning;
warning << *filename;
warning << ":";
warning << message->GetLineNumber(env->context()).FromMaybe(-1);
warning << " ";
v8::String::Utf8Value msg(isolate, message->Get());
warning << *msg;
USE(ProcessEmitWarningGeneric(env, warning.str().c_str(), "V8"));
break;
}
case Isolate::MessageErrorLevel::kMessageError:
TriggerUncaughtException(isolate, error, message);
break;
}
}
void SetPrepareStackTraceCallback(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsFunction());
env->set_prepare_stack_trace_callback(args[0].As<Function>());
}
static void SetSourceMapsEnabled(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsBoolean());
env->set_source_maps_enabled(args[0].As<Boolean>()->Value());
}
static void SetGetSourceMapErrorSource(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsFunction());
env->set_get_source_map_error_source(args[0].As<Function>());
}
static void SetMaybeCacheGeneratedSourceMap(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsFunction());
env->set_maybe_cache_generated_source_map(args[0].As<Function>());
}
static void SetEnhanceStackForFatalException(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsFunction());
CHECK(args[1]->IsFunction());
env->set_enhance_fatal_stack_before_inspector(args[0].As<Function>());
env->set_enhance_fatal_stack_after_inspector(args[1].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()) {
ReportFatalException(
env, exception, message, EnhanceFatalException::kEnhance);
Abort();
}
bool from_promise = args[1]->IsTrue();
errors::TriggerUncaughtException(isolate, exception, message, from_promise);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(SetPrepareStackTraceCallback);
registry->Register(SetGetSourceMapErrorSource);
registry->Register(SetSourceMapsEnabled);
registry->Register(SetMaybeCacheGeneratedSourceMap);
registry->Register(SetEnhanceStackForFatalException);
registry->Register(NoSideEffectsToString);
registry->Register(TriggerUncaughtException);
}
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
SetMethod(context,
target,
"setPrepareStackTraceCallback",
SetPrepareStackTraceCallback);
SetMethod(context,
target,
"setGetSourceMapErrorSource",
SetGetSourceMapErrorSource);
SetMethod(context, target, "setSourceMapsEnabled", SetSourceMapsEnabled);
SetMethod(context,
target,
"setMaybeCacheGeneratedSourceMap",
SetMaybeCacheGeneratedSourceMap);
SetMethod(context,
target,
"setEnhanceStackForFatalException",
SetEnhanceStackForFatalException);
SetMethodNoSideEffect(
context, target, "noSideEffectsToString", NoSideEffectsToString);
SetMethod(
context, target, "triggerUncaughtException", TriggerUncaughtException);
Isolate* isolate = context->GetIsolate();
Local<Object> exit_codes = Object::New(isolate);
READONLY_PROPERTY(target, "exitCodes", exit_codes);
#define V(Name, Code) \
constexpr int k##Name = static_cast<int>(ExitCode::k##Name); \
NODE_DEFINE_CONSTANT(exit_codes, k##Name);
EXIT_CODE_LIST(V)
#undef V
}
void DecorateErrorStack(Environment* env,
const errors::TryCatchScope& try_catch) {
Local<Value> exception = try_catch.Exception();
if (!exception->IsObject()) return;
Local<Object> err_obj = exception.As<Object>();
if (IsExceptionDecorated(env, err_obj)) return;
AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR);
TryCatchScope try_catch_scope(env); // Ignore exceptions below.
MaybeLocal<Value> stack = err_obj->Get(env->context(), env->stack_string());
MaybeLocal<Value> maybe_value =
err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol());
Local<Value> arrow;
if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) {
return;
}
if (stack.IsEmpty() || !stack.ToLocalChecked()->IsString()) {
return;
}
Local<String> decorated_stack = String::Concat(
env->isolate(),
String::Concat(env->isolate(),
arrow.As<String>(),
FIXED_ONE_BYTE_STRING(env->isolate(), "\n")),
stack.ToLocalChecked().As<String>());
USE(err_obj->Set(env->context(), env->stack_string(), decorated_stack));
err_obj->SetPrivate(
env->context(), env->decorated_private_symbol(), True(env->isolate()));
}
void TriggerUncaughtException(Isolate* isolate,
Local<Value> error,
Local<Message> message,
bool from_promise) {
CHECK(!error.IsEmpty());
HandleScope scope(isolate);
if (message.IsEmpty()) message = Exception::CreateMessage(isolate, error);
CHECK(isolate->InContext());
Local<Context> context = isolate->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);
if (env == nullptr) {
// This means that the exception happens before Environment is assigned
// to the context e.g. when there is a SyntaxError in a per-context
// script - which usually indicates that there is a bug because no JS
// error is supposed to be thrown at this point.
// Since we don't have access to Environment here, there is not
// much we can do, so we just print whatever is useful and crash.
PrintException(isolate, context, error, message);
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<String> fatal_exception_string = env->fatal_exception_string();
Local<Value> fatal_exception_function =
process_object->Get(env->context(),
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()) {
ReportFatalException(
env, error, message, EnhanceFatalException::kDontEnhance);
env->Exit(ExitCode::kInvalidFatalExceptionMonkeyPatching);
return;
}
MaybeLocal<Value> maybe_handled;
if (env->can_call_into_js()) {
// 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,
Boolean::New(env->isolate(), from_promise) };
maybe_handled = fatal_exception_function.As<Function>()->Call(
env->context(), process_object, arraysize(argv), argv);
}
// 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.
Local<Value> handled;
if (!maybe_handled.ToLocal(&handled)) {
return;
}
// The global uncaught exception handler returns true if the user handles it
// by e.g. listening to `uncaughtException`. In that case, continue program
// execution.
// 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->IsFalse()) {
return;
}
// Now we are certain that the exception is fatal.
ReportFatalException(env, error, message, EnhanceFatalException::kEnhance);
RunAtExit(env);
// If the global uncaught exception handler sets process.exitCode,
// exit with that code. Otherwise, exit with 1.
Local<String> exit_code = env->exit_code_string();
Local<Value> code;
if (process_object->Get(env->context(), exit_code).ToLocal(&code) &&
code->IsInt32()) {
env->Exit(static_cast<ExitCode>(code.As<Int32>()->Value()));
} else {
env->Exit(ExitCode::kGenericUserError);
}
}
void TriggerUncaughtException(Isolate* isolate, const v8::TryCatch& try_catch) {
// If the try_catch is verbose, the per-isolate message listener is going to
// handle it (which is going to call into another overload of
// 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
NODE_MODULE_CONTEXT_AWARE_INTERNAL(errors, node::errors::Initialize)
NODE_MODULE_EXTERNAL_REFERENCE(errors, node::errors::RegisterExternalReferences)