#ifndef mozilla_jni_Natives_h__ #define mozilla_jni_Natives_h__ #include <jni.h> #include "mozilla/IndexSequence.h" #include "mozilla/Move.h" #include "mozilla/Tuple.h" #include "mozilla/TypeTraits.h" #include "mozilla/UniquePtr.h" #include "mozilla/WeakPtr.h" #include "mozilla/Unused.h" #include "mozilla/jni/Accessors.h" #include "mozilla/jni/Refs.h" #include "mozilla/jni/Types.h" #include "mozilla/jni/Utils.h" namespace mozilla { namespace jni { /** * C++ classes implementing instance (non-static) native methods can choose * from one of two ownership models, when associating a C++ object with a Java * instance. * * * If the C++ class inherits from mozilla::SupportsWeakPtr, weak pointers * will be used. The Java instance will store and own the pointer to a * WeakPtr object. The C++ class itself is otherwise not owned or directly * referenced. To attach a Java instance to a C++ instance, pass in a pointer * to the C++ class (i.e. MyClass*). * * class MyClass : public SupportsWeakPtr<MyClass> * , public MyJavaClass::Natives<MyClass> * { * // ... * * public: * MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MyClass) * using MyJavaClass::Natives<MyClass>::Dispose; * * void AttachTo(const MyJavaClass::LocalRef& instance) * { * MyJavaClass::Natives<MyClass>::AttachInstance(instance, this); * * // "instance" does NOT own "this", so the C++ object * // lifetime is separate from the Java object lifetime. * } * }; * * * If the C++ class doesn't inherit from mozilla::SupportsWeakPtr, the Java * instance will store and own a pointer to the C++ object itself. This * pointer must not be stored or deleted elsewhere. To attach a Java instance * to a C++ instance, pass in a reference to a UniquePtr of the C++ class * (i.e. UniquePtr<MyClass>). * * class MyClass : public MyJavaClass::Natives<MyClass> * { * // ... * * public: * using MyJavaClass::Natives<MyClass>::Dispose; * * static void AttachTo(const MyJavaClass::LocalRef& instance) * { * MyJavaClass::Natives<MyClass>::AttachInstance( * instance, mozilla::MakeUnique<MyClass>()); * * // "instance" owns the newly created C++ object, so the C++ * // object is destroyed as soon as instance.dispose() is called. * } * }; */ namespace detail { inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) { if (!handle) { if (!env->ExceptionCheck()) { ThrowException(env, "java/lang/NullPointerException", "Null native pointer"); } return 0; } return handle; } template<class Impl, bool UseWeakPtr = mozilla::IsBaseOf< SupportsWeakPtr<Impl>, Impl>::value /* = false */> struct NativePtr { static Impl* Get(JNIEnv* env, jobject instance) { return reinterpret_cast<Impl*>(CheckNativeHandle( env, GetNativeHandle(env, instance))); } template<class LocalRef> static Impl* Get(const LocalRef& instance) { return Get(instance.Env(), instance.Get()); } template<class LocalRef> static void Set(const LocalRef& instance, UniquePtr<Impl>&& ptr) { Clear(instance); SetNativeHandle(instance.Env(), instance.Get(), reinterpret_cast<uintptr_t>(ptr.release())); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } template<class LocalRef> static void Clear(const LocalRef& instance) { UniquePtr<Impl> ptr(reinterpret_cast<Impl*>( GetNativeHandle(instance.Env(), instance.Get()))); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); if (ptr) { SetNativeHandle(instance.Env(), instance.Get(), 0); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } } }; template<class Impl> struct NativePtr<Impl, /* UseWeakPtr = */ true> { static Impl* Get(JNIEnv* env, jobject instance) { const auto ptr = reinterpret_cast<WeakPtr<Impl>*>( CheckNativeHandle(env, GetNativeHandle(env, instance))); if (!ptr) { return nullptr; } Impl* const impl = *ptr; if (!impl) { ThrowException(env, "java/lang/NullPointerException", "Native object already released"); } return impl; } template<class LocalRef> static Impl* Get(const LocalRef& instance) { return Get(instance.Env(), instance.Get()); } template<class LocalRef> static void Set(const LocalRef& instance, Impl* ptr) { Clear(instance); SetNativeHandle(instance.Env(), instance.Get(), reinterpret_cast<uintptr_t>(new WeakPtr<Impl>(ptr))); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } template<class LocalRef> static void Clear(const LocalRef& instance) { const auto ptr = reinterpret_cast<WeakPtr<Impl>*>( GetNativeHandle(instance.Env(), instance.Get())); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); if (ptr) { SetNativeHandle(instance.Env(), instance.Get(), 0); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); delete ptr; } } }; } // namespace detail using namespace detail; /** * For JNI native methods that are dispatched to a proxy, i.e. using * @WrapForJNI(dispatchTo = "proxy"), the implementing C++ class must provide a * OnNativeCall member. Subsequently, every native call is automatically * wrapped in a functor object, and the object is passed to OnNativeCall. The * OnNativeCall implementation can choose to invoke the call, save it, dispatch * it to a different thread, etc. Each copy of functor may only be invoked * once. * * class MyClass : public MyJavaClass::Natives<MyClass> * { * // ... * * template<class Functor> * class ProxyRunnable final : public Runnable * { * Functor mCall; * public: * ProxyRunnable(Functor&& call) : mCall(mozilla::Move(call)) {} * virtual void run() override { mCall(); } * }; * * public: * template<class Functor> * static void OnNativeCall(Functor&& call) * { * RunOnAnotherThread(new ProxyRunnable(mozilla::Move(call))); * } * }; */ namespace detail { // ProxyArg is used to handle JNI ref arguments for proxies. Because a proxied // call may happen outside of the original JNI native call, we must save all // JNI ref arguments as global refs to avoid the arguments going out of scope. template<typename T> struct ProxyArg { static_assert(mozilla::IsPod<T>::value, "T must be primitive type"); // Primitive types can be saved by value. typedef T Type; typedef typename TypeAdapter<T>::JNIType JNIType; static void Clear(JNIEnv* env, Type&) {} static Type From(JNIEnv* env, JNIType val) { return TypeAdapter<T>::ToNative(env, val); } }; template<class C, typename T> struct ProxyArg<Ref<C, T>> { // Ref types need to be saved by global ref. typedef typename C::GlobalRef Type; typedef typename TypeAdapter<Ref<C, T>>::JNIType JNIType; static void Clear(JNIEnv* env, Type& ref) { ref.Clear(env); } static Type From(JNIEnv* env, JNIType val) { return Type(env, C::Ref::From(val)); } }; template<typename C> struct ProxyArg<const C&> : ProxyArg<C> {}; template<> struct ProxyArg<StringParam> : ProxyArg<String::Ref> {}; template<class C> struct ProxyArg<LocalRef<C>> : ProxyArg<typename C::Ref> {}; // ProxyNativeCall implements the functor object that is passed to OnNativeCall template<class Impl, class Owner, bool IsStatic, bool HasThisArg /* has instance/class local ref in the call */, typename... Args> class ProxyNativeCall : public AbstractCall { // "this arg" refers to the Class::LocalRef (for static methods) or // Owner::LocalRef (for instance methods) that we optionally (as indicated // by HasThisArg) pass into the destination C++ function. typedef typename mozilla::Conditional<IsStatic, Class, Owner>::Type ThisArgClass; typedef typename mozilla::Conditional<IsStatic, jclass, jobject>::Type ThisArgJNIType; // Type signature of the destination C++ function, which matches the // Method template parameter in NativeStubImpl::Wrap. typedef typename mozilla::Conditional<IsStatic, typename mozilla::Conditional<HasThisArg, void (*) (const Class::LocalRef&, Args...), void (*) (Args...)>::Type, typename mozilla::Conditional<HasThisArg, void (Impl::*) (const typename Owner::LocalRef&, Args...), void (Impl::*) (Args...)>::Type>::Type NativeCallType; // Destination C++ function. NativeCallType mNativeCall; // Saved this arg. typename ThisArgClass::GlobalRef mThisArg; // Saved arguments. mozilla::Tuple<typename ProxyArg<Args>::Type...> mArgs; // We cannot use IsStatic and HasThisArg directly (without going through // extra hoops) because GCC complains about invalid overloads, so we use // another pair of template parameters, Static and ThisArg. template<bool Static, bool ThisArg, size_t... Indices> typename mozilla::EnableIf<Static && ThisArg, void>::Type Call(const Class::LocalRef& cls, mozilla::IndexSequence<Indices...>) const { (*mNativeCall)(cls, mozilla::Get<Indices>(mArgs)...); } template<bool Static, bool ThisArg, size_t... Indices> typename mozilla::EnableIf<Static && !ThisArg, void>::Type Call(const Class::LocalRef& cls, mozilla::IndexSequence<Indices...>) const { (*mNativeCall)(mozilla::Get<Indices>(mArgs)...); } template<bool Static, bool ThisArg, size_t... Indices> typename mozilla::EnableIf<!Static && ThisArg, void>::Type Call(const typename Owner::LocalRef& inst, mozilla::IndexSequence<Indices...>) const { Impl* const impl = NativePtr<Impl>::Get(inst); MOZ_CATCH_JNI_EXCEPTION(inst.Env()); (impl->*mNativeCall)(inst, mozilla::Get<Indices>(mArgs)...); } template<bool Static, bool ThisArg, size_t... Indices> typename mozilla::EnableIf<!Static && !ThisArg, void>::Type Call(const typename Owner::LocalRef& inst, mozilla::IndexSequence<Indices...>) const { Impl* const impl = NativePtr<Impl>::Get(inst); MOZ_CATCH_JNI_EXCEPTION(inst.Env()); (impl->*mNativeCall)(mozilla::Get<Indices>(mArgs)...); } template<size_t... Indices> void Clear(JNIEnv* env, mozilla::IndexSequence<Indices...>) { int dummy[] = { (ProxyArg<Args>::Clear(env, Get<Indices>(mArgs)), 0)... }; mozilla::Unused << dummy; } public: // The class that implements the call target. typedef Impl TargetClass; typedef typename ThisArgClass::Param ThisArgType; static const bool isStatic = IsStatic; ProxyNativeCall(ThisArgJNIType thisArg, NativeCallType nativeCall, JNIEnv* env, typename ProxyArg<Args>::JNIType... args) : mNativeCall(nativeCall) , mThisArg(env, ThisArgClass::Ref::From(thisArg)) , mArgs(ProxyArg<Args>::From(env, args)...) {} ProxyNativeCall(ProxyNativeCall&&) = default; ProxyNativeCall(const ProxyNativeCall&) = default; // Get class ref for static calls or object ref for instance calls. typename ThisArgClass::Param GetThisArg() const { return mThisArg; } // Return if target is the given function pointer / pointer-to-member. // Because we can only compare pointers of the same type, we use a // templated overload that is chosen only if given a different type of // pointer than our target pointer type. bool IsTarget(NativeCallType call) const { return call == mNativeCall; } template<typename T> bool IsTarget(T&&) const { return false; } // Redirect the call to another function / class member with the same // signature as the original target. Crash if given a wrong signature. void SetTarget(NativeCallType call) { mNativeCall = call; } template<typename T> void SetTarget(T&&) const { MOZ_CRASH(); } void operator()() override { JNIEnv* const env = GetEnvForThread(); typename ThisArgClass::LocalRef thisArg(env, mThisArg); Call<IsStatic, HasThisArg>( thisArg, typename IndexSequenceFor<Args...>::Type()); // Clear all saved global refs. We do this after the call is invoked, // and not inside the destructor because we already have a JNIEnv here, // so it's more efficient to clear out the saved args here. The // downside is that the call can only be invoked once. Clear(env, typename IndexSequenceFor<Args...>::Type()); mThisArg.Clear(env); } }; template<class Impl, bool HasThisArg, typename... Args> struct Dispatcher { template<class Traits, bool IsStatic = Traits::isStatic, typename... ProxyArgs> static typename EnableIf< Traits::dispatchTarget == DispatchTarget::PROXY, void>::Type Run(ProxyArgs&&... args) { Impl::OnNativeCall(ProxyNativeCall< Impl, typename Traits::Owner, IsStatic, HasThisArg, Args...>(Forward<ProxyArgs>(args)...)); } template<class Traits, bool IsStatic = Traits::isStatic, typename ThisArg, typename... ProxyArgs> static typename EnableIf< Traits::dispatchTarget == DispatchTarget::GECKO, void>::Type Run(ThisArg thisArg, ProxyArgs&&... args) { // For a static method, do not forward the "this arg" (i.e. the class // local ref) if the implementation does not request it. This saves us // a pair of calls to add/delete global ref. DispatchToGeckoThread(MakeUnique<ProxyNativeCall< Impl, typename Traits::Owner, IsStatic, HasThisArg, Args...>>(HasThisArg || !IsStatic ? thisArg : nullptr, Forward<ProxyArgs>(args)...)); } template<class Traits, bool IsStatic = false, typename... ProxyArgs> static typename EnableIf< Traits::dispatchTarget == DispatchTarget::CURRENT, void>::Type Run(ProxyArgs&&... args) {} }; } // namespace detail // Wrapper methods that convert arguments from the JNI types to the native // types, e.g. from jobject to jni::Object::Ref. For instance methods, the // wrapper methods also convert calls to calls on objects. // // We need specialization for static/non-static because the two have different // signatures (jobject vs jclass and Impl::*Method vs *Method). // We need specialization for return type, because void return type requires // us to not deal with the return value. // Bug 1207642 - Work around Dalvik bug by realigning stack on JNI entry #ifdef __i386__ #define MOZ_JNICALL JNICALL __attribute__((force_align_arg_pointer)) #else #define MOZ_JNICALL JNICALL #endif template<class Traits, class Impl, class Args = typename Traits::Args> class NativeStub; template<class Traits, class Impl, typename... Args> class NativeStub<Traits, Impl, jni::Args<Args...>> { using Owner = typename Traits::Owner; using ReturnType = typename Traits::ReturnType; static constexpr bool isStatic = Traits::isStatic; static constexpr bool isVoid = mozilla::IsVoid<ReturnType>::value; struct VoidType { using JNIType = void; }; using ReturnJNIType = typename Conditional< isVoid, VoidType, TypeAdapter<ReturnType>>::Type::JNIType; using ReturnTypeForNonVoidInstance = typename Conditional< !isStatic && !isVoid, ReturnType, VoidType>::Type; using ReturnTypeForVoidInstance = typename Conditional< !isStatic && isVoid, ReturnType, VoidType&>::Type; using ReturnTypeForNonVoidStatic = typename Conditional< isStatic && !isVoid, ReturnType, VoidType>::Type; using ReturnTypeForVoidStatic = typename Conditional< isStatic && isVoid, ReturnType, VoidType&>::Type; static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT || isVoid, "Dispatched calls must have void return type"); public: // Non-void instance method template<ReturnTypeForNonVoidInstance (Impl::*Method) (Args...)> static MOZ_JNICALL ReturnJNIType Wrap(JNIEnv* env, jobject instance, typename TypeAdapter<Args>::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); Impl* const impl = NativePtr<Impl>::Get(env, instance); if (!impl) { // There is a pending JNI exception at this point. return ReturnJNIType(); } return TypeAdapter<ReturnType>::FromNative(env, (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...)); } // Non-void instance method with instance reference template<ReturnTypeForNonVoidInstance (Impl::*Method) (const typename Owner::LocalRef&, Args...)> static MOZ_JNICALL ReturnJNIType Wrap(JNIEnv* env, jobject instance, typename TypeAdapter<Args>::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); Impl* const impl = NativePtr<Impl>::Get(env, instance); if (!impl) { // There is a pending JNI exception at this point. return ReturnJNIType(); } auto self = Owner::LocalRef::Adopt(env, instance); const auto res = TypeAdapter<ReturnType>::FromNative(env, (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...)); self.Forget(); return res; } // Void instance method template<ReturnTypeForVoidInstance (Impl::*Method) (Args...)> static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, typename TypeAdapter<Args>::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { Dispatcher<Impl, /* HasThisArg */ false, Args...>:: template Run<Traits>(instance, Method, env, args...); return; } Impl* const impl = NativePtr<Impl>::Get(env, instance); if (!impl) { // There is a pending JNI exception at this point. return; } (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...); } // Void instance method with instance reference template<ReturnTypeForVoidInstance (Impl::*Method) (const typename Owner::LocalRef&, Args...)> static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, typename TypeAdapter<Args>::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { Dispatcher<Impl, /* HasThisArg */ true, Args...>:: template Run<Traits>(instance, Method, env, args...); return; } Impl* const impl = NativePtr<Impl>::Get(env, instance); if (!impl) { // There is a pending JNI exception at this point. return; } auto self = Owner::LocalRef::Adopt(env, instance); (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...); self.Forget(); } // Overload for DisposeNative template<ReturnTypeForVoidInstance (*DisposeNative) (const typename Owner::LocalRef&)> static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { using LocalRef = typename Owner::LocalRef; Dispatcher<Impl, /* HasThisArg */ false, const LocalRef&>:: template Run<Traits, /* IsStatic */ true>( /* ThisArg */ nullptr, DisposeNative, env, instance); return; } auto self = Owner::LocalRef::Adopt(env, instance); (Impl::DisposeNative)(self); self.Forget(); } // Non-void static method template<ReturnTypeForNonVoidStatic (*Method) (Args...)> static MOZ_JNICALL ReturnJNIType Wrap(JNIEnv* env, jclass, typename TypeAdapter<Args>::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); return TypeAdapter<ReturnType>::FromNative(env, (*Method)(TypeAdapter<Args>::ToNative(env, args)...)); } // Non-void static method with class reference template<ReturnTypeForNonVoidStatic (*Method) (const Class::LocalRef&, Args...)> static MOZ_JNICALL ReturnJNIType Wrap(JNIEnv* env, jclass cls, typename TypeAdapter<Args>::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); auto clazz = Class::LocalRef::Adopt(env, cls); const auto res = TypeAdapter<ReturnType>::FromNative(env, (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...)); clazz.Forget(); return res; } // Void static method template<ReturnTypeForVoidStatic (*Method) (Args...)> static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, typename TypeAdapter<Args>::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { Dispatcher<Impl, /* HasThisArg */ false, Args...>:: template Run<Traits>(cls, Method, env, args...); return; } (*Method)(TypeAdapter<Args>::ToNative(env, args)...); } // Void static method with class reference template<ReturnTypeForVoidStatic (*Method) (const Class::LocalRef&, Args...)> static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, typename TypeAdapter<Args>::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { Dispatcher<Impl, /* HasThisArg */ true, Args...>:: template Run<Traits>(cls, Method, env, args...); return; } auto clazz = Class::LocalRef::Adopt(env, cls); (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...); clazz.Forget(); } }; // Generate a JNINativeMethod from a native // method's traits class and a wrapped stub. template<class Traits, typename Ret, typename... Args> constexpr JNINativeMethod MakeNativeMethod(MOZ_JNICALL Ret (*stub)(JNIEnv*, Args...)) { return { Traits::name, Traits::signature, reinterpret_cast<void*>(stub) }; } // Class inherited by implementing class. template<class Cls, class Impl> class NativeImpl { typedef typename Cls::template Natives<Impl> Natives; static bool sInited; public: static void Init() { if (sInited) { return; } const auto& ctx = typename Cls::Context(); ctx.Env()->RegisterNatives( ctx.ClassRef(), Natives::methods, sizeof(Natives::methods) / sizeof(Natives::methods[0])); MOZ_CATCH_JNI_EXCEPTION(ctx.Env()); sInited = true; } protected: // Associate a C++ instance with a Java instance. static void AttachNative(const typename Cls::LocalRef& instance, SupportsWeakPtr<Impl>* ptr) { static_assert(mozilla::IsBaseOf<SupportsWeakPtr<Impl>, Impl>::value, "Attach with UniquePtr&& when not using WeakPtr"); return NativePtr<Impl>::Set(instance, static_cast<Impl*>(ptr)); } static void AttachNative(const typename Cls::LocalRef& instance, UniquePtr<Impl>&& ptr) { static_assert(!mozilla::IsBaseOf<SupportsWeakPtr<Impl>, Impl>::value, "Attach with SupportsWeakPtr* when using WeakPtr"); return NativePtr<Impl>::Set(instance, mozilla::Move(ptr)); } // Get the C++ instance associated with a Java instance. // There is always a pending exception if the return value is nullptr. static Impl* GetNative(const typename Cls::LocalRef& instance) { return NativePtr<Impl>::Get(instance); } static void DisposeNative(const typename Cls::LocalRef& instance) { NativePtr<Impl>::Clear(instance); } NativeImpl() { // Initialize on creation if not already initialized. Init(); } }; // Define static member. template<class C, class I> bool NativeImpl<C, I>::sInited; } // namespace jni } // namespace mozilla #endif // mozilla_jni_Natives_h__