#pragma once #include #include #include #include "isolate/v8_version.h" namespace ivm { /** * JS + C++ exceptions, use with care */ // `RuntimeError` can be thrown when v8 already has an exception on deck class RuntimeError : public std::exception {}; namespace detail { // `RuntimeErrorWithMessage` is a general error that has an error message with it class RuntimeErrorWithMessage : public RuntimeError { public: explicit RuntimeErrorWithMessage(std::string message) : message{std::move(message)} {} auto GetMessage() const { return message; } private: std::string message; }; // `RuntimeErrorConstructible` is a abstract error that can be imported back into v8 class RuntimeErrorConstructible : public RuntimeErrorWithMessage { using RuntimeErrorWithMessage::RuntimeErrorWithMessage; public: virtual auto ConstructError() const -> v8::Local = 0; }; // `RuntimeErrorWithConstructor` can be used to construct any of the `v8::Exception` errors #if V8_AT_LEAST(11, 9, 154) template (*Error)(v8::Local, v8::Local)> #else template (*Error)(v8::Local)> #endif class RuntimeErrorWithConstructor : public RuntimeErrorConstructible { using RuntimeErrorConstructible::RuntimeErrorConstructible; public: RuntimeErrorWithConstructor(const std::string& message, std::string stack_trace) : RuntimeErrorConstructible{message}, stack_trace{std::move(stack_trace)} {} auto ConstructError() const -> v8::Local final { v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::MaybeLocal maybe_message = v8::String::NewFromUtf8(isolate, GetMessage().c_str(), v8::NewStringType::kNormal); v8::Local message_handle; if (maybe_message.ToLocal(&message_handle)) { v8::Local error = #if V8_AT_LEAST(11, 9, 154) Error(message_handle, {}).As(); #else Error(message_handle).As(); #endif if (!stack_trace.empty() && isolate->InContext()) { std::string stack_str = std::string(GetMessage()) + stack_trace; v8::MaybeLocal maybe_stack = v8::String::NewFromUtf8(isolate, stack_str.c_str(), v8::NewStringType::kNormal); v8::MaybeLocal maybe_stack_symbol = v8::String::NewFromUtf8(isolate, "stack", v8::NewStringType::kNormal); v8::Local stack; v8::Local stack_symbol; if (maybe_stack.ToLocal(&stack) && maybe_stack_symbol.ToLocal(&stack_symbol)) { error->Set(isolate->GetCurrentContext(), stack_symbol, stack).IsJust(); } } return error; } // If the MaybeLocal is empty then I think v8 will have an exception on deck. I don't know if // there's any way to assert() this though. return {}; } auto GetStackTrace() const -> std::string { return stack_trace; } private: std::string stack_trace; }; } // `FatalRuntimeError` is for very bad situations when the isolate is now in an unknown state class FatalRuntimeError : public detail::RuntimeErrorWithMessage { using RuntimeErrorWithMessage::RuntimeErrorWithMessage; }; // These correspond to the given JS error types using RuntimeGenericError = detail::RuntimeErrorWithConstructor; using RuntimeTypeError = detail::RuntimeErrorWithConstructor; using RuntimeRangeError = detail::RuntimeErrorWithConstructor; /** * Convert a MaybeLocal to Local and throw an error if it's empty. Someone else should throw * the v8 exception. */ template auto Unmaybe(v8::Maybe handle) -> Type { Type just; if (handle.To(&just)) { return just; } else { throw RuntimeError(); } } template auto Unmaybe(v8::MaybeLocal handle) -> v8::Local { v8::Local local; if (handle.ToLocal(&local)) { return local; } else { throw RuntimeError(); } } namespace detail { template inline void RunBarrier(Functor fn) { // Runs a function and converts C++ errors to immediate v8 errors. Pretty much the same as // `RunCallback` but with no return value. try { fn(); } catch (const FatalRuntimeError& cc_error) { // Execution is terminating } catch (const detail::RuntimeErrorConstructible& cc_error) { v8::Isolate::GetCurrent()->ThrowException(cc_error.ConstructError()); } catch (const RuntimeError& cc_error) { // A JS error is waiting in the isolate } } } // namespace detail } // namespace ivm