mirror of
https://github.com/nodejs/node.git
synced 2026-10-29 02:35:20 +08:00
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>
1192 lines
34 KiB
C++
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)
|