diff options
author | Joyee Cheung <joyeec9h3@gmail.com> | 2018-11-03 22:45:40 +0800 |
---|---|---|
committer | Joyee Cheung <joyeec9h3@gmail.com> | 2018-11-06 20:58:40 +0800 |
commit | 5850220229cdd8b62f6d5022779af7c232231d64 (patch) | |
tree | 7dbb9c7d94be088bebf535ec41ed4a8096af847e /src/node_errors.cc | |
parent | 7b1297d856dbd9af85c18478301b234caebf04e4 (diff) | |
download | android-node-v8-5850220229cdd8b62f6d5022779af7c232231d64.tar.gz android-node-v8-5850220229cdd8b62f6d5022779af7c232231d64.tar.bz2 android-node-v8-5850220229cdd8b62f6d5022779af7c232231d64.zip |
src: move error handling code into node_errors.cc
Move the following code into a new node_errors.cc file and
declare them in node_errors.h for clarity and make it possible
to include them with node_errors.h.
- AppendExceptionLine()
- DecorateErrorStack()
- FatalError()
- OnFatalError()
- PrintErrorString()
- FatalException()
- ReportException()
- FatalTryCatch
And move the following definitions (declared elsewhere than
node_errors.h) to node_errors.cc:
- Abort() (in util.h)
- Assert() (in util.h)
PR-URL: https://github.com/nodejs/node/pull/24058
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Diffstat (limited to 'src/node_errors.cc')
-rw-r--r-- | src/node_errors.cc | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/src/node_errors.cc b/src/node_errors.cc new file mode 100644 index 0000000000..cc8cff0f0e --- /dev/null +++ b/src/node_errors.cc @@ -0,0 +1,427 @@ +#include <stdarg.h> +#include "node_errors.h" +#include "node_internals.h" + +namespace node { +using v8::Context; +using v8::Exception; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Int32; +using v8::Isolate; +using v8::Just; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Message; +using v8::NewStringType; +using v8::Number; +using v8::Object; +using v8::ScriptOrigin; +using v8::String; +using v8::TryCatch; +using v8::Undefined; +using v8::Value; + +bool IsExceptionDecorated(Environment* env, Local<Value> er) { + if (!er.IsEmpty() && er->IsObject()) { + Local<Object> err_obj = er.As<Object>(); + auto maybe_value = + err_obj->GetPrivate(env->context(), env->decorated_private_symbol()); + Local<Value> decorated; + return maybe_value.ToLocal(&decorated) && decorated->IsTrue(); + } + return false; +} + +void AppendExceptionLine(Environment* env, + Local<Value> er, + Local<Message> message, + enum ErrorHandlingMode mode) { + if (message.IsEmpty()) return; + + HandleScope scope(env->isolate()); + Local<Object> err_obj; + if (!er.IsEmpty() && er->IsObject()) { + err_obj = er.As<Object>(); + } + + // Print (filename):(line number): (message). + ScriptOrigin origin = message->GetScriptOrigin(); + node::Utf8Value filename(env->isolate(), message->GetScriptResourceName()); + const char* filename_string = *filename; + int linenum = message->GetLineNumber(env->context()).FromJust(); + // Print line of source code. + MaybeLocal<String> source_line_maybe = message->GetSourceLine(env->context()); + node::Utf8Value sourceline(env->isolate(), + source_line_maybe.ToLocalChecked()); + const char* sourceline_string = *sourceline; + if (strstr(sourceline_string, "node-do-not-add-exception-line") != nullptr) + return; + + // Because of how node modules work, all scripts are wrapped with a + // "function (module, exports, __filename, ...) {" + // to provide script local variables. + // + // When reporting errors on the first line of a script, this wrapper + // function is leaked to the user. There used to be a hack here to + // truncate off the first 62 characters, but it caused numerous other + // problems when vm.runIn*Context() methods were used for non-module + // code. + // + // If we ever decide to re-instate such a hack, the following steps + // must be taken: + // + // 1. Pass a flag around to say "this code was wrapped" + // 2. Update the stack frame output so that it is also correct. + // + // It would probably be simpler to add a line rather than add some + // number of characters to the first line, since V8 truncates the + // sourceline to 78 characters, and we end up not providing very much + // useful debugging info to the user if we remove 62 characters. + + int script_start = (linenum - origin.ResourceLineOffset()->Value()) == 1 + ? origin.ResourceColumnOffset()->Value() + : 0; + int start = message->GetStartColumn(env->context()).FromMaybe(0); + int end = message->GetEndColumn(env->context()).FromMaybe(0); + if (start >= script_start) { + CHECK_GE(end, start); + start -= script_start; + end -= script_start; + } + + char arrow[1024]; + int max_off = sizeof(arrow) - 2; + + int off = snprintf(arrow, + sizeof(arrow), + "%s:%i\n%s\n", + filename_string, + linenum, + sourceline_string); + CHECK_GE(off, 0); + if (off > max_off) { + off = max_off; + } + + // Print wavy underline (GetUnderline is deprecated). + for (int i = 0; i < start; i++) { + if (sourceline_string[i] == '\0' || off >= max_off) { + break; + } + CHECK_LT(off, max_off); + arrow[off++] = (sourceline_string[i] == '\t') ? '\t' : ' '; + } + for (int i = start; i < end; i++) { + if (sourceline_string[i] == '\0' || off >= max_off) { + break; + } + CHECK_LT(off, max_off); + arrow[off++] = '^'; + } + CHECK_LE(off, max_off); + arrow[off] = '\n'; + arrow[off + 1] = '\0'; + + Local<String> arrow_str = + String::NewFromUtf8(env->isolate(), arrow, NewStringType::kNormal) + .ToLocalChecked(); + + const bool can_set_arrow = !arrow_str.IsEmpty() && !err_obj.IsEmpty(); + // If allocating arrow_str failed, print it out. There's not much else to do. + // If it's not an error, but something needs to be printed out because + // it's a fatal exception, also print it out from here. + // Otherwise, the arrow property will be attached to the object and handled + // by the caller. + if (!can_set_arrow || (mode == FATAL_ERROR && !err_obj->IsNativeError())) { + if (env->printed_error()) return; + Mutex::ScopedLock lock(process_mutex); + env->set_printed_error(true); + + uv_tty_reset_mode(); + PrintErrorString("\n%s", arrow); + return; + } + + CHECK(err_obj + ->SetPrivate( + env->context(), env->arrow_message_private_symbol(), arrow_str) + .FromMaybe(false)); +} + +[[noreturn]] void Abort() { + DumpBacktrace(stderr); + fflush(stderr); + ABORT_NO_BACKTRACE(); +} + +[[noreturn]] void Assert(const char* const (*args)[4]) { + auto filename = (*args)[0]; + auto linenum = (*args)[1]; + auto message = (*args)[2]; + auto function = (*args)[3]; + + char name[1024]; + GetHumanReadableProcessName(&name); + + fprintf(stderr, + "%s: %s:%s:%s%s Assertion `%s' failed.\n", + name, + filename, + linenum, + function, + *function ? ":" : "", + message); + fflush(stderr); + + Abort(); +} + +void ReportException(Environment* env, + Local<Value> er, + Local<Message> message) { + CHECK(!er.IsEmpty()); + HandleScope scope(env->isolate()); + + if (message.IsEmpty()) message = Exception::CreateMessage(env->isolate(), er); + + AppendExceptionLine(env, er, message, FATAL_ERROR); + + Local<Value> trace_value; + Local<Value> arrow; + const bool decorated = IsExceptionDecorated(env, er); + + if (er->IsUndefined() || er->IsNull()) { + trace_value = Undefined(env->isolate()); + } else { + Local<Object> err_obj = er->ToObject(env->context()).ToLocalChecked(); + + trace_value = err_obj->Get(env->stack_string()); + arrow = + err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol()) + .ToLocalChecked(); + } + + node::Utf8Value trace(env->isolate(), trace_value); + + // range errors have a trace member set to undefined + if (trace.length() > 0 && !trace_value->IsUndefined()) { + if (arrow.IsEmpty() || !arrow->IsString() || decorated) { + PrintErrorString("%s\n", *trace); + } else { + node::Utf8Value arrow_string(env->isolate(), arrow); + PrintErrorString("%s\n%s\n", *arrow_string, *trace); + } + } else { + // this really only happens for RangeErrors, since they're the only + // kind that won't have all this info in the trace, or when non-Error + // objects are thrown manually. + Local<Value> message; + Local<Value> name; + + if (er->IsObject()) { + Local<Object> err_obj = er.As<Object>(); + message = err_obj->Get(env->message_string()); + name = err_obj->Get(FIXED_ONE_BYTE_STRING(env->isolate(), "name")); + } + + if (message.IsEmpty() || message->IsUndefined() || name.IsEmpty() || + name->IsUndefined()) { + // Not an error object. Just print as-is. + String::Utf8Value message(env->isolate(), er); + + PrintErrorString("%s\n", + *message ? *message : "<toString() threw exception>"); + } else { + node::Utf8Value name_string(env->isolate(), name); + node::Utf8Value message_string(env->isolate(), message); + + if (arrow.IsEmpty() || !arrow->IsString() || decorated) { + PrintErrorString("%s: %s\n", *name_string, *message_string); + } else { + node::Utf8Value arrow_string(env->isolate(), arrow); + PrintErrorString( + "%s\n%s: %s\n", *arrow_string, *name_string, *message_string); + } + } + } + + fflush(stderr); + +#if HAVE_INSPECTOR + env->inspector_agent()->FatalException(er, message); +#endif +} + +void ReportException(Environment* env, const TryCatch& try_catch) { + ReportException(env, try_catch.Exception(), try_catch.Message()); +} + +void DecorateErrorStack(Environment* env, const TryCatch& try_catch) { + Local<Value> exception = try_catch.Exception(); + + if (!exception->IsObject()) return; + + Local<Object> err_obj = exception.As<Object>(); + + if (IsExceptionDecorated(env, err_obj)) return; + + AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR); + Local<Value> stack = err_obj->Get(env->stack_string()); + MaybeLocal<Value> maybe_value = + err_obj->GetPrivate(env->context(), env->arrow_message_private_symbol()); + + Local<Value> arrow; + if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) { + return; + } + + if (stack.IsEmpty() || !stack->IsString()) { + return; + } + + Local<String> decorated_stack = String::Concat( + env->isolate(), + String::Concat(env->isolate(), + arrow.As<String>(), + FIXED_ONE_BYTE_STRING(env->isolate(), "\n")), + stack.As<String>()); + err_obj->Set(env->stack_string(), decorated_stack); + err_obj->SetPrivate( + env->context(), env->decorated_private_symbol(), True(env->isolate())); +} + +void PrintErrorString(const char* format, ...) { + va_list ap; + va_start(ap, format); +#ifdef _WIN32 + HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + + // Check if stderr is something other than a tty/console + if (stderr_handle == INVALID_HANDLE_VALUE || stderr_handle == nullptr || + uv_guess_handle(_fileno(stderr)) != UV_TTY) { + vfprintf(stderr, format, ap); + va_end(ap); + return; + } + + // Fill in any placeholders + int n = _vscprintf(format, ap); + std::vector<char> out(n + 1); + vsprintf(out.data(), format, ap); + + // Get required wide buffer size + n = MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, nullptr, 0); + + std::vector<wchar_t> wbuf(n); + MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, wbuf.data(), n); + + // Don't include the null character in the output + CHECK_GT(n, 0); + WriteConsoleW(stderr_handle, wbuf.data(), n - 1, nullptr, nullptr); +#else + vfprintf(stderr, format, ap); +#endif + va_end(ap); +} + +[[noreturn]] void FatalError(const char* location, const char* message) { + OnFatalError(location, message); + // to suppress compiler warning + ABORT(); +} + +void OnFatalError(const char* location, const char* message) { + if (location) { + PrintErrorString("FATAL ERROR: %s %s\n", location, message); + } else { + PrintErrorString("FATAL ERROR: %s\n", message); + } + fflush(stderr); + ABORT(); +} + +FatalTryCatch::~FatalTryCatch() { + if (HasCaught()) { + HandleScope scope(env_->isolate()); + ReportException(env_, *this); + exit(7); + } +} + +void FatalException(Isolate* isolate, + Local<Value> error, + Local<Message> message) { + HandleScope scope(isolate); + + Environment* env = Environment::GetCurrent(isolate); + CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. + Local<Object> process_object = env->process_object(); + Local<String> fatal_exception_string = env->fatal_exception_string(); + Local<Value> fatal_exception_function = + process_object->Get(fatal_exception_string); + + if (!fatal_exception_function->IsFunction()) { + // Failed before the process._fatalException function was added! + // this is probably pretty bad. Nothing to do but report and exit. + ReportException(env, error, message); + exit(6); + } else { + TryCatch fatal_try_catch(isolate); + + // Do not call FatalException when _fatalException handler throws + fatal_try_catch.SetVerbose(false); + + // This will return true if the JS layer handled it, false otherwise + MaybeLocal<Value> caught = fatal_exception_function.As<Function>()->Call( + env->context(), process_object, 1, &error); + + if (fatal_try_catch.HasTerminated()) return; + + if (fatal_try_catch.HasCaught()) { + // The fatal exception function threw, so we must exit + ReportException(env, fatal_try_catch); + exit(7); + } else if (caught.ToLocalChecked()->IsFalse()) { + ReportException(env, error, message); + + // fatal_exception_function call before may have set a new exit code -> + // read it again, otherwise use default for uncaughtException 1 + Local<String> exit_code = env->exit_code_string(); + Local<Value> code; + if (!process_object->Get(env->context(), exit_code).ToLocal(&code) || + !code->IsInt32()) { + exit(1); + } + exit(code.As<Int32>()->Value()); + } + } +} + +void FatalException(Isolate* isolate, const TryCatch& try_catch) { + // If we try to print out a termination exception, we'd just get 'null', + // so just crashing here with that information seems like a better idea, + // and in particular it seems like we should handle terminations at the call + // site for this function rather than by printing them out somewhere. + CHECK(!try_catch.HasTerminated()); + + HandleScope scope(isolate); + if (!try_catch.IsVerbose()) { + FatalException(isolate, try_catch.Exception(), try_catch.Message()); + } +} + +void FatalException(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + Environment* env = Environment::GetCurrent(isolate); + if (env != nullptr && env->abort_on_uncaught_exception()) { + Abort(); + } + Local<Value> exception = args[0]; + Local<Message> message = Exception::CreateMessage(isolate, exception); + FatalException(isolate, exception, message); +} + +} // namespace node |