#pragma once #include #include "error.h" #include "extract_params.h" namespace ivm { namespace detail { // Helper to extract arguments out of function template struct extract_arguments; template struct extract_arguments : extract_arguments {}; template struct extract_arguments { using arguments = std::tuple; static constexpr auto index_sequence = std::make_index_sequence{}; }; template struct extract_arguments : extract_arguments {}; // Helper to turn Return(Class:*)(...) into Return(*)(Class&, ...) template struct unbind_member_function; template <> struct unbind_member_function { using type = std::nullptr_t; template static constexpr auto invoke = nullptr; }; template struct unbind_member_function {}; template struct unbind_member_function { using type = Return(*)(Class&, Args...); template static inline auto invoke(Class& instance, Args... args) -> Return { return (instance.*Function)(args...); } }; // Callbacks that return `void` will return this instead which lets us pass it as an argument struct VoidReturn {}; inline auto HandleCastImpl(VoidReturn /*value*/, HandleCastArguments arguments, HandleCastTag> /*tag*/) { return v8::Undefined(arguments.isolate); } // Returns a `VoidReturn{}` from void functions, which is later converted to `undefined`. This // is needed because the return value from any function needs to be used as a parameter to // another function. template inline auto InvokeWithoutVoid( Function function, typename std::enable_if::value>::type* /*conditional*/ = nullptr ) { function(); return VoidReturn{}; } template inline decltype(auto) InvokeWithoutVoid( Function function, typename std::enable_if::value>::type* /*conditional*/ = nullptr ) { return function(); } // The return value of C++ callbacks are run through this to forward the value to v8 template inline void Returner(Type return_value, const v8::FunctionCallbackInfo& info) { info.GetReturnValue().Set(HandleCast>(return_value, info)); } template inline void Returner(Type return_value, v8::Local /*name*/, const v8::PropertyCallbackInfo& info) { info.GetReturnValue().Set(HandleCast>(return_value, info)); } inline void Returner( VoidReturn /*return_value*/, v8::Local /*name*/, v8::Local /*value*/, const v8::PropertyCallbackInfo& /*info*/ ) {} // This the main entry point for all parameterized v8 callbacks template struct CallbackMaker { // This is responsible for unpacking the arguments from the C++ function, invoking the param // extracter on the v8 info descriptors, and passing the results to the C++ function template static inline void Spread(Args... args, std::index_sequence /*indices*/) { using FnArgs = typename extract_arguments::arguments; return Returner(InvokeWithoutVoid([&]() { // ParamExtractor is implemented as a struct which holds the arguments because earlier // versions of gcc segfault on nested template packs ParamExtractor extractor{args...}; // try/catch is here instead of in ParamExtractor to avoid having to set up / tear down stack // guard once per parameter try { extractor.CheckLength(static_cast(nullptr)); return Function(extractor.template Invoke>()...); } catch (const ParamIncorrect& ex) { extractor.Caught(ex); } }), args...); } // Passed directly to v8 static void Callback(Args... args) { RunBarrier([&]() { Spread(args..., extract_arguments::index_sequence); }); } }; template struct CallbackMaker { static constexpr std::nullptr_t Callback = nullptr; }; // Base type for all native callback pointers template struct GenericCallback { constexpr explicit GenericCallback(Type callback) : callback{callback} {} const Type callback; template static constexpr auto ToCallback() -> Type { return CallbackMaker::Callback; } }; // Any callbacks in the form of `void callback(const v8::FunctionCallbackInfo&)` struct FunctionCallbackImpl : GenericCallback&> { constexpr FunctionCallbackImpl(std::nullptr_t /*nullptr*/) : GenericCallback{nullptr}, length{0} {} // NOLINT(hicpp-explicit-conversions) constexpr FunctionCallbackImpl(v8::FunctionCallback callback, int length) : GenericCallback{callback}, length{length} {} const int length; }; // Functions meant to be called without a `this` parameter struct FreeFunctionHolder : FunctionCallbackImpl { using FunctionCallbackImpl::FunctionCallbackImpl; }; // Functions where `this` is castable into a native pointer struct MemberFunctionHolder : FunctionCallbackImpl { using FunctionCallbackImpl::FunctionCallbackImpl; }; // Member getters and setters struct MemberGetterHolder : FunctionCallbackImpl { using FunctionCallbackImpl::FunctionCallbackImpl; }; struct MemberSetterHolder : FunctionCallbackImpl { using FunctionCallbackImpl::FunctionCallbackImpl; }; struct MemberAccessorHolder { constexpr MemberAccessorHolder(MemberGetterHolder getter, MemberSetterHolder setter) : getter{getter}, setter{setter} {} const MemberGetterHolder getter; const MemberSetterHolder setter; }; // Static getters and setters struct StaticGetterHolder : FreeFunctionHolder { using FreeFunctionHolder::FreeFunctionHolder; }; struct StaticSetterHolder : FreeFunctionHolder { using FreeFunctionHolder::FreeFunctionHolder; }; struct StaticAccessorHolder { constexpr StaticAccessorHolder(StaticGetterHolder getter, StaticSetterHolder setter) : getter{getter}, setter{setter} {} const StaticGetterHolder getter; const StaticSetterHolder setter; }; } // namespace detail // Public interfaces for implementation classes are kept separate to ensure there is a common // interface for all instances of this template. For example it's easier to accept // `detail::FreeFunctionHolder` as parameter vs `FreeFunction<..., ...>`. template struct FreeFunction : detail::FreeFunctionHolder { using FreeFunctionHolder::FreeFunctionHolder; constexpr FreeFunction() : FreeFunctionHolder{ ToCallback<0, Signature, Function>(), std::tuple_size::arguments>::value } {} }; template struct MemberFunction : detail::MemberFunctionHolder { using MemberFunctionHolder::MemberFunctionHolder; constexpr MemberFunction() : MemberFunctionHolder{ ToCallback<-1, typename Unbound::type, Unbound::template invoke>(), std::tuple_size::arguments>::value } {} private: using Unbound = detail::unbind_member_function; }; template struct FreeFunctionWithData : detail::FreeFunctionHolder { using FreeFunctionHolder::FreeFunctionHolder; constexpr FreeFunctionWithData() : FreeFunctionHolder{ ToCallback<-2, Signature, Function>(), std::tuple_size::arguments>::value } {} }; template < class GetterSignature, GetterSignature Getter, class SetterSignature = std::nullptr_t, SetterSignature Setter = nullptr > struct MemberAccessor : detail::MemberAccessorHolder { constexpr MemberAccessor() : MemberAccessorHolder{ detail::MemberGetterHolder{detail::MemberGetterHolder::ToCallback<-1, typename UnboundGetter::type, UnboundGetter::template invoke>(), 0}, detail::MemberSetterHolder{detail::MemberSetterHolder::ToCallback<-1, typename UnboundSetter::type, UnboundSetter::template invoke>(), 1}, } {} private: using UnboundGetter = detail::unbind_member_function; using UnboundSetter = detail::unbind_member_function; }; // gcc seems to have trouble explicitly substituting std::nullptr_t into these templates so this one // is partially specialized. Theoretically you could just remove this whole definition but I // wouldn't try it. template struct MemberAccessor : detail::MemberAccessorHolder { constexpr MemberAccessor() : MemberAccessorHolder{ detail::MemberGetterHolder{detail::MemberGetterHolder::ToCallback<-1, typename UnboundGetter::type, UnboundGetter::template invoke>(), 0}, detail::MemberSetterHolder{nullptr}, } {} private: using UnboundGetter = detail::unbind_member_function; }; template < class GetterSignature, GetterSignature Getter, class SetterSignature = std::nullptr_t, SetterSignature Setter = nullptr > struct StaticAccessor : detail::StaticAccessorHolder { constexpr StaticAccessor() : StaticAccessorHolder{ detail::StaticGetterHolder{detail::StaticGetterHolder::ToCallback<0, GetterSignature, Getter>(), 0}, detail::StaticSetterHolder{detail::StaticSetterHolder::ToCallback<0, SetterSignature, Setter>(), 1} } {} }; } // namespace ivm