diff options
| author | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2024-07-17 16:35:30 +0200 |
|---|---|---|
| committer | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2025-11-28 22:58:44 +0100 |
| commit | d045e2d2b604152450a45846fc87cb53d89bf50a (patch) | |
| tree | faa2e6b0710226b211be57ab9ad08df219907eb0 /src/corelib/kernel/qjniobject.h | |
| parent | 7ceacc9a7c24be37a0251a7011fe7fe45e911cf2 (diff) | |
JNI: Allow clients to opt into their own exception handling
Detect whether the return type of a call is similar to std::expected.
This indicates that the caller wants to handle exceptions explicitly, so
in case of error, we pass it through the the std::expected-like value.
We still clear the exception state. The caller is responsible for
freeing the jthrowable local reference.
For this to work, we have to propagate errors through to the outer-most
function. This includes allowing QJniObject to create QJniEnvironment
instances that don't implicitly return a clean environment in the
destructor. As long as we can call checkAndClearExceptions() in the
public functions (unless the caller opts in), this should not break any
existing code that expects QJniObject to implicitly clear exceptions.
Add tests for all overloads to make sure that exceptions (from wrong
class or method names, and thrown in methods) are caught.
Add "Impl" helpers that do not handle exceptions if instantiated
accordingly, and call those if we have to maintain compatibility in
public functions while also enabling opt-in handling for modern APIs.
Add tests that show that we can now handle exceptions ourselves for all
public QJniObject APIs. If possible, we build the test with C++23 so
that we can use std::expected; otherwise, try to use tl::expected by
downloading the header-only implementation from github; and failing
that, use a minimal implementation of a type that could be used instead
and makes the test pass.
Fixes: QTBUG-93800
Fixes: QTBUG-119791
Task-number: QTBUG-92952
Change-Id: I1cfac37ac9af8fd421bc0af030a1d448dd0e259e
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
Diffstat (limited to 'src/corelib/kernel/qjniobject.h')
| -rw-r--r-- | src/corelib/kernel/qjniobject.h | 523 |
1 files changed, 334 insertions, 189 deletions
diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index 236a49544be..06dfc328b4b 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -27,10 +27,29 @@ struct StoresGlobalRefTest<T, std::void_t<decltype(std::declval<T>().object())>> : std::is_same<decltype(std::declval<T>().object()), jobject> {}; -template <typename ...Args> +// detect if a type is std::expected-like +template <typename R, typename = void> +struct CallerHandlesException : std::false_type { + using value_type = R; +}; +template <typename R> +struct CallerHandlesException<R, std::void_t<typename R::unexpected_type, + typename R::value_type, + typename R::error_type>> : std::true_type +{ + using value_type = typename R::value_type; +}; + +template <typename ReturnType> +static constexpr bool callerHandlesException = CallerHandlesException<ReturnType>::value; + +template <typename Ret, typename ...Args> struct LocalFrame { + using ReturnType = Ret; + mutable JNIEnv *env; bool hasFrame = false; + explicit LocalFrame(JNIEnv *env = nullptr) noexcept : env(env) { @@ -52,9 +71,12 @@ struct LocalFrame { env = QJniEnvironment::getJniEnv(); return env; } - bool checkAndClearExceptions() + bool checkAndClearExceptions() const { - return env ? QJniEnvironment::checkAndClearExceptions(env) : false; + if constexpr (callerHandlesException<ReturnType>) + return false; + else + return QJniEnvironment::checkAndClearExceptions(jniEnv()); } template <typename T> auto convertToJni(T &&value) @@ -79,13 +101,46 @@ struct LocalFrame { using Type = q20::remove_cvref_t<T>; return QtJniTypes::Traits<Type>::convertFromJni(std::move(object)); } + + template <typename T> + auto convertFromJni(jobject object); + + auto makeResult() + { + if constexpr (callerHandlesException<ReturnType>) { + JNIEnv *env = jniEnv(); + if (env->ExceptionCheck()) { + jthrowable exception = env->ExceptionOccurred(); + env->ExceptionClear(); + return ReturnType(typename ReturnType::unexpected_type(exception)); + } + return ReturnType(); + } else { + checkAndClearExceptions(); + } + } + + template <typename Value> + auto makeResult(Value &&value) + { + if constexpr (callerHandlesException<ReturnType>) { + auto maybeValue = makeResult(); + if (maybeValue) + return ReturnType(std::forward<Value>(value)); + return std::move(maybeValue); + } else { + checkAndClearExceptions(); + return std::forward<Value>(value); + } + } }; } } class Q_CORE_EXPORT QJniObject { - template <typename ...Args> using LocalFrame = QtJniTypes::Detail::LocalFrame<Args...>; + template <typename Ret, typename ...Args> using LocalFrame + = QtJniTypes::Detail::LocalFrame<Ret, Args...>; public: QJniObject(); @@ -97,12 +152,12 @@ public: #endif > explicit QJniObject(const char *className, Args &&...args) - : QJniObject(LocalFrame<Args...>{}, className, std::forward<Args>(args)...) + : QJniObject(LocalFrame<QJniObject, Args...>{}, className, std::forward<Args>(args)...) { } private: template<typename ...Args> - explicit QJniObject(LocalFrame<Args...> localFrame, const char *className, Args &&...args) + explicit QJniObject(LocalFrame<QJniObject, Args...> localFrame, const char *className, Args &&...args) : QJniObject(className, QtJniTypes::constructorSignature<Args...>().data(), localFrame.convertToJni(std::forward<Args>(args))...) { @@ -130,13 +185,23 @@ public: void swap(QJniObject &other) noexcept { d.swap(other.d); } - template<typename Class, typename ...Args> - static inline QJniObject construct(Args &&...args) + template<typename Class, typename ...Args +#ifndef Q_QDOC + , QtJniTypes::IfValidSignatureTypes<Class, Args...> = true +#endif + > + static inline auto construct(Args &&...args) { - LocalFrame<Args...> frame; - return QJniObject(QtJniTypes::Traits<Class>::className().data(), - QtJniTypes::constructorSignature<Args...>().data(), - frame.convertToJni(std::forward<Args>(args))...); + LocalFrame<Class, Args...> frame; + jclass clazz = QJniObject::loadClassKeepExceptions(QtJniTypes::Traits<Class>::className().data(), + frame.jniEnv()); + auto res = clazz + ? QJniObject(clazz, QtJniTypes::constructorSignature<Args...>().data(), + frame.convertToJni(std::forward<Args>(args))...) + : QtJniTypes::Detail::callerHandlesException<Class> + ? QJniObject(Qt::Initialization::Uninitialized) + : QJniObject(); + return frame.makeResult(std::move(res)); } jobject object() const; @@ -149,47 +214,49 @@ public: jclass objectClass() const; QByteArray className() const; - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<Ret> = true + , QtJniTypes::IfValidFieldType<ReturnType> = true #endif > auto callMethod(const char *methodName, const char *signature, Args &&...args) const { - LocalFrame<Args...> frame(jniEnv()); + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<ReturnType, Args...> frame(jniEnv()); + jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); + if constexpr (QtJniTypes::isObjectType<Ret>()) { - return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, - frame.convertToJni(std::forward<Args>(args))...)); + return frame.makeResult(frame.template convertFromJni<Ret>(callObjectMethodImpl( + id, frame.convertToJni(std::forward<Args>(args))...)) + ); } else { - jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); if (id) { if constexpr (std::is_same_v<Ret, void>) { callVoidMethodV(frame.jniEnv(), id, frame.convertToJni(std::forward<Args>(args))...); - frame.checkAndClearExceptions(); } else { Ret res{}; callMethodForType<Ret>(frame.jniEnv(), res, object(), id, frame.convertToJni(std::forward<Args>(args))...); - if (frame.checkAndClearExceptions()) - res = {}; - return res; + return frame.makeResult(res); } } if constexpr (!std::is_same_v<Ret, void>) - return Ret{}; + return frame.makeResult(Ret{}); + else + return frame.makeResult(); } } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > auto callMethod(const char *methodName, Args &&...args) const { - constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - return callMethod<Ret>(methodName, signature.data(), std::forward<Args>(args)...); + constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>(); + return callMethod<ReturnType>(methodName, signature.data(), std::forward<Args>(args)...); } template <typename Ret, typename ...Args @@ -201,9 +268,11 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame(jniEnv()); - return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, + LocalFrame<Ret, Args...> frame(jniEnv()); + auto object = frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, frame.convertToJni(std::forward<Args>(args))...)); + frame.checkAndClearExceptions(); + return object; } QJniObject callObjectMethod(const char *methodName, const char *signature, ...) const; @@ -211,90 +280,93 @@ public: template <typename Ret = void, typename ...Args> static auto callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); + LocalFrame<Ret, Args...> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); return callStaticMethod<Ret>(clazz, methodName, signature, std::forward<Args>(args)...); } template <typename Ret = void, typename ...Args> static auto callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jmethodID id = clazz ? getMethodID(env, clazz, methodName, signature, true) + LocalFrame<Ret, Args...> frame; + jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, signature, true) : 0; return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<Ret> = true + , QtJniTypes::IfValidFieldType<ReturnType> = true #endif > static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args) { - LocalFrame<Args...> frame; + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<ReturnType, Args...> frame; if constexpr (QtJniTypes::isObjectType<Ret>()) { - return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodId, - frame.convertToJni(std::forward<Args>(args))...)); + return frame.makeResult(frame.template convertFromJni<Ret>(callStaticObjectMethod( + clazz, methodId, + frame.convertToJni(std::forward<Args>(args))...)) + ); } else { if (clazz && methodId) { if constexpr (std::is_same_v<Ret, void>) { callStaticMethodForVoid(frame.jniEnv(), clazz, methodId, frame.convertToJni(std::forward<Args>(args))...); - frame.checkAndClearExceptions(); } else { Ret res{}; callStaticMethodForType<Ret>(frame.jniEnv(), res, clazz, methodId, frame.convertToJni(std::forward<Args>(args))...); - if (frame.checkAndClearExceptions()) - res = {}; - return res; + return frame.makeResult(res); } } if constexpr (!std::is_same_v<Ret, void>) - return Ret{}; + return frame.makeResult(Ret{}); + else + return frame.makeResult(); } } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(const char *className, const char *methodName, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); - const jmethodID id = clazz ? getMethodID(env, clazz, methodName, + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<Ret, Args...> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); + const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, QtJniTypes::methodSignature<Ret, Args...>().data(), true) : 0; - return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); + return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...); } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(jclass clazz, const char *methodName, Args &&...args) { - constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - return callStaticMethod<Ret>(clazz, methodName, signature.data(), std::forward<Args>(args)...); + constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>(); + return callStaticMethod<ReturnType>(clazz, methodName, signature.data(), std::forward<Args>(args)...); } - template <typename Klass, typename Ret = void, typename ...Args + template <typename Klass, typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(const char *methodName, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); + LocalFrame<ReturnType, Args...> frame; const jclass clazz = QJniObject::loadClass(QtJniTypes::Traits<Klass>::className().data(), - env); - const jmethodID id = clazz ? getMethodID(env, clazz, methodName, - QtJniTypes::methodSignature<Ret, Args...>().data(), true) + frame.jniEnv()); + const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, + QtJniTypes::methodSignature<ReturnType, Args...>().data(), true) : 0; - return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); + return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...); } static QJniObject callStaticObjectMethod(const char *className, const char *methodName, @@ -315,7 +387,7 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame; + LocalFrame<QJniObject, Args...> frame; return frame.template convertFromJni<Ret>(callStaticObjectMethod(className, methodName, signature.data(), frame.convertToJni(std::forward<Args>(args))...)); } @@ -329,98 +401,95 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame; + LocalFrame<QJniObject, Args...> frame; return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodName, signature.data(), - frame.convertToJni(std::forward<Args>(args))...)); + frame.convertToJni(std::forward<Args>(args))...)); } - template <typename T + template <typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > auto getField(const char *fieldName) const { - LocalFrame<T> frame(jniEnv()); + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame(jniEnv()); + constexpr auto signature = QtJniTypes::fieldSignature<T>(); + jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); + if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getObjectField<T>(fieldName)); + return frame.makeResult(frame.template convertFromJni<T>(getObjectFieldImpl( + frame.jniEnv(), id)) + ); } else { T res{}; - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); - if (id) { + if (id) getFieldForType<T>(frame.jniEnv(), res, object(), id); - if (frame.checkAndClearExceptions()) - res = {}; - } - return res; + return frame.makeResult(res); } } - template <typename T + template <typename Klass, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - static auto getStaticField(const char *className, const char *fieldName) + static auto getStaticField(const char *fieldName) { - LocalFrame<T> frame; - if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getStaticObjectField<T>(className, fieldName)); - } else { - jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); - if (!clazz) - return T{}; - return getStaticField<T>(clazz, fieldName); - } + return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName); } template <typename T #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true #endif > - static auto getStaticField(jclass clazz, const char *fieldName) + QJniObject getObjectField(const char *fieldName) const { - LocalFrame<T> frame; - if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getStaticObjectField<T>(clazz, fieldName)); - } else { - T res{}; - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); - if (id) { - getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id); - if (frame.checkAndClearExceptions()) - res = {}; - } - return res; - } + constexpr auto signature = QtJniTypes::fieldSignature<T>(); + return getObjectField(fieldName, signature); } - template <typename Klass, typename T + QJniObject getObjectField(const char *fieldName, const char *signature) const; + + template <typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static auto getStaticField(const char *fieldName) + static auto getStaticField(const char *className, const char *fieldName) { - return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName); + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); + return getStaticField<Type>(clazz, fieldName); } - template <typename T + template <typename Type #ifndef Q_QDOC - , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - QJniObject getObjectField(const char *fieldName) const + static auto getStaticField(jclass clazz, const char *fieldName) { + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame; constexpr auto signature = QtJniTypes::fieldSignature<T>(); - return getObjectField(fieldName, signature); + jfieldID id = clazz ? getFieldID(frame.jniEnv(), clazz, fieldName, signature, true) + : nullptr; + if constexpr (QtJniTypes::isObjectType<T>()) { + return frame.makeResult(frame.template convertFromJni<T>(getStaticObjectFieldImpl( + frame.jniEnv(), clazz, id)) + ); + } else { + T res{}; + if (id) + getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id); + return frame.makeResult(res); + } } - QJniObject getObjectField(const char *fieldName, const char *signature) const; - template <typename T #ifndef Q_QDOC , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true @@ -450,114 +519,122 @@ public: static QJniObject getStaticObjectField(jclass clazz, const char *fieldName, const char *signature); - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - void setField(const char *fieldName, T value) + auto setField(const char *fieldName, Type value) { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame(jniEnv()); constexpr auto signature = QtJniTypes::fieldSignature<T>(); jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); - if (id) { + if (id) setFieldForType<T>(jniEnv(), object(), id, value); - QJniEnvironment::checkAndClearExceptions(jniEnv()); - } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - void setField(const char *fieldName, const char *signature, T value) - { - jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); - if (id) { + auto setField(const char *fieldName, const char *signature, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame(jniEnv()); + jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); + if (id) setFieldForType<T>(jniEnv(), object(), id, value); - QJniEnvironment::checkAndClearExceptions(jniEnv()); - } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *className, const char *fieldName, T value) - { - LocalFrame<T> frame; - jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); - if (!clazz) - return; - - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, - signature, true); - if (!id) - return; - - setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); - frame.checkAndClearExceptions(); + static auto setStaticField(const char *className, const char *fieldName, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { + constexpr auto signature = QtJniTypes::fieldSignature<q20::remove_cvref_t<T>>(); + jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, + signature, true); + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); + } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *className, const char *fieldName, - const char *signature, T value) - { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); - - if (!clazz) - return; - - jfieldID id = getCachedFieldID(env, clazz, className, fieldName, - signature, true); - if (id) { - setStaticFieldForType<T>(env, clazz, id, value); - QJniEnvironment::checkAndClearExceptions(env); + static auto setStaticField(const char *className, const char *fieldName, + const char *signature, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { + jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, + signature, true); + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(jclass clazz, const char *fieldName, - const char *signature, T value) + static auto setStaticField(jclass clazz, const char *fieldName, + const char *signature, Type value) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jfieldID id = getFieldID(env, clazz, fieldName, signature, true); + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); - if (id) { - setStaticFieldForType<T>(env, clazz, id, value); - QJniEnvironment::checkAndClearExceptions(env); - } + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(jclass clazz, const char *fieldName, T value) + static auto setStaticField(jclass clazz, const char *fieldName, Type value) { - setStaticField(clazz, fieldName, QtJniTypes::fieldSignature<T>(), value); + return setStaticField<Ret, Type>(clazz, fieldName, + QtJniTypes::fieldSignature<q20::remove_cvref_t<Type>>(), + value); } - template <typename Klass, typename T + template <typename Klass, typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *fieldName, T value) + static auto setStaticField(const char *fieldName, Type value) { - setStaticField(QtJniTypes::Traits<Klass>::className(), fieldName, value); + return setStaticField<Ret, Type>(QtJniTypes::Traits<Klass>::className(), fieldName, value); } static QJniObject fromString(const QString &string); @@ -583,6 +660,7 @@ protected: private: static jclass loadClass(const QByteArray &className, JNIEnv *env); + static jclass loadClassKeepExceptions(const QByteArray &className, JNIEnv *env); #if QT_CORE_REMOVED_SINCE(6, 7) // these need to stay in the ABI as they were used in inline methods before 6.7 @@ -620,12 +698,25 @@ private: template<typename T> static constexpr void callMethodForType(JNIEnv *env, T &res, jobject obj, jmethodID id, ...) { + if (!id) + return; + va_list args = {}; va_start(args, id); QtJniTypes::Caller<T>::callMethodForType(env, res, obj, id, args); va_end(args); } + jobject callObjectMethodImpl(jmethodID method, ...) const + { + va_list args; + va_start(args, method); + jobject res = method ? jniEnv()->CallObjectMethodV(javaObject(), method, args) + : nullptr; + va_end(args); + return res; + } + template<typename T> static constexpr void callStaticMethodForType(JNIEnv *env, T &res, jclass clazz, jmethodID id, ...) @@ -652,6 +743,9 @@ private: template<typename T> static constexpr void getFieldForType(JNIEnv *env, T &res, jobject obj, jfieldID id) { + if (!id) + return; + QtJniTypes::Caller<T>::getFieldForType(env, res, obj, id); } @@ -661,22 +755,42 @@ private: QtJniTypes::Caller<T>::getStaticFieldForType(env, res, clazz, id); } - template<typename T> - static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, T value) + template<typename Type> + static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, Type value) { + if (!id) + return; + + using T = q20::remove_cvref_t<Type>; if constexpr (QtJniTypes::isObjectType<T>()) { - LocalFrame<T> frame(env); + LocalFrame<T, T> frame(env); env->SetObjectField(obj, id, static_cast<jobject>(frame.convertToJni(value))); } else { - QtJniTypes::Caller<T>::setFieldForType(env, obj, id, value); + using ValueType = typename QtJniTypes::Detail::CallerHandlesException<T>::value_type; + QtJniTypes::Caller<ValueType>::setFieldForType(env, obj, id, value); } } - template<typename T> - static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, T value) + jobject getObjectFieldImpl(JNIEnv *env, jfieldID field) const + { + return field ? env->GetObjectField(javaObject(), field) : nullptr; + } + + static jobject getStaticObjectFieldImpl(JNIEnv *env, jclass clazz, jfieldID field) + { + return clazz && field ? env->GetStaticObjectField(clazz, field) + : nullptr; + } + + template<typename Type> + static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, Type value) { + if (!clazz || !id) + return; + + using T = q20::remove_cvref_t<Type>; if constexpr (QtJniTypes::isObjectType<T>()) { - LocalFrame<T> frame(env); + LocalFrame<T, T> frame(env); env->SetStaticObjectField(clazz, id, static_cast<jobject>(frame.convertToJni(value))); } else { QtJniTypes::Caller<T>::setStaticFieldForType(env, clazz, id, value); @@ -797,14 +911,14 @@ public: { return QJniObject::getStaticField<Class, T>(field); } - template <typename T + template <typename Ret = void, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - static void setStaticField(const char *field, T &&value) + static auto setStaticField(const char *field, T &&value) { - QJniObject::setStaticField<Class, T>(field, std::forward<T>(value)); + return QJniObject::setStaticField<Class, Ret, T>(field, std::forward<T>(value)); } // keep only these overloads, the rest is made private @@ -827,14 +941,14 @@ public: return m_object.getField<T>(fieldName); } - template <typename T + template <typename Ret = void, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - void setField(const char *fieldName, T &&value) + auto setField(const char *fieldName, T &&value) { - m_object.setField(fieldName, std::forward<T>(value)); + return m_object.setField<Ret>(fieldName, std::forward<T>(value)); } QByteArray className() const { @@ -911,6 +1025,37 @@ struct Traits<QString> } }; +template <typename T> +struct Traits<T, std::enable_if_t<QtJniTypes::Detail::callerHandlesException<T>>> +{ + static constexpr auto className() + { + return Traits<typename T::value_type>::className(); + } + + static constexpr auto signature() + { + return Traits<typename T::value_type>::signature(); + } +}; + +} + +template <typename ReturnType, typename ...Args> +template <typename T> +auto QtJniTypes::Detail::LocalFrame<ReturnType, Args...>::convertFromJni(jobject object) +{ + // If the caller wants to handle exceptions through a std::expected-like + // type, then we cannot turn the jobject into a QJniObject, as a + // std::expected<jobject, jthrowable> cannot be constructed from a QJniObject. + // The caller will have to take care of this themselves, by asking for a + // std::expected<QJniObject, ...>, or (typically) using a declared JNI class + // or implicitly supported Qt type (QString or array type). + if constexpr (callerHandlesException<ReturnType> && + std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>) + return static_cast<T>(object); + else + return convertFromJni<T>(object ? QJniObject::fromLocalRef(object) : QJniObject()); } QT_END_NAMESPACE |
