#include #include #include "node_errors.h" #include "node_internals.h" #ifdef NODE_REPORT #include "node_report.h" #endif namespace node { using errors::TryCatchScope; 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::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; } namespace per_process { static Mutex tty_mutex; } // namespace per_process 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(per_process::tty_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 AssertionInfo& info) { char name[1024]; GetHumanReadableProcessName(&name); fprintf(stderr, "%s: %s:%s%s Assertion `%s' failed.\n", name, info.file_line, info.function, *info.function ? ":" : "", info.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(); if (!err_obj->Get(env->context(), env->stack_string()) .ToLocal(&trace_value)) { trace_value = Undefined(env->isolate()); } 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. MaybeLocal message; MaybeLocal name; if (er->IsObject()) { Local err_obj = er.As(); 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. String::Utf8Value message(env->isolate(), er); PrintErrorString("%s\n", *message ? *message : ""); } else { node::Utf8Value name_string(env->isolate(), name.ToLocalChecked()); node::Utf8Value message_string(env->isolate(), message.ToLocalChecked()); 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 v8::TryCatch& try_catch) { ReportException(env, try_catch.Exception(), try_catch.Message()); } 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); } #ifdef NODE_REPORT Isolate* isolate = Isolate::GetCurrent(); HandleScope handle_scope(isolate); Environment* env = Environment::GetCurrent(isolate); if (env != nullptr) { std::shared_ptr options = env->isolate_data()->options(); if (options->report_on_fatalerror) { report::TriggerNodeReport( isolate, env, message, __func__, "", Local()); } } else { report::TriggerNodeReport( isolate, nullptr, message, __func__, "", Local()); } #endif // NODE_REPORT fflush(stderr); ABORT(); } namespace errors { TryCatchScope::~TryCatchScope() { if (HasCaught() && !HasTerminated() && mode_ == CatchMode::kFatal) { HandleScope scope(env_->isolate()); ReportException(env_, Exception(), Message()); env_->Exit(7); } } 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 ""; } } } // namespace errors void DecorateErrorStack(Environment* env, const errors::TryCatchScope& 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); TryCatchScope try_catch_scope(env); // Ignore exceptions below. MaybeLocal stack = err_obj->Get(env->context(), 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.ToLocalChecked()->IsString()) { return; } Local decorated_stack = String::Concat( env->isolate(), String::Concat(env->isolate(), arrow.As(), FIXED_ONE_BYTE_STRING(env->isolate(), "\n")), stack.ToLocalChecked().As()); USE(err_obj->Set(env->context(), env->stack_string(), decorated_stack)); err_obj->SetPrivate( env->context(), env->decorated_private_symbol(), True(env->isolate())); } 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(env->context(), fatal_exception_string).ToLocalChecked(); 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); env->Exit(6); } else { errors::TryCatchScope fatal_try_catch(env); // 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); env->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()) { env->Exit(1); } env->Exit(code.As()->Value()); } } } 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