From bbc14f10f577a9f88b6078c6d7825120604ff78e Mon Sep 17 00:00:00 2001 From: Jamieson Pryor Date: Mon, 8 May 2023 11:32:17 -0700 Subject: [PATCH] Rev JNI Bind release header to v 0.9.4.1. PiperOrigin-RevId: 530360032 --- README.md | 4 +- jni_bind_release.h | 890 ++++++++++++++++++++++++------------ jni_bind_release_leader.inc | 2 +- 3 files changed, 601 insertions(+), 295 deletions(-) diff --git a/README.md b/README.md index d74c0235..10e067ab 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,8 @@ If you're already using Bazel add the following to your WORKSPACE: ```starlark http_archive( name = "jni-bind", - urls = ["https://github.com/google/jni-bind/archive/refs/tags/Release-0.9.3-alpha.zip"], - strip_prefix = "jni-bind-Release-0.9.3-alpha", + urls = ["https://github.com/google/jni-bind/archive/refs/tags/Release-0.9.4.1-alpha.zip"], + strip_prefix = "jni-bind-Release-0.9.4.1-alpha", ) ``` diff --git a/jni_bind_release.h b/jni_bind_release.h index 4915bd4f..7ca6dde9 100644 --- a/jni_bind_release.h +++ b/jni_bind_release.h @@ -15,7 +15,7 @@ */ /******************************************************************************* - * JNI Bind Version 0.9.3. + * JNI Bind Version 0.9.4. * Alpha Public Release. ******************************************************************************** * This header is the single header version which you can use to quickly test or @@ -1038,11 +1038,7 @@ using BaseFilterWithDefault_t = namespace jni { -/** -// JniHelper is a shim to using a JNIenv object. -// This extra layer of indirection allows for quickly shimming all JNICalls -// (e.g. adding exception checking, logging each JNI call, etc). -**/ +// Helper JNI shim for object, method, class, etc. lookup. class JniHelper { public: // Finds a class with "name". Note, the classloader used is whatever is @@ -1054,9 +1050,6 @@ class JniHelper { // Note, if the object is polymorphic it may be a sub or superclass. static jclass GetObjectClass(jobject object); - // See FindClass and jni::Jvm. - static void ReleaseClass(jclass clazz); - // Gets a method for a signature (no caching is performed). static inline jmethodID GetMethodID(jclass clazz, const char* method_name, const char* method_signature); @@ -1074,35 +1067,7 @@ class JniHelper { static inline jfieldID GetStaticFieldID(jclass clazz, const char* field_name, const char* field_signature); - // Objects. - template - static jobject NewLocalObject(jclass clazz, jmethodID ctor_method, - CtorArgs&&... ctor_args); - - // Creates a new GlobalRef to |local_object|, then deletes the local - // reference. - static jobject PromoteLocalToGlobalObject(jobject local_object); - - // Creates a new GlobalRef to |local_class|, then deletes the local - // reference. - static jclass PromoteLocalToGlobalClass(jclass local_class); - - static void DeleteLocalObject(jobject object); - - template - static jobject NewGlobalObject(jclass clazz, jmethodID ctor_method, - CtorArgs&&... ctor_args); - - static void DeleteGlobalObject(jobject obj_ref); - // Strings. - static jstring NewLocalString(const char*); - - // Creates a new GlobalRef to |local_string| , then deletes the local string. - static jstring PromoteLocalToGlobalString(jstring local_string); - - static void DeleteGlobalString(jstring string); - static const char* GetStringUTFChars(jstring str); static void ReleaseStringUTFChars(jstring str, const char* chars); @@ -1118,10 +1083,6 @@ inline jclass JniHelper::GetObjectClass(jobject object) { return jni::JniEnv::GetEnv()->GetObjectClass(object); } -inline void JniHelper::ReleaseClass(jclass clazz) { - jni::JniEnv::GetEnv()->DeleteGlobalRef(clazz); -} - jmethodID JniHelper::GetMethodID(jclass clazz, const char* method_name, const char* method_signature) { return jni::JniEnv::GetEnv()->GetMethodID(clazz, method_name, @@ -1144,69 +1105,6 @@ jfieldID JniHelper::GetStaticFieldID(jclass clazz, const char* name, return jni::JniEnv::GetEnv()->GetStaticFieldID(clazz, name, signature); } -template -jobject JniHelper::NewLocalObject(jclass clazz, jmethodID ctor_method, - CtorArgs&&... ctor_args) { - return jni::JniEnv::GetEnv()->NewObject(clazz, ctor_method, ctor_args...); -} - -inline void JniHelper::DeleteLocalObject(jobject object) { - jni::JniEnv::GetEnv()->DeleteLocalRef(object); -} - -inline jobject JniHelper::PromoteLocalToGlobalObject(jobject local_object) { - JNIEnv* const env = jni::JniEnv::GetEnv(); - jobject global_object = env->NewGlobalRef(local_object); - env->DeleteLocalRef(local_object); - return global_object; -} - -inline jclass JniHelper::PromoteLocalToGlobalClass(jclass local_class) { - return reinterpret_cast(JniEnv::GetEnv()->NewGlobalRef(local_class)); -} - -template -inline jobject JniHelper::NewGlobalObject(jclass clazz, jmethodID ctor_method, - CtorArgs&&... ctor_args) { - // Note, this local ref handle created below is never leaked outside of this - // scope and should naturally be cleaned up when invoking JNI function - // completes. That said, the maximum number of local refs can be extremely - // limited (the standard only requires 16), and if the caller doesn't - // explicitly reach for the performant option, it doesn't make sense to - // provide a micro optimisation of skipping the delete call below. - // - // If consumers want the most performant option, they should use LocalRef - // implementations when building their dynamic object. - JNIEnv* const env = jni::JniEnv::GetEnv(); - jobject local_object = NewLocalObject(env, clazz, ctor_method, - std::forward(ctor_args)...); - jobject global_object = env->NewGlobalRef(local_object); - - env->DeleteLocalRef(local_object); - - return global_object; -} - -inline void JniHelper::DeleteGlobalObject(jobject obj_ref) { - jni::JniEnv::GetEnv()->DeleteGlobalRef(obj_ref); -} - -inline jstring JniHelper::NewLocalString(const char* chars) { - return jni::JniEnv::GetEnv()->NewStringUTF(chars); -} - -inline jstring JniHelper::PromoteLocalToGlobalString(jstring local_string) { - // jstrings follow the semantics of regular objects. - JNIEnv* const env = jni::JniEnv::GetEnv(); - jstring global_string = static_cast(env->NewGlobalRef(local_string)); - env->DeleteLocalRef(local_string); - return global_string; -} - -inline void JniHelper::DeleteGlobalString(jstring string) { - jni::JniEnv::GetEnv()->DeleteGlobalRef(string); -} - inline const char* JniHelper::GetStringUTFChars(jstring str) { // If is_copy is an address of bool it will be set to true or false if a copy // is made. That said, this seems to be of no consequence, as the API still @@ -1922,6 +1820,7 @@ static constexpr std::size_t kClassNotInLoaderSetIdx = // that classes are explicitly listed under a loader's class list. class DefaultClassLoader { public: + const char* name_ = "__JNI_BIND_DEFAULT_CLASS_LOADER__"; std::tuple<> supported_classes_{}; // Note, this will return true iff ignore_default_loader is true, but the @@ -1964,6 +1863,8 @@ class DefaultClassLoader { // for most user defined classes. class NullClassLoader { public: + const char* name_ = "__JNI_BIND_NULL_CLASS_LOADER__"; + template constexpr bool SupportedDirectlyOrIndirectly() const { return false; @@ -1991,8 +1892,14 @@ class NullClassLoader { } }; -inline constexpr NullClassLoader kNullClassLoader; -inline constexpr DefaultClassLoader kDefaultClassLoader; +static constexpr NullClassLoader kNullClassLoader; +static constexpr DefaultClassLoader kDefaultClassLoader; + +// DO NOT USE: This obviates a compiler bug for value based enablement on ctor. +static constexpr auto kShadowNullClassLoader = kNullClassLoader; + +// DO NOT USE: This obviates a compiler bug for value based enablement on ctor. +static constexpr auto kShadowDefaultClassLoader = kDefaultClassLoader; } // namespace jni @@ -2006,7 +1913,9 @@ inline constexpr Class kJavaLangObject{"java/lang/Object"}; inline constexpr Class kJavaLangClassLoader{ "java/lang/ClassLoader", - Method{"loadClass", Return{kJavaLangClass}, Params{}}}; + Method{"loadClass", Return{kJavaLangClass}, Params{}}, + Method{"toString", Return{jstring{}}, Params<>{}}, +}; static constexpr Class kJavaLangString{ "java/lang/String", @@ -2096,6 +2005,95 @@ static constexpr auto StringConcatenate_v = StringConcatenate::value; } // namespace jni::metaprogramming +#include +#include +#include + +#define STR(x) []() { return x; } + +namespace jni::metaprogramming { + +template +using identifier_type = decltype(std::declval()()); + +constexpr std::size_t ConstexprStrlen(const char* str) { + return str[0] == 0 ? 0 : ConstexprStrlen(str + 1) + 1; +} + +struct StringAsTypeBase {}; + +// Represents a string by embedding a sequence of characters in a type. +template +struct StringAsType : StringAsTypeBase { + static constexpr char static_chars[] = {chars..., 0}; + static constexpr std::string_view chars_as_sv = {static_chars, + sizeof...(chars)}; +}; + +template +constexpr auto LambdaToStr(Identifier id, std::index_sequence) { + return StringAsType{}; +} + +template < + typename Identifier, + std::enable_if_t, const char*>, + int> = 0> +constexpr auto LambdaToStr(Identifier id) { + return LambdaToStr(id, std::make_index_sequence{}); +} + +template +using LambdaStringToType = decltype(LambdaToStr(std::declval())); + +} // namespace jni::metaprogramming + +#include + +namespace jni { + +enum class LifecycleType { + LOCAL, + GLOBAL, + // WEAK, // not implemented yet. +}; + +template +struct LifecycleHelper; + +// Shared implementation for local jobjects (jobject, jstring). +template +struct LifecycleLocalBase { + static inline void Delete(Span object) { + JniEnv::GetEnv()->DeleteLocalRef(object); + } + + static inline Span NewReference(Span object) { + return static_cast(JniEnv::GetEnv()->NewLocalRef(object)); + } +}; + +// Shared implementation for global jobjects (jobject, jstring). +template +struct LifecycleGlobalBase { + static inline Span Promote(Span object) { + jobject ret = JniEnv::GetEnv()->NewGlobalRef(object); + JniEnv::GetEnv()->DeleteLocalRef(object); + + return static_cast(ret); + } + + static inline void Delete(Span object) { + JniEnv::GetEnv()->DeleteGlobalRef(object); + } + + static inline Span NewReference(Span object) { + return static_cast(JniEnv::GetEnv()->NewGlobalRef(object)); + } +}; + +} // namespace jni + #include #include #include @@ -2105,31 +2103,56 @@ namespace jni { inline constexpr struct NoClassLoader { } kNoClassLoaderSpecified; -// Represents the compile time info we have about a class loader. In general, -// this is just the list of classes we expect to be loadable from a class loader +// This is just the list of classes we expect to be loadable from a class loader // and its parent loader. // // Classes from different loaders are typically incompatible, but Class loaders // delegate classes that they cannot directly load to their parent loaders, so // classes attached to two different class loaders will still be compatible if // they were loaded by a shared parent loader. +// +// To annotate a class in a function or field declaration, use `LoadedBy`. template -class ClassLoader { +class ClassLoader : public Object { public: const ParentLoader_ parent_loader_; const std::tuple supported_classes_; - // TODO (b/143908983): Loaders should not be able to supply classes that their - // parents do. + explicit constexpr ClassLoader(const char* class_loader_name) + : Object(class_loader_name) {} + + // Default classloader (no name needed). + explicit constexpr ClassLoader( + ParentLoader_ parent_loader, + SupportedClassSet supported_class_set) + __attribute__(( + enable_if(parent_loader == kDefaultClassLoader, + "You must provide a name for classloaders (except " + "kNullClassLoader and kDefaultClassLoader)"))) + : Object("__JNI_BIND_DEFAULT_CLASS_LOADER__"), + parent_loader_(parent_loader), + supported_classes_(supported_class_set.supported_classes_) {} + + // Null classloader (no name needed). explicit constexpr ClassLoader( ParentLoader_ parent_loader, SupportedClassSet supported_class_set) - : parent_loader_(parent_loader), + __attribute__(( + enable_if(parent_loader == kNullClassLoader, + "You must provide a name for classloaders (except " + "kNullClassLoader and kDefaultClassLoader)"))) + : Object("__JNI_BIND_NULL_CLASS_LOADER__"), + parent_loader_(parent_loader), supported_classes_(supported_class_set.supported_classes_) {} + // TODO(b/143908983): Loaders should not be able to supply classes that their + // parents do. explicit constexpr ClassLoader( + const char* class_loader_name, ParentLoader_ parent_loader, SupportedClassSet supported_class_set) - : parent_loader_(kDefaultClassLoader), + + : Object(class_loader_name), + parent_loader_(parent_loader), supported_classes_(supported_class_set.supported_classes_) {} bool constexpr operator==( @@ -2195,6 +2218,12 @@ class ClassLoader { } }; +// Note: Null is chosen, not default, because LoadedBy requires a syntax like +// LoadedBy{ClassLoader{"kClass"}} (using the CTAD loader type below), but +// we want to prevent explicit usage of a default loader (as it makes no sense). +ClassLoader(const char*) + -> ClassLoader>; + template ClassLoader(ParentLoader_ parent_loader, SupportedClassSet supported_classes) @@ -2301,6 +2330,33 @@ static constexpr bool ValsEqual_cr_v = } // namespace jni::metaprogramming +#include +#include + +namespace jni::metaprogramming { + +template +struct Replace { + template + struct Helper; + + template + struct Helper> { + static constexpr std::string_view val = StringAsType<( + str[Is] == sought_char ? new_char : str[Is])...>::chars_as_sv; + }; + + template + static constexpr std::string_view val = + Helper>::val; +}; + +template +static constexpr auto Replace_v = + Replace::template val; + +} // namespace jni::metaprogramming + namespace jni::metaprogramming { template @@ -2396,6 +2452,47 @@ struct Constants { } // namespace jni::metaprogramming +namespace jni { + +// jobject. +template <> +struct LifecycleHelper + : public LifecycleLocalBase { + template + static inline jobject Construct(jclass clazz, jmethodID ctor_method, + CtorArgs&&... ctor_args) { + return JniEnv::GetEnv()->NewObject(clazz, ctor_method, ctor_args...); + } +}; + +template <> +struct LifecycleHelper + : public LifecycleGlobalBase { + template + static inline jobject Construct(jclass clazz, jmethodID ctor_method, + CtorArgs&&... ctor_args) { + using Local = LifecycleHelper; + + jobject local_object = Local::Construct( + clazz, ctor_method, std::forward(ctor_args)...); + jobject global_object = Promote(local_object); + Local::Delete(local_object); + + return global_object; + } +}; + +// jclass. +template <> +struct LifecycleHelper + : public LifecycleLocalBase {}; + +template <> +struct LifecycleHelper + : public LifecycleGlobalBase {}; + +} // namespace jni + #include namespace jni { @@ -2757,6 +2854,61 @@ static constexpr std::string_view kInit{""}; } // namespace jni +namespace jni { + +struct LoaderTag {}; + +// Annotation for use in function and field declarations. When used as argument +// the underlying object must come from the same class loader. +template +struct LoadedBy : LoaderTag { + const ClassLoaderT class_loader_; + const ClassT class_; + + static_assert( + !std::is_same_v>, + "LoadedBy is not required for the default loader (it's implicit)."); + + constexpr LoadedBy(ClassLoaderT class_loader, ClassT clazz) + : class_loader_(class_loader), class_(clazz) {} +}; + +template +LoadedBy(ClassLoaderT, ClassT) -> LoadedBy; + +template +struct IsLoadedBy { + static constexpr bool val = false; +}; + +template +struct IsLoadedBy> { + static constexpr bool val = true; +}; + +template +static constexpr bool IsLoadedBy_v = IsLoadedBy::val; + +template +constexpr auto StripClassFromLoadedBy(T val) { + if constexpr (IsLoadedBy_v) { + return val.class_; + } else { + return val; + } +} + +template +constexpr auto StripClassLoaderFromLoadedBy(T val) { + if constexpr (IsLoadedBy_v) { + return val.class_loader_; + } else { + return val; + } +} + +} // namespace jni + #include #include #include @@ -3015,6 +3167,33 @@ static constexpr bool Detect_v = Detect::template val; } // namespace jni::metaprogramming +namespace jni { + +template <> +struct LifecycleHelper + : public LifecycleLocalBase { + static inline jstring Construct(const char* chars) { + return jni::JniEnv::GetEnv()->NewStringUTF(chars); + } +}; + +template <> +struct LifecycleHelper + : public LifecycleGlobalBase { + template + static inline jstring Construct(const char* chars) { + using Local = LifecycleHelper; + + jstring local_string = Local::Construct(chars); + jstring global_string = Promote(local_string); + Local::Delete(local_string); + + return global_string; + } +}; + +} // namespace jni + #include namespace jni { @@ -3355,7 +3534,7 @@ struct SelectorStaticInfo { // Strangely, the compiler refuses to peer through Val and loses the // constexpr-ness (i.e std::decay_t; is not a constant // expression). - static constexpr inline const auto& Val() { + static constexpr inline auto Val() { if constexpr (Selector::kRank == 0) { return Selector::Val(); } else { @@ -3530,22 +3709,28 @@ struct JniT { : (class_loader_idx_ == kNoIdx ? true : false)); } - static constexpr const auto& GetClassLoader() { - if constexpr (class_loader_idx_ != kNoIdx) { - return std::get(jvm_v_.class_loaders_); + static constexpr auto GetClass() { + if constexpr (class_idx_ != kNoIdx) { + return StripClassFromLoadedBy( + std::get(GetClassLoader().supported_classes_)); } else { - return class_loader_v_; + return StripClassFromLoadedBy(class_v_); } } - static constexpr const auto& GetClass() { - if constexpr (class_idx_ != kNoIdx) { - return std::get(GetClassLoader().supported_classes_); + static constexpr auto GetClassLoader() { + if constexpr (class_loader_idx_ != kNoIdx) { + return StripClassLoaderFromLoadedBy( + std::get(jvm_v_.class_loaders_)); } else { - return class_v_; + return StripClassLoaderFromLoadedBy(class_loader_v_); } } + static constexpr std::string_view kName{GetClass().name_}; + static constexpr std::string_view kNameWithDots{ + metaprogramming::Replace_v}; + static constexpr const auto& GetJvm() { return jvm_v_; } static constexpr auto GetStatic() { @@ -3672,7 +3857,7 @@ class ArrayView { using reference = SpanType&; Iterator(SpanType* ptr, std::size_t size, std::size_t idx) - : ptr_(ptr), size_(size), idx_(idx) {} + : size_(size), ptr_(ptr), idx_(idx) {} Iterator& operator++() { idx_++; @@ -4068,13 +4253,14 @@ class ClassRef { return return_value.LoadAndMaybeInit([]() { GetDefaultLoadedClassList().push_back(&return_value); - return JniHelper::PromoteLocalToGlobalClass( - JniHelper::FindClass(JniT::GetClass().name_)); + return static_cast( + LifecycleHelper::Promote( + JniHelper::FindClass(JniT::kName.data()))); }); } else { // For non default classloader, storage in class member. return class_ref_.LoadAndMaybeInit([=]() { - return LoadClassFromObject(JniT::GetClass().name_, + return LoadClassFromObject(JniT::kNameWithDots.data(), optional_object_to_build_loader_from); }); } @@ -4086,7 +4272,8 @@ class ClassRef { static void MaybeReleaseClassRef() { class_ref_.Reset([](jclass maybe_loaded_class) { - JniHelper::ReleaseClass(maybe_loaded_class); + LifecycleHelper::Delete( + maybe_loaded_class); }); } @@ -4131,15 +4318,18 @@ static inline jclass LoadClassFromObject(const char* name, jobject object_ref) { JniHelper::GetMethodID(java_lang_class_loader_jclass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - jstring name_string = JniHelper::NewLocalString(name); + jstring name_string = + LifecycleHelper::Construct(name); jobject local_jclass_of_correct_loader = InvokeHelper::Invoke(object_ref_class_loader_jobject, nullptr, load_class_jmethod, name_string); jobject promote_jclass_of_correct_loader = - JniHelper::PromoteLocalToGlobalObject(local_jclass_of_correct_loader); + LifecycleHelper::Promote( + local_jclass_of_correct_loader); - JniHelper::DeleteLocalObject(object_ref_class_loader_jobject); + LifecycleHelper::Delete( + object_ref_class_loader_jobject); return static_cast(promote_jclass_of_correct_loader); } @@ -4441,7 +4631,7 @@ class LocalArray ~LocalArray() { if (Base::object_ref_) { - JniHelper::DeleteLocalObject(Base::object_ref_); + LifecycleHelper::Delete(Base::object_ref_); } } }; @@ -4478,7 +4668,8 @@ template ; - static constexpr const auto& Val() { + static constexpr auto Val() { if constexpr (kIdType == IdType::CLASS) { - return root; + return Class(); } else if constexpr (kIdType == IdType::STATIC_FIELD) { static_assert(idx != kNoIdx); - return std::get(root.static_.fields_).raw_; + return std::get(Class().static_.fields_).raw_; } else if constexpr (kIdType == IdType::STATIC_OVERLOAD_SET) { // Overload (no such thing as static constructor). static_assert(idx != kNoIdx); - return std::get(root.static_.methods_); + return std::get(Class().static_.methods_); } else if constexpr (kIdType == IdType::STATIC_OVERLOAD) { // Overload (no such thing as static constructor). static_assert(idx != kNoIdx); return std::get( - std::get(root.static_.methods_).invocations_); + std::get(Class().static_.methods_).invocations_); } else if constexpr (kIdType == IdType::STATIC_OVERLOAD_PARAM) { // Overload. if constexpr (tertiary_idx == kNoIdx) { // Return. return std::get( - std::get(root.static_.methods_).invocations_) + std::get(Class().static_.methods_).invocations_) .return_.raw_; } else { return std::get( std::get( - std::get(root.static_.methods_).invocations_) + std::get(Class().static_.methods_).invocations_) .params_.values_); } } else if constexpr (kIdType == IdType::OVERLOAD_SET) { if constexpr (idx == kNoIdx) { // Constructor. - return root.constructors_; + return Class().constructors_; } else { // Overload. - return std::get(root.methods_); + return std::get(Class().methods_); } } else if constexpr (kIdType == IdType::OVERLOAD) { if constexpr (idx == kNoIdx) { // Constructor. - return std::get(root.constructors_); + return std::get(Class().constructors_); } else { // Overload. return std::get( - std::get(root.methods_).invocations_); + std::get(Class().methods_).invocations_); } } else if constexpr (kIdType == IdType::OVERLOAD_PARAM) { if constexpr (idx == kNoIdx) { // Constructor. if constexpr (tertiary_idx == kNoIdx) { // Return. - return root; + return Class(); } else { // Overload return. return std::get( - std::get(root.constructors_).params_.values_); + std::get(Class().constructors_).params_.values_); } } else { // Overload. if constexpr (tertiary_idx == kNoIdx) { // Return. return std::get( - std::get(root.methods_).invocations_) + std::get(Class().methods_).invocations_) .return_.raw_; } else { return std::get( - std::get(std::get(root.methods_).invocations_) + std::get( + std::get(Class().methods_).invocations_) .params_.values_); } } @@ -4576,71 +4768,31 @@ struct Id { static_assert(idx != kNoIdx); return std::get( - std::get(root.static_methods_).invocations_); + std::get(Class().static_methods_).invocations_); } else if constexpr (kIdType == IdType::FIELD) { static_assert(idx != kNoIdx); - return std::get(root.fields_).raw_; + return std::get(Class().fields_).raw_; } } // Returns root for constructor, else return's "raw_" member. static constexpr auto Materialize() { if constexpr (kIdType == IdType::STATIC_OVERLOAD) { - static_assert(kIdx != kNoIdx); - - // Overload return value. - return std::get( - std::get(root.static_.methods_).invocations_) - .return_.raw_; - } else if constexpr (kIdType == IdType::STATIC_OVERLOAD_PARAM) { - static_assert(kIdx != kNoIdx); - - if constexpr (tertiary_idx == kNoIdx) { - // Overload return value. - return std::get( - std::get(root.static_.methods_).invocations_) - .return_.raw_; - } else { - // Overload. - return std::get( - std::get( - std::get(root.static_.methods_).invocations_) - .params_.values_); - } - } - - else if constexpr (kIdType == IdType::OVERLOAD) { + return Val().return_.raw_; + } else if constexpr (kIdType == IdType::OVERLOAD) { if constexpr (kIdx == kNoIdx) { // Constructor. - return root; + return Class(); } else { // Overload return value. - return std::get( - std::get(root.methods_).invocations_) - .return_.raw_; - } - } else if constexpr (kIdType == IdType::OVERLOAD_PARAM) { - if constexpr (kIdx == kNoIdx) { - // Constructor. - return root; - } else if constexpr (tertiary_idx == kNoIdx) { - // Overload return value. - return std::get( - std::get(root.methods_).invocations_) - .return_.raw_; - } else { - // Overload. - return std::get( - std::get(std::get(root.methods_).invocations_) - .params_.values_); + return Val().return_.raw_; } - } else if constexpr (kIdType == IdType::FIELD) { - return std::get(root.fields_).raw_; - } else if constexpr (kIdType == IdType::STATIC_FIELD) { - return std::get(root.static_.fields_).raw_; - } else { + } else if constexpr (kIdType == IdType::STATIC_OVERLOAD_SET || + kIdType == IdType::OVERLOAD_SET) { // Not implemented. return Void{}; + } else { + return Val(); } } @@ -4655,15 +4807,16 @@ struct Id { using CDecl = CDecl_t>; static constexpr std::size_t kRank = Rankifier::Rank(Val()); - static constexpr Return kObjectWhenConstructed{root}; static constexpr const char* Name() { - if constexpr (kIdType == IdType::STATIC_OVERLOAD_SET) { + if constexpr (kIdType == IdType::CLASS) { + return Class().name_; + } else if constexpr (kIdType == IdType::STATIC_OVERLOAD_SET) { return Val().name_; } else if constexpr (kIdType == IdType::STATIC_OVERLOAD) { return Id::Name(); } else if constexpr (kIdType == IdType::STATIC_FIELD) { - return std::get(root.static_.fields_).name_; + return std::get(Class().static_.fields_).name_; } else if constexpr (kIdType == IdType::OVERLOAD_SET && idx == kNoIdx) { return ""; } else if constexpr (kIdType == IdType::OVERLOAD_SET) { @@ -4671,11 +4824,14 @@ struct Id { } else if constexpr (kIdType == IdType::OVERLOAD) { return Id::Name(); } else if constexpr (kIdType == IdType::FIELD) { - return std::get(root.fields_).name_; + return std::get(Class().fields_).name_; } else { return "NO_NAME"; } } + static constexpr std::string_view kName = Name(); + static constexpr std::string_view kNameUsingDots = + metaprogramming::Replace_v; static constexpr std::size_t NumParams() { if constexpr (kIdType == IdType::OVERLOAD || @@ -5618,6 +5774,7 @@ struct InvokeHelper 1), jobject>, kRank, true> { namespace jni { class LocalString; +class GlobalString; template @@ -5638,8 +5795,8 @@ struct Proxy; + using AsArg = std::tuple>; template using AsReturn = typename Helper::type; @@ -5650,7 +5807,8 @@ struct Proxy::template value || IsConvertibleKey::template value || IsConvertibleKey::template value || - IsConvertibleKey::template value; + IsConvertibleKey::template value || + std::is_same_v || std::is_same_v; // These leak local instances of strings. Usually, RAII mechanisms would // correctly release local instances, but here we are stripping that so it can @@ -5664,11 +5822,26 @@ struct Proxy>> static jstring ProxyAsArg(T s) { if constexpr (std::is_same_v) { - return JniHelper::NewLocalString(s); + return LifecycleHelper::Construct(s); } else { - return JniHelper::NewLocalString(s.data()); + return LifecycleHelper::Construct( + s.data()); } } + + template || + std::is_same_v>> + static jstring ProxyAsArg(T& t) { + return jstring{t}; + } + + template || + std::is_same_v>> + static jstring ProxyAsArg(T&& t) { + return t.Release(); + } }; } // namespace jni @@ -5872,7 +6045,7 @@ struct Proxy>> : public ProxyBase { using AsDecl = std::tuple; - using AsArg = std::tuple>; + using AsArg = std::tuple, LoaderTag>; template struct ContextualViabilityHelper { @@ -5896,9 +6069,10 @@ struct Proxy struct Helper { static constexpr auto kClass{Id::Val()}; + static constexpr auto kClassLoader{Id::JniT::GetClassLoader()}; // TODO(b/174272629): Class loaders should also be enforced. - using type = LocalObject; + using type = LocalObject; }; template @@ -6137,7 +6311,6 @@ struct OverloadRef { if constexpr (IdT::kIsStatic) { return jni::JniHelper::GetStaticMethodID(clazz, IdT::Name(), Signature_v.data()); - } else { return jni::JniHelper::GetMethodID(clazz, IdT::Name(), Signature_v.data()); @@ -6157,9 +6330,10 @@ struct OverloadRef { object, clazz, mthd, Proxy_t::ProxyAsArg(std::forward(params))...); } else if constexpr (IdT::kIsConstructor) { - return ReturnProxied{JniHelper::NewLocalObject( - clazz, mthd, - Proxy_t::ProxyAsArg(std::forward(params))...)}; + return ReturnProxied{ + LifecycleHelper::Construct( + clazz, mthd, + Proxy_t::ProxyAsArg(std::forward(params))...)}; } else { return static_cast( InvokeHelper::Invoke( @@ -6253,6 +6427,28 @@ class FieldRef { } // namespace jni +namespace jni::metaprogramming { + +template +struct StringContains { + template + struct Helper; + + template + struct Helper> { + static constexpr bool val = ((str[Is] == sought) || ...); + }; + + template + static constexpr bool val = + Helper>::val; +}; + +template +static constexpr bool StringContains_v = + StringContains::template val; + +} // namespace jni::metaprogramming #include #include @@ -6359,16 +6555,18 @@ struct OverloadSelector { namespace jni { -template +template class JvmRef; class ThreadGuard; +struct ThreadLocalGuardDestructor; // Helper for JvmRef to enforce correct sequencing of getting and setting // process level static fo JavaVM*. class JvmRefBase { protected: friend class ThreadGuard; + friend class ThreadLocalGuardDestructor; JvmRefBase(JavaVM* vm) { process_level_jvm_.store(vm); } ~JvmRefBase() { process_level_jvm_.store(nullptr); } @@ -6379,6 +6577,27 @@ class JvmRefBase { static inline std::atomic process_level_jvm_ = nullptr; }; +// Designed to be the very last JniBind object to execute on the thread. +// Objects passed by move for lambdas will be destructed after any contents +// statements within their lambda, and `ThreadGuard` can't be moved into the +// lambda because its construction will be on the host thread. This static +// teardown guarantees a delayed destruction beyond any GlobalObject. +struct ThreadLocalGuardDestructor { + bool detach_thread_when_all_guards_released_ = false; + + // By calling this the compiler is obligated to perform initalisation. + void ForceDestructionOnThreadClose() {} + + ~ThreadLocalGuardDestructor() { + if (detach_thread_when_all_guards_released_) { + JavaVM* jvm = JvmRefBase::GetJavaVm(); + if (jvm) { + jvm->DetachCurrentThread(); + } + } + } +}; + // ThreadGuard attaches and detaches JNIEnv* objects on the creation of new // threads. All new threads which want to use JNI Wrapper must hold a // ThreadGuard beyond the scope of all created objects. If the ThreadGuard @@ -6387,9 +6606,6 @@ class ThreadGuard { public: ~ThreadGuard() { thread_guard_count_--; - if (thread_guard_count_ == 0 && detach_thread_when_all_guards_released_) { - JvmRefBase::GetJavaVm()->DetachCurrentThread(); - } } ThreadGuard(ThreadGuard&) = delete; @@ -6401,6 +6617,8 @@ class ThreadGuard { // This constructor must *never* be called before a |JvmRef| has been // constructed. It depends on static setup from |JvmRef|. [[nodiscard]] ThreadGuard() { + thread_local_guard_destructor.ForceDestructionOnThreadClose(); + // Nested ThreadGuards should be permitted in the same way mutex locks are. thread_guard_count_++; if (thread_guard_count_ != 1) { @@ -6417,16 +6635,15 @@ class ThreadGuard { metaprogramming::FunctionTraitsArg_t; const int code = vm->GetEnv(reinterpret_cast(&jni_env), JNI_VERSION_1_6); + if (code != JNI_OK) { using TypeForAttachment = metaprogramming::FunctionTraitsArg_t< decltype(&JavaVM::AttachCurrentThread), 1>; vm->AttachCurrentThread(reinterpret_cast(&jni_env), nullptr); - detach_thread_when_all_guards_released_ = true; - } else { - detach_thread_when_all_guards_released_ = false; + thread_local_guard_destructor.detach_thread_when_all_guards_released_ = + true; } - // Why not store this locally to ThreadGuard? // // JNIEnv is thread local static, and the context an object is built from @@ -6439,7 +6656,8 @@ class ThreadGuard { private: static inline thread_local int thread_guard_count_ = 0; - static inline thread_local bool detach_thread_when_all_guards_released_; + static inline thread_local ThreadLocalGuardDestructor + thread_local_guard_destructor{}; }; // Represents a runtime instance of a Java Virtual Machine. @@ -6512,8 +6730,9 @@ class JvmRef : public JvmRefBase { auto& default_loaded_class_list = GetDefaultLoadedClassList(); for (metaprogramming::DoubleLockedValue* maybe_loaded_class_id : default_loaded_class_list) { - maybe_loaded_class_id->Reset( - [](jclass clazz) { JniHelper::ReleaseClass(clazz); }); + maybe_loaded_class_id->Reset([](jclass clazz) { + LifecycleHelper::Delete(clazz); + }); } default_loaded_class_list.clear(); @@ -6547,8 +6766,78 @@ class JvmRef : public JvmRefBase { const ThreadGuard thread_guard_ = {}; }; +JvmRef(JNIEnv*) -> JvmRef; +JvmRef(JavaVM*) -> JvmRef; + } // namespace jni +namespace jni { + +// Creates an additional reference to the underlying object. +// When used for local, presumes local, for global, presumes global. +struct CreateCopy {}; + +// This tag allows the constructor to promote underlying jobject for you. +struct PromoteToGlobal {}; + +// CAUTION: This tag assume the underlying jobject has been pinned as a global. +// This is atypical when solely using JNI Bind, use with caution. +struct AdoptGlobal {}; + +template +struct LocalCtor : public CrtpBase { + using CrtpBase::CrtpBase; +}; + +// Augments a a local constructor of type |Span| (created by |LoadedBy|). +// Inheritance and ctor inheritance will continue through |Base|. +template +struct LocalCtor + : public LocalCtor { + using Base = LocalCtor; + using Base::Base; + using Span = typename JniT::SpanType; + using LifecycleT = LifecycleHelper; + + // "Copy" constructor: Additional reference to object will be created. + LocalCtor(CreateCopy, ViableSpan object) + : Base(static_cast( + LifecycleT::NewReference(static_cast(object)))) {} + + // "Wrap" constructor: Object released at end of scope. + LocalCtor(ViableSpan object) : Base(static_cast(object)) {} +}; + +template +struct GlobalCtor : public CrtpBase { + using CrtpBase::CrtpBase; +}; + +// Augments a a local constructor of type |Span| (created by |LoadedBy|). +// Inheritance and ctor inheritance will continue through |Base|. +template +struct GlobalCtor + : public GlobalCtor { + using Base = GlobalCtor; + using Base::Base; + using Span = typename JniT::SpanType; + using LifecycleT = LifecycleHelper; + + // "Copy" constructor: Additional reference to object will be created. + GlobalCtor(CreateCopy, ViableSpan object) + : Base(static_cast(LifecycleT::NewReference(object))) {} + + // "Promote" constructor: Creates new global, frees |obj| (standard). + explicit GlobalCtor(PromoteToGlobal, ViableSpan obj) + : Base(LifecycleT::Promote(obj)) {} + + // "Adopts" a global by wrapping a jstring (non-standard). + explicit GlobalCtor(AdoptGlobal, ViableSpan obj) : Base(obj) {} +}; + +} // namespace jni #include #include @@ -6576,6 +6865,9 @@ class ObjectRef JniT::class_loader_v .template SupportedDirectlyOrIndirectly(), "This class is not directly or indirectly supported by this loader."); + static_assert(!metaprogramming::StringContains_v, + "Use '/', not '.' in class names (for maximum) portability."); + using RefBase = RefBase; ObjectRef() = delete; @@ -6745,16 +7037,18 @@ template class LocalObject - : public ObjectRefBuilder_t { + : public LocalCtor, + JniT, + jobject> { public: - using ObjectRefT = ObjectRefBuilder_t; - using ObjectRefT::ObjectRefT; - - LocalObject(jobject object) : ObjectRefT(object) {} + using Base = + LocalCtor, + JniT, jobject>; + using Base::Base; template LocalObject(LocalObject&& rhs) - : ObjectRefT(rhs.Release()) { + : Base(rhs.Release()) { static_assert( std::string_view(class_v.name_) == std::string_view(class_v_.name_), "You are attempting to initialise a LocalObject from another class " @@ -6762,8 +7056,8 @@ class LocalObject } ~LocalObject() { - if (ObjectRefT::object_ref_) { - JniHelper::DeleteLocalObject(ObjectRefT::object_ref_); + if (Base::object_ref_) { + LifecycleHelper::Delete(Base::object_ref_); } } }; @@ -6782,21 +7076,25 @@ namespace jni { // In order to use a string in memory (as opposed to only using it for function // arguments), "Pin" the string. // -// Like jobjects, jstrings can be either local or global with the same ownership -// semantics. -class LocalString : public StringRefBase { +// Like |jobjects|, |jstring|s can be either local or global with the same +// ownership semantics. +class LocalString + : public LocalCtor< + StringRefBase, + JniT, + jobject, jstring> { public: - using StringRefBase::StringRefBase; - friend class StringRefBase; + friend StringRefBase; - // Constructors to support the that jstring and jobject are interchangeable. - LocalString(jobject java_string_as_object) - : StringRefBase( - static_cast(java_string_as_object)) {} + using Base = LocalCtor< + StringRefBase, + JniT, jobject, + jstring>; + using Base::Base; LocalString( LocalObject&& obj) - : StringRefBase(static_cast(obj.Release())) {} + : Base(static_cast(obj.Release())) {} // Returns a StringView which possibly performs an expensive pinning // operation. String objects can be pinned multiple times. @@ -6805,7 +7103,9 @@ class LocalString : public StringRefBase { private: // Invoked through CRTP on dtor. void ClassSpecificDeleteObjectRef(jstring object_ref) { - JniHelper::DeleteLocalObject(object_ref); + if (Base::object_ref_) { + LifecycleHelper::Delete(object_ref); + } } }; @@ -6816,29 +7116,34 @@ namespace jni { template class ClassLoaderRef; -// Pass this tag to allow Global object's constructor to promote for you. -struct PromoteToGlobal {}; - -// WARNING: Avoid using a global jobject in a constructor unless you are -// confident the underlying jobject has been pinned as a global. -struct AdoptGlobal {}; - template class GlobalObject - : public ObjectRefBuilder_t { + : public GlobalCtor, + JniT, + + jobject> { public: template friend class ClassLoaderRef; - using ObjectRefT = ObjectRefBuilder_t; - using ObjectRefT::ObjectRefT; + using Base = + GlobalCtor, + JniT, jobject>; + using Base::Base; + + using LifecycleT = LifecycleHelper; + + // Constructs a new global object using the local object's default + // constructor. + explicit GlobalObject() + : GlobalObject(LifecycleT::Promote( + LocalObject{}.Release())) {} template GlobalObject(LocalObject&& local_object) - : ObjectRefT( - JniHelper::PromoteLocalToGlobalObject(jobject{local_object})) { + : Base(LifecycleT::Promote(local_object.Release())) { static_assert( std::string_view(class_v.name_) == std::string_view(class_v_.name_), "You are attempting to initialise a LocalObject from another class " @@ -6847,45 +7152,33 @@ class GlobalObject template GlobalObject(GlobalObject&& rhs) - : ObjectRefT(rhs.Release()) { + : Base(rhs.Release()) { static_assert( std::string_view(class_v.name_) == std::string_view(class_v_.name_), "You are attempting to initialise a GlobalObject from another class " "type"); } - // Constructs a new object using the local object's default constructor. - explicit GlobalObject() - : GlobalObject(JniHelper::PromoteLocalToGlobalObject( - LocalObject{}.Release())) {} - // Constructs a new object using arg based ctor. template explicit GlobalObject(Ts&&... ts) - : GlobalObject(JniHelper::PromoteLocalToGlobalObject( - LocalObject{ + : GlobalObject( + LifecycleT::Promote(LocalObject{ std::forward(ts)...} - .Release())) {} - - // Constructs a global promoting a local object to a global (standard). - explicit GlobalObject(PromoteToGlobal, jobject obj) - : ObjectRefT(JniHelper::PromoteLocalToGlobalObject(obj)) {} - - // Constructs a global by wrapping a jobject (non-standard). - explicit GlobalObject(AdoptGlobal, jobject obj) : ObjectRefT(obj) {} + .Release())) {} GlobalObject(const GlobalObject&) = delete; GlobalObject(GlobalObject&& rhs) = default; ~GlobalObject() { - if (ObjectRefT::object_ref_) { - JniHelper::DeleteGlobalObject(ObjectRefT::object_ref_); + if (Base::object_ref_) { + LifecycleT::Delete(Base::object_ref_); } } private: // Construction from jobject requires |PromoteToGlobal| or |AdoptGlobal|. - explicit GlobalObject(jobject obj) : ObjectRefT(obj) {} + explicit GlobalObject(jobject obj) : Base(obj) {} }; template @@ -6927,6 +7220,8 @@ class ClassLoaderRef template [[nodiscard]] auto BuildLocalObject(Params&&... params) { + using JniClassT = JniT; + using IdClassT = Id; static_assert( !(ParentLoaderForClass() == kNullClassLoader), "Cannot build this class with this loader."); @@ -6937,10 +7232,11 @@ class ClassLoaderRef 0>>::PrimeJClassFromClassLoader([=]() { // Prevent the object (which is a runtime instance of a class) from // falling out of scope so it is not released. - LocalObject loaded_class = (*this)("loadClass", class_v.name_); + LocalObject loaded_class = + (*this)("loadClass", IdClassT::kNameUsingDots); // We only want to create global references if we are actually going - // to use it them so that they do not leak. + // to use them so that they do not leak. jclass test_class{ static_cast(static_cast(loaded_class))}; return static_cast(JniEnv::GetEnv()->NewGlobalRef(test_class)); @@ -6954,11 +7250,10 @@ class ClassLoaderRef template [[nodiscard]] auto BuildGlobalObject(Params&&... params) { - // TODO(b/174256299): Promotion of locals to globals is clumsy. LocalObject obj = BuildLocalObject(std::forward(params)...); jobject promoted_local = - JniHelper::PromoteLocalToGlobalObject(obj.Release()); + LifecycleHelper::Promote(obj.Release()); return GlobalObject(), @@ -7044,7 +7339,7 @@ class LocalClassLoader : public ClassLoaderRef { ~LocalClassLoader() { if (Base::object_ref_) { - JniHelper::DeleteLocalObject(Base::object_ref_); + LifecycleHelper::Delete(Base::object_ref_); } } @@ -7103,32 +7398,43 @@ class LocalArray namespace jni { -class GlobalString : public StringRefBase { +class GlobalString + : public GlobalCtor< + StringRefBase, + JniT, + jobject, jstring> { public: - using StringRefBase::StringRefBase; friend class StringRefBase; - GlobalString(jobject java_string_as_object) - : StringRefBase(JniHelper::PromoteLocalToGlobalString( - static_cast(java_string_as_object))) {} + using Base = GlobalCtor< + StringRefBase, + JniT, jobject, + jstring>; + using Base::Base; + + using LifecycleT = LifecycleHelper; GlobalString(GlobalObject&& global_string) - : StringRefBase( - static_cast(global_string.Release())) {} + : Base(static_cast(global_string.Release())) {} GlobalString(LocalString&& local_string) - : StringRefBase( - JniHelper::PromoteLocalToGlobalString(local_string.Release())) {} + : Base(LifecycleT::Promote(local_string.Release())) {} // Returns a StringView which possibly performs an expensive pinning // operation. String objects can be pinned multiple times. UtfStringView Pin() { return {RefBaseTag::object_ref_}; } private: + // Construction from jstring requires |PromoteToGlobal| or |AdoptGlobal|. + explicit GlobalString(jstring obj) : Base(obj) {} + + // Construction from jstring requires |PromoteToGlobal| or |AdoptGlobal|. + explicit GlobalString(jobject obj) : Base(static_cast(obj)) {} + // Invoked through CRTP on dtor. void ClassSpecificDeleteObjectRef(jstring object_ref) { - JniHelper::DeleteGlobalString(object_ref); + LifecycleT::Delete(object_ref); } }; @@ -7140,11 +7446,11 @@ template class GlobalClassLoader : public ClassLoaderRef { public: using Base = ClassLoaderRef; + using LifecycleT = LifecycleHelper; - // TODO(b/174256299): Make "global" from jobject more intuitive. GlobalClassLoader(jobject class_loader) : ClassLoaderRef( - JniHelper::PromoteLocalToGlobalObject(class_loader)) {} + LifecycleT::Promote(class_loader)) {} template GlobalClassLoader(GlobalClassLoader&& rhs) @@ -7152,7 +7458,7 @@ class GlobalClassLoader : public ClassLoaderRef { ~GlobalClassLoader() { if (Base::object_ref_) { - JniHelper::DeleteGlobalObject(Base::object_ref_); + LifecycleT::Delete(Base::object_ref_); } } }; diff --git a/jni_bind_release_leader.inc b/jni_bind_release_leader.inc index 4c4ee9fe..39eb3708 100644 --- a/jni_bind_release_leader.inc +++ b/jni_bind_release_leader.inc @@ -15,7 +15,7 @@ */ /******************************************************************************* - * JNI Bind Version 0.9.3. + * JNI Bind Version 0.9.4. * Alpha Public Release. ******************************************************************************** * This header is the single header version which you can use to quickly test or