| #include <cerrno> |
| #include <cstdarg> |
| |
| #include "debug_utils-inl.h" |
| #include "node_errors.h" |
| #include "node_internals.h" |
| #include "node_report.h" |
| #include "node_process.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::Number; |
| 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 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()); |
| |
| if (sourceline.find("node-do-not-add-exception-line") != std::string::npos) { |
| *added_exception_line = false; |
| return sourceline; |
| } |
| |
| // 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.ResourceLineOffset()->Value()) == 1 |
| ? origin.ResourceColumnOffset()->Value() |
| : 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); |
| |
| 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'; |
| |
| *added_exception_line = true; |
| 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>(); |
| } |
| |
| 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); |
| |
| // 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()); |
| |
| 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->options()->trace_uncaught) { |
| Local<StackTrace> trace = message->GetStackTrace(); |
| if (!trace.IsEmpty()) { |
| FPrintF(stderr, "Thrown at:\n"); |
| PrintStackTrace(env->isolate(), trace); |
| } |
| } |
| |
| 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::GetCurrent(); |
| Environment* env = Environment::GetCurrent(isolate); |
| 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) { |
| report::TriggerNodeReport( |
| isolate, env, message, "FatalError", "", Local<String>()); |
| } |
| |
| fflush(stderr); |
| ABORT(); |
| } |
| |
| 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(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 ""; |
| } |
| } |
| |
| 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 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 Initialize(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| Environment* env = Environment::GetCurrent(context); |
| env->SetMethod( |
| target, "setPrepareStackTraceCallback", SetPrepareStackTraceCallback); |
| env->SetMethod(target, |
| "setEnhanceStackForFatalException", |
| SetEnhanceStackForFatalException); |
| env->SetMethodNoSideEffect( |
| target, "noSideEffectsToString", NoSideEffectsToString); |
| env->SetMethod(target, "triggerUncaughtException", TriggerUncaughtException); |
| } |
| |
| 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(6); |
| return; |
| } |
| |
| MaybeLocal<Value> 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) }; |
| |
| 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. |
| if (handled.IsEmpty()) { |
| 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.ToLocalChecked()->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(code.As<Int32>()->Value()); |
| } else { |
| env->Exit(1); |
| } |
| } |
| |
| 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) |