#pragma once #include #include "environment.h" #include "functor_runners.h" #include "util.h" #include "generic/callbacks.h" #include "generic/extract_params.h" #include "generic/handle_cast.h" #include "generic/read_option.h" #include "v8-function-callback.h" #include #include #include #include namespace ivm { namespace detail { struct ConstructorFunctionHolder : detail::FreeFunctionHolder { using FreeFunctionHolder::FreeFunctionHolder; }; template struct ConstructorFunctionImpl; template struct TemplateHolder { static IsolateSpecific specific; }; template IsolateSpecific TemplateHolder::specific; template struct DontFreezePrototype : std::false_type{}; template struct DontFreezePrototype : std::true_type {}; template struct DontFreezeInstance : std::false_type{}; template struct DontFreezeInstance : std::true_type {}; } /** * Analogous to node::ObjectWrap but Isolate-aware. Also has a bunch of unnecessary template stuff. */ class ClassHandle { template friend struct detail::ConstructorFunctionImpl; private: v8::Persistent handle; /** * Utility methods to set up object prototype */ struct TemplateDefinition { v8::Isolate* isolate; v8::Local& tmpl; v8::Local& proto; v8::Local& sig; // This add regular methods template void Add(const char* name, detail::MemberFunctionHolder impl, Args... args) { v8::Local name_handle = v8_symbol(name); proto->Set(name_handle, v8::FunctionTemplate::New(isolate, impl.callback, {}, sig, impl.length)); Add(args...); } // This adds function templates to the prototype template void Add(const char* name, v8::Local& value, Args... args) { v8::Local name_handle = v8_symbol(name); proto->Set(name_handle, value); Add(args...); } // This adds static functions on the object constructor template void Add(const char* name, detail::FreeFunctionHolder impl, Args... args) { v8::Local name_handle = v8_symbol(name); tmpl->Set(name_handle, v8::FunctionTemplate::New(isolate, impl.callback, {}, v8::Local(), impl.length)); Add(args...); } // This adds accessors template void Add(const char* name, detail::MemberAccessorHolder impl, Args... args) { v8::Local name_handle = v8_symbol(name); v8::Local setter; if (impl.setter.callback != nullptr) { setter = v8::FunctionTemplate::New(isolate, impl.setter.callback, name_handle); } proto->SetAccessorProperty(name_handle, v8::FunctionTemplate::New(isolate, impl.getter.callback, {}), setter); Add(args...); } // This adds static accessors template void Add(const char* name, detail::StaticAccessorHolder impl, Args... args) { v8::Local name_handle = v8_symbol(name); v8::Local setter; if (impl.setter.callback != nullptr) { setter = v8::FunctionTemplate::New(isolate, impl.setter.callback, name_handle); } tmpl->SetAccessorProperty(name_handle, v8::FunctionTemplate::New(isolate, impl.getter.callback, {}), setter); Add(args...); } void Add() {} // NOLINT }; /** * Convenience wrapper for the obtuse SetWeak function signature. When the callback is called * the handle will already be gone. */ template void SetWeak(P* param) { auto& isolate = IsolateEnvironment::GetCurrent(); isolate.AddWeakCallback(&this->handle, (void(*)(void*))F, param); handle.SetWeak(param, WeakCallback, v8::WeakCallbackType::kParameter); } template static void WeakCallback(const v8::WeakCallbackInfo

& info) { F(info.GetParameter()); } /** * Invoked once JS loses all references to this object */ static void WeakCallback(void* param) { auto& isolate = IsolateEnvironment::GetCurrent(); auto* that = reinterpret_cast(param); isolate.RemoveWeakCallback(&that->handle); delete that; // NOLINT } /** * Transfer ownership of this C++ pointer to the v8 handle lifetime. */ static void Wrap(std::unique_ptr ptr, v8::Local handle) { handle->SetAlignedPointerInInternalField(0, ptr.get()); ptr->handle.Reset(v8::Isolate::GetCurrent(), handle); ClassHandle* ptr_raw = ptr.release(); ptr_raw->SetWeak(ptr_raw); } /** * It just throws when you call it; used when `nullptr` is passed as constructor */ static void PrivateConstructor(const v8::FunctionCallbackInfo& info) { throw RuntimeTypeError(detail::CalleeName(info)+ " constructor is private"); } protected: /** * Sets up this object's FunctionTemplate inside the current isolate */ template static auto MakeClass(const char* class_name, detail::ConstructorFunctionHolder New, Args... args) -> v8::Local { v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Local name_handle = v8_symbol(class_name); v8::Local tmpl = v8::FunctionTemplate::New( isolate, New.callback == nullptr ? PrivateConstructor : New.callback, name_handle, {}, New.length ); tmpl->SetClassName(name_handle); auto instance_tmpl = tmpl->InstanceTemplate(); instance_tmpl->SetImmutableProto(); instance_tmpl->SetInternalFieldCount(1); auto proto = tmpl->PrototypeTemplate(); proto->SetImmutableProto(); v8::Local sig = v8::Signature::New(isolate, tmpl); TemplateDefinition def{isolate, tmpl, proto, sig}; def.Add(args...); return tmpl; } /** * Inherit from another class's FunctionTemplate */ template static auto Inherit(v8::Local definition) -> v8::Local { v8::Local parent = GetFunctionTemplate(); definition->Inherit(parent); return definition; } public: ClassHandle() = default; ClassHandle(const ClassHandle&) = delete; auto operator= (const ClassHandle&) -> ClassHandle& = delete; virtual ~ClassHandle() { if (!handle.IsEmpty()) { handle.ClearWeak(); handle.Reset(); } } /** * Returns instance of this class for this context. */ template static auto Init() -> v8::Local { return GetFunctionTemplate()->GetFunction(); } /** * Returns the FunctionTemplate for this isolate, generating it if needed. */ template static auto GetFunctionTemplate() -> v8::Local { return detail::TemplateHolder::specific.Deref([&]() { return Type::Definition(); }); } /** * Freeze the handle and prototype unless `DontFreezeInstance` is set on the class handle */ template static auto MaybeFreeze(v8::Local handle) { auto context = handle->GetIsolate()->GetCurrentContext(); if (!detail::DontFreezePrototype::value) { handle->GetPrototype().As()->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen); } if (!detail::DontFreezeInstance::value) { handle->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen); } } /** * Builds a new instance of T from scratch, used in factory functions. */ template static auto NewInstance(Args&&... args) -> v8::Local { auto context = v8::Isolate::GetCurrent()->GetCurrentContext(); v8::Local instance = Unmaybe(GetFunctionTemplate()->InstanceTemplate()->NewInstance(context)); MaybeFreeze(instance); Wrap(std::make_unique(std::forward(args)...), instance); return instance; } /** * Pull out native pointer from v8 handle */ template static auto Unwrap(v8::Local handle) -> T* { assert(!handle.IsEmpty()); if (!ClassHandle::GetFunctionTemplate()->HasInstance(handle)) { return nullptr; } assert(handle->InternalFieldCount() > 0); return dynamic_cast(static_cast(handle->GetAlignedPointerFromInternalField(0))); } /** * Returns the JS value that this ClassHandle points to */ auto This() -> v8::Local { return Deref(handle).As(); } }; // Conversions from v8::Value -> Type& template inline auto HandleCastImpl(v8::Local value, const HandleCastArguments& /*arguments*/, HandleCastTag /*tag*/) -> Type& { if (!value->IsObject()) { throw ParamIncorrect("an object"); } v8::Local handle = value.As(); if (handle->InternalFieldCount() != 1) { throw ParamIncorrect("something else"); } auto* ptr = ClassHandle::Unwrap(handle); if (ptr == nullptr) { throw ParamIncorrect("something else"); } else { return *ptr; } } namespace detail { template struct ConstructorFunctionImpl; template struct ConstructorFunctionImpl : ConstructorFunctionImpl {}; template struct ConstructorFunctionImpl { using Type = v8::Local(*)(v8::Local This, Args... args); template static inline auto Invoke(v8::Local This, Args... args) -> v8::Local { auto instance = Function(args...); if (instance) { v8::Local handle = This.As(); ClassHandle::MaybeFreeze(handle); ClassHandle::Wrap(std::move(instance), handle); return handle; } else { return Undefined(v8::Isolate::GetCurrent()); } } }; } // namespace detail template struct ConstructorFunction : detail::ConstructorFunctionHolder { using ConstructorFunctionHolder::ConstructorFunctionHolder; ConstructorFunction() : ConstructorFunctionHolder{ Entry, std::tuple_size::arguments>::value} {} static void Entry(const v8::FunctionCallbackInfo& info) { detail::RunBarrier([&]() { if (!info.IsConstructCall()) { throw RuntimeTypeError(detail::CalleeName(info)+ " must be called with `new`"); } ToCallback<-1, typename Impl::Type, &Impl::template Invoke>()(info); }); } using Impl = detail::ConstructorFunctionImpl; }; } // namespace ivm