summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--node.gyp1
-rw-r--r--src/async_wrap.cc3
-rw-r--r--src/inspector_agent.cc3
-rw-r--r--src/node.cc388
-rw-r--r--src/node_api.cc3
-rw-r--r--src/node_contextify.cc43
-rw-r--r--src/node_contextify.h2
-rw-r--r--src/node_errors.cc427
-rw-r--r--src/node_errors.h32
-rw-r--r--src/node_internals.h111
-rw-r--r--src/node_url.cc3
-rw-r--r--src/node_watchdog.cc5
12 files changed, 516 insertions, 505 deletions
diff --git a/node.gyp b/node.gyp
index 13c21e33ee..5a42d2b714 100644
--- a/node.gyp
+++ b/node.gyp
@@ -345,6 +345,7 @@
'src/node_domain.cc',
'src/node_encoding.cc',
'src/node_errors.h',
+ 'src/node_errors.cc',
'src/node_file.cc',
'src/node_http2.cc',
'src/node_http_parser.cc',
diff --git a/src/async_wrap.cc b/src/async_wrap.cc
index 17636ceb31..471fa231c2 100644
--- a/src/async_wrap.cc
+++ b/src/async_wrap.cc
@@ -21,9 +21,10 @@
#include "async_wrap-inl.h"
#include "env-inl.h"
+#include "node_errors.h"
#include "node_internals.h"
-#include "util-inl.h"
#include "tracing/traced_value.h"
+#include "util-inl.h"
#include "v8.h"
#include "v8-profiler.h"
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
index ebb7b7d5bc..e22e8ee959 100644
--- a/src/inspector_agent.cc
+++ b/src/inspector_agent.cc
@@ -1,12 +1,13 @@
#include "inspector_agent.h"
-#include "inspector_io.h"
#include "inspector/main_thread_interface.h"
#include "inspector/node_string.h"
#include "inspector/tracing_agent.h"
#include "inspector/worker_agent.h"
#include "inspector/worker_inspector.h"
+#include "inspector_io.h"
#include "node/inspector/protocol/Protocol.h"
+#include "node_errors.h"
#include "node_internals.h"
#include "node_url.h"
#include "v8-inspector.h"
diff --git a/src/node.cc b/src/node.cc
index 0764adc10b..b612ef3c34 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -21,14 +21,16 @@
#include "node_buffer.h"
#include "node_constants.h"
+#include "node_context_data.h"
+#include "node_errors.h"
+#include "node_internals.h"
#include "node_javascript.h"
#include "node_code_cache.h"
#include "node_platform.h"
#include "node_version.h"
#include "node_internals.h"
#include "node_revert.h"
-#include "node_perf.h"
-#include "node_context_data.h"
+#include "node_version.h"
#include "tracing/traced_value.h"
#if HAVE_OPENSSL
@@ -387,41 +389,6 @@ tracing::AgentWriterHandle* GetTracingAgentWriter() {
static const unsigned kMaxSignal = 32;
#endif
-void PrintErrorString(const char* format, ...) {
- va_list ap;
- va_start(ap, format);
-#ifdef _WIN32
- HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
-
- // Check if stderr is something other than a tty/console
- if (stderr_handle == INVALID_HANDLE_VALUE ||
- stderr_handle == nullptr ||
- uv_guess_handle(_fileno(stderr)) != UV_TTY) {
- vfprintf(stderr, format, ap);
- va_end(ap);
- return;
- }
-
- // Fill in any placeholders
- int n = _vscprintf(format, ap);
- std::vector<char> out(n + 1);
- vsprintf(out.data(), format, ap);
-
- // Get required wide buffer size
- n = MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, nullptr, 0);
-
- std::vector<wchar_t> wbuf(n);
- MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, wbuf.data(), n);
-
- // Don't include the null character in the output
- CHECK_GT(n, 0);
- WriteConsoleW(stderr_handle, wbuf.data(), n - 1, nullptr, nullptr);
-#else
- vfprintf(stderr, format, ap);
-#endif
- va_end(ap);
-}
-
const char* signo_string(int signo) {
#define SIGNO_CASE(e) case e: return #e;
switch (signo) {
@@ -777,223 +744,6 @@ Local<Value> MakeCallback(Isolate* isolate,
.FromMaybe(Local<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;
-}
-
-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>();
- }
-
- // Print (filename):(line number): (message).
- ScriptOrigin origin = message->GetScriptOrigin();
- node::Utf8Value filename(env->isolate(), message->GetScriptResourceName());
- const char* filename_string = *filename;
- int linenum = message->GetLineNumber(env->context()).FromJust();
- // Print line of source code.
- MaybeLocal<String> source_line_maybe = message->GetSourceLine(env->context());
- node::Utf8Value sourceline(env->isolate(),
- source_line_maybe.ToLocalChecked());
- const char* sourceline_string = *sourceline;
- if (strstr(sourceline_string, "node-do-not-add-exception-line") != nullptr)
- return;
-
- // 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.
-
- int script_start =
- (linenum - origin.ResourceLineOffset()->Value()) == 1 ?
- origin.ResourceColumnOffset()->Value() : 0;
- int start = message->GetStartColumn(env->context()).FromMaybe(0);
- int end = message->GetEndColumn(env->context()).FromMaybe(0);
- if (start >= script_start) {
- CHECK_GE(end, start);
- start -= script_start;
- end -= script_start;
- }
-
- char arrow[1024];
- int max_off = sizeof(arrow) - 2;
-
- int off = snprintf(arrow,
- sizeof(arrow),
- "%s:%i\n%s\n",
- filename_string,
- linenum,
- sourceline_string);
- CHECK_GE(off, 0);
- if (off > max_off) {
- off = max_off;
- }
-
- // Print wavy underline (GetUnderline is deprecated).
- for (int i = 0; i < start; i++) {
- if (sourceline_string[i] == '\0' || off >= max_off) {
- break;
- }
- CHECK_LT(off, max_off);
- arrow[off++] = (sourceline_string[i] == '\t') ? '\t' : ' ';
- }
- for (int i = start; i < end; i++) {
- if (sourceline_string[i] == '\0' || off >= max_off) {
- break;
- }
- CHECK_LT(off, max_off);
- arrow[off++] = '^';
- }
- CHECK_LE(off, max_off);
- arrow[off] = '\n';
- arrow[off + 1] = '\0';
-
- Local<String> arrow_str = String::NewFromUtf8(env->isolate(), arrow,
- NewStringType::kNormal).ToLocalChecked();
-
- 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(process_mutex);
- env->set_printed_error(true);
-
- uv_tty_reset_mode();
- PrintErrorString("\n%s", arrow);
- return;
- }
-
- CHECK(err_obj->SetPrivate(
- env->context(),
- env->arrow_message_private_symbol(),
- arrow_str).FromMaybe(false));
-}
-
-
-void ReportException(Environment* env,
- Local<Value> er,
- Local<Message> message) {
- CHECK(!er.IsEmpty());
- HandleScope scope(env->isolate());
-
- if (message.IsEmpty())
- message = Exception::CreateMessage(env->isolate(), er);
-
- AppendExceptionLine(env, er, message, FATAL_ERROR);
-
- Local<Value> trace_value;
- Local<Value> arrow;
- const bool decorated = IsExceptionDecorated(env, er);
-
- if (er->IsUndefined() || er->IsNull()) {
- trace_value = Undefined(env->isolate());
- } else {
- Local<Object> err_obj = er->ToObject(env->context()).ToLocalChecked();
-
- trace_value = err_obj->Get(env->stack_string());
- arrow =
- err_obj->GetPrivate(
- env->context(),
- env->arrow_message_private_symbol()).ToLocalChecked();
- }
-
- node::Utf8Value trace(env->isolate(), trace_value);
-
- // range errors have a trace member set to undefined
- if (trace.length() > 0 && !trace_value->IsUndefined()) {
- if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
- PrintErrorString("%s\n", *trace);
- } else {
- node::Utf8Value arrow_string(env->isolate(), arrow);
- PrintErrorString("%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.
- Local<Value> message;
- Local<Value> name;
-
- if (er->IsObject()) {
- Local<Object> err_obj = er.As<Object>();
- message = err_obj->Get(env->message_string());
- name = err_obj->Get(FIXED_ONE_BYTE_STRING(env->isolate(), "name"));
- }
-
- if (message.IsEmpty() ||
- message->IsUndefined() ||
- name.IsEmpty() ||
- name->IsUndefined()) {
- // Not an error object. Just print as-is.
- String::Utf8Value message(env->isolate(), er);
-
- PrintErrorString("%s\n", *message ? *message :
- "<toString() threw exception>");
- } else {
- node::Utf8Value name_string(env->isolate(), name);
- node::Utf8Value message_string(env->isolate(), message);
-
- if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
- PrintErrorString("%s: %s\n", *name_string, *message_string);
- } else {
- node::Utf8Value arrow_string(env->isolate(), arrow);
- PrintErrorString("%s\n%s: %s\n",
- *arrow_string,
- *name_string,
- *message_string);
- }
- }
- }
-
- fflush(stderr);
-
-#if HAVE_INSPECTOR
- env->inspector_agent()->FatalException(er, message);
-#endif
-}
-
-
-static void ReportException(Environment* env, const TryCatch& try_catch) {
- ReportException(env, try_catch.Exception(), try_catch.Message());
-}
-
-
// Executes a str within the current v8 context.
static MaybeLocal<Value> ExecuteString(Environment* env,
Local<String> source,
@@ -1028,31 +778,6 @@ static MaybeLocal<Value> ExecuteString(Environment* env,
return scope.Escape(result.ToLocalChecked());
}
-
-[[noreturn]] void Abort() {
- DumpBacktrace(stderr);
- fflush(stderr);
- ABORT_NO_BACKTRACE();
-}
-
-
-[[noreturn]] void Assert(const char* const (*args)[4]) {
- auto filename = (*args)[0];
- auto linenum = (*args)[1];
- auto message = (*args)[2];
- auto function = (*args)[3];
-
- char name[1024];
- GetHumanReadableProcessName(&name);
-
- fprintf(stderr, "%s: %s:%s:%s%s Assertion `%s' failed.\n",
- name, filename, linenum, function, *function ? ":" : "", message);
- fflush(stderr);
-
- Abort();
-}
-
-
static void WaitForInspectorDisconnect(Environment* env) {
#if HAVE_INSPECTOR
if (env->inspector_agent()->IsActive()) {
@@ -1333,111 +1058,6 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
// coverity[leaked_storage]
}
-
-static void OnFatalError(const char* location, const char* message) {
- if (location) {
- PrintErrorString("FATAL ERROR: %s %s\n", location, message);
- } else {
- PrintErrorString("FATAL ERROR: %s\n", message);
- }
- fflush(stderr);
- ABORT();
-}
-
-
-[[noreturn]] void FatalError(const char* location, const char* message) {
- OnFatalError(location, message);
- // to suppress compiler warning
- ABORT();
-}
-
-
-FatalTryCatch::~FatalTryCatch() {
- if (HasCaught()) {
- HandleScope scope(env_->isolate());
- ReportException(env_, *this);
- exit(7);
- }
-}
-
-
-void FatalException(Isolate* isolate,
- Local<Value> error,
- Local<Message> message) {
- HandleScope scope(isolate);
-
- Environment* env = Environment::GetCurrent(isolate);
- CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
- Local<Object> process_object = env->process_object();
- Local<String> fatal_exception_string = env->fatal_exception_string();
- Local<Value> fatal_exception_function =
- process_object->Get(fatal_exception_string);
-
- 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);
- exit(6);
- } else {
- TryCatch fatal_try_catch(isolate);
-
- // Do not call FatalException when _fatalException handler throws
- fatal_try_catch.SetVerbose(false);
-
- // This will return true if the JS layer handled it, false otherwise
- MaybeLocal<Value> caught = fatal_exception_function.As<Function>()->Call(
- env->context(), process_object, 1, &error);
-
- if (fatal_try_catch.HasTerminated())
- return;
-
- if (fatal_try_catch.HasCaught()) {
- // The fatal exception function threw, so we must exit
- ReportException(env, fatal_try_catch);
- exit(7);
- } else if (caught.ToLocalChecked()->IsFalse()) {
- ReportException(env, error, message);
-
- // fatal_exception_function call before may have set a new exit code ->
- // read it again, otherwise use default for uncaughtException 1
- Local<String> exit_code = env->exit_code_string();
- Local<Value> code;
- if (!process_object->Get(env->context(), exit_code).ToLocal(&code) ||
- !code->IsInt32()) {
- exit(1);
- }
- exit(code.As<Int32>()->Value());
- }
- }
-}
-
-
-void FatalException(Isolate* isolate, const TryCatch& try_catch) {
- // If we try to print out a termination exception, we'd just get 'null',
- // 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());
- }
-}
-
-
-static void FatalException(const FunctionCallbackInfo<Value>& args) {
- Isolate* isolate = args.GetIsolate();
- Environment* env = Environment::GetCurrent(isolate);
- if (env != nullptr && env->abort_on_uncaught_exception()) {
- Abort();
- }
- Local<Value> exception = args[0];
- Local<Message> message = Exception::CreateMessage(isolate, exception);
- FatalException(isolate, exception, message);
-}
-
-
static void OnMessage(Local<Message> message, Local<Value> error) {
// The current version of V8 sends messages for errors only
// (thus `error` is always set).
diff --git a/src/node_api.cc b/src/node_api.cc
index 1f7b140553..c023920da4 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -6,9 +6,10 @@
#include <cmath>
#include <vector>
#define NAPI_EXPERIMENTAL
+#include "env.h"
#include "node_api.h"
+#include "node_errors.h"
#include "node_internals.h"
-#include "env.h"
static
napi_status napi_set_last_error(napi_env env, napi_status error_code,
diff --git a/src/node_contextify.cc b/src/node_contextify.cc
index 79943f1ad6..e878507731 100644
--- a/src/node_contextify.cc
+++ b/src/node_contextify.cc
@@ -847,47 +847,6 @@ void ContextifyScript::RunInContext(const FunctionCallbackInfo<Value>& args) {
TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script);
}
-void ContextifyScript::DecorateErrorStack(
- Environment* env, const TryCatch& 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);
- Local<Value> stack = err_obj->Get(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->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.As<String>());
- err_obj->Set(env->stack_string(), decorated_stack);
- err_obj->SetPrivate(
- env->context(),
- env->decorated_private_symbol(),
- True(env->isolate()));
-}
-
bool ContextifyScript::EvalMachine(Environment* env,
const int64_t timeout,
const bool display_errors,
@@ -1080,7 +1039,7 @@ void ContextifyContext::CompileFunction(
Local<Function> fun;
if (maybe_fun.IsEmpty() || !maybe_fun.ToLocal(&fun)) {
- ContextifyScript::DecorateErrorStack(env, try_catch);
+ DecorateErrorStack(env, try_catch);
try_catch.ReThrow();
return;
}
diff --git a/src/node_contextify.h b/src/node_contextify.h
index 6d6dc12b4b..1221233bb8 100644
--- a/src/node_contextify.h
+++ b/src/node_contextify.h
@@ -119,8 +119,6 @@ class ContextifyScript : public BaseObject {
const v8::FunctionCallbackInfo<v8::Value>& args);
static void RunInThisContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RunInContext(const v8::FunctionCallbackInfo<v8::Value>& args);
- static void DecorateErrorStack(Environment* env,
- const v8::TryCatch& try_catch);
static bool EvalMachine(Environment* env,
const int64_t timeout,
const bool display_errors,
diff --git a/src/node_errors.cc b/src/node_errors.cc
new file mode 100644
index 0000000000..cc8cff0f0e
--- /dev/null
+++ b/src/node_errors.cc
@@ -0,0 +1,427 @@
+#include <stdarg.h>
+#include "node_errors.h"
+#include "node_internals.h"
+
+namespace node {
+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::NewStringType;
+using v8::Number;
+using v8::Object;
+using v8::ScriptOrigin;
+using v8::String;
+using v8::TryCatch;
+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;
+}
+
+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>();
+ }
+
+ // Print (filename):(line number): (message).
+ ScriptOrigin origin = message->GetScriptOrigin();
+ node::Utf8Value filename(env->isolate(), message->GetScriptResourceName());
+ const char* filename_string = *filename;
+ int linenum = message->GetLineNumber(env->context()).FromJust();
+ // Print line of source code.
+ MaybeLocal<String> source_line_maybe = message->GetSourceLine(env->context());
+ node::Utf8Value sourceline(env->isolate(),
+ source_line_maybe.ToLocalChecked());
+ const char* sourceline_string = *sourceline;
+ if (strstr(sourceline_string, "node-do-not-add-exception-line") != nullptr)
+ return;
+
+ // 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.
+
+ int script_start = (linenum - origin.ResourceLineOffset()->Value()) == 1
+ ? origin.ResourceColumnOffset()->Value()
+ : 0;
+ int start = message->GetStartColumn(env->context()).FromMaybe(0);
+ int end = message->GetEndColumn(env->context()).FromMaybe(0);
+ if (start >= script_start) {
+ CHECK_GE(end, start);
+ start -= script_start;
+ end -= script_start;
+ }
+
+ char arrow[1024];
+ int max_off = sizeof(arrow) - 2;
+
+ int off = snprintf(arrow,
+ sizeof(arrow),
+ "%s:%i\n%s\n",
+ filename_string,
+ linenum,
+ sourceline_string);
+ CHECK_GE(off, 0);
+ if (off > max_off) {
+ off = max_off;
+ }
+
+ // Print wavy underline (GetUnderline is deprecated).
+ for (int i = 0; i < start; i++) {
+ if (sourceline_string[i] == '\0' || off >= max_off) {
+ break;
+ }
+ CHECK_LT(off, max_off);
+ arrow[off++] = (sourceline_string[i] == '\t') ? '\t' : ' ';
+ }
+ for (int i = start; i < end; i++) {
+ if (sourceline_string[i] == '\0' || off >= max_off) {
+ break;
+ }
+ CHECK_LT(off, max_off);
+ arrow[off++] = '^';
+ }
+ CHECK_LE(off, max_off);
+ arrow[off] = '\n';
+ arrow[off + 1] = '\0';
+
+ Local<String> arrow_str =
+ String::NewFromUtf8(env->isolate(), arrow, NewStringType::kNormal)
+ .ToLocalChecked();
+
+ 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(process_mutex);
+ env->set_printed_error(true);
+
+ uv_tty_reset_mode();
+ PrintErrorString("\n%s", arrow);
+ return;
+ }
+
+ CHECK(err_obj
+ ->SetPrivate(
+ env->context(), env->arrow_message_private_symbol(), arrow_str)
+ .FromMaybe(false));
+}
+
+[[noreturn]] void Abort() {
+ DumpBacktrace(stderr);
+ fflush(stderr);
+ ABORT_NO_BACKTRACE();
+}
+
+[[noreturn]] void Assert(const char* const (*args)[4]) {
+ auto filename = (*args)[0];
+ auto linenum = (*args)[1];
+ auto message = (*args)[2];
+ auto function = (*args)[3];
+
+ char name[1024];
+ GetHumanReadableProcessName(&name);
+
+ fprintf(stderr,
+ "%s: %s:%s:%s%s Assertion `%s' failed.\n",
+ name,
+ filename,
+ linenum,
+ function,
+ *function ? ":" : "",
+ message);
+ fflush(stderr);
+
+ Abort();
+}
+
+void ReportException(Environment* env,
+ Local<Value> er,
+ Local<Message> message) {
+ CHECK(!er.IsEmpty());
+ HandleScope scope(env->isolate());
+
+ if (message.IsEmpty()) message = Exception::CreateMessage(env->isolate(), er);
+
+ AppendExceptionLine(env, er, message, FATAL_ERROR);
+
+ Local<Value> trace_value;
+ Local<Value> arrow;
+ const bool decorated = IsExceptionDecorated(env, er);
+
+ if (er->IsUndefined() || er->IsNull()) {
+ trace_value = Undefined(env->isolate());
+ } else {
+ Local<Object> err_obj = er->ToObject(env->context()).ToLocalChecked();
+
+ trace_value = err_obj->Get(env->stack_string());
+ arrow =
+ err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol())
+ .ToLocalChecked();
+ }
+
+ node::Utf8Value trace(env->isolate(), trace_value);
+
+ // range errors have a trace member set to undefined
+ if (trace.length() > 0 && !trace_value->IsUndefined()) {
+ if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
+ PrintErrorString("%s\n", *trace);
+ } else {
+ node::Utf8Value arrow_string(env->isolate(), arrow);
+ PrintErrorString("%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.
+ Local<Value> message;
+ Local<Value> name;
+
+ if (er->IsObject()) {
+ Local<Object> err_obj = er.As<Object>();
+ message = err_obj->Get(env->message_string());
+ name = err_obj->Get(FIXED_ONE_BYTE_STRING(env->isolate(), "name"));
+ }
+
+ if (message.IsEmpty() || message->IsUndefined() || name.IsEmpty() ||
+ name->IsUndefined()) {
+ // Not an error object. Just print as-is.
+ String::Utf8Value message(env->isolate(), er);
+
+ PrintErrorString("%s\n",
+ *message ? *message : "<toString() threw exception>");
+ } else {
+ node::Utf8Value name_string(env->isolate(), name);
+ node::Utf8Value message_string(env->isolate(), message);
+
+ if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
+ PrintErrorString("%s: %s\n", *name_string, *message_string);
+ } else {
+ node::Utf8Value arrow_string(env->isolate(), arrow);
+ PrintErrorString(
+ "%s\n%s: %s\n", *arrow_string, *name_string, *message_string);
+ }
+ }
+ }
+
+ fflush(stderr);
+
+#if HAVE_INSPECTOR
+ env->inspector_agent()->FatalException(er, message);
+#endif
+}
+
+void ReportException(Environment* env, const TryCatch& try_catch) {
+ ReportException(env, try_catch.Exception(), try_catch.Message());
+}
+
+void DecorateErrorStack(Environment* env, const TryCatch& 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);
+ Local<Value> stack = err_obj->Get(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->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.As<String>());
+ err_obj->Set(env->stack_string(), decorated_stack);
+ err_obj->SetPrivate(
+ env->context(), env->decorated_private_symbol(), True(env->isolate()));
+}
+
+void PrintErrorString(const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+#ifdef _WIN32
+ HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
+
+ // Check if stderr is something other than a tty/console
+ if (stderr_handle == INVALID_HANDLE_VALUE || stderr_handle == nullptr ||
+ uv_guess_handle(_fileno(stderr)) != UV_TTY) {
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ return;
+ }
+
+ // Fill in any placeholders
+ int n = _vscprintf(format, ap);
+ std::vector<char> out(n + 1);
+ vsprintf(out.data(), format, ap);
+
+ // Get required wide buffer size
+ n = MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, nullptr, 0);
+
+ std::vector<wchar_t> wbuf(n);
+ MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, wbuf.data(), n);
+
+ // Don't include the null character in the output
+ CHECK_GT(n, 0);
+ WriteConsoleW(stderr_handle, wbuf.data(), n - 1, nullptr, nullptr);
+#else
+ vfprintf(stderr, format, ap);
+#endif
+ va_end(ap);
+}
+
+[[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) {
+ PrintErrorString("FATAL ERROR: %s %s\n", location, message);
+ } else {
+ PrintErrorString("FATAL ERROR: %s\n", message);
+ }
+ fflush(stderr);
+ ABORT();
+}
+
+FatalTryCatch::~FatalTryCatch() {
+ if (HasCaught()) {
+ HandleScope scope(env_->isolate());
+ ReportException(env_, *this);
+ exit(7);
+ }
+}
+
+void FatalException(Isolate* isolate,
+ Local<Value> error,
+ Local<Message> message) {
+ HandleScope scope(isolate);
+
+ Environment* env = Environment::GetCurrent(isolate);
+ CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
+ Local<Object> process_object = env->process_object();
+ Local<String> fatal_exception_string = env->fatal_exception_string();
+ Local<Value> fatal_exception_function =
+ process_object->Get(fatal_exception_string);
+
+ 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);
+ exit(6);
+ } else {
+ TryCatch fatal_try_catch(isolate);
+
+ // Do not call FatalException when _fatalException handler throws
+ fatal_try_catch.SetVerbose(false);
+
+ // This will return true if the JS layer handled it, false otherwise
+ MaybeLocal<Value> caught = fatal_exception_function.As<Function>()->Call(
+ env->context(), process_object, 1, &error);
+
+ if (fatal_try_catch.HasTerminated()) return;
+
+ if (fatal_try_catch.HasCaught()) {
+ // The fatal exception function threw, so we must exit
+ ReportException(env, fatal_try_catch);
+ exit(7);
+ } else if (caught.ToLocalChecked()->IsFalse()) {
+ ReportException(env, error, message);
+
+ // fatal_exception_function call before may have set a new exit code ->
+ // read it again, otherwise use default for uncaughtException 1
+ Local<String> exit_code = env->exit_code_string();
+ Local<Value> code;
+ if (!process_object->Get(env->context(), exit_code).ToLocal(&code) ||
+ !code->IsInt32()) {
+ exit(1);
+ }
+ exit(code.As<Int32>()->Value());
+ }
+ }
+}
+
+void FatalException(Isolate* isolate, const TryCatch& try_catch) {
+ // If we try to print out a termination exception, we'd just get 'null',
+ // 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());
+ }
+}
+
+void FatalException(const FunctionCallbackInfo<Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ Environment* env = Environment::GetCurrent(isolate);
+ if (env != nullptr && env->abort_on_uncaught_exception()) {
+ Abort();
+ }
+ Local<Value> exception = args[0];
+ Local<Message> message = Exception::CreateMessage(isolate, exception);
+ FatalException(isolate, exception, message);
+}
+
+} // namespace node
diff --git a/src/node_errors.h b/src/node_errors.h
index a435695693..2c52007c51 100644
--- a/src/node_errors.h
+++ b/src/node_errors.h
@@ -14,6 +14,38 @@
namespace node {
+void DecorateErrorStack(Environment* env, const v8::TryCatch& try_catch);
+
+enum ErrorHandlingMode { CONTEXTIFY_ERROR, FATAL_ERROR, MODULE_ERROR };
+void AppendExceptionLine(Environment* env,
+ v8::Local<v8::Value> er,
+ v8::Local<v8::Message> message,
+ enum ErrorHandlingMode mode);
+
+[[noreturn]] void FatalError(const char* location, const char* message);
+void OnFatalError(const char* location, const char* message);
+
+// Like a `TryCatch` but exits the process if an exception was caught.
+class FatalTryCatch : public v8::TryCatch {
+ public:
+ explicit FatalTryCatch(Environment* env)
+ : TryCatch(env->isolate()), env_(env) {}
+ ~FatalTryCatch();
+
+ private:
+ Environment* env_;
+};
+
+void PrintErrorString(const char* format, ...);
+
+void ReportException(Environment* env, const v8::TryCatch& try_catch);
+
+void FatalException(v8::Isolate* isolate,
+ v8::Local<v8::Value> error,
+ v8::Local<v8::Message> message);
+
+void FatalException(const v8::FunctionCallbackInfo<v8::Value>& args);
+
// Helpers to construct errors similar to the ones provided by
// lib/internal/errors.js.
// Example: with `V(ERR_INVALID_ARG_TYPE, TypeError)`, there will be
diff --git a/src/node_internals.h b/src/node_internals.h
index b307019852..b83c23681b 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -102,46 +102,46 @@ struct sockaddr;
// function. This helps the built-in modules are loaded properly when
// node is built as static library. No need to depend on the
// __attribute__((constructor)) like mechanism in GCC.
-#define NODE_BUILTIN_STANDARD_MODULES(V) \
- V(async_wrap) \
- V(buffer) \
- V(cares_wrap) \
- V(config) \
- V(contextify) \
- V(domain) \
- V(fs) \
- V(fs_event_wrap) \
- V(heap_utils) \
- V(http2) \
- V(http_parser) \
- V(inspector) \
- V(js_stream) \
- V(messaging) \
- V(module_wrap) \
- V(options) \
- V(os) \
- V(performance) \
- V(pipe_wrap) \
- V(process_wrap) \
- V(serdes) \
- V(signal_wrap) \
- V(spawn_sync) \
- V(stream_pipe) \
- V(stream_wrap) \
- V(string_decoder) \
- V(symbols) \
- V(tcp_wrap) \
- V(timers) \
- V(trace_events) \
- V(tty_wrap) \
- V(types) \
- V(udp_wrap) \
- V(url) \
- V(util) \
- V(uv) \
- V(v8) \
- V(worker) \
- V(zlib)
+#define NODE_BUILTIN_STANDARD_MODULES(V) \
+ V(async_wrap) \
+ V(buffer) \
+ V(cares_wrap) \
+ V(config) \
+ V(contextify) \
+ V(domain) \
+ V(fs) \
+ V(fs_event_wrap) \
+ V(heap_utils) \
+ V(http2) \
+ V(http_parser) \
+ V(inspector) \
+ V(js_stream) \
+ V(messaging) \
+ V(module_wrap) \
+ V(options) \
+ V(os) \
+ V(performance) \
+ V(pipe_wrap) \
+ V(process_wrap) \
+ V(serdes) \
+ V(signal_wrap) \
+ V(spawn_sync) \
+ V(stream_pipe) \
+ V(stream_wrap) \
+ V(string_decoder) \
+ V(symbols) \
+ V(tcp_wrap) \
+ V(timers) \
+ V(trace_events) \
+ V(tty_wrap) \
+ V(types) \
+ V(udp_wrap) \
+ V(url) \
+ V(util) \
+ V(uv) \
+ V(v8) \
+ V(worker) \
+ V(zlib)
#define NODE_BUILTIN_MODULES(V) \
NODE_BUILTIN_STANDARD_MODULES(V) \
@@ -214,11 +214,6 @@ void GetSockOrPeerName(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(err);
}
-void FatalException(v8::Isolate* isolate,
- v8::Local<v8::Value> error,
- v8::Local<v8::Message> message);
-
-
void SignalExit(int signo);
#ifdef __POSIX__
void RegisterSignalHandler(int signal,
@@ -244,27 +239,6 @@ constexpr size_t arraysize(const T(&)[N]) { return N; }
# define MUST_USE_RESULT
#endif
-bool IsExceptionDecorated(Environment* env, v8::Local<v8::Value> er);
-
-enum ErrorHandlingMode { CONTEXTIFY_ERROR, FATAL_ERROR, MODULE_ERROR };
-void AppendExceptionLine(Environment* env,
- v8::Local<v8::Value> er,
- v8::Local<v8::Message> message,
- enum ErrorHandlingMode mode);
-
-[[noreturn]] void FatalError(const char* location, const char* message);
-
-// Like a `TryCatch` but exits the process if an exception was caught.
-class FatalTryCatch : public v8::TryCatch {
- public:
- explicit FatalTryCatch(Environment* env)
- : TryCatch(env->isolate()), env_(env) {}
- ~FatalTryCatch();
-
- private:
- Environment* env_;
-};
-
class SlicedArguments {
public:
inline explicit SlicedArguments(
@@ -298,10 +272,6 @@ SlicedArguments::SlicedArguments(
size_ = size;
}
-void ReportException(Environment* env,
- v8::Local<v8::Value> er,
- v8::Local<v8::Message> message);
-
v8::Maybe<bool> ProcessEmitWarning(Environment* env, const char* fmt, ...);
v8::Maybe<bool> ProcessEmitDeprecationWarning(Environment* env,
const char* warning,
@@ -829,7 +799,6 @@ static inline const char* errno_string(int errorno) {
// Functions defined in node.cc that are exposed via the bootstrapper object
extern double prog_start_time;
-void PrintErrorString(const char* format, ...);
void Abort(const v8::FunctionCallbackInfo<v8::Value>& args);
void Chdir(const v8::FunctionCallbackInfo<v8::Value>& args);
diff --git a/src/node_url.cc b/src/node_url.cc
index 0be777e03d..a3f22f7c98 100644
--- a/src/node_url.cc
+++ b/src/node_url.cc
@@ -1,7 +1,8 @@
#include "node_url.h"
-#include "node_internals.h"
#include "base_object-inl.h"
+#include "node_errors.h"
#include "node_i18n.h"
+#include "node_internals.h"
#include <string>
#include <vector>
diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc
index 09b10d1693..b8665dc1be 100644
--- a/src/node_watchdog.cc
+++ b/src/node_watchdog.cc
@@ -20,9 +20,10 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "node_watchdog.h"
-#include "node_internals.h"
-#include "debug_utils.h"
#include <algorithm>
+#include "debug_utils.h"
+#include "node_errors.h"
+#include "node_internals.h"
namespace node {