diff --git a/node.gyp b/node.gyp index 13c21e33ee88d4..5a42d2b7149b78 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 17636ceb31c27d..471fa231c2e861 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 ebb7b7d5bc3e72..e22e8ee9592921 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 0764adc10bc9e0..b612ef3c348302 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 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 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 MakeCallback(Isolate* isolate, .FromMaybe(Local())); } -bool IsExceptionDecorated(Environment* env, Local er) { - if (!er.IsEmpty() && er->IsObject()) { - Local err_obj = er.As(); - auto maybe_value = - err_obj->GetPrivate(env->context(), env->decorated_private_symbol()); - Local decorated; - return maybe_value.ToLocal(&decorated) && decorated->IsTrue(); - } - return false; -} - -void AppendExceptionLine(Environment* env, - Local er, - Local message, - enum ErrorHandlingMode mode) { - if (message.IsEmpty()) - return; - - HandleScope scope(env->isolate()); - Local err_obj; - if (!er.IsEmpty() && er->IsObject()) { - err_obj = er.As(); - } - - // 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 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 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 er, - Local message) { - CHECK(!er.IsEmpty()); - HandleScope scope(env->isolate()); - - if (message.IsEmpty()) - message = Exception::CreateMessage(env->isolate(), er); - - AppendExceptionLine(env, er, message, FATAL_ERROR); - - Local trace_value; - Local arrow; - const bool decorated = IsExceptionDecorated(env, er); - - if (er->IsUndefined() || er->IsNull()) { - trace_value = Undefined(env->isolate()); - } else { - Local 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 message; - Local name; - - if (er->IsObject()) { - Local err_obj = er.As(); - 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 : - ""); - } 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 ExecuteString(Environment* env, Local source, @@ -1028,31 +778,6 @@ static MaybeLocal 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& 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 error, - Local message) { - HandleScope scope(isolate); - - Environment* env = Environment::GetCurrent(isolate); - CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. - Local process_object = env->process_object(); - Local fatal_exception_string = env->fatal_exception_string(); - Local 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 caught = fatal_exception_function.As()->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 exit_code = env->exit_code_string(); - Local code; - if (!process_object->Get(env->context(), exit_code).ToLocal(&code) || - !code->IsInt32()) { - exit(1); - } - exit(code.As()->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& args) { - Isolate* isolate = args.GetIsolate(); - Environment* env = Environment::GetCurrent(isolate); - if (env != nullptr && env->abort_on_uncaught_exception()) { - Abort(); - } - Local exception = args[0]; - Local message = Exception::CreateMessage(isolate, exception); - FatalException(isolate, exception, message); -} - - static void OnMessage(Local message, Local 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 1f7b1405539e7b..c023920da41f74 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -6,9 +6,10 @@ #include #include #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 79943f1ad6a7a4..e878507731ef1c 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -847,47 +847,6 @@ void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script); } -void ContextifyScript::DecorateErrorStack( - Environment* env, const TryCatch& try_catch) { - Local exception = try_catch.Exception(); - - if (!exception->IsObject()) - return; - - Local err_obj = exception.As(); - - if (IsExceptionDecorated(env, err_obj)) - return; - - AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR); - Local stack = err_obj->Get(env->stack_string()); - MaybeLocal maybe_value = - err_obj->GetPrivate( - env->context(), - env->arrow_message_private_symbol()); - - Local arrow; - if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) { - return; - } - - if (stack.IsEmpty() || !stack->IsString()) { - return; - } - - Local decorated_stack = String::Concat( - env->isolate(), - String::Concat(env->isolate(), - arrow.As(), - FIXED_ONE_BYTE_STRING(env->isolate(), "\n")), - stack.As()); - 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 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 6d6dc12b4b8d5d..1221233bb8bfe5 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -119,8 +119,6 @@ class ContextifyScript : public BaseObject { const v8::FunctionCallbackInfo& args); static void RunInThisContext(const v8::FunctionCallbackInfo& args); static void RunInContext(const v8::FunctionCallbackInfo& 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 00000000000000..cc8cff0f0ed2b8 --- /dev/null +++ b/src/node_errors.cc @@ -0,0 +1,427 @@ +#include +#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 er) { + if (!er.IsEmpty() && er->IsObject()) { + Local err_obj = er.As(); + auto maybe_value = + err_obj->GetPrivate(env->context(), env->decorated_private_symbol()); + Local decorated; + return maybe_value.ToLocal(&decorated) && decorated->IsTrue(); + } + return false; +} + +void AppendExceptionLine(Environment* env, + Local er, + Local message, + enum ErrorHandlingMode mode) { + if (message.IsEmpty()) return; + + HandleScope scope(env->isolate()); + Local err_obj; + if (!er.IsEmpty() && er->IsObject()) { + err_obj = er.As(); + } + + // 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 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 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 er, + Local message) { + CHECK(!er.IsEmpty()); + HandleScope scope(env->isolate()); + + if (message.IsEmpty()) message = Exception::CreateMessage(env->isolate(), er); + + AppendExceptionLine(env, er, message, FATAL_ERROR); + + Local trace_value; + Local arrow; + const bool decorated = IsExceptionDecorated(env, er); + + if (er->IsUndefined() || er->IsNull()) { + trace_value = Undefined(env->isolate()); + } else { + Local 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 message; + Local name; + + if (er->IsObject()) { + Local err_obj = er.As(); + 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 : ""); + } 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 exception = try_catch.Exception(); + + if (!exception->IsObject()) return; + + Local err_obj = exception.As(); + + if (IsExceptionDecorated(env, err_obj)) return; + + AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR); + Local stack = err_obj->Get(env->stack_string()); + MaybeLocal maybe_value = + err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol()); + + Local arrow; + if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) { + return; + } + + if (stack.IsEmpty() || !stack->IsString()) { + return; + } + + Local decorated_stack = String::Concat( + env->isolate(), + String::Concat(env->isolate(), + arrow.As(), + FIXED_ONE_BYTE_STRING(env->isolate(), "\n")), + stack.As()); + 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 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 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 error, + Local message) { + HandleScope scope(isolate); + + Environment* env = Environment::GetCurrent(isolate); + CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. + Local process_object = env->process_object(); + Local fatal_exception_string = env->fatal_exception_string(); + Local 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 caught = fatal_exception_function.As()->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 exit_code = env->exit_code_string(); + Local code; + if (!process_object->Get(env->context(), exit_code).ToLocal(&code) || + !code->IsInt32()) { + exit(1); + } + exit(code.As()->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& args) { + Isolate* isolate = args.GetIsolate(); + Environment* env = Environment::GetCurrent(isolate); + if (env != nullptr && env->abort_on_uncaught_exception()) { + Abort(); + } + Local exception = args[0]; + Local message = Exception::CreateMessage(isolate, exception); + FatalException(isolate, exception, message); +} + +} // namespace node diff --git a/src/node_errors.h b/src/node_errors.h index a435695693ba18..2c52007c5110c6 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 er, + v8::Local 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 error, + v8::Local message); + +void FatalException(const v8::FunctionCallbackInfo& 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 b3070198529b19..b83c23681b3b94 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& args) { args.GetReturnValue().Set(err); } -void FatalException(v8::Isolate* isolate, - v8::Local error, - v8::Local 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 er); - -enum ErrorHandlingMode { CONTEXTIFY_ERROR, FATAL_ERROR, MODULE_ERROR }; -void AppendExceptionLine(Environment* env, - v8::Local er, - v8::Local 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 er, - v8::Local message); - v8::Maybe ProcessEmitWarning(Environment* env, const char* fmt, ...); v8::Maybe 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& args); void Chdir(const v8::FunctionCallbackInfo& args); diff --git a/src/node_url.cc b/src/node_url.cc index 0be777e03d1bad..a3f22f7c983e7d 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 #include diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc index 09b10d169371da..b8665dc1be225d 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 +#include "debug_utils.h" +#include "node_errors.h" +#include "node_internals.h" namespace node {