%PDF- %PDF-
Direktori : /home2/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/ |
Current File : //home2/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/node_errors.cc |
#include <cerrno> #include <cstdarg> #include <sstream> #include "debug_utils-inl.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" #include "node_process-inl.h" #include "node_report.h" #include "node_v8_platform-inl.h" #include "util-inl.h" namespace node { using errors::TryCatchScope; using v8::Boolean; using v8::Context; using v8::EscapableHandleScope; using v8::Exception; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; using v8::Int32; using v8::Isolate; using v8::Just; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Message; using v8::Object; using v8::ScriptOrigin; using v8::StackFrame; using v8::StackTrace; using v8::String; using v8::Undefined; using v8::Value; bool IsExceptionDecorated(Environment* env, Local<Value> er) { if (!er.IsEmpty() && er->IsObject()) { Local<Object> err_obj = er.As<Object>(); auto maybe_value = err_obj->GetPrivate(env->context(), env->decorated_private_symbol()); Local<Value> decorated; return maybe_value.ToLocal(&decorated) && decorated->IsTrue(); } return false; } namespace per_process { static Mutex tty_mutex; } // namespace per_process static std::string GetSourceMapErrorSource(Isolate* isolate, Local<Context> context, Local<Message> message, bool* added_exception_line) { v8::TryCatch try_catch(isolate); HandleScope handle_scope(isolate); Environment* env = Environment::GetCurrent(context); // The ScriptResourceName of the message may be different from the one we use // to compile the script. V8 replaces it when it detects magic comments in // the source texts. Local<Value> script_resource_name = message->GetScriptResourceName(); int linenum = message->GetLineNumber(context).FromJust(); int columnum = message->GetStartColumn(context).FromJust(); Local<Value> argv[] = {script_resource_name, v8::Int32::New(isolate, linenum), v8::Int32::New(isolate, columnum)}; MaybeLocal<Value> maybe_ret = env->get_source_map_error_source()->Call( context, Undefined(isolate), arraysize(argv), argv); Local<Value> ret; if (!maybe_ret.ToLocal(&ret)) { // Ignore the caught exceptions. DCHECK(try_catch.HasCaught()); return std::string(); } if (!ret->IsString()) { return std::string(); } *added_exception_line = true; node::Utf8Value error_source_utf8(isolate, ret.As<String>()); return *error_source_utf8; } static std::string GetErrorSource(Isolate* isolate, Local<Context> context, Local<Message> message, bool* added_exception_line) { MaybeLocal<String> source_line_maybe = message->GetSourceLine(context); node::Utf8Value encoded_source(isolate, source_line_maybe.ToLocalChecked()); std::string sourceline(*encoded_source, encoded_source.length()); *added_exception_line = false; if (sourceline.find("node-do-not-add-exception-line") != std::string::npos) { return sourceline; } // If source maps have been enabled, the exception line will instead be // added in the JavaScript context: Environment* env = Environment::GetCurrent(isolate); const bool has_source_map_url = !message->GetScriptOrigin().SourceMapUrl().IsEmpty() && !message->GetScriptOrigin().SourceMapUrl()->IsUndefined(); if (has_source_map_url && env != nullptr && env->source_maps_enabled()) { std::string source = GetSourceMapErrorSource( isolate, context, message, added_exception_line); if (*added_exception_line) { return source; } } // Because of how node modules work, all scripts are wrapped with a // "function (module, exports, __filename, ...) {" // to provide script local variables. // // When reporting errors on the first line of a script, this wrapper // function is leaked to the user. There used to be a hack here to // truncate off the first 62 characters, but it caused numerous other // problems when vm.runIn*Context() methods were used for non-module // code. // // If we ever decide to re-instate such a hack, the following steps // must be taken: // // 1. Pass a flag around to say "this code was wrapped" // 2. Update the stack frame output so that it is also correct. // // It would probably be simpler to add a line rather than add some // number of characters to the first line, since V8 truncates the // sourceline to 78 characters, and we end up not providing very much // useful debugging info to the user if we remove 62 characters. // Print (filename):(line number): (message). ScriptOrigin origin = message->GetScriptOrigin(); node::Utf8Value filename(isolate, message->GetScriptResourceName()); const char* filename_string = *filename; int linenum = message->GetLineNumber(context).FromJust(); int script_start = (linenum - origin.LineOffset()) == 1 ? origin.ColumnOffset() : 0; int start = message->GetStartColumn(context).FromMaybe(0); int end = message->GetEndColumn(context).FromMaybe(0); if (start >= script_start) { CHECK_GE(end, start); start -= script_start; end -= script_start; } std::string buf = SPrintF("%s:%i\n%s\n", filename_string, linenum, sourceline.c_str()); CHECK_GT(buf.size(), 0); *added_exception_line = true; if (start > end || start < 0 || static_cast<size_t>(end) > sourceline.size()) { return buf; } constexpr int kUnderlineBufsize = 1020; char underline_buf[kUnderlineBufsize + 4]; int off = 0; // Print wavy underline (GetUnderline is deprecated). for (int i = 0; i < start; i++) { if (sourceline[i] == '\0' || off >= kUnderlineBufsize) { break; } CHECK_LT(off, kUnderlineBufsize); underline_buf[off++] = (sourceline[i] == '\t') ? '\t' : ' '; } for (int i = start; i < end; i++) { if (sourceline[i] == '\0' || off >= kUnderlineBufsize) { break; } CHECK_LT(off, kUnderlineBufsize); underline_buf[off++] = '^'; } CHECK_LE(off, kUnderlineBufsize); underline_buf[off++] = '\n'; return buf + std::string(underline_buf, off); } static std::atomic<bool> is_in_oom{false}; static std::atomic<bool> is_retrieving_js_stacktrace{false}; MaybeLocal<StackTrace> GetCurrentStackTrace(Isolate* isolate, int frame_count) { if (isolate == nullptr) { return MaybeLocal<StackTrace>(); } // Generating JavaScript stack trace can result in V8 fatal error, // which can re-enter this function. if (is_retrieving_js_stacktrace.load()) { return MaybeLocal<StackTrace>(); } // Can not capture the stacktrace when the isolate is in a OOM state or no // context is entered. if (is_in_oom.load() || !isolate->InContext()) { return MaybeLocal<StackTrace>(); } constexpr StackTrace::StackTraceOptions options = static_cast<StackTrace::StackTraceOptions>( StackTrace::kDetailed | StackTrace::kExposeFramesAcrossSecurityOrigins); is_retrieving_js_stacktrace.store(true); EscapableHandleScope scope(isolate); Local<StackTrace> stack = StackTrace::CurrentStackTrace(isolate, frame_count, options); is_retrieving_js_stacktrace.store(false); if (stack->GetFrameCount() == 0) { return MaybeLocal<StackTrace>(); } return scope.Escape(stack); } static std::string FormatStackTrace( Isolate* isolate, Local<StackTrace> stack, StackTracePrefix prefix = StackTracePrefix::kAt) { std::string result; 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(); std::string prefix_str = prefix == StackTracePrefix::kAt ? " at " : std::to_string(i + 1) + ": "; if (stack_frame->IsEval()) { if (stack_frame->GetScriptId() == Message::kNoScriptIdInfo) { result += SPrintF("%s[eval]:%i:%i\n", prefix_str, line_number, column); } else { std::vector<char> buf(script_name.length() + 64); snprintf(buf.data(), buf.size(), "%s[eval] (%s:%i:%i)\n", prefix_str.c_str(), *script_name, line_number, column); result += std::string(buf.data()); } break; } if (fn_name_s.length() == 0) { std::vector<char> buf(script_name.length() + 64); snprintf(buf.data(), buf.size(), "%s%s:%i:%i\n", prefix_str.c_str(), *script_name, line_number, column); result += std::string(buf.data()); } else { std::vector<char> buf(fn_name_s.length() + script_name.length() + 64); snprintf(buf.data(), buf.size(), "%s%s (%s:%i:%i)\n", prefix_str.c_str(), *fn_name_s, *script_name, line_number, column); result += std::string(buf.data()); } } return result; } static void PrintToStderrAndFlush(const std::string& str) { FPrintF(stderr, "%s\n", str); fflush(stderr); } void PrintStackTrace(Isolate* isolate, Local<StackTrace> stack, StackTracePrefix prefix) { PrintToStderrAndFlush(FormatStackTrace(isolate, stack, prefix)); } void PrintCurrentStackTrace(Isolate* isolate, StackTracePrefix prefix) { Local<StackTrace> stack; if (GetCurrentStackTrace(isolate).ToLocal(&stack)) { PrintStackTrace(isolate, stack, prefix); } } std::string FormatCaughtException(Isolate* isolate, Local<Context> context, Local<Value> err, Local<Message> message, bool add_source_line = true) { std::string result; node::Utf8Value reason(isolate, err->ToDetailString(context) .FromMaybe(Local<String>())); if (add_source_line) { bool added_exception_line = false; std::string source = GetErrorSource(isolate, context, message, &added_exception_line); result = source + '\n'; } result += reason.ToString() + '\n'; Local<v8::StackTrace> stack = message->GetStackTrace(); if (!stack.IsEmpty()) result += FormatStackTrace(isolate, stack); return result; } std::string FormatCaughtException(Isolate* isolate, Local<Context> context, const v8::TryCatch& try_catch) { CHECK(try_catch.HasCaught()); return FormatCaughtException( isolate, context, try_catch.Exception(), try_catch.Message()); } void PrintCaughtException(Isolate* isolate, Local<Context> context, const v8::TryCatch& try_catch) { PrintToStderrAndFlush(FormatCaughtException(isolate, context, try_catch)); } void AppendExceptionLine(Environment* env, Local<Value> er, Local<Message> message, enum ErrorHandlingMode mode) { if (message.IsEmpty()) return; HandleScope scope(env->isolate()); Local<Object> err_obj; if (!er.IsEmpty() && er->IsObject()) { err_obj = er.As<Object>(); // If arrow_message is already set, skip. auto maybe_value = err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol()); Local<Value> lvalue; if (!maybe_value.ToLocal(&lvalue) || lvalue->IsString()) return; } bool added_exception_line = false; std::string source = GetErrorSource( env->isolate(), env->context(), message, &added_exception_line); if (!added_exception_line) { return; } MaybeLocal<Value> arrow_str = ToV8Value(env->context(), source); const bool can_set_arrow = !arrow_str.IsEmpty() && !err_obj.IsEmpty(); // If allocating arrow_str failed, print it out. There's not much else to do. // If it's not an error, but something needs to be printed out because // it's a fatal exception, also print it out from here. // Otherwise, the arrow property will be attached to the object and handled // by the caller. if (!can_set_arrow || (mode == FATAL_ERROR && !err_obj->IsNativeError())) { if (env->printed_error()) return; Mutex::ScopedLock lock(per_process::tty_mutex); env->set_printed_error(true); ResetStdio(); FPrintF(stderr, "\n%s", source); return; } CHECK(err_obj ->SetPrivate(env->context(), env->arrow_message_private_symbol(), arrow_str.ToLocalChecked()) .FromMaybe(false)); } void Assert(const AssertionInfo& info) { std::string name = GetHumanReadableProcessName(); fprintf(stderr, "\n" " # %s: %s at %s\n" " # Assertion failed: %s\n\n", name.c_str(), info.function ? info.function : "(unknown function)", info.file_line ? info.file_line : "(unknown source location)", info.message); fflush(stderr); ABORT(); } enum class EnhanceFatalException { kEnhance, kDontEnhance }; /** * Report the exception to the inspector, then print it to stderr. * This should only be used when the Node.js instance is about to exit * (i.e. this should be followed by a env->Exit() or an ABORT()). * * Use enhance_stack = EnhanceFatalException::kDontEnhance * when it's unsafe to call into JavaScript. */ static void ReportFatalException(Environment* env, Local<Value> error, Local<Message> message, EnhanceFatalException enhance_stack) { if (!env->can_call_into_js()) enhance_stack = EnhanceFatalException::kDontEnhance; Isolate* isolate = env->isolate(); CHECK(!error.IsEmpty()); CHECK(!message.IsEmpty()); HandleScope scope(isolate); AppendExceptionLine(env, error, message, FATAL_ERROR); auto report_to_inspector = [&]() { #if HAVE_INSPECTOR env->inspector_agent()->ReportUncaughtException(error, message); #endif }; Local<Value> arrow; Local<Value> stack_trace; bool decorated = IsExceptionDecorated(env, error); if (!error->IsObject()) { // We can only enhance actual errors. report_to_inspector(); stack_trace = Undefined(isolate); // If error is not an object, AppendExceptionLine() has already print the // source line and the arrow to stderr. // TODO(joyeecheung): move that side effect out of AppendExceptionLine(). // It is done just to preserve the source line as soon as possible. } else { Local<Object> err_obj = error.As<Object>(); auto enhance_with = [&](Local<Function> enhancer) { Local<Value> enhanced; Local<Value> argv[] = {err_obj}; if (!enhancer.IsEmpty() && enhancer ->Call(env->context(), Undefined(isolate), arraysize(argv), argv) .ToLocal(&enhanced)) { stack_trace = enhanced; } }; switch (enhance_stack) { case EnhanceFatalException::kEnhance: { enhance_with(env->enhance_fatal_stack_before_inspector()); report_to_inspector(); enhance_with(env->enhance_fatal_stack_after_inspector()); break; } case EnhanceFatalException::kDontEnhance: { USE(err_obj->Get(env->context(), env->stack_string()) .ToLocal(&stack_trace)); report_to_inspector(); break; } default: UNREACHABLE(); } arrow = err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol()) .ToLocalChecked(); } node::Utf8Value trace(env->isolate(), stack_trace); std::string report_message = "Exception"; // range errors have a trace member set to undefined if (trace.length() > 0 && !stack_trace->IsUndefined()) { if (arrow.IsEmpty() || !arrow->IsString() || decorated) { FPrintF(stderr, "%s\n", trace); } else { node::Utf8Value arrow_string(env->isolate(), arrow); FPrintF(stderr, "%s\n%s\n", arrow_string, trace); } } else { // this really only happens for RangeErrors, since they're the only // kind that won't have all this info in the trace, or when non-Error // objects are thrown manually. MaybeLocal<Value> message; MaybeLocal<Value> name; if (error->IsObject()) { Local<Object> err_obj = error.As<Object>(); message = err_obj->Get(env->context(), env->message_string()); name = err_obj->Get(env->context(), env->name_string()); } if (message.IsEmpty() || message.ToLocalChecked()->IsUndefined() || name.IsEmpty() || name.ToLocalChecked()->IsUndefined()) { // Not an error object. Just print as-is. node::Utf8Value message(env->isolate(), error); FPrintF( stderr, "%s\n", *message ? message.ToStringView() : "<toString() threw exception>"); } else { node::Utf8Value name_string(env->isolate(), name.ToLocalChecked()); node::Utf8Value message_string(env->isolate(), message.ToLocalChecked()); // Update the report message if it is an object has message property. report_message = message_string.ToString(); if (arrow.IsEmpty() || !arrow->IsString() || decorated) { FPrintF(stderr, "%s: %s\n", name_string, message_string); } else { node::Utf8Value arrow_string(env->isolate(), arrow); FPrintF(stderr, "%s\n%s: %s\n", arrow_string, name_string, message_string); } } if (!env->options()->trace_uncaught) { std::string argv0; if (!env->argv().empty()) argv0 = env->argv()[0]; if (argv0.empty()) argv0 = "node"; FPrintF(stderr, "(Use `%s --trace-uncaught ...` to show where the exception " "was thrown)\n", fs::Basename(argv0, ".exe")); } } if (env->isolate_data()->options()->report_uncaught_exception) { TriggerNodeReport(env, report_message.c_str(), "Exception", "", error); } if (env->options()->trace_uncaught) { Local<StackTrace> trace = message->GetStackTrace(); if (!trace.IsEmpty()) { FPrintF(stderr, "Thrown at:\n"); PrintStackTrace(env->isolate(), trace); } } if (env->options()->extra_info_on_fatal_exception) { FPrintF(stderr, "\nNode.js %s\n", NODE_VERSION); } fflush(stderr); } [[noreturn]] void OnFatalError(const char* location, const char* message) { if (location) { FPrintF(stderr, "FATAL ERROR: %s %s\n", location, message); } else { FPrintF(stderr, "FATAL ERROR: %s\n", message); } Isolate* isolate = Isolate::TryGetCurrent(); bool report_on_fatalerror; { Mutex::ScopedLock lock(node::per_process::cli_options_mutex); report_on_fatalerror = per_process::cli_options->report_on_fatalerror; } if (report_on_fatalerror) { TriggerNodeReport(isolate, message, "FatalError", "", Local<Object>()); } fflush(stderr); ABORT(); } void OOMErrorHandler(const char* location, const v8::OOMDetails& details) { // We should never recover from this handler so once it's true it's always // true. is_in_oom.store(true); const char* message = details.is_heap_oom ? "Allocation failed - JavaScript heap out of memory" : "Allocation failed - process out of memory"; if (location) { FPrintF(stderr, "FATAL ERROR: %s %s\n", location, message); } else { FPrintF(stderr, "FATAL ERROR: %s\n", message); } Isolate* isolate = Isolate::TryGetCurrent(); bool report_on_fatalerror; { Mutex::ScopedLock lock(node::per_process::cli_options_mutex); report_on_fatalerror = per_process::cli_options->report_on_fatalerror; } if (report_on_fatalerror) { // Trigger report with the isolate. Environment::GetCurrent may return // nullptr here: // - If the OOM is reported by a young generation space allocation, // Isolate::GetCurrentContext returns an empty handle. // - Otherwise, Isolate::GetCurrentContext returns a non-empty handle. TriggerNodeReport(isolate, message, "OOMError", "", Local<Object>()); } fflush(stderr); ABORT(); } v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings( v8::Local<v8::Context> context, v8::Local<v8::Value> source, bool is_code_like) { HandleScope scope(context->GetIsolate()); if (context->GetNumberOfEmbedderDataFields() <= ContextEmbedderIndex::kAllowCodeGenerationFromStrings) { // The context is not (yet) configured by Node.js for this. We don't // have enough information to make a decision, just allow it which is // the default. return {true, {}}; } Environment* env = Environment::GetCurrent(context); if (env == nullptr) { return {true, {}}; } if (env->source_maps_enabled() && env->can_call_into_js()) { // We do not expect the maybe_cache_generated_source_map to throw any more // exceptions. If it does, just ignore it. errors::TryCatchScope try_catch(env); Local<Function> maybe_cache_source_map = env->maybe_cache_generated_source_map(); Local<Value> argv[1] = {source}; MaybeLocal<Value> maybe_cached = maybe_cache_source_map->Call( context, context->Global(), arraysize(argv), argv); if (maybe_cached.IsEmpty()) { DCHECK(try_catch.HasCaught()); } } Local<Value> allow_code_gen = context->GetEmbedderData( ContextEmbedderIndex::kAllowCodeGenerationFromStrings); bool codegen_allowed = allow_code_gen->IsUndefined() || allow_code_gen->IsTrue(); return { codegen_allowed, {}, }; } namespace errors { TryCatchScope::~TryCatchScope() { if (HasCaught() && !HasTerminated() && mode_ == CatchMode::kFatal) { HandleScope scope(env_->isolate()); Local<v8::Value> exception = Exception(); Local<v8::Message> message = Message(); EnhanceFatalException enhance = CanContinue() ? EnhanceFatalException::kEnhance : EnhanceFatalException::kDontEnhance; if (message.IsEmpty()) message = Exception::CreateMessage(env_->isolate(), exception); ReportFatalException(env_, exception, message, enhance); env_->Exit(ExitCode::kExceptionInFatalExceptionHandler); } } const char* errno_string(int errorno) { #define ERRNO_CASE(e) \ case e: \ return #e; switch (errorno) { #ifdef EACCES ERRNO_CASE(EACCES); #endif #ifdef EADDRINUSE ERRNO_CASE(EADDRINUSE); #endif #ifdef EADDRNOTAVAIL ERRNO_CASE(EADDRNOTAVAIL); #endif #ifdef EAFNOSUPPORT ERRNO_CASE(EAFNOSUPPORT); #endif #ifdef EAGAIN ERRNO_CASE(EAGAIN); #endif #ifdef EWOULDBLOCK #if EAGAIN != EWOULDBLOCK ERRNO_CASE(EWOULDBLOCK); #endif #endif #ifdef EALREADY ERRNO_CASE(EALREADY); #endif #ifdef EBADF ERRNO_CASE(EBADF); #endif #ifdef EBADMSG ERRNO_CASE(EBADMSG); #endif #ifdef EBUSY ERRNO_CASE(EBUSY); #endif #ifdef ECANCELED ERRNO_CASE(ECANCELED); #endif #ifdef ECHILD ERRNO_CASE(ECHILD); #endif #ifdef ECONNABORTED ERRNO_CASE(ECONNABORTED); #endif #ifdef ECONNREFUSED ERRNO_CASE(ECONNREFUSED); #endif #ifdef ECONNRESET ERRNO_CASE(ECONNRESET); #endif #ifdef EDEADLK ERRNO_CASE(EDEADLK); #endif #ifdef EDESTADDRREQ ERRNO_CASE(EDESTADDRREQ); #endif #ifdef EDOM ERRNO_CASE(EDOM); #endif #ifdef EDQUOT ERRNO_CASE(EDQUOT); #endif #ifdef EEXIST ERRNO_CASE(EEXIST); #endif #ifdef EFAULT ERRNO_CASE(EFAULT); #endif #ifdef EFBIG ERRNO_CASE(EFBIG); #endif #ifdef EHOSTUNREACH ERRNO_CASE(EHOSTUNREACH); #endif #ifdef EIDRM ERRNO_CASE(EIDRM); #endif #ifdef EILSEQ ERRNO_CASE(EILSEQ); #endif #ifdef EINPROGRESS ERRNO_CASE(EINPROGRESS); #endif #ifdef EINTR ERRNO_CASE(EINTR); #endif #ifdef EINVAL ERRNO_CASE(EINVAL); #endif #ifdef EIO ERRNO_CASE(EIO); #endif #ifdef EISCONN ERRNO_CASE(EISCONN); #endif #ifdef EISDIR ERRNO_CASE(EISDIR); #endif #ifdef ELOOP ERRNO_CASE(ELOOP); #endif #ifdef EMFILE ERRNO_CASE(EMFILE); #endif #ifdef EMLINK ERRNO_CASE(EMLINK); #endif #ifdef EMSGSIZE ERRNO_CASE(EMSGSIZE); #endif #ifdef EMULTIHOP ERRNO_CASE(EMULTIHOP); #endif #ifdef ENAMETOOLONG ERRNO_CASE(ENAMETOOLONG); #endif #ifdef ENETDOWN ERRNO_CASE(ENETDOWN); #endif #ifdef ENETRESET ERRNO_CASE(ENETRESET); #endif #ifdef ENETUNREACH ERRNO_CASE(ENETUNREACH); #endif #ifdef ENFILE ERRNO_CASE(ENFILE); #endif #ifdef ENOBUFS ERRNO_CASE(ENOBUFS); #endif #ifdef ENODATA ERRNO_CASE(ENODATA); #endif #ifdef ENODEV ERRNO_CASE(ENODEV); #endif #ifdef ENOENT ERRNO_CASE(ENOENT); #endif #ifdef ENOEXEC ERRNO_CASE(ENOEXEC); #endif #ifdef ENOLINK ERRNO_CASE(ENOLINK); #endif #ifdef ENOLCK #if ENOLINK != ENOLCK ERRNO_CASE(ENOLCK); #endif #endif #ifdef ENOMEM ERRNO_CASE(ENOMEM); #endif #ifdef ENOMSG ERRNO_CASE(ENOMSG); #endif #ifdef ENOPROTOOPT ERRNO_CASE(ENOPROTOOPT); #endif #ifdef ENOSPC ERRNO_CASE(ENOSPC); #endif #ifdef ENOSR ERRNO_CASE(ENOSR); #endif #ifdef ENOSTR ERRNO_CASE(ENOSTR); #endif #ifdef ENOSYS ERRNO_CASE(ENOSYS); #endif #ifdef ENOTCONN ERRNO_CASE(ENOTCONN); #endif #ifdef ENOTDIR ERRNO_CASE(ENOTDIR); #endif #ifdef ENOTEMPTY #if ENOTEMPTY != EEXIST ERRNO_CASE(ENOTEMPTY); #endif #endif #ifdef ENOTSOCK ERRNO_CASE(ENOTSOCK); #endif #ifdef ENOTSUP ERRNO_CASE(ENOTSUP); #else #ifdef EOPNOTSUPP ERRNO_CASE(EOPNOTSUPP); #endif #endif #ifdef ENOTTY ERRNO_CASE(ENOTTY); #endif #ifdef ENXIO ERRNO_CASE(ENXIO); #endif #ifdef EOVERFLOW ERRNO_CASE(EOVERFLOW); #endif #ifdef EPERM ERRNO_CASE(EPERM); #endif #ifdef EPIPE ERRNO_CASE(EPIPE); #endif #ifdef EPROTO ERRNO_CASE(EPROTO); #endif #ifdef EPROTONOSUPPORT ERRNO_CASE(EPROTONOSUPPORT); #endif #ifdef EPROTOTYPE ERRNO_CASE(EPROTOTYPE); #endif #ifdef ERANGE ERRNO_CASE(ERANGE); #endif #ifdef EROFS ERRNO_CASE(EROFS); #endif #ifdef ESPIPE ERRNO_CASE(ESPIPE); #endif #ifdef ESRCH ERRNO_CASE(ESRCH); #endif #ifdef ESTALE ERRNO_CASE(ESTALE); #endif #ifdef ETIME ERRNO_CASE(ETIME); #endif #ifdef ETIMEDOUT ERRNO_CASE(ETIMEDOUT); #endif #ifdef ETXTBSY ERRNO_CASE(ETXTBSY); #endif #ifdef EXDEV ERRNO_CASE(EXDEV); #endif default: return ""; } } void PerIsolateMessageListener(Local<Message> message, Local<Value> error) { Isolate* isolate = message->GetIsolate(); switch (message->ErrorLevel()) { case Isolate::MessageErrorLevel::kMessageWarning: { Environment* env = Environment::GetCurrent(isolate); if (!env) { break; } Utf8Value filename(isolate, message->GetScriptOrigin().ResourceName()); // (filename):(line) (message) std::stringstream warning; warning << *filename; warning << ":"; warning << message->GetLineNumber(env->context()).FromMaybe(-1); warning << " "; v8::String::Utf8Value msg(isolate, message->Get()); warning << *msg; USE(ProcessEmitWarningGeneric(env, warning.str().c_str(), "V8")); break; } case Isolate::MessageErrorLevel::kMessageError: TriggerUncaughtException(isolate, error, message); break; } } void SetPrepareStackTraceCallback(const FunctionCallbackInfo<Value>& args) { Realm* realm = Realm::GetCurrent(args); CHECK(args[0]->IsFunction()); realm->set_prepare_stack_trace_callback(args[0].As<Function>()); } static void SetSourceMapsEnabled(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsBoolean()); env->set_source_maps_enabled(args[0].As<Boolean>()->Value()); } static void SetGetSourceMapErrorSource( const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsFunction()); env->set_get_source_map_error_source(args[0].As<Function>()); } static void SetMaybeCacheGeneratedSourceMap( const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsFunction()); env->set_maybe_cache_generated_source_map(args[0].As<Function>()); } static void SetEnhanceStackForFatalException( const FunctionCallbackInfo<Value>& args) { Realm* realm = Realm::GetCurrent(args); CHECK(args[0]->IsFunction()); CHECK(args[1]->IsFunction()); realm->set_enhance_fatal_stack_before_inspector(args[0].As<Function>()); realm->set_enhance_fatal_stack_after_inspector(args[1].As<Function>()); } // Side effect-free stringification that will never throw exceptions. static void NoSideEffectsToString(const FunctionCallbackInfo<Value>& args) { Local<Context> context = args.GetIsolate()->GetCurrentContext(); Local<String> detail_string; if (args[0]->ToDetailString(context).ToLocal(&detail_string)) args.GetReturnValue().Set(detail_string); } static void TriggerUncaughtException(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); Environment* env = Environment::GetCurrent(isolate); Local<Value> exception = args[0]; Local<Message> message = Exception::CreateMessage(isolate, exception); if (env != nullptr && env->abort_on_uncaught_exception()) { ReportFatalException( env, exception, message, EnhanceFatalException::kEnhance); ABORT(); } bool from_promise = args[1]->IsTrue(); errors::TriggerUncaughtException(isolate, exception, message, from_promise); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(SetPrepareStackTraceCallback); registry->Register(SetGetSourceMapErrorSource); registry->Register(SetSourceMapsEnabled); registry->Register(SetMaybeCacheGeneratedSourceMap); registry->Register(SetEnhanceStackForFatalException); registry->Register(NoSideEffectsToString); registry->Register(TriggerUncaughtException); } void Initialize(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) { SetMethod(context, target, "setPrepareStackTraceCallback", SetPrepareStackTraceCallback); SetMethod(context, target, "setGetSourceMapErrorSource", SetGetSourceMapErrorSource); SetMethod(context, target, "setSourceMapsEnabled", SetSourceMapsEnabled); SetMethod(context, target, "setMaybeCacheGeneratedSourceMap", SetMaybeCacheGeneratedSourceMap); SetMethod(context, target, "setEnhanceStackForFatalException", SetEnhanceStackForFatalException); SetMethodNoSideEffect( context, target, "noSideEffectsToString", NoSideEffectsToString); SetMethod( context, target, "triggerUncaughtException", TriggerUncaughtException); Isolate* isolate = context->GetIsolate(); Local<Object> exit_codes = Object::New(isolate); READONLY_PROPERTY(target, "exitCodes", exit_codes); #define V(Name, Code) \ constexpr int k##Name = static_cast<int>(ExitCode::k##Name); \ NODE_DEFINE_CONSTANT(exit_codes, k##Name); EXIT_CODE_LIST(V) #undef V } void DecorateErrorStack(Environment* env, const errors::TryCatchScope& try_catch) { Local<Value> exception = try_catch.Exception(); if (!exception->IsObject()) return; Local<Object> err_obj = exception.As<Object>(); if (IsExceptionDecorated(env, err_obj)) return; AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR); TryCatchScope try_catch_scope(env); // Ignore exceptions below. MaybeLocal<Value> stack = err_obj->Get(env->context(), env->stack_string()); MaybeLocal<Value> maybe_value = err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol()); Local<Value> arrow; if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) { return; } if (stack.IsEmpty() || !stack.ToLocalChecked()->IsString()) { return; } Local<String> decorated_stack = String::Concat( env->isolate(), String::Concat(env->isolate(), arrow.As<String>(), FIXED_ONE_BYTE_STRING(env->isolate(), "\n")), stack.ToLocalChecked().As<String>()); USE(err_obj->Set(env->context(), env->stack_string(), decorated_stack)); err_obj->SetPrivate( env->context(), env->decorated_private_symbol(), True(env->isolate())); } void TriggerUncaughtException(Isolate* isolate, Local<Value> error, Local<Message> message, bool from_promise) { CHECK(!error.IsEmpty()); HandleScope scope(isolate); if (message.IsEmpty()) message = Exception::CreateMessage(isolate, error); CHECK(isolate->InContext()); Local<Context> context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (env == nullptr) { // This means that the exception happens before Environment is assigned // to the context e.g. when there is a SyntaxError in a per-context // script - which usually indicates that there is a bug because no JS // error is supposed to be thrown at this point. // Since we don't have access to Environment here, there is not // much we can do, so we just print whatever is useful and crash. PrintToStderrAndFlush( FormatCaughtException(isolate, context, error, message)); ABORT(); } // Invoke process._fatalException() to give user a chance to handle it. // We have to grab it from the process object since this has been // monkey-patchable. Local<Object> process_object = env->process_object(); Local<String> fatal_exception_string = env->fatal_exception_string(); Local<Value> fatal_exception_function = process_object->Get(env->context(), fatal_exception_string).ToLocalChecked(); // If the exception happens before process._fatalException is attached // during bootstrap, or if the user has patched it incorrectly, exit // the current Node.js instance. if (!fatal_exception_function->IsFunction()) { ReportFatalException( env, error, message, EnhanceFatalException::kDontEnhance); env->Exit(ExitCode::kInvalidFatalExceptionMonkeyPatching); return; } MaybeLocal<Value> maybe_handled; if (env->can_call_into_js()) { // We do not expect the global uncaught exception itself to throw any more // exceptions. If it does, exit the current Node.js instance. errors::TryCatchScope try_catch(env, errors::TryCatchScope::CatchMode::kFatal); // Explicitly disable verbose exception reporting - // if process._fatalException() throws an error, we don't want it to // trigger the per-isolate message listener which will call this // function and recurse. try_catch.SetVerbose(false); Local<Value> argv[2] = { error, Boolean::New(env->isolate(), from_promise) }; maybe_handled = fatal_exception_function.As<Function>()->Call( env->context(), process_object, arraysize(argv), argv); } // If process._fatalException() throws, we are now exiting the Node.js // instance so return to continue the exit routine. // TODO(joyeecheung): return a Maybe here to prevent the caller from // stepping on the exit. Local<Value> handled; if (!maybe_handled.ToLocal(&handled)) { return; } // The global uncaught exception handler returns true if the user handles it // by e.g. listening to `uncaughtException`. In that case, continue program // execution. // TODO(joyeecheung): This has been only checking that the return value is // exactly false. Investigate whether this can be turned to an "if true" // similar to how the worker global uncaught exception handler handles it. if (!handled->IsFalse()) { return; } // Now we are certain that the exception is fatal. ReportFatalException(env, error, message, EnhanceFatalException::kEnhance); RunAtExit(env); // If the global uncaught exception handler sets process.exitCode, // exit with that code. Otherwise, exit with `ExitCode::kGenericUserError`. env->Exit(env->exit_code(ExitCode::kGenericUserError)); } void TriggerUncaughtException(Isolate* isolate, const v8::TryCatch& try_catch) { // If the try_catch is verbose, the per-isolate message listener is going to // handle it (which is going to call into another overload of // TriggerUncaughtException()). if (try_catch.IsVerbose()) { return; } // If the user calls TryCatch::TerminateExecution() on this TryCatch // they must call CancelTerminateExecution() again before invoking // TriggerUncaughtException() because it will invoke // process._fatalException() in the JS land. CHECK(!try_catch.HasTerminated()); CHECK(try_catch.HasCaught()); HandleScope scope(isolate); TriggerUncaughtException(isolate, try_catch.Exception(), try_catch.Message(), false /* from_promise */); } PrinterTryCatch::~PrinterTryCatch() { if (!HasCaught()) { return; } std::string str = FormatCaughtException(isolate_, isolate_->GetCurrentContext(), Exception(), Message(), print_source_line_ == kPrintSourceLine); PrintToStderrAndFlush(str); } } // namespace errors } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(errors, node::errors::Initialize) NODE_BINDING_EXTERNAL_REFERENCE(errors, node::errors::RegisterExternalReferences)