/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_jni_Natives_h__ #define mozilla_jni_Natives_h__ #include #include #include #include "mozilla/RefPtr.h" #include "mozilla/RWLock.h" #include "mozilla/Tuple.h" #include "mozilla/TypeTraits.h" #include "mozilla/UniquePtr.h" #include "mozilla/Unused.h" #include "mozilla/WeakPtr.h" #include "mozilla/jni/Accessors.h" #include "mozilla/jni/Refs.h" #include "mozilla/jni/Types.h" #include "mozilla/jni/Utils.h" #include "nsThreadUtils.h" struct NativeException { const char* str; }; template static NativeException NullHandle() { return {__func__}; } template static NativeException NullWeakPtr() { return {__func__}; } 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. Note that mozilla::SupportsWeakPtr only supports being used on * a single thread. To attach a Java instance to a C++ instance, pass in a * mozilla::SupportsWeakPtr pointer to the C++ class (i.e. MyClass*). * * class MyClass : public SupportsWeakPtr * , public MyJavaClass::Natives * { * // ... * * public: * using MyJavaClass::Natives::DisposeNative; * * void AttachTo(const MyJavaClass::LocalRef& instance) * { * MyJavaClass::Natives::AttachNative( * instance, static_cast(this)); * * // "instance" does NOT own "this", so the C++ object * // lifetime is separate from the Java object lifetime. * } * }; * * * If the C++ class contains public members AddRef() and Release(), the Java * instance will store and own the pointer to a RefPtr object, which holds a * strong reference on the C++ instance. Normal ref-counting considerations * apply in this case; for example, disposing may cause the C++ instance to * be deleted and the destructor to be run on the current thread, which may * not be desirable. To attach a Java instance to a C++ instance, pass in a * pointer to the C++ class (i.e. MyClass*). * * class MyClass : public RefCounted * , public MyJavaClass::Natives * { * // ... * * public: * using MyJavaClass::Natives::DisposeNative; * * void AttachTo(const MyJavaClass::LocalRef& instance) * { * MyJavaClass::Natives::AttachNative(instance, this); * * // "instance" owns "this" through the RefPtr, so the C++ object * // may be destroyed as soon as instance.disposeNative() is called. * } * }; * * * In other cases, 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). * * class MyClass : public MyJavaClass::Natives * { * // ... * * public: * using MyJavaClass::Natives::DisposeNative; * * static void AttachTo(const MyJavaClass::LocalRef& instance) * { * MyJavaClass::Natives::AttachNative( * instance, mozilla::MakeUnique()); * * // "instance" owns the newly created C++ object, so the C++ * // object is destroyed as soon as instance.disposeNative() is * // called. * } * }; */ namespace detail { /** * Type trait that determines whether a given class has a member named * T::OnWeakNonIntrusiveDetach. * * Example usage: * class Foo {}; * class Bar { * public: * void OnWeakNonIntrusiveDetach(already_AddRefed aRunnable); * }; * * constexpr bool foo = HasWeakNonIntrusiveDetach::value; // Expect false * constexpr bool bar = HasWeakNonIntrusiveDetach::value; // Expect true */ template > struct HasWeakNonIntrusiveDetach : std::false_type {}; template struct HasWeakNonIntrusiveDetach< T, std::void_t().OnWeakNonIntrusiveDetach( std::declval>()))>> : std::true_type { }; /** * Type trait that determines whether a given class is refcounted, ie. it has * both T::AddRef and T::Release methods. * * Example usage: * class Foo {}; * class Bar { * public: * void AddRef(); * void Release(); * }; * * constexpr bool foo = IsRefCounted::value; // Expect false * constexpr bool bar = IsRefCounted::value; // Expect true */ template > struct IsRefCounted : std::false_type {}; template struct IsRefCounted().AddRef(), std::declval().Release())>> : std::true_type {}; /** * This enum is used for classifying the type of pointer that is stored * within a NativeWeakPtr. This classification is different from the one used * for normal native pointers. */ enum class NativePtrInternalType : size_t { OWNING = 1, WEAK = 2, REFPTR = 3, }; /** * NativePtrInternalPicker uses some C++ SFINAE template-fu to figure out * what type of pointer the class specified by Impl needs to be. * * It does this by supplying multiple overloads of a method named Test. * Various overloads are enabled or disabled depending on whether or not Impl * can possibly support them. * * Each overload "returns" a reference to an array whose size corresponds to the * value of each enum in NativePtrInternalType. That size is then converted back * to the enum value, yielding the right type. */ template class NativePtrInternalPicker { // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK template static std::enable_if_t< std::is_base_of::value, char (&)[static_cast(NativePtrInternalType::WEAK)]> Test(char); // Enable if Impl implements AddRef and Release, yielding type REFPTR template static char (&Test(int))[static_cast(NativePtrInternalType::REFPTR)]; // This overload uses '...' as its param to make its arguments less specific; // the compiler prefers more-specific overloads to less-specific ones. // OWNING is the fallback type. template static char (&Test(...))[static_cast(NativePtrInternalType::OWNING)]; public: // Given a hypothetical function call Test, convert the size of its // resulting array back into a NativePtrInternalType enum value. static const NativePtrInternalType value = static_cast( sizeof(Test('\0')) / sizeof(char)); }; /** * This enum is used for classifying the type of pointer that is stored in a * JNIObject's handle. * * We have two different weak pointer types: * * WEAK_INTRUSIVE is a pointer to a class that derives from * mozilla::SupportsWeakPtr. * * WEAK_NON_INTRUSIVE is a pointer to a class that does not have any * internal support for weak pointers, but does supply a * OnWeakNonIntrusiveDetach method. */ enum class NativePtrType : size_t { OWNING = 1, WEAK_INTRUSIVE = 2, WEAK_NON_INTRUSIVE = 3, REFPTR = 4, }; /** * NativePtrPicker uses some C++ SFINAE template-fu to figure out what type of * pointer the class specified by Impl needs to be. * * It does this by supplying multiple overloads of a method named Test. * Various overloads are enabled or disabled depending on whether or not Impl * can possibly support them. * * Each overload "returns" a reference to an array whose size corresponds to the * value of each enum in NativePtrInternalType. That size is then converted back * to the enum value, yielding the right type. */ template class NativePtrPicker { // Just shorthand for each overload's return type template using ResultTypeT = char (&)[static_cast(PtrType)]; // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK_INTRUSIVE template static auto Test(void*) -> std::enable_if_t::value, ResultTypeT>; // Enable if Impl implements OnWeakNonIntrusiveDetach, yielding type // WEAK_NON_INTRUSIVE template static auto Test(void*) -> std::enable_if_t::value, ResultTypeT>; // We want the WEAK_NON_INTRUSIVE overload to take precedence over this one, // so we only enable this overload if Impl is refcounted AND it does not // implement OnWeakNonIntrusiveDetach. Yields type REFPTR. template static auto Test(void*) -> std::enable_if_t< std::conjunction_v, std::negation>>, ResultTypeT>; // This overload uses '...' as its param to make its arguments less specific; // the compiler prefers more-specific overloads to less-specific ones. // OWNING is the fallback type. template static char (&Test(...))[static_cast(NativePtrType::OWNING)]; public: // Given a hypothetical function call Test, convert the size of its // resulting array back into a NativePtrType enum value. static const NativePtrType value = static_cast(sizeof(Test(nullptr))); }; template inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) { if (!handle) { if (!env->ExceptionCheck()) { ThrowException(env, "java/lang/NullPointerException", NullHandle().str); } return 0; } return handle; } /** * This struct is used to describe various traits of a native pointer of type * Impl that will be attached to a JNIObject. * * See the definition of the NativePtrType::OWNING specialization for comments * describing the required fields. */ template ::value> struct NativePtrTraits; template struct NativePtrTraits { using AccessorType = Impl*; // Pointer-like type returned by Access() (an actual pointer in // this case, but this is not strictly necessary) using HandleType = Impl*; // Type of the pointer stored in JNIObject.mHandle using RefType = Impl*; // Type of the pointer returned by Get() /** * Returns a RefType to the native implementation belonging to * the given Java object. */ static RefType Get(JNIEnv* env, jobject instance) { static_assert( std::is_same::value, "HandleType and RefType must be identical for owning pointers"); return reinterpret_cast( CheckNativeHandle(env, GetNativeHandle(env, instance))); } /** * Returns a RefType to the native implementation belonging to * the given Java object. */ template static RefType Get(const LocalRef& instance) { return Get(instance.Env(), instance.Get()); } /** * Given a RefType, returns the pointer-like AccessorType used for * manipulating the native object. */ static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) { static_assert( std::is_same::value, "AccessorType and RefType must be identical for owning pointers"); return aImpl; } /** * Set the JNIObject's handle to the provided pointer, clearing any previous * handle if necessary. */ template static void Set(const LocalRef& instance, UniquePtr&& ptr) { Clear(instance); SetNativeHandle(instance.Env(), instance.Get(), reinterpret_cast(ptr.release())); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } /** * Clear the JNIObject's handle. */ template static void Clear(const LocalRef& instance) { UniquePtr ptr(reinterpret_cast( 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 struct NativePtrTraits { using AccessorType = Impl*; using HandleType = WeakPtr*; using RefType = WeakPtr; static RefType Get(JNIEnv* env, jobject instance) { const auto ptr = reinterpret_cast( CheckNativeHandle(env, GetNativeHandle(env, instance))); return *ptr; } template static RefType Get(const LocalRef& instance) { return Get(instance.Env(), instance.Get()); } static AccessorType Access(RefType aPtr, JNIEnv* aEnv = nullptr) { AccessorType const impl = *aPtr; if (!impl) { JNIEnv* env = aEnv ? aEnv : mozilla::jni::GetEnvForThread(); ThrowException(env, "java/lang/NullPointerException", NullWeakPtr().str); } return impl; } template static void Set(const LocalRef& instance, Impl* ptr) { // Create the new handle first before clearing any old handle, so the // new handle is guaranteed to have different value than any old handle. const uintptr_t handle = reinterpret_cast(new WeakPtr(ptr)); Clear(instance); SetNativeHandle(instance.Env(), instance.Get(), handle); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } template static void Clear(const LocalRef& instance) { const auto ptr = reinterpret_cast( 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; } } }; template struct NativePtrTraits { using AccessorType = Impl*; using HandleType = RefPtr*; using RefType = Impl*; static RefType Get(JNIEnv* env, jobject instance) { const auto ptr = reinterpret_cast( CheckNativeHandle(env, GetNativeHandle(env, instance))); if (!ptr) { return nullptr; } MOZ_ASSERT(*ptr); return *ptr; } template static RefType Get(const LocalRef& instance) { return Get(instance.Env(), instance.Get()); } static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) { static_assert(std::is_same::value, "AccessorType and RefType must be identical for refpointers"); return aImpl; } template static void Set(const LocalRef& instance, RefType ptr) { // Create the new handle first before clearing any old handle, so the // new handle is guaranteed to have different value than any old handle. const uintptr_t handle = reinterpret_cast(new RefPtr(ptr)); Clear(instance); SetNativeHandle(instance.Env(), instance.Get(), handle); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } template static void Clear(const LocalRef& instance) { const auto ptr = reinterpret_cast( 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 // Forward declarations template class NativeWeakPtr; template class NativeWeakPtrHolder; namespace detail { /** * Given the class of a native implementation, as well as its * NativePtrInternalType, resolve traits for that type that will be used by * the NativeWeakPtrControlBlock. * * Note that we only implement specializations for OWNING and REFPTR types, * as a WEAK_INTRUSIVE type should not be using NativeWeakPtr anyway. The build * will fail if such an attempt is made. * * Traits need to implement two things: * 1. A |Type| field that resolves to a pointer type to be stored in the * JNIObject's handle. It is assumed that setting a |Type| object to nullptr * is sufficient to delete the underlying object. * 2. A static |AsRaw| method that converts a pointer of |Type| into a raw * pointer. */ template < typename NativeImpl, NativePtrInternalType PtrType = ::mozilla::jni::detail::NativePtrInternalPicker::value> struct NativeWeakPtrControlBlockStorageTraits; template struct NativeWeakPtrControlBlockStorageTraits< NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::OWNING> { using Type = UniquePtr; static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); } }; template struct NativeWeakPtrControlBlockStorageTraits< NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::REFPTR> { using Type = RefPtr; static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); } }; // Forward Declaration template class Accessor; /** * This class contains the shared data that is referenced by all NativeWeakPtr * objects that reference the same object. * * It retains a WeakRef to the Java object that owns this native object. * It uses a RWLock to control access to the native pointer itself. * Read locks are used when accessing the pointer (even when calling non-const * methods on the native object). * A write lock is only used when it is time to destroy the native object and * we need to clear the value of mNativeImpl. */ template class MOZ_HEAP_CLASS NativeWeakPtrControlBlock final { public: using StorageTraits = NativeWeakPtrControlBlockStorageTraits; using StorageType = typename StorageTraits::Type; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeWeakPtrControlBlock) NativeWeakPtrControlBlock(const NativeWeakPtrControlBlock&) = delete; NativeWeakPtrControlBlock(NativeWeakPtrControlBlock&&) = delete; NativeWeakPtrControlBlock& operator=(const NativeWeakPtrControlBlock&) = delete; NativeWeakPtrControlBlock& operator=(NativeWeakPtrControlBlock&&) = delete; // This is safe to call on any thread because mJavaOwner is immutable. mozilla::jni::Object::WeakRef GetJavaOwner() const { return mJavaOwner; } private: NativeWeakPtrControlBlock(::mozilla::jni::Object::Param aJavaOwner, StorageType&& aNativeImpl) : mJavaOwner(aJavaOwner), mLock("mozilla::jni::detail::NativeWeakPtrControlBlock"), mNativeImpl(std::move(aNativeImpl)) {} ~NativeWeakPtrControlBlock() { // Make sure that somebody, somewhere, has detached us before destroying. MOZ_ASSERT(!(*this)); } /** * Clear the native pointer so that subsequent accesses to the native pointer * via this control block are no longer available. * * We return the native pointer to the caller so that it may proceed with * cleaning up its resources. */ StorageType Clear() { StorageType nativeImpl(nullptr); { // Scope for lock AutoWriteLock lock(mLock); std::swap(mNativeImpl, nativeImpl); } return nativeImpl; } void Lock() const { mLock.ReadLock(); } void Unlock() const { mLock.ReadUnlock(); } #if defined(DEBUG) // This is kind of expensive, so we only support it in debug builds. explicit operator bool() const { AutoReadLock lock(mLock); return !!mNativeImpl; } #endif // defined(DEBUG) private: friend class Accessor; friend class NativeWeakPtr; friend class NativeWeakPtrHolder; private: const mozilla::jni::Object::WeakRef mJavaOwner; mutable RWLock mLock; // Protects mNativeImpl StorageType mNativeImpl; }; /** * When a NativeWeakPtr is detached from its owning Java object, the calling * thread invokes the implementation's OnWeakNonIntrusiveDetach to perform * cleanup. We complete the remainder of the cleanup sequence on the Gecko * main thread by expecting OnWeakNonIntrusiveDetach implementations to invoke * this Runnable before exiting. It will move itself to the main thread if it * is not already there. */ template class NativeWeakPtrDetachRunnable final : public Runnable { public: NativeWeakPtrDetachRunnable( already_AddRefed> aCtlBlock, const Object::LocalRef& aOwner, typename NativeWeakPtrControlBlockStorageTraits::Type aNativeImpl) : Runnable("mozilla::jni::detail::NativeWeakPtrDetachRunnable"), mCtlBlock(aCtlBlock), mOwner(aOwner), mNativeImpl(std::move(aNativeImpl)), mHasRun(false) { MOZ_RELEASE_ASSERT(!!mCtlBlock); MOZ_RELEASE_ASSERT(!!mNativeImpl); } NS_INLINE_DECL_REFCOUNTING_INHERITED(NativeWeakPtrDetachRunnable, Runnable) NS_IMETHOD Run() override { mHasRun = true; if (!NS_IsMainThread()) { NS_DispatchToMainThread(this); return NS_OK; } // Get the owner object's native implementation auto owner = ToLocalRef(mOwner); auto attachedNativeImpl = NativePtrTraits::Get(owner); MOZ_RELEASE_ASSERT(!!attachedNativeImpl); // NativePtrTraits::ClearFinish cleans out the JNIObject's handle, which // obviously we don't want to attempt unless that handle still points to // our native implementation. if (attachedNativeImpl->IsSame(mCtlBlock)) { NativePtrTraits::ClearFinish(owner); } // Now we destroy that native object. mNativeImpl = nullptr; return NS_OK; } private: ~NativeWeakPtrDetachRunnable() { // Guard against somebody forgetting to call this runnable. MOZ_RELEASE_ASSERT(mHasRun, "You must run/dispatch this runnable!"); } private: RefPtr> mCtlBlock; Object::GlobalRef mOwner; typename NativeWeakPtrControlBlockStorageTraits::Type mNativeImpl; bool mHasRun; }; /** * If you want to temporarily access the object held by a NativeWeakPtr, you * must obtain one of these Accessor objects from the pointer. Access must * be done _exclusively_ using once of these objects! */ template class MOZ_STACK_CLASS Accessor final { public: ~Accessor() { if (mCtlBlock) { mCtlBlock->Unlock(); } } // Check whether the object is still valid before doing anything else explicit operator bool() const { return mCtlBlock && mCtlBlock->mNativeImpl; } // Normal member access NativeImpl* operator->() const { return NativeWeakPtrControlBlockStorageTraits::AsRaw( mCtlBlock->mNativeImpl); } // This allows us to support calling a pointer to a member function template auto operator->*(Member aMember) const { NativeImpl* impl = NativeWeakPtrControlBlockStorageTraits::AsRaw( mCtlBlock->mNativeImpl); return [impl, member = aMember](auto&&... aArgs) { return (impl->*member)(std::forward(aArgs)...); }; } // Only available for NativeImpl types that actually use refcounting. // The idea here is that it should be possible to obtain a strong ref from // a NativeWeakPtr if and only if NativeImpl supports refcounting. template auto AsRefPtr() const -> std::enable_if_t::value, RefPtr> { MOZ_ASSERT(I::HasThreadSafeRefCnt::value || NS_IsMainThread()); return mCtlBlock->mNativeImpl; } Accessor(const Accessor&) = delete; Accessor(Accessor&&) = delete; Accessor& operator=(const Accessor&) = delete; Accessor& operator=(Accessor&&) = delete; private: explicit Accessor( const RefPtr>& aCtlBlock) : mCtlBlock(aCtlBlock) { if (aCtlBlock) { aCtlBlock->Lock(); } } private: friend class NativeWeakPtr; friend class NativeWeakPtrHolder; private: const RefPtr> mCtlBlock; }; } // namespace detail /** * This class implements support for thread-safe weak pointers to native objects * that are owned by Java objects deriving from JNIObject. * * Any code that wants to access such a native object must have a copy of * a NativeWeakPtr to that object. */ template class NativeWeakPtr { public: using Accessor = detail::Accessor; /** * Call this method to access the underlying object referenced by this * NativeWeakPtr. * * Always check the returned Accessor object for availability before calling * methods on it. * * For example, given: * * NativeWeakPtr foo; * auto accessor = foo.Access(); * if (accessor) { * // Okay, safe to work with * accessor->DoStuff(); * } else { * // The object's strong reference was cleared and is no longer available! * } */ Accessor Access() const { return Accessor(mCtlBlock); } /** * Detach the underlying object's strong reference from its owning Java object * and clean it up. */ void Detach() { if (!IsAttached()) { // Never attached to begin with; no-op return; } auto native = mCtlBlock->Clear(); if (!native) { // Detach already in progress return; } Object::LocalRef owner(mCtlBlock->GetJavaOwner()); MOZ_RELEASE_ASSERT(!!owner); // Save the raw pointer before we move native into the runnable so that we // may call OnWeakNonIntrusiveDetach on it even after moving native into // the runnable. NativeImpl* rawImpl = detail::NativeWeakPtrControlBlock::StorageTraits::AsRaw( native); rawImpl->OnWeakNonIntrusiveDetach( do_AddRef(new NativeWeakPtrDetachRunnable( mCtlBlock.forget(), owner, std::move(native)))); } /** * This method does not indicate whether or not the weak pointer is still * valid; it only indicates whether we're actually attached to one. */ bool IsAttached() const { return !!mCtlBlock; } /** * Does this pointer reference the same object as the one referenced by the * provided Accessor? */ bool IsSame(const Accessor& aAccessor) const { return mCtlBlock == aAccessor.mCtlBlock; } /** * Does this pointer reference the same object as the one referenced by the * provided Control Block? */ bool IsSame(const RefPtr>& aOther) const { return mCtlBlock == aOther; } NativeWeakPtr() = default; MOZ_IMPLICIT NativeWeakPtr(decltype(nullptr)) {} NativeWeakPtr(const NativeWeakPtr& aOther) = default; NativeWeakPtr(NativeWeakPtr&& aOther) = default; NativeWeakPtr& operator=(const NativeWeakPtr& aOther) = default; NativeWeakPtr& operator=(NativeWeakPtr&& aOther) = default; NativeWeakPtr& operator=(decltype(nullptr)) { mCtlBlock = nullptr; return *this; } protected: // Construction of initial NativeWeakPtr for aCtlBlock explicit NativeWeakPtr( already_AddRefed> aCtlBlock) : mCtlBlock(aCtlBlock) {} private: // Construction of subsequent NativeWeakPtrs for aCtlBlock explicit NativeWeakPtr( const RefPtr>& aCtlBlock) : mCtlBlock(aCtlBlock) {} friend class NativeWeakPtrHolder; protected: RefPtr> mCtlBlock; }; /** * A pointer to an instance of this class should be stored in a Java object's * JNIObject handle. New instances of native objects wrapped by NativeWeakPtr * are created using the static methods of this class. * * Why do we have distinct methods here instead of using AttachNative like other * pointer types that may be stored in JNIObject? * * Essentially, we want the creation and use of NativeWeakPtr to be as * deliberate as possible. Forcing a different creation mechanism is part of * that emphasis. * * Example: * * class NativeFoo { * public: * NativeFoo(); * void Bar(); * // The following method is required to be used with NativeWeakPtr * void OnWeakNonIntrusiveDetach(already_AddRefed aDisposer); * }; * * java::Object::LocalRef javaObj(...); * * // Create a new Foo that is attached to javaObj * auto weakFoo = NativeWeakPtrHolder::Attach(javaObj); * * // Now I can save weakFoo, access it, do whatever I want * if (auto accWeakFoo = weakFoo.Access()) { * accWeakFoo->Bar(); * } * * // Detach from javaObj and clean up * weakFoo.Detach(); */ template class MOZ_HEAP_CLASS NativeWeakPtrHolder final : public NativeWeakPtr { using Base = NativeWeakPtr; public: using Accessor = typename Base::Accessor; using StorageTraits = typename detail::NativeWeakPtrControlBlock::StorageTraits; using StorageType = typename StorageTraits::Type; /** * Create a new NativeImpl object, wrap it in a NativeWeakPtr, and store it * in the Java object's JNIObject handle. * * @return A NativeWeakPtr object that references the newly-attached object. */ template static NativeWeakPtr Attach(const Ref& aJavaObject, Args&&... aArgs) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); StorageType nativeImpl(new NativeImpl(std::forward(aArgs)...)); return AttachInternal(aJavaObject, std::move(nativeImpl)); } /** * Given a new NativeImpl object, wrap it in a NativeWeakPtr, and store it * in the Java object's JNIObject handle. * * @return A NativeWeakPtr object that references the newly-attached object. */ template static NativeWeakPtr AttachExisting( const Ref& aJavaObject, already_AddRefed aNativeImpl) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); StorageType nativeImpl(aNativeImpl); return AttachInternal(aJavaObject, std::move(nativeImpl)); } ~NativeWeakPtrHolder() = default; MOZ_IMPLICIT NativeWeakPtrHolder(decltype(nullptr)) = delete; NativeWeakPtrHolder(const NativeWeakPtrHolder&) = delete; NativeWeakPtrHolder(NativeWeakPtrHolder&&) = delete; NativeWeakPtrHolder& operator=(const NativeWeakPtrHolder&) = delete; NativeWeakPtrHolder& operator=(NativeWeakPtrHolder&&) = delete; NativeWeakPtrHolder& operator=(decltype(nullptr)) = delete; private: template NativeWeakPtrHolder(const LocalRef& aJavaObject, StorageType&& aNativeImpl) : NativeWeakPtr( do_AddRef(new NativeWeakPtrControlBlock( aJavaObject, std::move(aNativeImpl)))) {} /** * Internal function that actually wraps the native pointer, binds it to the * JNIObject, and then returns the NativeWeakPtr result. */ template static NativeWeakPtr AttachInternal( const Ref& aJavaObject, StorageType&& aPtr) { auto localJavaObject = ToLocalRef(aJavaObject); NativeWeakPtrHolder* holder = new NativeWeakPtrHolder(localJavaObject, std::move(aPtr)); static_assert( NativePtrPicker::value == NativePtrType::WEAK_NON_INTRUSIVE, "This type is not compatible with mozilla::jni::NativeWeakPtr"); NativePtrTraits::Set(localJavaObject, holder); return NativeWeakPtr(holder->mCtlBlock); } }; namespace detail { /** * NativePtrTraits for the WEAK_NON_INTRUSIVE pointer type. */ template struct NativePtrTraits { using AccessorType = typename NativeWeakPtrHolder::Accessor; using HandleType = NativeWeakPtrHolder*; using RefType = NativeWeakPtrHolder* const; static RefType Get(JNIEnv* env, jobject instance) { return GetHandle(env, instance); } template static RefType Get(const LocalRef& instance) { return GetHandle(instance.Env(), instance.Get()); } static AccessorType Access(RefType aPtr) { return aPtr->Access(); } template static void Set(const LocalRef& instance, HandleType ptr) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); const uintptr_t handle = reinterpret_cast(ptr); Clear(instance); SetNativeHandle(instance.Env(), instance.Get(), handle); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } template static void Clear(const LocalRef& instance) { auto ptr = reinterpret_cast( GetNativeHandle(instance.Env(), instance.Get())); MOZ_CATCH_JNI_EXCEPTION(instance.Env()); if (!ptr) { return; } ptr->Detach(); } // This call is not safe to do unless we know for sure that instance's // native handle has not changed. It is up to NativeWeakPtrDetachRunnable // to perform this check. template static void ClearFinish(const LocalRef& instance) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); JNIEnv* const env = instance.Env(); auto ptr = reinterpret_cast(GetNativeHandle(env, instance.Get())); MOZ_CATCH_JNI_EXCEPTION(env); MOZ_RELEASE_ASSERT(!!ptr); SetNativeHandle(env, instance.Get(), 0); MOZ_CATCH_JNI_EXCEPTION(env); // Deletion of ptr is done by the caller } // The call is stale if the native object has been destroyed on the // Gecko side, but the Java object is still attached to it through // a weak pointer. Stale calls should be discarded. Note that it's // an error if holder is nullptr here; we return false but the // native call will throw an error. template static bool IsStale(const LocalRef& instance) { JNIEnv* const env = mozilla::jni::GetEnvForThread(); // We cannot use Get here because that method throws an exception when the // object is null, which is a valid state for a stale call. const auto holder = reinterpret_cast(GetNativeHandle(env, instance.Get())); MOZ_CATCH_JNI_EXCEPTION(env); if (!holder || !holder->IsAttached()) { return true; } auto acc(holder->Access()); return !acc; } private: static HandleType GetHandle(JNIEnv* env, jobject instance) { return reinterpret_cast( CheckNativeHandle(env, GetNativeHandle(env, instance))); } template static HandleType GetHandle(const LocalRef& instance) { return GetHandle(instance.Env(), instance.Get()); } friend class NativeWeakPtrHolder; }; } // 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 * { * // ... * * template * class ProxyRunnable final : public Runnable * { * Functor mCall; * public: * ProxyRunnable(Functor&& call) : mCall(std::move(call)) {} * virtual void run() override { mCall(); } * }; * * public: * template * static void OnNativeCall(Functor&& call) * { * RunOnAnotherThread(new ProxyRunnable(std::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 struct ProxyArg { static_assert(mozilla::IsPod::value, "T must be primitive type"); // Primitive types can be saved by value. typedef T Type; typedef typename TypeAdapter::JNIType JNIType; static void Clear(JNIEnv* env, Type&) {} static Type From(JNIEnv* env, JNIType val) { return TypeAdapter::ToNative(env, val); } }; template struct ProxyArg> { // Ref types need to be saved by global ref. typedef typename C::GlobalRef Type; typedef typename TypeAdapter>::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 struct ProxyArg : ProxyArg {}; template <> struct ProxyArg : ProxyArg {}; template struct ProxyArg> : ProxyArg {}; // ProxyNativeCall implements the functor object that is passed to OnNativeCall template class ProxyNativeCall { // "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. using ThisArgClass = std::conditional_t; using ThisArgJNIType = std::conditional_t; // Type signature of the destination C++ function, which matches the // Method template parameter in NativeStubImpl::Wrap. using NativeCallType = std::conditional_t< IsStatic, std::conditional_t, std::conditional_t< HasThisArg, void (Impl::*)(const typename Owner::LocalRef&, Args...), void (Impl::*)(Args...)>>; // Destination C++ function. NativeCallType mNativeCall; // Saved this arg. typename ThisArgClass::GlobalRef mThisArg; // Saved arguments. mozilla::Tuple::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 std::enable_if_t Call( const Class::LocalRef& cls, std::index_sequence) const { (*mNativeCall)(cls, mozilla::Get(mArgs)...); } template std::enable_if_t Call( const Class::LocalRef& cls, std::index_sequence) const { (*mNativeCall)(mozilla::Get(mArgs)...); } template std::enable_if_t Call( const typename Owner::LocalRef& inst, std::index_sequence) const { auto impl = NativePtrTraits::Access(NativePtrTraits::Get(inst)); MOZ_CATCH_JNI_EXCEPTION(inst.Env()); (impl->*mNativeCall)(inst, mozilla::Get(mArgs)...); } template std::enable_if_t Call( const typename Owner::LocalRef& inst, std::index_sequence) const { auto impl = NativePtrTraits::Access(NativePtrTraits::Get(inst)); MOZ_CATCH_JNI_EXCEPTION(inst.Env()); (impl->*mNativeCall)(mozilla::Get(mArgs)...); } template void Clear(JNIEnv* env, std::index_sequence) { int dummy[] = {(ProxyArg::Clear(env, Get(mArgs)), 0)...}; mozilla::Unused << dummy; } static decltype(auto) GetNativeObject(Class::Param thisArg) { return nullptr; } static decltype(auto) GetNativeObject(typename Owner::Param thisArg) { return NativePtrTraits::Access( NativePtrTraits::Get(GetEnvForThread(), thisArg.Get())); } 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::JNIType... args) : mNativeCall(nativeCall), mThisArg(env, ThisArgClass::Ref::From(thisArg)), mArgs(ProxyArg::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; } // Get the native object targeted by this call. // Returns nullptr for static calls. decltype(auto) GetNativeObject() const { return GetNativeObject(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 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 void SetTarget(T&&) const { MOZ_CRASH(); } void operator()() { JNIEnv* const env = GetEnvForThread(); typename ThisArgClass::LocalRef thisArg(env, mThisArg); Call(thisArg, std::index_sequence_for{}); // 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, std::index_sequence_for{}); mThisArg.Clear(env); } }; template struct Dispatcher { template static std::enable_if_t Run(ProxyArgs&&... args) { Impl::OnNativeCall( ProxyNativeCall(std::forward(args)...)); } template static std::enable_if_t< Traits::dispatchTarget == DispatchTarget::GECKO_PRIORITY, void> 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. auto proxy = ProxyNativeCall((HasThisArg || !IsStatic) ? thisArg : nullptr, std::forward(args)...); DispatchToGeckoPriorityQueue( NS_NewRunnableFunction("PriorityNativeCall", std::move(proxy))); } template static std::enable_if_t 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. auto proxy = ProxyNativeCall((HasThisArg || !IsStatic) ? thisArg : nullptr, std::forward(args)...); NS_DispatchToMainThread( NS_NewRunnableFunction("GeckoNativeCall", std::move(proxy))); } template static std::enable_if_t Run(ProxyArgs&&... args) { MOZ_CRASH("Unreachable code"); } }; } // 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 NativeStub; template class NativeStub> { using Owner = typename Traits::Owner; using ReturnType = typename Traits::ReturnType; static constexpr bool isStatic = Traits::isStatic; static constexpr bool isVoid = std::is_void_v; struct VoidType { using JNIType = void; }; using ReturnJNIType = typename std::conditional_t>::JNIType; using ReturnTypeForNonVoidInstance = std::conditional_t; using ReturnTypeForVoidInstance = std::conditional_t; using ReturnTypeForNonVoidStatic = std::conditional_t; using ReturnTypeForVoidStatic = std::conditional_t; static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT || isVoid, "Dispatched calls must have void return type"); public: // Non-void instance method template static MOZ_JNICALL ReturnJNIType Wrap(JNIEnv* env, jobject instance, typename TypeAdapter::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); auto impl = NativePtrTraits::Access( NativePtrTraits::Get(env, instance)); if (!impl) { // There is a pending JNI exception at this point. return ReturnJNIType(); } return TypeAdapter::FromNative( env, (impl->*Method)(TypeAdapter::ToNative(env, args)...)); } // Non-void instance method with instance reference template static MOZ_JNICALL ReturnJNIType Wrap(JNIEnv* env, jobject instance, typename TypeAdapter::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); auto impl = NativePtrTraits::Access( NativePtrTraits::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::FromNative( env, (impl->*Method)(self, TypeAdapter::ToNative(env, args)...)); self.Forget(); return res; } // Void instance method template static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, typename TypeAdapter::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { Dispatcher::template Run( instance, Method, env, args...); return; } auto impl = NativePtrTraits::Access( NativePtrTraits::Get(env, instance)); if (!impl) { // There is a pending JNI exception at this point. return; } (impl->*Method)(TypeAdapter::ToNative(env, args)...); } // Void instance method with instance reference template static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, typename TypeAdapter::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { Dispatcher::template Run( instance, Method, env, args...); return; } auto impl = NativePtrTraits::Access( NativePtrTraits::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::ToNative(env, args)...); self.Forget(); } // Overload for DisposeNative template 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::template Run< Traits, /* IsStatic */ true>( /* ThisArg */ nullptr, DisposeNative, env, instance); return; } auto self = Owner::LocalRef::Adopt(env, instance); DisposeNative(self); self.Forget(); } // Non-void static method template static MOZ_JNICALL ReturnJNIType Wrap(JNIEnv* env, jclass, typename TypeAdapter::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); return TypeAdapter::FromNative( env, (*Method)(TypeAdapter::ToNative(env, args)...)); } // Non-void static method with class reference template static MOZ_JNICALL ReturnJNIType Wrap(JNIEnv* env, jclass cls, typename TypeAdapter::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); auto clazz = Class::LocalRef::Adopt(env, cls); const auto res = TypeAdapter::FromNative( env, (*Method)(clazz, TypeAdapter::ToNative(env, args)...)); clazz.Forget(); return res; } // Void static method template static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, typename TypeAdapter::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { Dispatcher::template Run( cls, Method, env, args...); return; } (*Method)(TypeAdapter::ToNative(env, args)...); } // Void static method with class reference template static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, typename TypeAdapter::JNIType... args) { MOZ_ASSERT_JNI_THREAD(Traits::callingThread); if (Traits::dispatchTarget != DispatchTarget::CURRENT) { Dispatcher::template Run( cls, Method, env, args...); return; } auto clazz = Class::LocalRef::Adopt(env, cls); (*Method)(clazz, TypeAdapter::ToNative(env, args)...); clazz.Forget(); } }; // Generate a JNINativeMethod from a native // method's traits class and a wrapped stub. template constexpr JNINativeMethod MakeNativeMethod(MOZ_JNICALL Ret (*stub)(JNIEnv*, Args...)) { return {Traits::name, Traits::signature, reinterpret_cast(stub)}; } // Class inherited by implementing class. template class NativeImpl { typedef typename Cls::template Natives 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* ptr) { static_assert(NativePtrPicker::value == NativePtrType::WEAK_INTRUSIVE, "Use another AttachNative for non-WeakPtr usage"); return NativePtrTraits::Set(instance, static_cast(ptr)); } static void AttachNative(const typename Cls::LocalRef& instance, UniquePtr&& ptr) { static_assert(NativePtrPicker::value == NativePtrType::OWNING, "Use another AttachNative for WeakPtr or RefPtr usage"); return NativePtrTraits::Set(instance, std::move(ptr)); } static void AttachNative(const typename Cls::LocalRef& instance, Impl* ptr) { static_assert(NativePtrPicker::value == NativePtrType::REFPTR, "Use another AttachNative for non-RefPtr usage"); return NativePtrTraits::Set(instance, ptr); } // Get the C++ instance associated with a Java instance. // There is always a pending exception if the return value is nullptr. static decltype(auto) GetNative(const typename Cls::LocalRef& instance) { return NativePtrTraits::Get(instance); } static void DisposeNative(const typename Cls::LocalRef& instance) { NativePtrTraits::Clear(instance); } NativeImpl() { // Initialize on creation if not already initialized. Init(); } }; // Define static member. template bool NativeImpl::sInited; } // namespace jni } // namespace mozilla #endif // mozilla_jni_Natives_h__