xiaoxiaojx / blog

Project for records problems solved in my work and study.
https://xiaoxiaojx.github.io/
MIT License
253 stars 6 forks source link

v8::TryCatch 的使用 #39

Open xiaoxiaojx opened 2 years ago

xiaoxiaojx commented 2 years ago

v8::TryCatch

Creates a new try/catch block and registers it with v8.

v8docs 中就很简单的介绍了一句「在一个作用域内注册一个 try catch」。光看文档很难明白, 除非接触过类似的实现才能够联想到这里的实际使用场景, 所以不够了解的时候还是得多看代码。

使用场景

如下 Node.js 中的代码可知, TryCatch 主要用于在 C++ 中捕获 JavaScript 调用的异常。在创建 try_catch 实例后, 你可以获取 C++ 代码中调用 JavaScript 函数的状态, 比如通过 try_catch 的 HasCaught 的返回值判断运行 JavaScript 函数是否抛错。

// src/inspector_agent.cc

void Agent::ToggleAsyncHook(Isolate* isolate, Local<Function> fn) {
  // Guard against running this during cleanup -- no async events will be
  // emitted anyway at that point anymore, and calling into JS is not possible.
  // This should probably not be something we're attempting in the first place,
  // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
  if (!parent_env_->can_call_into_js()) return;
  CHECK(parent_env_->has_run_bootstrapping_code());
  HandleScope handle_scope(isolate);
  CHECK(!fn.IsEmpty());
  auto context = parent_env_->context();
  v8::TryCatch try_catch(isolate);
  USE(fn->Call(context, Undefined(isolate), 0, nullptr));
  if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
    PrintCaughtException(isolate, context, try_catch);
    FatalError("\nnode::inspector::Agent::ToggleAsyncHook",
               "Cannot toggle Inspector's AsyncHook, please report this.");
  }
}

SetVerbose

Set verbosity of the external exception handler. By default, exceptions that are caught by an external exception handler are not reported. Call SetVerbose with true on an external exception handler to have exceptions caught by the handler reported as if they were not caught.

有些代码中创建 try_catch 实例后紧接着会调用 SetVerbose 函数, 看了文档的解释后, 也不够清楚, 于是继续看看代码。

// demo

static inline void trigger_fatal_exception(
    napi_env env, v8::Local<v8::Value> local_err) {
  v8::TryCatch try_catch(env->isolate);
  try_catch.SetVerbose(true);
  env->isolate->ThrowException(local_err);
  node::FatalException(env->isolate, try_catch);
}

上面的代码手动抛出了一个错误, 然后调用 node::FatalException 函数, node::FatalException 函数里面又调用了 TriggerUncaughtException 函数。从而我们可以知道 SetVerbose 为 true 后, 此时代码将不会继续往下走。

// src/node_errors.cc

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 */);
}

情况一 SetVerbose 为 false 一直运行到最后调用 TriggerUncaughtException 函数。

情况二 SetVerbose 为 true TriggerUncaughtException 函数直接被 return, 错误将由创建 try_catch 实例传入的 isolate 实例上通过 AddMessageListenerWithErrorLevel 注册的错误监听函数 PerIsolateMessageListener 处理

// src/api/environment.cc

void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
  if (s.flags & MESSAGE_LISTENER_WITH_ERROR_LEVEL)
    isolate->AddMessageListenerWithErrorLevel(
            errors::PerIsolateMessageListener,
            Isolate::MessageErrorLevel::kMessageError |
                Isolate::MessageErrorLevel::kMessageWarning);

  auto* abort_callback = s.should_abort_on_uncaught_exception_callback ?
      s.should_abort_on_uncaught_exception_callback :
      ShouldAbortOnUncaughtException;
  isolate->SetAbortOnUncaughtExceptionCallback(abort_callback);

  auto* fatal_error_cb = s.fatal_error_callback ?
      s.fatal_error_callback : OnFatalError;
  isolate->SetFatalErrorHandler(fatal_error_cb);

  if ((s.flags & SHOULD_NOT_SET_PREPARE_STACK_TRACE_CALLBACK) == 0) {
    auto* prepare_stack_trace_cb = s.prepare_stack_trace_callback ?
        s.prepare_stack_trace_callback : PrepareStackTraceCallback;
    isolate->SetPrepareStackTraceCallback(prepare_stack_trace_cb);
  }
}

PerIsolateMessageListener 函数根据 ErrorLevel 进行不同的处理, 如果是 kMessageWarning 则是通过 ProcessEmitWarningGeneric 函数触发 JavaScript 上的 process.on('warn') 事件, 否则通过 TriggerUncaughtException 触发 JavaScript 上的 process.on('uncaughtException') 事件

// src/node_errors.cc

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;
  }
}