Android: print jni exceptions from Qt instead of ExceptionDescribe()

This makes the exceptions prints tagged with the app's name/tag,
and also can allow QTest::ignoreMessage() to handle exceptions as now
it cannot filter messages out because they're printed by the Android
system.

Change-Id: I9f5132b9ec5b5cd8fb35707eaaf68aa517f417ec
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
(cherry picked from commit dbb622a38d6ce4fbd7fba010aea238e5f9552c67)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Assam Boudjelthia 2022-12-20 18:39:30 +02:00 committed by Qt Cherry-pick Bot
parent 7216a936bc
commit 47dfdc2c61

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qjnienvironment.h" #include "qjnienvironment.h"
#include "qjniobject.h"
#include "qjnihelpers_p.h" #include "qjnihelpers_p.h"
#include <QtCore/QThread> #include <QtCore/QThread>
@ -444,16 +443,57 @@ bool QJniEnvironment::registerNativeMethods(jclass clazz, const JNINativeMethod
*/ */
bool QJniEnvironment::checkAndClearExceptions(QJniEnvironment::OutputMode outputMode) bool QJniEnvironment::checkAndClearExceptions(QJniEnvironment::OutputMode outputMode)
{ {
if (Q_UNLIKELY(d->jniEnv->ExceptionCheck())) { return checkAndClearExceptions(d->jniEnv, outputMode);
if (outputMode != OutputMode::Silent) }
d->jniEnv->ExceptionDescribe();
d->jniEnv->ExceptionClear();
namespace {
// Any pending exception need to be cleared before calling this
QString exceptionMessage(JNIEnv *env, const jthrowable &exception)
{
if (!exception)
return {};
auto logError = []() {
qWarning() << "QJniEnvironment: a null object returned or an exception occurred while "
"fetching a prior exception message";
};
auto checkAndClear = [env]() {
if (Q_UNLIKELY(env->ExceptionCheck())) {
env->ExceptionClear();
return true; return true;
} }
return false; return false;
} };
const jclass logClazz = env->FindClass("android/util/Log");
if (checkAndClear() || !logClazz) {
logError();
return {};
}
const jmethodID methodId = env->GetStaticMethodID(logClazz, "getStackTraceString",
"(Ljava/lang/Throwable;)Ljava/lang/String;");
if (checkAndClear() || !methodId) {
logError();
return {};
}
jvalue value;
value.l = static_cast<jobject>(exception);
const jobject messageObj = env->CallStaticObjectMethodA(logClazz, methodId, &value);
const jstring jmessage = static_cast<jstring>(messageObj);
if (checkAndClear())
return {};
char const *utfMessage = env->GetStringUTFChars(jmessage, 0);
const QString message = QString::fromUtf8(utfMessage);
env->ReleaseStringUTFChars(jmessage, utfMessage);
return message;
}
} // end namespace
/*! /*!
\fn QJniEnvironment::checkAndClearExceptions(JNIEnv *env, OutputMode outputMode = OutputMode::Verbose) \fn QJniEnvironment::checkAndClearExceptions(JNIEnv *env, OutputMode outputMode = OutputMode::Verbose)
@ -472,9 +512,22 @@ bool QJniEnvironment::checkAndClearExceptions(QJniEnvironment::OutputMode output
bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::OutputMode outputMode) bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::OutputMode outputMode)
{ {
if (Q_UNLIKELY(env->ExceptionCheck())) { if (Q_UNLIKELY(env->ExceptionCheck())) {
if (outputMode != OutputMode::Silent) if (outputMode == OutputMode::Verbose) {
if (jthrowable exception = env->ExceptionOccurred()) {
env->ExceptionClear();
const QString message = exceptionMessage(env, exception);
// Print to QWARN since env->ExceptionDescribe() does the same
if (!message.isEmpty())
qWarning().noquote() << message;
env->DeleteLocalRef(exception);
} else {
// if the exception object is null for some reason just
env->ExceptionDescribe(); env->ExceptionDescribe();
env->ExceptionClear(); env->ExceptionClear();
}
} else {
env->ExceptionClear();
}
return true; return true;
} }