summaryrefslogtreecommitdiffstats
path: root/src/corelib
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib')
-rw-r--r--src/corelib/CMakeLists.txt1
-rw-r--r--src/corelib/Qt6AndroidMacros.cmake21
-rw-r--r--src/corelib/configure.cmake9
-rw-r--r--src/corelib/doc/qtcore.qdocconf7
-rw-r--r--src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc30
-rw-r--r--src/corelib/doc/src/cmake/qt_add_android_permission.qdoc4
-rw-r--r--src/corelib/doc/src/qtcore.qdoc9
-rw-r--r--src/corelib/global/qcompilerdetection.h2
-rw-r--r--src/corelib/io/qfile.cpp13
-rw-r--r--src/corelib/io/qfilesystemengine.cpp8
-rw-r--r--src/corelib/io/qfilesystemengine_p.h2
-rw-r--r--src/corelib/io/qfsfileengine_p.h2
-rw-r--r--src/corelib/itemmodels/qrangemodel_impl.h41
-rw-r--r--src/corelib/kernel/qjniarray.h4
-rw-r--r--src/corelib/kernel/qjnienvironment.cpp8
-rw-r--r--src/corelib/kernel/qjnienvironment.h1
-rw-r--r--src/corelib/kernel/qjniobject.cpp135
-rw-r--r--src/corelib/kernel/qjniobject.h523
-rw-r--r--src/corelib/kernel/qmetacontainer.cpp8
-rw-r--r--src/corelib/kernel/qobject.cpp35
-rw-r--r--src/corelib/kernel/qobject_impl.h4
-rw-r--r--src/corelib/kernel/qpermissions.cpp6
-rw-r--r--src/corelib/platform/darwin/qdarwinsecurityscopedfileengine.mm552
-rw-r--r--src/corelib/platform/darwin/qdarwinsecurityscopedfileengine_p.h29
-rw-r--r--src/corelib/platform/wasm/qstdweb.cpp164
-rw-r--r--src/corelib/platform/wasm/qstdweb_p.h96
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp54
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h5
-rw-r--r--src/corelib/text/qlocale_icu.cpp12
-rw-r--r--src/corelib/time/qdatetime.cpp12
-rw-r--r--src/corelib/tools/qflatmap_p.h54
31 files changed, 1451 insertions, 400 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index f31968f8199..32b70a1f288 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -701,6 +701,7 @@ qt_internal_extend_target(Core CONDITION APPLE
kernel/qcore_mac.mm kernel/qcore_mac_p.h
kernel/qcoreapplication_mac.cpp
kernel/qeventdispatcher_cf.mm kernel/qeventdispatcher_cf_p.h
+ platform/darwin/qdarwinsecurityscopedfileengine.mm platform/darwin/qdarwinsecurityscopedfileengine_p.h
LIBRARIES
${FWCoreFoundation}
${FWFoundation}
diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake
index 6a83e947146..be362ba1925 100644
--- a/src/corelib/Qt6AndroidMacros.cmake
+++ b/src/corelib/Qt6AndroidMacros.cmake
@@ -106,6 +106,27 @@ function(qt6_add_android_dynamic_features target)
endif()
endfunction()
+
+function(qt_add_android_dynamic_feature_java_source_dirs)
+ qt6_add_android_dynamic_feature_java_source_dirs(${ARGV})
+endfunction()
+
+# Add java source directories for dynamic feature. Intermediate solution until java library
+# support exists.
+function(qt6_add_android_dynamic_feature_java_source_dirs target)
+
+ set(opt_args "")
+ set(single_args "")
+ set(multi_args
+ SOURCE_DIRS
+ )
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ if(arg_SOURCE_DIRS)
+ set_property(TARGET ${target} APPEND PROPERTY
+ _qt_android_gradle_java_source_dirs ${arg_SOURCE_DIRS})
+ endif()
+endfunction()
+
# Generate the deployment settings json file for a cmake target.
function(qt6_android_generate_deployment_settings target)
# Information extracted from mkspecs/features/android/android_deployment_settings.prf
diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake
index edcfba0f6ce..08908082991 100644
--- a/src/corelib/configure.cmake
+++ b/src/corelib/configure.cmake
@@ -31,7 +31,10 @@ qt_find_package_extend_sbom(TARGETS GLIB2::GLIB2
LICENSE_EXPRESSION "LGPL-2.1-or-later"
)
qt_find_package(ICU 50.1 COMPONENTS i18n uc data PROVIDED_TARGETS ICU::i18n ICU::uc ICU::data
- MODULE_NAME core QMAKE_LIB icu)
+ MODULE_NAME core QMAKE_LIB icu
+ VCPKG_PORT icu
+ VCPKG_PLATFORM !windows
+)
if(QT_FEATURE_dlopen)
qt_add_qmake_lib_dependency(icu libdl)
@@ -49,7 +52,9 @@ qt_find_package_extend_sbom(TARGETS Libb2::Libb2
qt_find_package(WrapRt MODULE
PROVIDED_TARGETS WrapRt::WrapRt MODULE_NAME core QMAKE_LIB librt)
qt_find_package(WrapSystemPCRE2 10.20 MODULE
- PROVIDED_TARGETS WrapSystemPCRE2::WrapSystemPCRE2 MODULE_NAME core QMAKE_LIB pcre2)
+ PROVIDED_TARGETS WrapSystemPCRE2::WrapSystemPCRE2 MODULE_NAME core QMAKE_LIB pcre2
+ VCPKG_PORT pcre2
+)
set_package_properties(WrapPCRE2 PROPERTIES TYPE REQUIRED)
if((QNX) OR QT_FIND_ALL_PACKAGES_ALWAYS)
qt_find_package(PPS MODULE PROVIDED_TARGETS PPS::PPS MODULE_NAME core QMAKE_LIB pps)
diff --git a/src/corelib/doc/qtcore.qdocconf b/src/corelib/doc/qtcore.qdocconf
index d2b386373a0..b3e4e9d30a9 100644
--- a/src/corelib/doc/qtcore.qdocconf
+++ b/src/corelib/doc/qtcore.qdocconf
@@ -21,7 +21,7 @@ qhp.QtCore.virtualFolder = qtcore
qhp.QtCore.indexTitle = Qt Core
qhp.QtCore.indexRoot =
-qhp.QtCore.subprojects = manual classes
+qhp.QtCore.subprojects = manual examples classes
qhp.QtCore.subprojects.manual.title = Qt Core
qhp.QtCore.subprojects.manual.indexTitle = Qt Core module topics
qhp.QtCore.subprojects.manual.type = manual
@@ -31,6 +31,11 @@ qhp.QtCore.subprojects.classes.indexTitle = Qt Core C++ Classes
qhp.QtCore.subprojects.classes.selectors = class fake:headerfile
qhp.QtCore.subprojects.classes.sortPages = true
+qhp.QtCore.subprojects.examples.title = Examples
+qhp.QtCore.subprojects.examples.indexTitle = Qt Core Examples
+qhp.QtCore.subprojects.examples.selectors = example
+qhp.QtCore.subprojects.examples.sortPages = true
+
tagfile = ../../../doc/qtcore/qtcore.tags
# Make QtCore depend on all doc modules; this ensures complete inheritance
diff --git a/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc b/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc
new file mode 100644
index 00000000000..cf670110cab
--- /dev/null
+++ b/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qt_add_android_dynamic_feature_java_source_dirs.html
+\ingroup cmake-commands-qtcore
+
+\title qt_add_android_dynamic_feature_java_source_dirs
+\keyword qt6_add_android_dynamic_feature_java_source_dirs
+
+\summary {Adds Java source directories to a dynamic feature build.}
+
+\include cmake-find-package-core.qdocinc
+
+\cmakecommandsince 6.11
+
+\section1 Synopsis
+
+\badcode
+qt_add_android_dynamic_feature_java_source_dirs(target [SOURCE_DIRS <directory1> <directory2> ...])
+\endcode
+
+\versionlessCMakeCommandsNote qt6_add_android_dynamic_feature_java_source_dirs()
+
+\section1 Description
+
+The command adds extra Java/Kotlin source directories to the \c {target}
+executable when building the executable app with dynamic feature functionality.
+To be used in conjunction with qt6_add_android_dynamic_features().
+*/
diff --git a/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc b/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc
index 68fe6a8e5cc..8fa6c2bb6d7 100644
--- a/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc
+++ b/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc
@@ -14,6 +14,10 @@
\cmakecommandsince 6.9
+\note When using this API, the \c{<!-- %%INSERT_PERMISSIONS -->} tag must be present
+in the AndroidManifest.xml. For further information on the use of this tag,
+see \l {Qt Permissions and Features}
+
\section1 Synopsis
\badcode
diff --git a/src/corelib/doc/src/qtcore.qdoc b/src/corelib/doc/src/qtcore.qdoc
index ec5fa564639..fbcd02aeea5 100644
--- a/src/corelib/doc/src/qtcore.qdoc
+++ b/src/corelib/doc/src/qtcore.qdoc
@@ -31,3 +31,12 @@
target_link_libraries(mytarget PRIVATE Qt6::CorePrivate)
\endcode
*/
+
+/*!
+ \group corelib_examples
+ \title Qt Core Examples
+
+ \brief Examples for the Qt Core.
+
+ To learn how to use features of the Qt Core module, see examples:
+*/
diff --git a/src/corelib/global/qcompilerdetection.h b/src/corelib/global/qcompilerdetection.h
index 7bb2e5b04ef..0b42af7686c 100644
--- a/src/corelib/global/qcompilerdetection.h
+++ b/src/corelib/global/qcompilerdetection.h
@@ -1446,7 +1446,7 @@ QT_WARNING_DISABLE_MSVC(4355) /* 'this' : used in base member initializer list *
QT_WARNING_DISABLE_MSVC(4710) /* function not inlined */
QT_WARNING_DISABLE_MSVC(4530) /* C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc */
# elif defined(Q_CC_CLANG_ONLY)
-# if Q_CC_CLANG >= 2100
+# if __has_warning("-Wcharacter-conversion")
QT_WARNING_DISABLE_CLANG("-Wcharacter-conversion") /* until https://github.com/llvm/llvm-project/issues/163719 is fixed */
# endif
# elif defined(Q_CC_BOR)
diff --git a/src/corelib/io/qfile.cpp b/src/corelib/io/qfile.cpp
index e1fc043a0ff..0184fd838aa 100644
--- a/src/corelib/io/qfile.cpp
+++ b/src/corelib/io/qfile.cpp
@@ -592,6 +592,10 @@ QFile::rename(const QString &newName)
return false;
}
+ // Keep engine for target alive during the operation
+ // FIXME: Involve the target engine in the operation
+ auto targetEngine = QFileSystemEngine::createLegacyEngine(newName);
+
// If the file exists and it is a case-changing rename ("foo" -> "Foo"),
// compare Ids to make sure it really is a different file.
// Note: this does not take file engines into account.
@@ -738,6 +742,11 @@ QFile::link(const QString &linkName)
qWarning("QFile::link: Empty or null file name");
return false;
}
+
+ // Keep engine for target alive during the operation
+ // FIXME: Involve the target engine in the operation
+ auto targetEngine = QFileSystemEngine::createLegacyEngine(linkName);
+
QFileInfo fi(linkName);
if (d->engine()->link(fi.absoluteFilePath())) {
unsetError();
@@ -771,6 +780,10 @@ bool QFilePrivate::copy(const QString &newName)
Q_ASSERT(error == QFile::NoError);
Q_ASSERT(!q->isOpen());
+ // Keep engine for target alive during the operation
+ // FIXME: Involve the target engine in the operation
+ auto targetEngine = QFileSystemEngine::createLegacyEngine(newName);
+
// Some file engines can perform this copy more efficiently (e.g., Windows
// calling CopyFile).
if (engine()->copy(newName))
diff --git a/src/corelib/io/qfilesystemengine.cpp b/src/corelib/io/qfilesystemengine.cpp
index 03da2331e05..46d4cb709e2 100644
--- a/src/corelib/io/qfilesystemengine.cpp
+++ b/src/corelib/io/qfilesystemengine.cpp
@@ -190,6 +190,14 @@ QFileSystemEngine::createLegacyEngine(QFileSystemEntry &entry, QFileSystemMetaDa
return engine;
}
+std::unique_ptr<QAbstractFileEngine>
+QFileSystemEngine::createLegacyEngine(const QString &fileName)
+{
+ QFileSystemEntry entry(fileName);
+ QFileSystemMetaData metaData;
+ return createLegacyEngine(entry, metaData);
+}
+
//static
QString QFileSystemEngine::resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
{
diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h
index ee70ccc1e1b..46eeeda569e 100644
--- a/src/corelib/io/qfilesystemengine_p.h
+++ b/src/corelib/io/qfilesystemengine_p.h
@@ -161,6 +161,8 @@ public:
static std::unique_ptr<QAbstractFileEngine>
createLegacyEngine(QFileSystemEntry &entry, QFileSystemMetaData &data);
+ static std::unique_ptr<QAbstractFileEngine>
+ createLegacyEngine(const QString &fileName);
private:
static QString slowCanonicalized(const QString &path);
diff --git a/src/corelib/io/qfsfileengine_p.h b/src/corelib/io/qfsfileengine_p.h
index 2de6cb0cb73..8ad673bf0bf 100644
--- a/src/corelib/io/qfsfileengine_p.h
+++ b/src/corelib/io/qfsfileengine_p.h
@@ -82,7 +82,7 @@ public:
bool setFileTime(const QDateTime &newDate, QFile::FileTime time) override;
QDateTime fileTime(QFile::FileTime time) const override;
void setFileName(const QString &file) override;
- void setFileEntry(QFileSystemEntry &&entry);
+ virtual void setFileEntry(QFileSystemEntry &&entry);
int handle() const override;
#ifndef QT_NO_FILESYSTEMITERATOR
diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h
index 70552cbfe05..88bd6cf444e 100644
--- a/src/corelib/itemmodels/qrangemodel_impl.h
+++ b/src/corelib/itemmodels/qrangemodel_impl.h
@@ -28,7 +28,7 @@
#include <functional>
#include <iterator>
#include <type_traits>
-#include <QtCore/q20type_traits.h>
+#include <QtCore/qxptype_traits.h>
#include <tuple>
#include <QtCore/q23utility.h>
@@ -562,35 +562,30 @@ namespace QRangeModelDetails
}
};
- template <typename P, typename R, typename = void>
- struct protocol_parentRow : std::false_type {};
template <typename P, typename R>
- struct protocol_parentRow<P, R,
- std::void_t<decltype(std::declval<P&>().parentRow(std::declval<wrapped_t<R>&>()))>>
- : std::true_type {};
+ using protocol_parentRow_test = decltype(std::declval<P&>()
+ .parentRow(std::declval<QRangeModelDetails::wrapped_t<R>&>()));
+ template <typename P, typename R>
+ using protocol_parentRow = qxp::is_detected<protocol_parentRow_test, P, R>;
- template <typename P, typename R, typename = void>
- struct protocol_childRows : std::false_type {};
template <typename P, typename R>
- struct protocol_childRows<P, R,
- std::void_t<decltype(std::declval<P&>().childRows(std::declval<wrapped_t<R>&>()))>>
- : std::true_type {};
+ using protocol_childRows_test = decltype(std::declval<P&>()
+ .childRows(std::declval<QRangeModelDetails::wrapped_t<R>&>()));
+ template <typename P, typename R>
+ using protocol_childRows = qxp::is_detected<protocol_childRows_test, P, R>;
- template <typename P, typename R, typename = void>
- struct protocol_setParentRow : std::false_type {};
template <typename P, typename R>
- struct protocol_setParentRow<P, R,
- std::void_t<decltype(std::declval<P&>().setParentRow(std::declval<wrapped_t<R>&>(),
- std::declval<wrapped_t<R>*>()))>>
- : std::true_type {};
+ using protocol_setParentRow_test = decltype(std::declval<P&>()
+ .setParentRow(std::declval<QRangeModelDetails::wrapped_t<R>&>(),
+ std::declval<QRangeModelDetails::wrapped_t<R>*>()));
+ template <typename P, typename R>
+ using protocol_setParentRow = qxp::is_detected<protocol_setParentRow_test, P, R>;
- template <typename P, typename R, typename = void>
- struct protocol_mutable_childRows : std::false_type {};
template <typename P, typename R>
- struct protocol_mutable_childRows<P, R,
- std::void_t<decltype(refTo(std::declval<P&>().childRows(std::declval<wrapped_t<R>&>()))
- = {}) >>
- : std::true_type {};
+ using protocol_mutable_childRows_test = decltype(refTo(std::declval<P&>()
+ .childRows(std::declval<wrapped_t<R>&>())) = {});
+ template <typename P, typename R>
+ using protocol_mutable_childRows = qxp::is_detected<protocol_mutable_childRows_test, P, R>;
template <typename P, typename = void>
struct protocol_newRow : std::false_type {};
diff --git a/src/corelib/kernel/qjniarray.h b/src/corelib/kernel/qjniarray.h
index 13349688d20..97d0cd37682 100644
--- a/src/corelib/kernel/qjniarray.h
+++ b/src/corelib/kernel/qjniarray.h
@@ -872,7 +872,7 @@ auto QJniArrayBase::makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion)
const size_type length = size_type(std::size(list));
JNIEnv *env = QJniEnvironment::getJniEnv();
auto localArray = (env->*newArray)(length);
- if (QJniEnvironment::checkAndClearExceptions(env)) {
+ if (env->ExceptionCheck()) {
if (localArray)
env->DeleteLocalRef(localArray);
return QJniArray<ElementType>();
@@ -916,7 +916,7 @@ auto QJniArrayBase::makeObjectArray(List &&list)
elementClass = env->GetObjectClass(*std::begin(list));
}
auto localArray = env->NewObjectArray(length, elementClass, nullptr);
- if (QJniEnvironment::checkAndClearExceptions(env)) {
+ if (env->ExceptionCheck()) {
if (localArray)
env->DeleteLocalRef(localArray);
return ResultType();
diff --git a/src/corelib/kernel/qjnienvironment.cpp b/src/corelib/kernel/qjnienvironment.cpp
index b4f8497ddda..1ee658fd18d 100644
--- a/src/corelib/kernel/qjnienvironment.cpp
+++ b/src/corelib/kernel/qjnienvironment.cpp
@@ -559,4 +559,12 @@ bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::Outp
return false;
}
+/*!
+ Returns the stack trace that resulted in \a exception being thrown.
+*/
+QStringList QJniEnvironment::stackTrace(jthrowable exception)
+{
+ return exceptionMessage(getJniEnv(), exception).split(u'\n');
+}
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qjnienvironment.h b/src/corelib/kernel/qjnienvironment.h
index b473f75bed1..a5f3700b1f0 100644
--- a/src/corelib/kernel/qjnienvironment.h
+++ b/src/corelib/kernel/qjnienvironment.h
@@ -89,6 +89,7 @@ public:
static bool checkAndClearExceptions(JNIEnv *env, OutputMode outputMode = OutputMode::Verbose);
static JNIEnv *getJniEnv();
+ static QStringList stackTrace(jthrowable exception);
private:
Q_DISABLE_COPY_MOVE(QJniEnvironment)
diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp
index 8f3da9a8595..59117bd01d4 100644
--- a/src/corelib/kernel/qjniobject.cpp
+++ b/src/corelib/kernel/qjniobject.cpp
@@ -348,7 +348,10 @@ static inline QByteArray cacheKey(Args &&...args)
return (QByteArrayView(":") + ... + QByteArrayView(args));
}
-typedef QHash<QByteArray, jclass> JClassHash;
+struct JClassHash : QHash<QByteArray, jclass>
+{
+ jmethodID loadClassMethod = 0;
+};
Q_GLOBAL_STATIC(JClassHash, cachedClasses)
Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock)
@@ -391,29 +394,22 @@ bool QJniObjectPrivate::isJString(JNIEnv *env) const
*/
static QJniObject getCleanJniObject(jobject object, JNIEnv *env)
{
- if (QJniEnvironment::checkAndClearExceptions(env) || !object) {
- if (object)
- env->DeleteLocalRef(object);
+ if (!object || env->ExceptionCheck())
return QJniObject();
- }
- QJniObject res(object);
- env->DeleteLocalRef(object);
- return res;
+ return QJniObject::fromLocalRef(object);
}
-/*!
- \internal
- \a className must be slash-encoded
-*/
-jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
+namespace {
+
+jclass loadClassHelper(const QByteArray &className, JNIEnv *env)
{
Q_ASSERT(env);
QByteArray classNameArray(className);
#ifdef QT_DEBUG
if (classNameArray.contains('.')) {
qWarning("QtAndroidPrivate::findClass: className '%s' should use slash separators!",
- className);
+ className.constData());
}
#endif
classNameArray.replace('.', '/');
@@ -442,21 +438,40 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
if (!clazz) {
// Wrong class loader, try our own
- QJniObject classLoader(QtAndroidPrivate::classLoader());
- if (!classLoader.isValid())
+ jobject classLoader = QtAndroidPrivate::classLoader();
+ if (!classLoader)
return nullptr;
+ if (!cachedClasses->loadClassMethod) {
+ jclass classLoaderClass = env->GetObjectClass(classLoader);
+ if (!classLoaderClass)
+ return nullptr;
+ cachedClasses->loadClassMethod =
+ env->GetMethodID(classLoaderClass,
+ "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+ env->DeleteLocalRef(classLoaderClass);
+ if (!cachedClasses->loadClassMethod) {
+ qCritical("Couldn't find the 'loadClass' method in the Qt class loader");
+ return nullptr;
+ }
+ }
+
// ClassLoader::loadClass on the other hand wants the binary name of the class,
// e.g. dot-separated. In testing it works also with /, but better to stick to
// the specification.
const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.');
- jstring classNameObject = env->NewString(reinterpret_cast<const jchar*>(binaryClassName.constData()),
- binaryClassName.length());
- QJniObject classObject = classLoader.callMethod<jclass>("loadClass", classNameObject);
+ jstring classNameObject = env->NewString(binaryClassName.utf16(), binaryClassName.length());
+ jobject classObject = env->CallObjectMethod(classLoader,
+ cachedClasses->loadClassMethod,
+ classNameObject);
env->DeleteLocalRef(classNameObject);
- if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid())
- clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object()));
+ if (classObject && !env->ExceptionCheck()) {
+ clazz = static_cast<jclass>(env->NewGlobalRef(classObject));
+ env->DeleteLocalRef(classObject);
+ }
+ // Clearing the exception is the caller's responsibility (see
+ // QtAndroidPrivate::findClass()) and QJniObject::loadClass{KeepExceptions}
}
if (clazz)
@@ -465,11 +480,32 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
return clazz;
}
+} // unnamed namespace
+
+/*!
+ \internal
+ \a className must be slash-encoded
+*/
+jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
+{
+ jclass clazz = loadClassHelper(className, env);
+ if (!clazz)
+ QJniEnvironment::checkAndClearExceptions(env);
+ return clazz;
+}
+
jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env)
{
return QtAndroidPrivate::findClass(className, env);
}
+jclass QJniObject::loadClassKeepExceptions(const QByteArray &className, JNIEnv *env)
+{
+ return loadClassHelper(className, env);
+}
+
+
+
typedef QHash<QByteArray, jmethodID> JMethodIDHash;
Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID)
Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock)
@@ -483,9 +519,6 @@ jmethodID QJniObject::getMethodID(JNIEnv *env,
jmethodID id = isStatic ? env->GetStaticMethodID(clazz, name, signature)
: env->GetMethodID(clazz, name, signature);
- if (QJniEnvironment::checkAndClearExceptions(env))
- return nullptr;
-
return id;
}
@@ -525,7 +558,8 @@ jmethodID QJniObject::getCachedMethodID(JNIEnv *env,
jmethodID id = getMethodID(env, clazz, name, signature, isStatic);
- cachedMethodID->insert(key, id);
+ if (id)
+ cachedMethodID->insert(key, id);
return id;
}
}
@@ -549,9 +583,6 @@ jfieldID QJniObject::getFieldID(JNIEnv *env,
jfieldID id = isStatic ? env->GetStaticFieldID(clazz, name, signature)
: env->GetFieldID(clazz, name, signature);
- if (QJniEnvironment::checkAndClearExceptions(env))
- return nullptr;
-
return id;
}
@@ -583,7 +614,8 @@ jfieldID QJniObject::getCachedFieldID(JNIEnv *env,
jfieldID id = getFieldID(env, clazz, name, signature, isStatic);
- cachedFieldID->insert(key, id);
+ if (id)
+ cachedFieldID->insert(key, id);
return id;
}
}
@@ -1008,15 +1040,15 @@ QJniObject QJniObject::callObjectMethod(const char *methodName, const char *sign
{
JNIEnv *env = jniEnv();
jmethodID id = getCachedMethodID(env, methodName, signature);
- if (id) {
- va_list args;
- va_start(args, signature);
- QJniObject res = getCleanJniObject(env->CallObjectMethodV(d->m_jobject, id, args), env);
- va_end(args);
- return res;
- }
+ va_list args;
+ va_start(args, signature);
+ // can't go back from variadic arguments to variadic templates
+ jobject object = id ? jniEnv()->CallObjectMethodV(javaObject(), id, args) : nullptr;
+ QJniObject res = getCleanJniObject(object, env);
+ va_end(args);
- return QJniObject();
+ QJniEnvironment::checkAndClearExceptions(env);
+ return res;
}
/*!
@@ -1150,7 +1182,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId,
*/
/*!
- \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, T value);
+ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, Type value);
Sets the static field \a fieldName on the class \a className to \a value
using the setter with \a signature.
@@ -1158,7 +1190,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId,
*/
/*!
- \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, T value);
+ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, Type value);
Sets the static field \a fieldName on the class \a clazz to \a value using
the setter with \a signature.
@@ -1196,19 +1228,19 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId,
*/
/*!
- \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, T value)
+ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, Type value)
Sets the static field \a fieldName of the class \a className to \a value.
*/
/*!
- \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, T value)
+ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, Type value)
Sets the static field \a fieldName of the class \a clazz to \a value.
*/
/*!
- \fn template <typename Klass, typename T> auto QJniObject::setStaticField(const char *fieldName, T value)
+ \fn template <typename Klass, typename Ret, typename Type> auto QJniObject::setStaticField(const char *fieldName, Type value)
Sets the static field \a fieldName of the class \c Klass to \a value.
@@ -1263,11 +1295,16 @@ QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName,
{
JNIEnv *env = QJniEnvironment::getJniEnv();
jfieldID id = getFieldID(env, clazz, fieldName, signature, true);
- return getCleanJniObject(env->GetStaticObjectField(clazz, id), env);
+
+ const auto clearExceptions = qScopeGuard([env]{
+ QJniEnvironment::checkAndClearExceptions(env);
+ });
+
+ return getCleanJniObject(getStaticObjectFieldImpl(env, clazz, id), env);
}
/*!
- \fn template <typename T> void QJniObject::setField(const char *fieldName, const char *signature, T value)
+ \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, const char *signature, Type value)
Sets the value of \a fieldName with \a signature to \a value.
@@ -1304,14 +1341,16 @@ QJniObject QJniObject::getObjectField(const char *fieldName, const char *signatu
{
JNIEnv *env = jniEnv();
jfieldID id = getCachedFieldID(env, fieldName, signature);
- if (!id)
- return QJniObject();
- return getCleanJniObject(env->GetObjectField(d->m_jobject, id), env);
+ const auto clearExceptions = qScopeGuard([env]{
+ QJniEnvironment::checkAndClearExceptions(env);
+ });
+
+ return getCleanJniObject(getObjectFieldImpl(env, id), env);
}
/*!
- \fn template <typename T> void QJniObject::setField(const char *fieldName, T value)
+ \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, Type value)
Sets the value of \a fieldName to \a value.
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
diff --git a/src/corelib/kernel/qmetacontainer.cpp b/src/corelib/kernel/qmetacontainer.cpp
index 4b4ea06d8b9..6173198a972 100644
--- a/src/corelib/kernel/qmetacontainer.cpp
+++ b/src/corelib/kernel/qmetacontainer.cpp
@@ -210,7 +210,7 @@ void QMetaContainer::destroyIterator(const void *iterator) const
*/
bool QMetaContainer::compareIterator(const void *i, const void *j) const
{
- return hasIterator() ? d_ptr->compareIteratorFn(i, j) : false;
+ return i == j || (hasIterator() && d_ptr->compareIteratorFn(i, j));
}
/*!
@@ -249,7 +249,7 @@ void QMetaContainer::advanceIterator(void *iterator, qsizetype step) const
*/
qsizetype QMetaContainer::diffIterator(const void *i, const void *j) const
{
- return hasIterator() ? d_ptr->diffIteratorFn(i, j) : 0;
+ return (i != j && hasIterator()) ? d_ptr->diffIteratorFn(i, j) : 0;
}
/*!
@@ -327,7 +327,7 @@ void QMetaContainer::destroyConstIterator(const void *iterator) const
*/
bool QMetaContainer::compareConstIterator(const void *i, const void *j) const
{
- return hasConstIterator() ? d_ptr->compareConstIteratorFn(i, j) : false;
+ return i == j || (hasConstIterator() && d_ptr->compareConstIteratorFn(i, j));
}
/*!
@@ -366,7 +366,7 @@ void QMetaContainer::advanceConstIterator(void *iterator, qsizetype step) const
*/
qsizetype QMetaContainer::diffConstIterator(const void *i, const void *j) const
{
- return hasConstIterator() ? d_ptr->diffConstIteratorFn(i, j) : 0;
+ return (i != j && hasConstIterator()) ? d_ptr->diffConstIteratorFn(i, j) : 0;
}
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp
index 02c9f00f301..607dc23f56c 100644
--- a/src/corelib/kernel/qobject.cpp
+++ b/src/corelib/kernel/qobject.cpp
@@ -2696,23 +2696,38 @@ static void err_method_notfound(const QObject *object,
case QSIGNAL_CODE: type = "signal"; break;
}
const char *loc = extract_location(method);
+ const char *err;
if (strchr(method, ')') == nullptr) // common typing mistake
- qCWarning(lcConnect, "QObject::%s: Parentheses expected, %s %s::%s%s%s", func, type,
- object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : "");
+ err = "Parentheses expected,";
else
- qCWarning(lcConnect, "QObject::%s: No such %s %s::%s%s%s", func, type,
- object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : "");
+ err = "No such";
+ qCWarning(lcConnect, "QObject::%s: %s %s %s::%s%s%s", func, err, type,
+ object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : "");
+}
+
+enum class ConnectionEnd : bool { Sender, Receiver };
+Q_DECL_COLD_FUNCTION
+static void err_info_about_object(const char *func, const QObject *o, ConnectionEnd end)
+{
+ if (!o)
+ return;
+ const QString name = o->objectName();
+ if (name.isEmpty())
+ return;
+ const bool sender = end == ConnectionEnd::Sender;
+ qCWarning(lcConnect, "QObject::%s: (%s name:%*s'%ls')",
+ func,
+ sender ? "sender" : "receiver",
+ sender ? 3 : 1, // ← length of generated whitespace
+ "",
+ qUtf16Printable(name));
}
Q_DECL_COLD_FUNCTION
static void err_info_about_objects(const char *func, const QObject *sender, const QObject *receiver)
{
- QString a = sender ? sender->objectName() : QString();
- QString b = receiver ? receiver->objectName() : QString();
- if (!a.isEmpty())
- qCWarning(lcConnect, "QObject::%s: (sender name: '%s')", func, a.toLocal8Bit().data());
- if (!b.isEmpty())
- qCWarning(lcConnect, "QObject::%s: (receiver name: '%s')", func, b.toLocal8Bit().data());
+ err_info_about_object(func, sender, ConnectionEnd::Sender);
+ err_info_about_object(func, receiver, ConnectionEnd::Receiver);
}
Q_DECL_COLD_FUNCTION
diff --git a/src/corelib/kernel/qobject_impl.h b/src/corelib/kernel/qobject_impl.h
index b57d7e50ccd..34e6bd84f3f 100644
--- a/src/corelib/kernel/qobject_impl.h
+++ b/src/corelib/kernel/qobject_impl.h
@@ -1,8 +1,6 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef Q_QDOC
-
#ifndef QOBJECT_H
#error Do not include qobject_impl.h directly
#endif
@@ -41,5 +39,3 @@ namespace QtPrivate {
QT_END_NAMESPACE
-
-#endif
diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp
index bbcea8338ca..6767917e176 100644
--- a/src/corelib/kernel/qpermissions.cpp
+++ b/src/corelib/kernel/qpermissions.cpp
@@ -124,7 +124,11 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg);
The relevant permission names are described in the documentation
for each permission type.
- \sa {Qt Creator: Editing Manifest Files}
+ \note When using this API, the \c{<!-- %%INSERT_PERMISSIONS -->} tag must be present in
+ the AndroidManifest.xml. For further information on the use of this tag,
+ see \l {Qt Permissions and Features}
+
+ \sa {Qt Creator: Editing Manifest Files}.
\section1 Available Permissions
diff --git a/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine.mm b/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine.mm
new file mode 100644
index 00000000000..cb38445f4fe
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine.mm
@@ -0,0 +1,552 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Qt-Security score:significant reason:default
+
+#include "qdarwinsecurityscopedfileengine_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qstandardpaths.h>
+#include <QtCore/qreadwritelock.h>
+#include <QtCore/qscopedvaluerollback.h>
+
+#include <QtCore/private/qcore_mac_p.h>
+#include <QtCore/private/qfsfileengine_p.h>
+#include <QtCore/private/qfilesystemengine_p.h>
+
+#include <thread>
+#include <mutex>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Foundation/NSURL.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+Q_STATIC_LOGGING_CATEGORY(lcSecEngine, "qt.core.io.security-scoped-fileengine", QtCriticalMsg)
+
+template<typename T> class BackgroundLoader;
+
+/*
+ File engine handler for security scoped file paths.
+
+ Installs itself as soon as QtCore is loaded if the application
+ is sandboxed (optionally on macOS, and always on iOS and friends).
+*/
+class SecurityScopedFileEngineHandler : public QAbstractFileEngineHandler
+{
+public:
+ SecurityScopedFileEngineHandler();
+ ~SecurityScopedFileEngineHandler();
+
+ void registerPossiblySecurityScopedURL(NSURL *url);
+
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override;
+
+ static BackgroundLoader<SecurityScopedFileEngineHandler>& get();
+
+private:
+ Q_DISABLE_COPY_MOVE(SecurityScopedFileEngineHandler)
+
+ void saveBookmark(NSURL *url);
+ void saveBookmarks();
+
+ NSURL *bookmarksFile() const;
+
+ static NSString *cacheKeyForUrl(NSURL *url);
+ static NSString *cacheKeyForPath(const QString &url);
+
+ NSMutableDictionary *m_bookmarks = nullptr;
+ mutable QReadWriteLock m_bookmarkLock;
+
+ friend class SecurityScopedFileEngine;
+};
+
+/*
+ Helper class for asynchronous instantiation of types.
+*/
+template<typename T>
+class BackgroundLoader
+{
+public:
+ explicit BackgroundLoader(bool shouldLoad) {
+ if (shouldLoad) {
+ m_thread = std::thread([this]() {
+ m_instance = std::make_unique<T>();
+ });
+ }
+ }
+
+ ~BackgroundLoader()
+ {
+ std::scoped_lock lock(m_mutex);
+ if (m_thread.joinable())
+ m_thread.join();
+ }
+
+ T* operator->() const
+ {
+ std::scoped_lock lock(m_mutex);
+ if (m_thread.joinable())
+ m_thread.join();
+ return m_instance.get();
+ }
+
+ explicit operator bool() const
+ {
+ std::scoped_lock lock(m_mutex);
+ return m_thread.joinable() || m_instance;
+ }
+
+private:
+ mutable std::mutex m_mutex;
+ mutable std::thread m_thread;
+ std::unique_ptr<T> m_instance;
+};
+
+/*
+ Thread-safe background-loading of optional security scoped handler,
+ with the ability to kick off instantiation early during program load.
+*/
+BackgroundLoader<SecurityScopedFileEngineHandler>& SecurityScopedFileEngineHandler::get()
+{
+ using Handler = BackgroundLoader<SecurityScopedFileEngineHandler>;
+ static Handler handler = []() -> Handler {
+ if (!qt_apple_isSandboxed())
+ return Handler{false};
+
+ qCInfo(lcSecEngine) << "Application sandbox is active. Registering security-scoped file engine.";
+ return Handler{true};
+ }();
+ return handler;
+}
+
+static void initializeSecurityScopedFileEngineHandler()
+{
+ // Kick off loading of bookmarks early in the background
+ std::ignore = SecurityScopedFileEngineHandler::get();
+}
+Q_CONSTRUCTOR_FUNCTION(initializeSecurityScopedFileEngineHandler);
+
+/*
+ Registration function for possibly security scoped URLs.
+
+ Entry points that might provide security scoped URLs such as file
+ dialogs or drag-and-drop should use this function to ensure that
+ the security scoped file engine handler knows about the URL.
+*/
+QUrl qt_apple_urlFromPossiblySecurityScopedURL(NSURL *url)
+{
+ if (auto &handler = SecurityScopedFileEngineHandler::get())
+ handler->registerPossiblySecurityScopedURL(url);
+
+ // Note: The URL itself doesn't encode any of the bookmark data,
+ // neither in the scheme or as fragments or query parameters,
+ // as it's all handled by the bookmark cache in the file engine.
+ return QUrl(QString::fromNSString(url.absoluteString)
+ .normalized(QString::NormalizationForm_C));
+}
+
+static bool checkIfResourceIsReachable(NSURL *url)
+{
+ NSError *error = nullptr;
+ if ([url checkResourceIsReachableAndReturnError:&error])
+ return true;
+
+ // Our goal is to check whether the file exists or not, and if
+ // not, defer creating a bookmark for it. If we get any other
+ // error we want to know.
+ if (![error.domain isEqualToString:NSCocoaErrorDomain] || error.code != NSFileReadNoSuchFileError) {
+ qCWarning(lcSecEngine) << "Unexpected" << error
+ << "when resolving reachability for" << url;
+ }
+
+ return false;
+}
+
+/*
+ File engine for maintaining access lifetime of security-scoped
+ resources on sandboxed Apple platforms.
+
+ Note that there isn't necessarily a 1:1 relationship between
+ the file being operated on by the QFSFileEngine and the security
+ scoped resource that allows access to it, for example in the
+ case of a folder giving access to all files (and sub-folders)
+ within it.
+*/
+class SecurityScopedFileEngine : public QFSFileEngine
+{
+ Q_DECLARE_PRIVATE(QFSFileEngine)
+public:
+ SecurityScopedFileEngine(const QString &fileName, NSURL *securityScopedUrl)
+ : QFSFileEngine(fileName)
+ , m_securityScopedUrl([securityScopedUrl retain])
+ {
+ startAccessingSecurityScopedResource();
+ }
+
+ ~SecurityScopedFileEngine()
+ {
+ stopAccessingSecurityScopedResource();
+ [m_securityScopedUrl release];
+ }
+
+ void setFileName(const QString &fileName) override
+ {
+ QFileSystemEntry entry(fileName);
+ setFileEntry(std::move(entry));
+ }
+
+ void setFileEntry(QFileSystemEntry &&entry) override
+ {
+ // We can't rely on the new entry being accessible under the same
+ // security scope as the original path, or even that the new path
+ // is a security scoped resource, so stop access here, and start
+ // access for the new resource below if needed.
+ stopAccessingSecurityScopedResource();
+ [m_securityScopedUrl release];
+ m_securityScopedUrl = nil;
+
+ const QString fileName = entry.filePath();
+ QFSFileEngine::setFileEntry(std::move(entry));
+
+ // The new path may not be a security scoped resource, but if it is
+ // we need to establish access to it. The only way to do that is to
+ // actually create an engine for it, including resolving bookmarks.
+ auto newEngine = SecurityScopedFileEngineHandler::get()->create(fileName);
+ if (auto *engine = dynamic_cast<SecurityScopedFileEngine*>(newEngine.get())) {
+ m_securityScopedUrl = [engine->m_securityScopedUrl retain];
+ startAccessingSecurityScopedResource();
+ }
+ }
+
+private:
+ void startAccessingSecurityScopedResource()
+ {
+ if ([m_securityScopedUrl startAccessingSecurityScopedResource]) {
+ qCDebug(lcSecEngine) << "Started accessing" << m_securityScopedUrl.path
+ << "on behalf of" << fileName(DefaultName);
+
+ m_securityScopeWasReachable = securityScopeIsReachable();
+ } else {
+ qCWarning(lcSecEngine) << "Unexpectedly using security scoped"
+ << "file engine for" << m_securityScopedUrl.path
+ << "on behalf of" << fileName(DefaultName)
+ << "without needing scoped access";
+ }
+ }
+
+ void stopAccessingSecurityScopedResource()
+ {
+ if (!m_securityScopeWasReachable && securityScopeIsReachable()) {
+ // The security scoped URL didn't exist when we first started
+ // accessing it, but it does now, so persist a bookmark for it.
+ qCDebug(lcSecEngine) << "Security scoped resource has been created. Saving bookmark.";
+ SecurityScopedFileEngineHandler::get()->saveBookmark(m_securityScopedUrl);
+ }
+
+ // Note: Stopping access is a no-op if we didn't have access
+ [m_securityScopedUrl stopAccessingSecurityScopedResource];
+ qCDebug(lcSecEngine) << "Stopped accessing" << m_securityScopedUrl.path
+ << "on behalf of" << fileName(DefaultName);
+ }
+
+ bool securityScopeIsReachable() const
+ {
+ return checkIfResourceIsReachable(m_securityScopedUrl);
+ }
+
+ // See note above about relationship to fileName
+ NSURL *m_securityScopedUrl = nullptr;
+ bool m_securityScopeWasReachable = false;
+};
+
+// ----------------------------------------------------------------------
+
+SecurityScopedFileEngineHandler::SecurityScopedFileEngineHandler()
+{
+ QMacAutoReleasePool pool;
+
+ NSURL *savedBookmarks = bookmarksFile();
+ if ([NSFileManager.defaultManager fileExistsAtPath:savedBookmarks.path]) {
+ NSError *error = nullptr;
+ m_bookmarks = [[NSDictionary dictionaryWithContentsOfURL:savedBookmarks
+ error:&error] mutableCopy];
+
+ if (error) {
+ qCWarning(lcSecEngine) << "Failed to load bookmarks from"
+ << savedBookmarks << ":" << error;
+ } else {
+ qCInfo(lcSecEngine) << "Loaded existing bookmarks for" << m_bookmarks.allKeys;
+ }
+ }
+
+ if (!m_bookmarks)
+ m_bookmarks = [NSMutableDictionary new];
+}
+
+SecurityScopedFileEngineHandler::~SecurityScopedFileEngineHandler()
+{
+ [m_bookmarks release];
+}
+
+void SecurityScopedFileEngineHandler::registerPossiblySecurityScopedURL(NSURL *url)
+{
+ QMacAutoReleasePool pool;
+
+ // Start accessing the resource, to check if it's security scoped,
+ // and allow us to create a bookmark for it on both macOS and iOS.
+ if (![url startAccessingSecurityScopedResource])
+ return; // All good, not security scoped
+
+ if (checkIfResourceIsReachable(url)) {
+ // We can access the resource, which means it exists, so we can
+ // create a persistent bookmark for it right away. We want to do
+ // this as soon as possible, so that if the app is terminated the
+ // user can continue working on the file without the app needing
+ // to ask for access again via a file dialog.
+ saveBookmark(url);
+ } else {
+ // The file isn't accessible, likely because it doesn't exist.
+ // As we can only create security scoped bookmarks for files
+ // that exist we store the URL itself for now, and save it to
+ // a bookmark later when we detect that the file has been created.
+ qCInfo(lcSecEngine) << "Resource is not reachable."
+ << "Registering URL" << url << "instead";
+ QWriteLocker locker(&m_bookmarkLock);
+ m_bookmarks[cacheKeyForUrl(url)] = url;
+ }
+
+ // Balance access from above
+ [url stopAccessingSecurityScopedResource];
+
+#if defined(Q_OS_MACOS)
+ // On macOS, unlike iOS, URLs from file dialogs, etc, come with implicit
+ // access already, and we are expected to balance this access with an
+ // explicit stopAccessingSecurityScopedResource. We release the last
+ // access here to unify the behavior between macOS and iOS, and then
+ // leave it up to the SecurityScopedFileEngine to regain access, where
+ // we know the lifetime of resource use, and when to release access.
+ [url stopAccessingSecurityScopedResource];
+#endif
+}
+
+std::unique_ptr<QAbstractFileEngine> SecurityScopedFileEngineHandler::create(const QString &fileName) const
+{
+ QMacAutoReleasePool pool;
+
+ static thread_local bool recursionGuard = false;
+ if (recursionGuard)
+ return nullptr;
+
+ if (fileName.isEmpty())
+ return nullptr;
+
+ QFileSystemEntry fileSystemEntry(fileName);
+ QFileSystemMetaData metaData;
+
+ {
+ // Check if there's another engine that claims to handle the given file name.
+ // This covers non-QFSFileEngines like QTemporaryFileEngine, and QResourceFileEngine.
+ // If there isn't one, we'll get nullptr back, and know that we can access the
+ // file via our special QFSFileEngine.
+ QScopedValueRollback<bool> rollback(recursionGuard, true);
+ if (auto engine = QFileSystemEngine::createLegacyEngine(fileSystemEntry, metaData)) {
+ // Shortcut the logic of the createLegacyEngine call we're in by
+ // just returning this engine now.
+ qCDebug(lcSecEngine) << "Preferring non-QFSFileEngine engine"
+ << engine.get() << "for" << fileName;
+ return engine;
+ }
+ }
+
+ // We're mapping the file name to existing bookmarks below, so make sure
+ // we use as close as we can get to the canonical path. For files that
+ // do not exist we fall back to the cleaned absolute path.
+ auto canonicalEntry = QFileSystemEngine::canonicalName(fileSystemEntry, metaData);
+ if (canonicalEntry.isEmpty())
+ canonicalEntry = QFileSystemEngine::absoluteName(fileSystemEntry);
+
+ if (canonicalEntry.isRelative()) {
+ // We try to map relative paths to absolute above, but doing so requires
+ // knowledge of the current working directory, which we only have if the
+ // working directory has already started access through other means. We
+ // can't explicitly start access of the working directory here, as doing
+ // so requires its name, which we can't get from getcwd() without access.
+ // Fortunately all of the entry points of security scoped URLs such as
+ // file dialogs or drag-and-drop give us absolute paths, and APIs like
+ // QDir::filePath() will construct absolute URLs without needing the
+ // current working directory.
+ qCWarning(lcSecEngine) << "Could not resolve" << fileSystemEntry.filePath()
+ << "against current working working directory";
+ return nullptr;
+ }
+
+ // Clean the path as well, to remove any trailing slashes for directories
+ QString filePath = QDir::cleanPath(canonicalEntry.filePath());
+
+ // Files inside the sandbox container can always be accessed directly
+ static const QString sandboxRoot = QString::fromNSString(NSHomeDirectory());
+ if (filePath.startsWith(sandboxRoot))
+ return nullptr;
+
+ // The same applies to files inside the application's own bundle
+ static const QString bundleRoot = QString::fromNSString(NSBundle.mainBundle.bundlePath);
+ if (filePath.startsWith(bundleRoot))
+ return nullptr;
+
+ qCDebug(lcSecEngine) << "Looking up bookmark for" << filePath << "based on incoming fileName" << fileName;
+
+ // Check if we have a persisted bookmark for this fileName, or
+ // any of its containing directories (which will give us access
+ // to the file).
+ QReadLocker locker(&m_bookmarkLock);
+ auto *cacheKey = cacheKeyForPath(filePath);
+ NSObject *bookmarkData = nullptr;
+ while (cacheKey.length > 1) {
+ bookmarkData = m_bookmarks[cacheKey];
+ if (bookmarkData)
+ break;
+ cacheKey = [cacheKey stringByDeletingLastPathComponent];
+ }
+
+ // We didn't find a bookmark, so there's no point in trying to manage
+ // this file via a SecurityScopedFileEngine.
+ if (!bookmarkData) {
+ qCDebug(lcSecEngine) << "No bookmark found. Falling back to QFSFileEngine.";
+ return nullptr;
+ }
+
+ NSURL *securityScopedUrl = nullptr;
+ if ([bookmarkData isKindOfClass:NSURL.class]) {
+ securityScopedUrl = static_cast<NSURL*>(bookmarkData);
+ } else {
+ NSError *error = nullptr;
+ BOOL bookmarkDataIsStale = NO;
+ securityScopedUrl = [NSURL URLByResolvingBookmarkData:static_cast<NSData*>(bookmarkData)
+ options:
+ #if defined(Q_OS_MACOS)
+ NSURLBookmarkResolutionWithSecurityScope
+ #else
+ // iOS bookmarks are always security scoped, and we
+ // don't need or want any of the other options.
+ NSURLBookmarkResolutionOptions(0)
+ #endif
+ relativeToURL:nil /* app-scoped bookmark */
+ bookmarkDataIsStale:&bookmarkDataIsStale
+ error:&error];
+
+ if (!securityScopedUrl || error) {
+ qCWarning(lcSecEngine) << "Failed to resolve bookmark data for"
+ << fileName << ":" << error;
+ return nullptr;
+ }
+
+ if (bookmarkDataIsStale) {
+ // This occurs when for example the file has been renamed, moved,
+ // or deleted. Normally this would be the place to update the
+ // bookmark to point to the new location, but Qt clients may not
+ // be prepared for QFiles changing their file-names under their
+ // feet so we treat it as a missing file.
+ qCDebug(lcSecEngine) << "Bookmark for" << cacheKey << "was stale";
+ locker.unlock();
+ QWriteLocker writeLocker(&m_bookmarkLock);
+ [m_bookmarks removeObjectForKey:cacheKey];
+ auto *mutableThis = const_cast<SecurityScopedFileEngineHandler*>(this);
+ mutableThis->saveBookmarks();
+ return nullptr;
+ }
+ }
+
+ qCInfo(lcSecEngine) << "Resolved security scope" << securityScopedUrl
+ << "for path" << filePath;
+ return std::make_unique<SecurityScopedFileEngine>(fileName, securityScopedUrl);
+}
+
+/*
+ Create an app-scoped bookmark, and store it in our persistent cache.
+
+ We do this so that the user can continue accessing the file even after
+ application restarts.
+
+ Storing the bookmarks to disk (inside the sandbox) is safe, as only the
+ app that created the app-scoped bookmarks can obtain access to the file
+ system resource that the URL points to. Specifically, a bookmark created
+ with security scope fails to resolve if the caller does not have the same
+ code signing identity as the caller that created the bookmark.
+*/
+void SecurityScopedFileEngineHandler::saveBookmark(NSURL *url)
+{
+ NSError *error = nullptr;
+ NSData *bookmarkData = [url bookmarkDataWithOptions:
+ #if defined(Q_OS_MACOS)
+ NSURLBookmarkCreationWithSecurityScope
+ #else
+ // iOS bookmarks are always security scoped, and we
+ // don't need or want any of the other options.
+ NSURLBookmarkCreationOptions(0)
+ #endif
+ includingResourceValuesForKeys:nil
+ relativeToURL:nil /* app-scoped bookmark */
+ error:&error];
+
+ if (bookmarkData) {
+ QWriteLocker locker(&m_bookmarkLock);
+ NSString *cacheKey = cacheKeyForUrl(url);
+ qCInfo(lcSecEngine)
+ << (m_bookmarks[cacheKey] ? "Updating" : "Registering")
+ << "bookmark for" << cacheKey;
+ m_bookmarks[cacheKey] = bookmarkData;
+ saveBookmarks();
+ } else {
+ qCWarning(lcSecEngine) << "Failed to create bookmark data for" << url << error;
+ }
+}
+
+/*
+ Saves the bookmarks cache to disk.
+
+ We do this preemptively whenever we create a bookmark, to ensure
+ the file can be accessed later on even if the app crashes.
+*/
+void SecurityScopedFileEngineHandler::saveBookmarks()
+{
+ QMacAutoReleasePool pool;
+
+ NSError *error = nullptr;
+ NSURL *bookmarksFilePath = bookmarksFile();
+ [NSFileManager.defaultManager
+ createDirectoryAtURL:[bookmarksFilePath URLByDeletingLastPathComponent]
+ withIntermediateDirectories:YES attributes:nil error:&error];
+ if (error) {
+ qCWarning(lcSecEngine) << "Failed to create bookmarks path:" << error;
+ return;
+ }
+ [m_bookmarks writeToURL:bookmarksFile() error:&error];
+ if (error) {
+ qCWarning(lcSecEngine) << "Failed to save bookmarks to"
+ << bookmarksFile() << ":" << error;
+ }
+}
+
+NSURL *SecurityScopedFileEngineHandler::bookmarksFile() const
+{
+ NSURL *appSupportDir = [[NSFileManager.defaultManager URLsForDirectory:
+ NSApplicationSupportDirectory inDomains:NSUserDomainMask] firstObject];
+ return [appSupportDir URLByAppendingPathComponent:@"SecurityScopedBookmarks.plist"];
+}
+
+NSString *SecurityScopedFileEngineHandler::cacheKeyForUrl(NSURL *url)
+{
+ return cacheKeyForPath(QString::fromNSString(url.path));
+}
+
+NSString *SecurityScopedFileEngineHandler::cacheKeyForPath(const QString &path)
+{
+ auto normalized = path.normalized(QString::NormalizationForm_D);
+ // We assume the file paths we get via file dialogs and similar
+ // are already canonical, but clean it just in case.
+ return QDir::cleanPath(normalized).toNSString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine_p.h b/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine_p.h
new file mode 100644
index 00000000000..f6098fa977d
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine_p.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Qt-Security score:significant reason:default
+
+#ifndef QDARWINSECURITYSCOPEDFILEENGINE_H
+#define QDARWINSECURITYSCOPEDFILEENGINE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is part of the QPA API and is not meant to be used
+// in applications. Usage of this API may make your code
+// source and binary incompatible with future versions of Qt.
+//
+// We mean it.
+//
+
+#include <QtCore/qurl.h>
+
+Q_FORWARD_DECLARE_OBJC_CLASS(NSURL);
+
+QT_BEGIN_NAMESPACE
+
+Q_CORE_EXPORT QUrl qt_apple_urlFromPossiblySecurityScopedURL(NSURL *url);
+
+QT_END_NAMESPACE
+
+#endif // QDARWINSECURITYSCOPEDFILEENGINE_H
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp
index 287138bb915..bbaf2c442a0 100644
--- a/src/corelib/platform/wasm/qstdweb.cpp
+++ b/src/corelib/platform/wasm/qstdweb.cpp
@@ -178,12 +178,17 @@ Blob Blob::slice(uint32_t begin, uint32_t end) const
ArrayBuffer Blob::arrayBuffer_sync() const
{
emscripten::val buffer;
- uint32_t handlerIndex = qstdweb::Promise::make(m_blob, QStringLiteral("arrayBuffer"), {
- .thenFunc = [&buffer](emscripten::val arrayBuffer) {
- buffer = arrayBuffer;
+ QList<uint32_t> handlers;
+ qstdweb::Promise::make(
+ handlers,
+ m_blob,
+ QStringLiteral("arrayBuffer"),
+ {
+ .thenFunc = [&buffer](emscripten::val arrayBuffer) {
+ buffer = arrayBuffer;
}
});
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
return ArrayBuffer(buffer);
}
@@ -441,7 +446,120 @@ EventCallback::EventCallback(emscripten::val element, const std::string &name,
}
-uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks)
+size_t qstdweb::Promise::State::s_numInstances = 0;
+
+//
+// When a promise settles, all attached handlers will be called in
+// the order they where added.
+//
+// In particular a finally handler will be called according to its
+// position in the call chain. Which is not necessarily at the end,
+//
+// This makes cleanup difficult. If we cleanup to early, we will remove
+// handlers before they have a chance to be called. This would be the
+// case if we add a finally handler in the Promise constructor.
+//
+// For correct cleanup it is necessary that it happens after the
+// last handler has been called.
+//
+// We choose to implement this by making sure the last handler
+// is always a finally handler.
+//
+// In this case we have multiple finally handlers, so any called
+// handler checks if it is the last handler to be called.
+// If it is, the cleanup is performed, otherwise cleanup
+// is delayed to the last handler.
+//
+// We could try to let the handlers cleanup themselves, but this
+// only works for finally handlers. A then or catch handler is not
+// necessarily called, and would not cleanup itself.
+//
+// We could try to let a (then,catch) pair cleanup both handlers,
+// under the assumption that one of them will always be called.
+// This does not work in the case that we do not have both handlers,
+// or if the then handler throws (both should be called in this case).
+//
+Promise& Promise::addThenFunction(std::function<void(emscripten::val)> thenFunc)
+{
+ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
+ Q_ASSERT(suspendResume);
+
+ m_state->m_handlers.push_back(suspendResume->registerEventHandler(thenFunc));
+ m_state->m_promise =
+ m_state->m_promise.call<emscripten::val>(
+ "then",
+ suspendResume->jsEventHandlerAt(
+ m_state->m_handlers.back()));
+
+ addFinallyFunction([](){}); // Add a potential cleanup handler
+ return (*this);
+}
+
+Promise& Promise::addCatchFunction(std::function<void(emscripten::val)> catchFunc)
+{
+ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
+ Q_ASSERT(suspendResume);
+
+ m_state->m_handlers.push_back(suspendResume->registerEventHandler(catchFunc));
+ m_state->m_promise =
+ m_state->m_promise.call<emscripten::val>(
+ "catch",
+ suspendResume->jsEventHandlerAt(
+ m_state->m_handlers.back()));
+
+ addFinallyFunction([](){}); // Add a potential cleanup handler
+ return (*this);
+}
+
+Promise& Promise::addFinallyFunction(std::function<void()> finallyFunc)
+{
+ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
+ Q_ASSERT(suspendResume);
+
+ auto thisHandler = std::make_shared<uint32_t>((uint32_t)(-1));
+ auto state = m_state;
+
+ std::function<void(emscripten::val)> func =
+ [state, thisHandler, finallyFunc](emscripten::val element) {
+ Q_UNUSED(element);
+
+ finallyFunc();
+
+ // See comment at top, we can only do the cleanup
+ // if we are the last handler in the handler chain
+ if (state->m_handlers.back() == *thisHandler) {
+ auto guard = state; // removeEventHandler will remove also this function
+ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
+ Q_ASSERT(suspendResume);
+ for (int i = 0; i < guard->m_handlers.size(); ++i) {
+ suspendResume->removeEventHandler(guard->m_handlers[i]);
+ guard->m_handlers[i] = (uint32_t)(-1);
+ }
+ }
+ };
+
+ *thisHandler = suspendResume->registerEventHandler(func);
+ m_state->m_handlers.push_back(*thisHandler);
+ m_state->m_promise =
+ m_state->m_promise.call<emscripten::val>(
+ "finally",
+ suspendResume->jsEventHandlerAt(
+ m_state->m_handlers.back()));
+
+ return (*this);
+}
+
+void Promise::suspendExclusive()
+{
+ Promise::suspendExclusive(m_state->m_handlers);
+}
+
+emscripten::val Promise::getPromise() const
+{
+ return m_state->m_promise;
+}
+
+uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers)
{
Q_ASSERT_X(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc,
"Promise::adoptPromise", "must provide at least one callback function");
@@ -498,14 +616,21 @@ uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbac
promise = promise.call<emscripten::val>("finally",
suspendResume->jsEventHandlerAt(*finallyIndex));
+ if (handlers) {
+ if (thenIndex)
+ handlers->push_back(*thenIndex);
+ if (catchIndex)
+ handlers->push_back(*catchIndex);
+ handlers->push_back(*finallyIndex);
+ }
return *finallyIndex;
}
-void Promise::suspendExclusive(uint32_t handlerIndex)
+void Promise::suspendExclusive(QList<uint32_t> handlerIndices)
{
QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
Q_ASSERT(suspendResume);
- suspendResume->suspendExclusive(handlerIndex);
+ suspendResume->suspendExclusive(handlerIndices);
suspendResume->sendPendingEvents();
}
@@ -657,11 +782,12 @@ void FileSystemWritableFileStreamIODevice::close()
return;
}
- uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("close"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_stream.val(), QStringLiteral("close"), {
.thenFunc = [](emscripten::val) {
}
});
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
QIODevice::close();
}
@@ -683,14 +809,15 @@ bool FileSystemWritableFileStreamIODevice::seek(qint64 pos)
emscripten::val seekParams = emscripten::val::object();
seekParams.set("type", std::string("seek"));
seekParams.set("position", static_cast<double>(pos));
- uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_stream.val(), QStringLiteral("write"), {
.thenFunc = [&success](emscripten::val) {
success = true;
},
.catchFunc = [](emscripten::val) {
}
}, seekParams);
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
if (!success)
return false;
@@ -708,14 +835,15 @@ qint64 FileSystemWritableFileStreamIODevice::writeData(const char *data, qint64
bool success = false;
Uint8Array array = Uint8Array::copyFrom(data, size);
- uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_stream.val(), QStringLiteral("write"), {
.thenFunc = [&success](emscripten::val) {
success = true;
},
.catchFunc = [](emscripten::val) {
}
}, array.val());
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
if (success) {
qint64 newPos = pos() + size;
@@ -770,7 +898,8 @@ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
File file;
bool success = false;
- uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("getFile"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_fileHandle.val(), QStringLiteral("getFile"), {
.thenFunc = [&file, &success](emscripten::val fileVal) {
file = File(fileVal);
success = true;
@@ -778,7 +907,7 @@ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
.catchFunc = [](emscripten::val) {
}
});
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
if (success) {
m_blobDevice = std::make_unique<BlobIODevice>(file.slice(0, file.size()));
@@ -796,7 +925,8 @@ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
FileSystemWritableFileStream writableStream;
bool success = false;
- uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("createWritable"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_fileHandle.val(), QStringLiteral("createWritable"), {
.thenFunc = [&writableStream, &success](emscripten::val writable) {
writableStream = FileSystemWritableFileStream(writable);
success = true;
@@ -804,7 +934,7 @@ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
.catchFunc = [](emscripten::val) {
}
});
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
if (success) {
m_writableDevice = std::make_unique<FileSystemWritableFileStreamIODevice>(writableStream);
diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h
index 9a97370448e..07df021c444 100644
--- a/src/corelib/platform/wasm/qstdweb_p.h
+++ b/src/corelib/platform/wasm/qstdweb_p.h
@@ -237,11 +237,80 @@ namespace qstdweb {
std::function<void()> finallyFunc;
};
- namespace Promise {
- uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
+ // Note: it is ok for the Promise object to go out of scope,
+ // the resources will be cleaned up in the finally handler.
+ class Q_CORE_EXPORT Promise {
+ public:
+ template<typename... Args>
+ Promise(emscripten::val target, QString methodName, Args... args) {
+ m_state = std::make_shared<State>();
+ m_state->m_promise = target.call<emscripten::val>(
+ methodName.toStdString().c_str(), std::forward<Args>(args)...);
+ if (m_state->m_promise.isUndefined() || m_state->m_promise["constructor"]["name"].as<std::string>() != "Promise") {
+ qFatal("This function did not return a promise");
+ }
+ addFinallyFunction([](){});
+ }
+
+ Promise(emscripten::val promise) {
+ m_state = std::make_shared<State>();
+ m_state->m_promise = promise;
+ if (m_state->m_promise.isUndefined() || m_state->m_promise["constructor"]["name"].as<std::string>() != "Promise") {
+ qFatal("This function did not return a promise");
+ }
+ addFinallyFunction([](){});
+ }
+
+ Promise(const std::vector<Promise> &promises) {
+ std::vector<emscripten::val> all;
+ all.reserve(promises.size());
+ for (const auto &p : promises)
+ all.push_back(p.getPromise());
+
+ auto arr = emscripten::val::array(all);
+ m_state = std::make_shared<State>();
+ m_state->m_promise = emscripten::val::global("Promise").call<emscripten::val>("all", arr);
+ addFinallyFunction([](){});
+ }
+
+ Promise& addThenFunction(std::function<void(emscripten::val)> thenFunc);
+ Promise& addCatchFunction(std::function<void(emscripten::val)> catchFunc);
+ Promise& addFinallyFunction(std::function<void()> finallyFunc);
+
+ void suspendExclusive();
+
+ emscripten::val getPromise() const;
+
+ public:
+ class State {
+ private:
+ friend class Promise;
+
+ State(const State&) = delete;
+ State(State&&) = delete;
+ State& operator=(const State&) = delete;
+ State& operator=(State&&) = delete;
+
+ public:
+ State() { ++s_numInstances; }
+ ~State() { --s_numInstances; }
+ static size_t numInstances() { return s_numInstances; }
+
+ private:
+ emscripten::val m_promise = emscripten::val::undefined();
+ QList<uint32_t> m_handlers;
+ static size_t s_numInstances;
+ };
+
+ private:
+ std::shared_ptr<State> m_state;
+
+ public:
+ // Deprecated: To be backwards compatible
+ static uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers = nullptr);
template<typename... Args>
- uint32_t make(emscripten::val target,
+ static uint32_t make(emscripten::val target,
QString methodName,
PromiseCallbacks callbacks,
Args... args)
@@ -255,8 +324,25 @@ namespace qstdweb {
return adoptPromise(std::move(promiseObject), std::move(callbacks));
}
- void Q_CORE_EXPORT suspendExclusive(uint32_t handlerIndex);
- void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks);
+ template<typename... Args>
+ static void make(
+ QList<uint32_t> &handlers,
+ emscripten::val target,
+ QString methodName,
+ PromiseCallbacks callbacks,
+ Args... args)
+ {
+ emscripten::val promiseObject = target.call<emscripten::val>(
+ methodName.toStdString().c_str(), std::forward<Args>(args)...);
+ if (promiseObject.isUndefined() || promiseObject["constructor"]["name"].as<std::string>() != "Promise") {
+ qFatal("This function did not return a promise");
+ }
+
+ adoptPromise(std::move(promiseObject), std::move(callbacks), &handlers);
+ }
+
+ static void Q_CORE_EXPORT suspendExclusive(QList<uint32_t> handlerIndices);
+ static void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks);
};
template<class F>
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
index 5fe92926240..a4bc7843380 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
@@ -196,9 +196,13 @@ void QWasmSuspendResumeControl::suspend()
qtSuspendJs();
}
-void QWasmSuspendResumeControl::suspendExclusive(uint32_t eventHandlerIndex)
+void QWasmSuspendResumeControl::suspendExclusive(QList<uint32_t> eventHandlerIndices)
{
- suspendResumeControlJs().set("exclusiveEventHandler", eventHandlerIndex);
+ m_eventFilter = [eventHandlerIndices](int handler) {
+ return eventHandlerIndices.contains(handler);
+ };
+
+ suspendResumeControlJs().set("exclusiveEventHandler", eventHandlerIndices.back());
qtSuspendJs();
}
@@ -211,37 +215,27 @@ int QWasmSuspendResumeControl::sendPendingEvents()
emscripten::val control = suspendResumeControlJs();
emscripten::val pendingEvents = control["pendingEvents"];
- if (control["exclusiveEventHandler"].as<int>() > 0)
- return sendPendingExclusiveEvent();
-
- if (pendingEvents["length"].as<int>() == 0)
- return 0;
-
int count = 0;
- while (pendingEvents["length"].as<int>() > 0) { // Make sure it is reentrant
- // Grab one event (handler and arg), and call it
- emscripten::val event = pendingEvents.call<val>("shift");
- auto it = m_eventHandlers.find(event["index"].as<int>());
- if (it != m_eventHandlers.end())
- it->second(event["arg"]);
- ++count;
+ for (int i = 0; i < pendingEvents["length"].as<int>();) {
+ if (!m_eventFilter(pendingEvents[i]["index"].as<int>())) {
+ ++i;
+ } else {
+ // Grab one event (handler and arg), and call it
+ emscripten::val event = pendingEvents[i];
+ pendingEvents.call<void>("splice", i, 1);
+
+ auto it = m_eventHandlers.find(event["index"].as<int>());
+ if (it != m_eventHandlers.end())
+ it->second(event["arg"]);
+ ++count;
+ }
}
- return count;
-}
-// Sends the pending exclusive event, and resets the "exclusive" state
-int QWasmSuspendResumeControl::sendPendingExclusiveEvent()
-{
- emscripten::val control = suspendResumeControlJs();
- int exclusiveHandlerIndex = control["exclusiveEventHandler"].as<int>();
- control.set("exclusiveEventHandler", 0);
- emscripten::val event = control["pendingEvents"].call<val>("pop");
- int eventHandlerIndex = event["index"].as<int>();
- Q_ASSERT(exclusiveHandlerIndex == eventHandlerIndex);
- auto it = m_eventHandlers.find(eventHandlerIndex);
- Q_ASSERT(it != m_eventHandlers.end());
- it->second(event["arg"]);
- return 1;
+ if (control["exclusiveEventHandler"].as<int>() > 0) {
+ control.set("exclusiveEventHandler", 0);
+ m_eventFilter = [](int) { return true;};
+ }
+ return count;
}
void qtSendPendingEvents()
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
index b750d80314c..ff97ff3d7ea 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
@@ -38,15 +38,16 @@ public:
static emscripten::val suspendResumeControlJs();
void suspend();
- void suspendExclusive(uint32_t eventHandlerIndex);
+ // Accept events for all handlers, start to process events on last handler in list
+ void suspendExclusive(QList<uint32_t> eventHandlerIndices);
int sendPendingEvents();
- int sendPendingExclusiveEvent();
private:
friend void qtSendPendingEvents();
static QWasmSuspendResumeControl *s_suspendResumeControl;
std::map<int, std::function<void(emscripten::val)>> m_eventHandlers;
+ std::function<bool(int)> m_eventFilter = [](int) { return true; };
};
class Q_CORE_EXPORT QWasmEventHandler
diff --git a/src/corelib/text/qlocale_icu.cpp b/src/corelib/text/qlocale_icu.cpp
index a10ae1c84b2..7e1dba5ee92 100644
--- a/src/corelib/text/qlocale_icu.cpp
+++ b/src/corelib/text/qlocale_icu.cpp
@@ -17,10 +17,10 @@ static_assert(std::is_same_v<UChar, char16_t>,
namespace QtIcuPrivate {
-enum class CaseConversion : bool { Upper, Lower };
+enum class IcuCaseConversion : bool { Upper, Lower };
static bool qt_u_strToCase(const QString &str, QString *out, const char *localeID,
- CaseConversion conv)
+ IcuCaseConversion conv)
{
Q_ASSERT(out);
@@ -34,9 +34,9 @@ static bool qt_u_strToCase(const QString &str, QString *out, const char *localeI
// try to be a completely transparent wrapper:
using R [[maybe_unused]] = decltype(u_strToUpper(std::forward<decltype(args)>(args)...));
switch (conv) {
- case CaseConversion::Upper:
+ case IcuCaseConversion::Upper:
return u_strToUpper(std::forward<decltype(args)>(args)...);
- case CaseConversion::Lower:
+ case IcuCaseConversion::Lower:
return u_strToLower(std::forward<decltype(args)>(args)...);
};
Q_UNREACHABLE_RETURN(R{0});
@@ -79,7 +79,7 @@ QString QLocalePrivate::toUpper(const QString &str, bool *ok) const
Q_ASSERT(ok);
using namespace QtIcuPrivate;
QString out;
- *ok = qt_u_strToCase(str, &out, bcp47Name('_'), CaseConversion::Upper);
+ *ok = qt_u_strToCase(str, &out, bcp47Name('_'), IcuCaseConversion::Upper);
return out;
}
@@ -88,7 +88,7 @@ QString QLocalePrivate::toLower(const QString &str, bool *ok) const
Q_ASSERT(ok);
using namespace QtIcuPrivate;
QString out;
- *ok = qt_u_strToCase(str, &out, bcp47Name('_'), CaseConversion::Lower);
+ *ok = qt_u_strToCase(str, &out, bcp47Name('_'), IcuCaseConversion::Lower);
return out;
}
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp
index deac396061d..34e910fabec 100644
--- a/src/corelib/time/qdatetime.cpp
+++ b/src/corelib/time/qdatetime.cpp
@@ -3894,10 +3894,12 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
Selects a time on the standard time side of the transition.
\value PreferDaylightSaving
Selects a time on the daylight-saving-time side of the transition.
- \value LegacyBehavior
- An alias for RelativeToBefore, which is used as default for
- TransitionResolution parameters, as this most closely matches the
- behavior prior to Qt 6.7.
+ \omitvalue LegacyBehavior
+
+ An additional constant, \c LegacyBehavior, is used as a default value for
+ TransitionResolution parameters in some constructors and setter functions.
+ This is an alias for \c RelativeToBefore, which implements behavior that
+ most closely matches the behavior of QDateTime prior to Qt 6.7.
For \l addDays(), \l addMonths() or \l addYears(), the behavior is and
(mostly) was to use \c RelativeToBefore if adding a positive adjustment and \c
@@ -3909,7 +3911,7 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
where the daylight-saving mechanism is a decrease in offset from UTC in
winter (known as "negative DST"), the reverse applies, provided the
operating system reports - as it does on most platforms - whether a datetime
- is in DST or standard time. For some platforms, where transition times are
+ is in DST or standard time. For some platforms, where transition details are
unavailable even for Qt::TimeZone datetimes, QTimeZone is obliged to presume
that the side with lower offset from UTC is standard time, effectively
assuming positive DST.
diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h
index d2c0d45b79d..5a827fb4148 100644
--- a/src/corelib/tools/qflatmap_p.h
+++ b/src/corelib/tools/qflatmap_p.h
@@ -15,7 +15,9 @@
// We mean it.
//
+#include <QtCore/qcontainertools_impl.h>
#include "qlist.h"
+#include <QtCore/qtclasshelpermacros.h>
#include "private/qglobal_p.h"
#include <algorithm>
@@ -42,23 +44,9 @@ QT_BEGIN_NAMESPACE
QFlatMap<float, int, std::less<float>, std::vector<float>, std::vector<int>>
*/
-// Qt 6.4:
-// - removed QFlatMap API which was incompatible with STL semantics
-// - will be released with said API disabled, to catch any out-of-tree users
-// - also allows opting in to the new API using QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT
-// Qt 6.5
-// - will make QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT the default:
-
-#ifndef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT
-# if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
-# define QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT
-# endif
-#endif
-
namespace Qt {
-struct OrderedUniqueRange_t {};
-constexpr OrderedUniqueRange_t OrderedUniqueRange = {};
+QT_DEFINE_TAG(OrderedUniqueRange);
} // namespace Qt
@@ -83,35 +71,11 @@ public:
}
};
-namespace qflatmap {
-namespace detail {
-template <class T>
-class QFlatMapMockPointer
-{
- T ref;
-public:
- QFlatMapMockPointer(T r)
- : ref(r)
- {
- }
-
- T *operator->()
- {
- return &ref;
- }
-};
-} // namespace detail
-} // namespace qflatmap
-
template<class Key, class T, class Compare = std::less<Key>, class KeyContainer = QList<Key>,
class MappedContainer = QList<T>>
class QFlatMap : private QFlatMapValueCompare<Key, T, Compare>
{
static_assert(std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");
-
- template<class U>
- using mock_pointer = qflatmap::detail::QFlatMapMockPointer<U>;
-
public:
using key_type = Key;
using mapped_type = T;
@@ -134,7 +98,7 @@ public:
using difference_type = ptrdiff_t;
using value_type = std::pair<const Key, T>;
using reference = std::pair<const Key &, T &>;
- using pointer = mock_pointer<reference>;
+ using pointer = QtPrivate::ArrowProxy<reference>;
using iterator_category = std::random_access_iterator_tag;
iterator() = default;
@@ -266,7 +230,7 @@ public:
using difference_type = ptrdiff_t;
using value_type = std::pair<const Key, const T>;
using reference = std::pair<const Key &, const T &>;
- using pointer = mock_pointer<reference>;
+ using pointer = QtPrivate::ArrowProxy<reference>;
using iterator_category = std::random_access_iterator_tag;
const_iterator() = default;
@@ -415,7 +379,6 @@ private:
public:
QFlatMap() = default;
-#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT
explicit QFlatMap(const key_container_type &keys, const mapped_container_type &values)
: c{keys, values}
{
@@ -451,7 +414,6 @@ public:
initWithRange(first, last);
ensureOrderedUnique();
}
-#endif
explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys,
const mapped_container_type &values)
@@ -493,7 +455,6 @@ public:
{
}
-#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT
explicit QFlatMap(const key_container_type &keys, const mapped_container_type &values,
const Compare &compare)
: value_compare(compare), c{keys, values}
@@ -534,7 +495,6 @@ public:
initWithRange(first, last);
ensureOrderedUnique();
}
-#endif
explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys,
const mapped_container_type &values, const Compare &compare)
@@ -674,7 +634,6 @@ public:
return value(key);
}
-#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT
std::pair<iterator, bool> insert(const Key &key, const T &value)
{
return try_emplace(key, value);
@@ -694,7 +653,6 @@ public:
{
return try_emplace(std::move(key), std::move(value));
}
-#endif
template <typename...Args>
std::pair<iterator, bool> try_emplace(const Key &key, Args&&...args)
@@ -738,7 +696,6 @@ public:
return r;
}
-#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT
template <class InputIt, is_compatible_iterator<InputIt> = nullptr>
void insert(InputIt first, InputIt last)
{
@@ -764,7 +721,6 @@ public:
{
insertOrderedUniqueRange(first, last);
}
-#endif
iterator begin() { return { &c, 0 }; }
const_iterator begin() const { return { &c, 0 }; }