diff --git a/doc/global/config.qdocconf b/doc/global/config.qdocconf index 0b276c400f..16d4e5c63a 100644 --- a/doc/global/config.qdocconf +++ b/doc/global/config.qdocconf @@ -11,6 +11,12 @@ dita.metadata.default.audience = programmer navigation.homepage = index.html navigation.hometitle = "Qt $QT_VER" +#Words to ignore for auto-linking +ignorewords += \ + macOS \ + WebChannel \ + OpenGL + sourcedirs += includes $$BUILDDIR url = http://doc.qt.io/qt-5 diff --git a/mkspecs/android-clang/qmake.conf b/mkspecs/android-clang/qmake.conf index 81609c3962..ec6c765799 100644 --- a/mkspecs/android-clang/qmake.conf +++ b/mkspecs/android-clang/qmake.conf @@ -70,8 +70,8 @@ QMAKE_CFLAGS_THREAD = -D_REENTRANT QMAKE_CFLAGS_HIDESYMS = -fvisibility=hidden QMAKE_CFLAGS_NEON = -mfpu=neon -QMAKE_LFLAGS_APP = -Wl,--no-undefined -Wl,-z,noexecstack -shared -QMAKE_LFLAGS_SHLIB = -Wl,--no-undefined -Wl,-z,noexecstack -shared +QMAKE_LFLAGS_APP = -Wl,--build-id=sha1 -Wl,--no-undefined -Wl,-z,noexecstack -shared +QMAKE_LFLAGS_SHLIB = -Wl,--build-id=sha1 -Wl,--no-undefined -Wl,-z,noexecstack -shared QMAKE_LFLAGS_PLUGIN = $$QMAKE_LFLAGS_SHLIB QMAKE_LFLAGS_NOUNDEF = -Wl,--no-undefined QMAKE_LFLAGS_RPATH = -Wl,-rpath= diff --git a/mkspecs/features/metatypes.prf b/mkspecs/features/metatypes.prf index a0a548eeb2..64387458ac 100644 --- a/mkspecs/features/metatypes.prf +++ b/mkspecs/features/metatypes.prf @@ -2,17 +2,25 @@ qtPrepareTool(MOC_COLLECT_JSON, moc) QMAKE_MOC_OPTIONS += --output-json +MOC_JSON_H_BASE = $${QMAKE_H_MOD_MOC} +MOC_JSON_CPP_BASE = $${QMAKE_CPP_MOD_MOC} + +!isEmpty(MOC_DIR) { + MOC_JSON_H_BASE = $$MOC_DIR/$${MOC_JSON_H_BASE} + MOC_JSON_CPP_BASE = $$MOC_DIR/$${MOC_JSON_CPP_BASE} +} + moc_json_header.input = HEADERS -moc_json_header.output = $$MOC_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)}.json +moc_json_header.output = $${MOC_JSON_H_BASE}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)}.json moc_json_header.CONFIG = no_link moc_verify -moc_json_header.depends = $$MOC_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} +moc_json_header.depends = $${MOC_JSON_H_BASE}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} moc_json_header.commands = $$escape_expand(\\n) # force creation of rule moc_json_header.variable_out = MOC_JSON_FILES moc_json_source.input = SOURCES -moc_json_source.output = $$MOC_DIR/$${QMAKE_CPP_MOD_MOC}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC}.json +moc_json_source.output = $${MOC_JSON_CPP_BASE}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC}.json moc_json_source.CONFIG = no_link moc_verify -moc_json_source.depends = $$MOC_DIR/$${QMAKE_CPP_MOD_MOC}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC} +moc_json_source.depends = $${MOC_JSON_CPP_BASE}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC} moc_json_source.commands = $$escape_expand(\\n) # force creation of rule moc_json_source.variable_out = MOC_JSON_FILES diff --git a/qmake/Makefile.unix b/qmake/Makefile.unix index 4d4f05e78a..da0fccb834 100644 --- a/qmake/Makefile.unix +++ b/qmake/Makefile.unix @@ -24,7 +24,7 @@ QOBJS = \ qfile.o qfiledevice.o qfileinfo.o qfilesystemengine.o \ qfilesystementry.o qfsfileengine.o qfsfileengine_iterator.o \ qiodevice.o qsettings.o qtemporaryfile.o qtextstream.o \ - qjsonarray.o qjson.o qjsondocument.o qjsonobject.o qjsonparser.o qjsonvalue.o \ + qcborvalue.o qjsoncbor.o qjsonarray.o qjsondocument.o qjsonobject.o qjsonparser.o qjsonvalue.o \ qmetatype.o qsystemerror.o qvariant.o \ quuid.o \ qarraydata.o qbitarray.o qbytearray.o qbytearraylist.o qbytearraymatcher.o \ @@ -96,9 +96,10 @@ DEPEND_SRC = \ $(SOURCE_PATH)/src/corelib/kernel/qsystemerror.cpp \ $(SOURCE_PATH)/src/corelib/kernel/qvariant.cpp \ $(SOURCE_PATH)/src/corelib/plugin/quuid.cpp \ + $(SOURCE_PATH)/src/corelib/serialization/qcborvalue.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qdatastream.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qjsonarray.cpp \ - $(SOURCE_PATH)/src/corelib/serialization/qjson.cpp \ + $(SOURCE_PATH)/src/corelib/serialization/qjsoncbor.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qjsondocument.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qjsonobject.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qjsonparser.cpp \ @@ -466,7 +467,10 @@ qsystemlibrary.o: $(SOURCE_PATH)/src/corelib/plugin/qsystemlibrary.cpp qdatastream.o: $(SOURCE_PATH)/src/corelib/serialization/qdatastream.cpp $(CXX) -c -o $@ $(CXXFLAGS) $< -qjson.o: $(SOURCE_PATH)/src/corelib/serialization/qjson.cpp +qcborvalue.o: $(SOURCE_PATH)/src/corelib/serialization/qcborvalue.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $< + +qjsoncbor.o: $(SOURCE_PATH)/src/corelib/serialization/qjsoncbor.cpp $(CXX) -c -o $@ $(CXXFLAGS) $< qjsondocument.o: $(SOURCE_PATH)/src/corelib/serialization/qjsondocument.cpp diff --git a/qmake/Makefile.win32 b/qmake/Makefile.win32 index 7324817af2..74fb80e337 100644 --- a/qmake/Makefile.win32 +++ b/qmake/Makefile.win32 @@ -118,7 +118,8 @@ QTOBJS= \ qxmlutils.obj \ qnumeric.obj \ qlogging.obj \ - qjson.obj \ + qcborvalue.obj \ + qjsoncbor.obj \ qjsondocument.obj \ qjsonparser.obj \ qjsonarray.obj \ diff --git a/qmake/doc/src/qmake-manual.qdoc b/qmake/doc/src/qmake-manual.qdoc index da3aa6b248..ed7fe60fdc 100644 --- a/qmake/doc/src/qmake-manual.qdoc +++ b/qmake/doc/src/qmake-manual.qdoc @@ -4690,7 +4690,7 @@ The definitions above define a qmake target called \c mytarget, containing a Makefile target called \c{.buildfile} which in turn is generated with the - \l{touchfunction}{touch()} function. Finally, the + \c touch command. Finally, the \c{.depends} member specifies that \c mytarget depends on \c mytarget2, another target that is defined afterwards. \c mytarget2 is a dummy target. It is only defined to echo some text to the console. diff --git a/qmake/qmake.pro b/qmake/qmake.pro index a9d8b58da8..42c727b33e 100644 --- a/qmake/qmake.pro +++ b/qmake/qmake.pro @@ -116,6 +116,7 @@ SOURCES += \ qbytearray.cpp \ qbytearraymatcher.cpp \ qcalendar.cpp \ + qcborvalue.cpp \ qcryptographichash.cpp \ qdatetime.cpp \ qdir.cpp \ @@ -131,8 +132,8 @@ SOURCES += \ qgregoriancalendar.cpp \ qhash.cpp \ qiodevice.cpp \ - qjson.cpp \ qjsonarray.cpp \ + qjsoncbor.cpp \ qjsondocument.cpp \ qjsonobject.cpp \ qjsonparser.cpp \ @@ -174,6 +175,8 @@ HEADERS += \ qcalendar.h \ qcalendarbackend_p.h \ qcalendarmath_p.h \ + qcborvalue.h \ + qcborvalue_p.h \ qchar.h \ qcryptographichash.h \ qdatetime.h \ diff --git a/src/corelib/configure.json b/src/corelib/configure.json index 1c3e874c04..5ee7888d4f 100644 --- a/src/corelib/configure.json +++ b/src/corelib/configure.json @@ -1100,6 +1100,12 @@ Mozilla License) is included. The data is then also used in QNetworkCookieJar::v Note that this is required for plugin loading. Qt GUI needs QPA plugins for basic operation.", "section": "Utilities", "output": [ "publicFeature" ] + }, + "binaryjson": { + "label": "Binary JSON (deprecated)", + "purpose": "Provides support for the deprecated binary JSON format.", + "section": "Utilities", + "output": [ "publicFeature" ] } }, diff --git a/src/corelib/global/qconfig-bootstrapped.h b/src/corelib/global/qconfig-bootstrapped.h index da3a1a005f..c58dc56c42 100644 --- a/src/corelib/global/qconfig-bootstrapped.h +++ b/src/corelib/global/qconfig-bootstrapped.h @@ -74,6 +74,7 @@ #else # define QT_FEATURE_alloca_malloc_h -1 #endif +#define QT_FEATURE_binaryjson -1 #define QT_FEATURE_cborstream -1 #define QT_CRYPTOGRAPHICHASH_ONLY_SHA1 #define QT_FEATURE_cxx11_random (QT_HAS_INCLUDE() ? 1 : -1) diff --git a/src/corelib/global/qendian.h b/src/corelib/global/qendian.h index 615f523888..5cd9d3160b 100644 --- a/src/corelib/global/qendian.h +++ b/src/corelib/global/qendian.h @@ -327,9 +327,9 @@ public: return pre; } - static constexpr QSpecialInteger max() + static Q_DECL_CONSTEXPR QSpecialInteger max() { return QSpecialInteger(std::numeric_limits::max()); } - static constexpr QSpecialInteger min() + static Q_DECL_CONSTEXPR QSpecialInteger min() { return QSpecialInteger(std::numeric_limits::min()); } }; @@ -373,8 +373,8 @@ public: QLEInteger &operator ++(int); QLEInteger &operator --(int); - static constexpr QLEInteger max(); - static constexpr QLEInteger min(); + static Q_DECL_CONSTEXPR QLEInteger max(); + static Q_DECL_CONSTEXPR QLEInteger min(); }; template @@ -400,8 +400,8 @@ public: QBEInteger &operator ++(int); QBEInteger &operator --(int); - static constexpr QBEInteger max(); - static constexpr QBEInteger min(); + static Q_DECL_CONSTEXPR QBEInteger max(); + static Q_DECL_CONSTEXPR QBEInteger min(); }; #else diff --git a/src/corelib/global/qglobal.cpp b/src/corelib/global/qglobal.cpp index a06e9ab6c6..799d953c8d 100644 --- a/src/corelib/global/qglobal.cpp +++ b/src/corelib/global/qglobal.cpp @@ -114,8 +114,8 @@ extern "C" { // without full system POSIX. # pragma weak shm_area_password # pragma weak shm_area_name - char *shm_area_password = "dummy"; - char *shm_area_name = "dummy"; + char shm_area_password[] = "dummy"; + char shm_area_name[] = "dummy"; } #endif diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h index e44837747c..ecfdc03743 100644 --- a/src/corelib/io/qfilesystemengine_p.h +++ b/src/corelib/io/qfilesystemengine_p.h @@ -58,6 +58,36 @@ QT_BEGIN_NAMESPACE +#define Q_RETURN_ON_INVALID_FILENAME(message, result) \ + { \ + QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).warning(message); \ + errno = EINVAL; \ + return (result); \ + } + +inline bool qIsFilenameBroken(const QByteArray &name) +{ + return name.contains('\0'); +} + +inline bool qIsFilenameBroken(const QString &name) +{ + return name.contains(QLatin1Char('\0')); +} + +inline bool qIsFilenameBroken(const QFileSystemEntry &entry) +{ + return qIsFilenameBroken(entry.nativeFilePath()); +} + +#define Q_CHECK_FILE_NAME(name, result) \ + do { \ + if (Q_UNLIKELY((name).isEmpty())) \ + Q_RETURN_ON_INVALID_FILENAME("Empty filename passed to function", (result)); \ + if (Q_UNLIKELY(qIsFilenameBroken(name))) \ + Q_RETURN_ON_INVALID_FILENAME("Broken filename passed to function", (result)); \ + } while (false) + class QFileSystemEngine { public: diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index 74865fe31f..c3abec8989 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -118,13 +118,6 @@ enum { #endif }; -#define emptyFileEntryWarning() emptyFileEntryWarning_(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC) -static void emptyFileEntryWarning_(const char *file, int line, const char *function) -{ - QMessageLogger(file, line, function).warning("Empty filename passed to function"); - errno = EINVAL; -} - #if defined(Q_OS_DARWIN) static inline bool hasResourcePropertyFlag(const QFileSystemMetaData &data, const QFileSystemEntry &entry, @@ -625,8 +618,7 @@ void QFileSystemMetaData::fillFromDirEnt(const QT_DIRENT &entry) //static QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) { - if (Q_UNLIKELY(link.isEmpty())) - return emptyFileEntryWarning(), link; + Q_CHECK_FILE_NAME(link, link); QByteArray s = qt_readlink(link.nativeFilePath().constData()); if (s.length() > 0) { @@ -685,10 +677,7 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, //static QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), entry; - if (entry.isRoot()) - return entry; + Q_CHECK_FILE_NAME(entry, entry); #if !defined(Q_OS_MAC) && !defined(Q_OS_QNX) && !defined(Q_OS_ANDROID) && !defined(Q_OS_HAIKU) && _POSIX_VERSION < 200809L // realpath(X,0) is not supported @@ -738,8 +727,8 @@ QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, //static QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), entry; + Q_CHECK_FILE_NAME(entry, entry); + if (entry.isAbsolute() && entry.isClean()) return entry; @@ -773,8 +762,7 @@ QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) //static QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), QByteArray(); + Q_CHECK_FILE_NAME(entry, QByteArray()); QT_STATBUF statResult; if (QT_STAT(entry.nativeFilePath().constData(), &statResult)) { @@ -887,8 +875,7 @@ QString QFileSystemEngine::bundleName(const QFileSystemEntry &entry) bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(entry, false); #if defined(Q_OS_DARWIN) if (what & QFileSystemMetaData::BundleType) { @@ -1157,8 +1144,7 @@ static bool createDirectoryWithParents(const QByteArray &nativeName, bool should bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) { QString dirName = entry.filePath(); - if (Q_UNLIKELY(dirName.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(dirName, false); // Darwin doesn't support trailing /'s, so remove for everyone while (dirName.size() > 1 && dirName.endsWith(QLatin1Char('/'))) @@ -1177,8 +1163,7 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea //static bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(entry, false); if (removeEmptyParents) { QString dirName = QDir::cleanPath(entry.filePath()); @@ -1203,8 +1188,9 @@ bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool remo //static bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { - if (Q_UNLIKELY(source.isEmpty() || target.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(source, false); + Q_CHECK_FILE_NAME(target, false); + if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0) return true; error = QSystemError(errno, QSystemError::StandardLibraryError); @@ -1233,8 +1219,9 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy { QFileSystemEntry::NativePath srcPath = source.nativeFilePath(); QFileSystemEntry::NativePath tgtPath = target.nativeFilePath(); - if (Q_UNLIKELY(srcPath.isEmpty() || tgtPath.isEmpty())) - return emptyFileEntryWarning(), false; + + Q_CHECK_FILE_NAME(srcPath, false); + Q_CHECK_FILE_NAME(tgtPath, false); #if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2) if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0) @@ -1302,8 +1289,9 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy //static bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { - if (Q_UNLIKELY(source.isEmpty() || target.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(source, false); + Q_CHECK_FILE_NAME(target, false); + if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0) return true; error = QSystemError(errno, QSystemError::StandardLibraryError); @@ -1313,8 +1301,7 @@ bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, cons //static bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(entry, false); if (unlink(entry.nativeFilePath().constData()) == 0) return true; error = QSystemError(errno, QSystemError::StandardLibraryError); @@ -1349,8 +1336,7 @@ static mode_t toMode_t(QFile::Permissions permissions) //static bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(entry, false); mode_t mode = toMode_t(permissions); bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0; diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index 279918b812..ae29190848 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -461,7 +461,9 @@ void QFileSystemEngine::clearWinStatData(QFileSystemMetaData &data) QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) { - if (data.missingFlags(QFileSystemMetaData::LinkType)) + Q_CHECK_FILE_NAME(link, link); + + if (data.missingFlags(QFileSystemMetaData::LinkType)) QFileSystemEngine::fillMetaData(link, data, QFileSystemMetaData::LinkType); QString target; @@ -480,6 +482,8 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, //static QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) { + Q_CHECK_FILE_NAME(entry, entry); + if (data.missingFlags(QFileSystemMetaData::ExistsAttribute)) QFileSystemEngine::fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute); @@ -492,6 +496,8 @@ QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, //static QString QFileSystemEngine::nativeAbsoluteFilePath(const QString &path) { + Q_CHECK_FILE_NAME(path, QString()); + // can be //server or //server/share QString absPath; QVarLengthArray buf(qMax(MAX_PATH, path.size() + 1)); @@ -527,6 +533,8 @@ QString QFileSystemEngine::nativeAbsoluteFilePath(const QString &path) //static QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) { + Q_CHECK_FILE_NAME(entry, entry); + QString ret; if (!entry.isRelative()) { @@ -609,6 +617,8 @@ QByteArray fileIdWin8(HANDLE handle) //static QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry) { + Q_CHECK_FILE_NAME(entry, QByteArray()); + QByteArray result; #ifndef Q_OS_WINRT @@ -999,6 +1009,7 @@ static bool isDirPath(const QString &dirPath, bool *existed); bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what) { + Q_CHECK_FILE_NAME(entry, false); what |= QFileSystemMetaData::WinLnkType | QFileSystemMetaData::WinStatFlags; data.entryFlags &= ~what; @@ -1116,6 +1127,8 @@ static bool isDirPath(const QString &dirPath, bool *existed) bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) { QString dirName = entry.filePath(); + Q_CHECK_FILE_NAME(dirName, false); + if (createParents) { dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); // We spefically search for / so \ would break it.. @@ -1177,6 +1190,8 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents) { QString dirName = entry.filePath(); + Q_CHECK_FILE_NAME(dirName, false); + if (removeEmptyParents) { dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); for (int oldslash = 0, slash=dirName.length(); slash > 0; oldslash = slash) { @@ -1381,6 +1396,9 @@ bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSyst //static bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { + Q_CHECK_FILE_NAME(source, false); + Q_CHECK_FILE_NAME(target, false); + #ifndef Q_OS_WINRT bool ret = ::MoveFile((wchar_t*)source.nativeFilePath().utf16(), (wchar_t*)target.nativeFilePath().utf16()) != 0; @@ -1396,6 +1414,9 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy //static bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { + Q_CHECK_FILE_NAME(source, false); + Q_CHECK_FILE_NAME(target, false); + bool ret = ::MoveFileEx(reinterpret_cast(source.nativeFilePath().utf16()), reinterpret_cast(target.nativeFilePath().utf16()), MOVEFILE_REPLACE_EXISTING) != 0; @@ -1407,6 +1428,8 @@ bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, cons //static bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error) { + Q_CHECK_FILE_NAME(entry, false); + bool ret = ::DeleteFile((wchar_t*)entry.nativeFilePath().utf16()) != 0; if(!ret) error = QSystemError(::GetLastError(), QSystemError::NativeError); @@ -1417,6 +1440,8 @@ bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError & bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data) { + Q_CHECK_FILE_NAME(entry, false); + Q_UNUSED(data); int mode = 0; diff --git a/src/corelib/io/qstandardpaths_unix.cpp b/src/corelib/io/qstandardpaths_unix.cpp index c35d7adc9f..5f4955c53f 100644 --- a/src/corelib/io/qstandardpaths_unix.cpp +++ b/src/corelib/io/qstandardpaths_unix.cpp @@ -142,36 +142,45 @@ QString QStandardPaths::writableLocation(StandardLocation type) } case RuntimeLocation: { - const uint myUid = uint(geteuid()); // http://standards.freedesktop.org/basedir-spec/latest/ + const uint myUid = uint(geteuid()); + // since the current user is the owner, set both xxxUser and xxxOwner + const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser + | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; QFileInfo fileInfo; QString xdgRuntimeDir = QFile::decodeName(qgetenv("XDG_RUNTIME_DIR")); if (xdgRuntimeDir.isEmpty()) { const QString userName = QFileSystemEngine::resolveUserName(myUid); xdgRuntimeDir = QDir::tempPath() + QLatin1String("/runtime-") + userName; fileInfo.setFile(xdgRuntimeDir); - if (!fileInfo.isDir()) { - if (!QDir().mkdir(xdgRuntimeDir)) { - qErrnoWarning("QStandardPaths: error creating runtime directory %ls", - qUtf16Printable(xdgRuntimeDir)); - return QString(); - } - } #ifndef Q_OS_WASM qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%ls'", qUtf16Printable(xdgRuntimeDir)); #endif } else { fileInfo.setFile(xdgRuntimeDir); - if (!fileInfo.exists()) { - qWarning("QStandardPaths: XDG_RUNTIME_DIR points to non-existing path '%ls', " - "please create it with 0700 permissions.", qUtf16Printable(xdgRuntimeDir)); - return QString(); - } + } + if (fileInfo.exists()) { if (!fileInfo.isDir()) { qWarning("QStandardPaths: XDG_RUNTIME_DIR points to '%ls' which is not a directory", qUtf16Printable(xdgRuntimeDir)); return QString(); } + } else { + QFileSystemEntry entry(xdgRuntimeDir); + if (!QFileSystemEngine::createDirectory(entry, false)) { + if (errno != EEXIST) { + qErrnoWarning("QStandardPaths: error creating runtime directory %ls", + qUtf16Printable(xdgRuntimeDir)); + return QString(); + } + } else { + QSystemError error; + if (!QFileSystemEngine::setPermissions(entry, wantedPerms, error)) { + qWarning("QStandardPaths: could not set correct permissions on runtime directory %ls: %ls", + qUtf16Printable(xdgRuntimeDir), qUtf16Printable(error.toString())); + return QString(); + } + } } // "The directory MUST be owned by the user" if (fileInfo.ownerId() != myUid) { @@ -181,17 +190,12 @@ QString QStandardPaths::writableLocation(StandardLocation type) return QString(); } // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700." - // since the current user is the owner, set both xxxUser and xxxOwner - const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser - | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; if (fileInfo.permissions() != wantedPerms) { - QFile file(xdgRuntimeDir); - if (!file.setPermissions(wantedPerms)) { - qWarning("QStandardPaths: could not set correct permissions on runtime directory %ls: %ls", - qUtf16Printable(xdgRuntimeDir), qUtf16Printable(file.errorString())); - return QString(); - } + qWarning("QStandardPaths: wrong permissions on runtime directory %ls, %x instead of %x", + qUtf16Printable(xdgRuntimeDir), uint(fileInfo.permissions()), uint(wantedPerms)); + return QString(); } + return xdgRuntimeDir; } default: diff --git a/src/corelib/itemmodels/qitemselectionmodel.h b/src/corelib/itemmodels/qitemselectionmodel.h index 3c3f9fb1ac..5820695592 100644 --- a/src/corelib/itemmodels/qitemselectionmodel.h +++ b/src/corelib/itemmodels/qitemselectionmodel.h @@ -171,11 +171,11 @@ public: QModelIndex currentIndex() const; Q_INVOKABLE bool isSelected(const QModelIndex &index) const; - Q_INVOKABLE bool isRowSelected(int row, const QModelIndex &parent) const; - Q_INVOKABLE bool isColumnSelected(int column, const QModelIndex &parent) const; + Q_INVOKABLE bool isRowSelected(int row, const QModelIndex &parent = QModelIndex()) const; + Q_INVOKABLE bool isColumnSelected(int column, const QModelIndex &parent = QModelIndex()) const; - Q_INVOKABLE bool rowIntersectsSelection(int row, const QModelIndex &parent) const; - Q_INVOKABLE bool columnIntersectsSelection(int column, const QModelIndex &parent) const; + Q_INVOKABLE bool rowIntersectsSelection(int row, const QModelIndex &parent = QModelIndex()) const; + Q_INVOKABLE bool columnIntersectsSelection(int column, const QModelIndex &parent = QModelIndex()) const; bool hasSelection() const; diff --git a/src/corelib/serialization/qbinaryjson.cpp b/src/corelib/serialization/qbinaryjson.cpp new file mode 100644 index 0000000000..3d359f0998 --- /dev/null +++ b/src/corelib/serialization/qbinaryjson.cpp @@ -0,0 +1,415 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbinaryjson_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QBinaryJsonPrivate { + +static Q_CONSTEXPR Base emptyArray = { + { qle_uint(sizeof(Base)) }, + { 0 }, + { qle_uint(0) } +}; + +static Q_CONSTEXPR Base emptyObject = { + { qle_uint(sizeof(Base)) }, + { qToLittleEndian(1U) }, + { qle_uint(0) } +}; + +void MutableData::compact() +{ + Q_STATIC_ASSERT(sizeof(Value) == sizeof(offset)); + + Base *base = header->root(); + int reserve = 0; + if (base->is_object) { + auto *o = static_cast(base); + for (uint i = 0; i < o->length; ++i) + reserve += o->entryAt(i)->usedStorage(o); + } else { + auto *a = static_cast(base); + for (uint i = 0; i < a->length; ++i) + reserve += a->at(i)->usedStorage(a); + } + + uint size = sizeof(Base) + reserve + base->length * sizeof(offset); + uint alloc = sizeof(Header) + size; + auto *h = reinterpret_cast
(malloc(alloc)); + Q_CHECK_PTR(h); + h->tag = QJsonDocument::BinaryFormatTag; + h->version = 1; + Base *b = h->root(); + b->size = size; + b->is_object = header->root()->is_object; + b->length = base->length; + b->tableOffset = reserve + sizeof(Array); + + uint offset = sizeof(Base); + if (b->is_object) { + const auto *o = static_cast(base); + auto *no = static_cast(b); + + for (uint i = 0; i < o->length; ++i) { + no->table()[i] = offset; + + const Entry *e = o->entryAt(i); + Entry *ne = no->entryAt(i); + uint s = e->size(); + memcpy(ne, e, s); + offset += s; + uint dataSize = e->value.usedStorage(o); + if (dataSize) { + memcpy(reinterpret_cast(no) + offset, e->value.data(o), dataSize); + ne->value.value = offset; + offset += dataSize; + } + } + } else { + const auto *a = static_cast(base); + auto *na = static_cast(b); + + for (uint i = 0; i < a->length; ++i) { + const Value *v = a->at(i); + Value *nv = na->at(i); + *nv = *v; + uint dataSize = v->usedStorage(a); + if (dataSize) { + memcpy(reinterpret_cast(na) + offset, v->data(a), dataSize); + nv->value = offset; + offset += dataSize; + } + } + } + Q_ASSERT(offset == uint(b->tableOffset)); + + free(header); + header = h; + this->alloc = alloc; + compactionCounter = 0; +} + +bool ConstData::isValid() const +{ + if (header->tag != QJsonDocument::BinaryFormatTag || header->version != 1U) + return false; + + const Base *root = header->root(); + const uint maxSize = alloc - sizeof(Header); + return root->is_object + ? static_cast(root)->isValid(maxSize) + : static_cast(root)->isValid(maxSize); +} + +QJsonDocument ConstData::toJsonDocument() const +{ + const Base *root = header->root(); + return root->is_object + ? QJsonDocument(static_cast(root)->toJsonObject()) + : QJsonDocument(static_cast(root)->toJsonArray()); +} + +uint Base::reserveSpace(uint dataSize, uint posInTable, uint numItems, bool replace) +{ + Q_ASSERT(posInTable <= length); + if (size + dataSize >= Value::MaxSize) { + qWarning("QJson: Document too large to store in data structure %d %d %d", + uint(size), dataSize, Value::MaxSize); + return 0; + } + + offset off = tableOffset; + // move table to new position + if (replace) { + memmove(reinterpret_cast(table()) + dataSize, table(), length * sizeof(offset)); + } else { + memmove(reinterpret_cast(table() + posInTable + numItems) + dataSize, + table() + posInTable, (length - posInTable) * sizeof(offset)); + memmove(reinterpret_cast(table()) + dataSize, table(), posInTable * sizeof(offset)); + } + tableOffset += dataSize; + for (uint i = 0; i < numItems; ++i) + table()[posInTable + i] = off; + size += dataSize; + if (!replace) { + length += numItems; + size += numItems * sizeof(offset); + } + return off; +} + +uint Object::indexOf(QStringView key, bool *exists) const +{ + uint min = 0; + uint n = length; + while (n > 0) { + uint half = n >> 1; + uint middle = min + half; + if (*entryAt(middle) >= key) { + n = half; + } else { + min = middle + 1; + n -= half + 1; + } + } + if (min < length && *entryAt(min) == key) { + *exists = true; + return min; + } + *exists = false; + return min; +} + +QJsonObject Object::toJsonObject() const +{ + QJsonObject object; + for (uint i = 0; i < length; ++i) { + const Entry *e = entryAt(i); + object.insert(e->key(), e->value.toJsonValue(this)); + } + return object; +} + +bool Object::isValid(uint maxSize) const +{ + if (size > maxSize || tableOffset + length * sizeof(offset) > size) + return false; + + QString lastKey; + for (uint i = 0; i < length; ++i) { + if (table()[i] + sizeof(Entry) >= tableOffset) + return false; + const Entry *e = entryAt(i); + if (!e->isValid(tableOffset - table()[i])) + return false; + const QString key = e->key(); + if (key < lastKey) + return false; + if (!e->value.isValid(this)) + return false; + lastKey = key; + } + return true; +} + +QJsonArray Array::toJsonArray() const +{ + QJsonArray array; + const offset *values = table(); + for (uint i = 0; i < length; ++i) + array.append(reinterpret_cast(values + i)->toJsonValue(this)); + return array; +} + +bool Array::isValid(uint maxSize) const +{ + if (size > maxSize || tableOffset + length * sizeof(offset) > size) + return false; + + const offset *values = table(); + for (uint i = 0; i < length; ++i) { + if (!reinterpret_cast(values + i)->isValid(this)) + return false; + } + return true; +} + +uint Value::usedStorage(const Base *b) const +{ + uint s = 0; + switch (type) { + case QJsonValue::Double: + if (!latinOrIntValue) + s = sizeof(double); + break; + case QJsonValue::String: { + const char *d = data(b); + s = latinOrIntValue + ? (sizeof(ushort) + + qFromLittleEndian(*reinterpret_cast(d))) + : (sizeof(int) + + sizeof(ushort) * qFromLittleEndian(*reinterpret_cast(d))); + break; + } + case QJsonValue::Array: + case QJsonValue::Object: + s = base(b)->size; + break; + case QJsonValue::Null: + case QJsonValue::Bool: + default: + break; + } + return alignedSize(s); +} + +QJsonValue Value::toJsonValue(const Base *b) const +{ + switch (type) { + case QJsonValue::Null: + return QJsonValue(QJsonValue::Null); + case QJsonValue::Bool: + return QJsonValue(toBoolean()); + case QJsonValue::Double: + return QJsonValue(toDouble(b)); + case QJsonValue::String: + return QJsonValue(toString(b)); + case QJsonValue::Array: + return static_cast(base(b))->toJsonArray(); + case QJsonValue::Object: + return static_cast(base(b))->toJsonObject(); + case QJsonValue::Undefined: + return QJsonValue(QJsonValue::Undefined); + } + Q_UNREACHABLE(); + return QJsonValue(QJsonValue::Undefined); +} + +inline bool isValidValueOffset(uint offset, uint tableOffset) +{ + return offset >= sizeof(Base) + && offset + sizeof(uint) <= tableOffset; +} + +bool Value::isValid(const Base *b) const +{ + switch (type) { + case QJsonValue::Null: + case QJsonValue::Bool: + return true; + case QJsonValue::Double: + return latinOrIntValue || isValidValueOffset(value, b->tableOffset); + case QJsonValue::String: + if (!isValidValueOffset(value, b->tableOffset)) + return false; + if (latinOrIntValue) + return asLatin1String(b).isValid(b->tableOffset - value); + return asString(b).isValid(b->tableOffset - value); + case QJsonValue::Array: + return isValidValueOffset(value, b->tableOffset) + && static_cast(base(b))->isValid(b->tableOffset - value); + case QJsonValue::Object: + return isValidValueOffset(value, b->tableOffset) + && static_cast(base(b))->isValid(b->tableOffset - value); + default: + return false; + } +} + +uint Value::requiredStorage(const QBinaryJsonValue &v, bool *compressed) +{ + *compressed = false; + switch (v.type()) { + case QJsonValue::Double: + if (QBinaryJsonPrivate::compressedNumber(v.toDouble()) != INT_MAX) { + *compressed = true; + return 0; + } + return sizeof(double); + case QJsonValue::String: { + QString s = v.toString(); + *compressed = QBinaryJsonPrivate::useCompressed(s); + return QBinaryJsonPrivate::qStringSize(s, *compressed); + } + case QJsonValue::Array: + case QJsonValue::Object: + return v.base ? uint(v.base->size) : sizeof(QBinaryJsonPrivate::Base); + case QJsonValue::Undefined: + case QJsonValue::Null: + case QJsonValue::Bool: + break; + } + return 0; +} + +uint Value::valueToStore(const QBinaryJsonValue &v, uint offset) +{ + switch (v.type()) { + case QJsonValue::Undefined: + case QJsonValue::Null: + break; + case QJsonValue::Bool: + return v.toBool(); + case QJsonValue::Double: { + int c = QBinaryJsonPrivate::compressedNumber(v.toDouble()); + if (c != INT_MAX) + return c; + } + Q_FALLTHROUGH(); + case QJsonValue::String: + case QJsonValue::Array: + case QJsonValue::Object: + return offset; + } + return 0; +} + +void Value::copyData(const QBinaryJsonValue &v, char *dest, bool compressed) +{ + switch (v.type()) { + case QJsonValue::Double: + if (!compressed) + qToLittleEndian(v.toDouble(), dest); + break; + case QJsonValue::String: { + const QString str = v.toString(); + QBinaryJsonPrivate::copyString(dest, str, compressed); + break; + } + case QJsonValue::Array: + case QJsonValue::Object: { + const QBinaryJsonPrivate::Base *b = v.base; + if (!b) + b = (v.type() == QJsonValue::Array ? &emptyArray : &emptyObject); + memcpy(dest, b, b->size); + break; + } + default: + break; + } +} + +} // namespace QBinaryJsonPrivate + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qbinaryjson_p.h b/src/corelib/serialization/qbinaryjson_p.h new file mode 100644 index 0000000000..132c36f227 --- /dev/null +++ b/src/corelib/serialization/qbinaryjson_p.h @@ -0,0 +1,617 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBINARYJSON_P_H +#define QBINARYJSON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include + +#include + +QT_REQUIRE_CONFIG(binaryjson); + +QT_BEGIN_NAMESPACE + +// in qstring.cpp +void qt_to_latin1_unchecked(uchar *dst, const ushort *uc, qsizetype len); +void qt_from_latin1(ushort *dst, const char *str, size_t size) noexcept; + +/* + This defines a binary data structure for Json data. The data structure is optimised for fast reading + and minimum allocations. The whole data structure can be mmap'ed and used directly. + + In most cases the binary structure is not as space efficient as a utf8 encoded text representation, but + much faster to access. + + The size requirements are: + + String: + Latin1 data: 2 bytes header + string.length() + Full Unicode: 4 bytes header + 2*(string.length()) + + Values: 4 bytes + size of data (size can be 0 for some data) + bool: 0 bytes + double: 8 bytes (0 if integer with less than 27bits) + string: see above + array: size of array + object: size of object + Array: 12 bytes + 4*length + size of Value data + Object: 12 bytes + 8*length + size of Key Strings + size of Value data + + For an example such as + + { // object: 12 + 5*8 = 52 + "firstName": "John", // key 12, value 8 = 20 + "lastName" : "Smith", // key 12, value 8 = 20 + "age" : 25, // key 8, value 0 = 8 + "address" : // key 12, object below = 140 + { // object: 12 + 4*8 + "streetAddress": "21 2nd Street", // key 16, value 16 + "city" : "New York", // key 8, value 12 + "state" : "NY", // key 8, value 4 + "postalCode" : "10021" // key 12, value 8 + }, // object total: 128 + "phoneNumber": // key: 16, value array below = 172 + [ // array: 12 + 2*4 + values below: 156 + { // object 12 + 2*8 + "type" : "home", // key 8, value 8 + "number": "212 555-1234" // key 8, value 16 + }, // object total: 68 + { // object 12 + 2*8 + "type" : "fax", // key 8, value 8 + "number": "646 555-4567" // key 8, value 16 + } // object total: 68 + ] // array total: 156 + } // great total: 412 bytes + + The uncompressed text file used roughly 500 bytes, so in this case we end up using about + the same space as the text representation. + + Other measurements have shown a slightly bigger binary size than a compact text + representation where all possible whitespace was stripped out. +*/ +namespace QBinaryJsonPrivate { + +class Array; +class Object; +class Value; +class Entry; + +template +using q_littleendian = QLEInteger; + +using qle_short = q_littleendian; +using qle_ushort = q_littleendian; +using qle_int = q_littleendian; +using qle_uint = q_littleendian; + +template +using qle_bitfield = QLEIntegerBitfield; + +template +using qle_signedbitfield = QLEIntegerBitfield; + +using offset = qle_uint; + +// round the size up to the next 4 byte boundary +inline uint alignedSize(uint size) { return (size + 3) & ~3; } + +const int MaxLatin1Length = 0x7fff; + +static inline bool useCompressed(QStringView s) +{ + if (s.length() > MaxLatin1Length) + return false; + return QtPrivate::isLatin1(s); +} + +static inline bool useCompressed(QLatin1String s) +{ + return s.size() <= MaxLatin1Length; +} + +static inline uint qStringSize(const QString &string, bool compress) +{ + uint l = 2 + string.size(); + if (!compress) + l *= 2; + return alignedSize(l); +} + +// returns INT_MAX if it can't compress it into 28 bits +static inline int compressedNumber(double d) +{ + // this relies on details of how ieee floats are represented + const int exponent_off = 52; + const quint64 fraction_mask = 0x000fffffffffffffULL; + const quint64 exponent_mask = 0x7ff0000000000000ULL; + + quint64 val; + memcpy (&val, &d, sizeof(double)); + int exp = (int)((val & exponent_mask) >> exponent_off) - 1023; + if (exp < 0 || exp > 25) + return std::numeric_limits::max(); + + quint64 non_int = val & (fraction_mask >> exp); + if (non_int) + return std::numeric_limits::max(); + + bool neg = (val >> 63) != 0; + val &= fraction_mask; + val |= ((quint64)1 << 52); + int res = (int)(val >> (52 - exp)); + return neg ? -res : res; +} + +class Latin1String; + +class String +{ +public: + explicit String(const char *data) : d(reinterpret_cast(data)) {} + + struct Data { + qle_uint length; + qle_ushort utf16[1]; + }; + const Data *d; + + uint byteSize() const { return sizeof(uint) + sizeof(ushort) * d->length; } + bool isValid(uint maxSize) const + { + // Check byteSize() <= maxSize, avoiding integer overflow + return maxSize >= sizeof(uint) + && uint(d->length) <= (maxSize - sizeof(uint)) / sizeof(ushort); + } + + static void copy(char *dest, QStringView str) + { + Data *data = reinterpret_cast(dest); + data->length = str.length(); + qToLittleEndian(str.utf16(), str.length(), data->utf16); + fillTrailingZeros(data); + } + + static void fillTrailingZeros(Data *data) + { + if (data->length & 1) + data->utf16[data->length] = 0; + } + + bool operator ==(QStringView str) const + { + int slen = str.length(); + int l = d->length; + if (slen != l) + return false; + const auto *s = reinterpret_cast(str.utf16()); + const qle_ushort *a = d->utf16; + const ushort *b = s; + while (l-- && *a == *b) + a++,b++; + return (l == -1); + } + + bool operator ==(const String &str) const + { + if (d->length != str.d->length) + return false; + return !memcmp(d->utf16, str.d->utf16, d->length * sizeof(ushort)); + } + + QString toString() const + { +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + return QString(reinterpret_cast(d->utf16), d->length); +#else + const uint l = d->length; + QString str(l, Qt::Uninitialized); + QChar *ch = str.data(); + for (uint i = 0; i < l; ++i) + ch[i] = QChar(d->utf16[i]); + return str; +#endif + } +}; + +class Latin1String +{ +public: + explicit Latin1String(const char *data) : d(reinterpret_cast(data)) {} + + struct Data { + qle_ushort length; + char latin1[1]; + }; + const Data *d; + + uint byteSize() const { return sizeof(ushort) + sizeof(char) * (d->length); } + bool isValid(uint maxSize) const { return byteSize() <= maxSize; } + + static void copy(char *dest, QStringView src) + { + Data *data = reinterpret_cast(dest); + data->length = src.length(); + auto *l = reinterpret_cast(data->latin1); + const auto *uc = reinterpret_cast(src.utf16()); + qt_to_latin1_unchecked(l, uc, data->length); + + for (uint len = data->length; quintptr(l + len) & 0x3; ++len) + l[len] = 0; + } + + QLatin1String toQLatin1String() const noexcept { return QLatin1String(d->latin1, d->length); } + QString toString() const { return QString::fromLatin1(d->latin1, d->length); } +}; + +static inline void copyString(char *dest, QStringView str, bool compress) +{ + if (compress) + Latin1String::copy(dest, str); + else + String::copy(dest, str); +} + +/* + Base is the base class for both Object and Array. Both classes work more or less the same way. + The class starts with a header (defined by the struct below), then followed by data (the data for + values in the Array case and Entry's (see below) for objects. + + After the data a table follows (tableOffset points to it) containing Value objects for Arrays, and + offsets from the beginning of the object to Entry's in the case of Object. + + Entry's in the Object's table are lexicographically sorted by key in the table(). This allows the usage + of a binary search over the keys in an Object. + */ +class Base +{ +public: + qle_uint size; + union { + uint _dummy; + qle_bitfield<0, 1> is_object; + qle_bitfield<1, 31> length; + }; + offset tableOffset; + // content follows here + + bool isObject() const { return !!is_object; } + bool isArray() const { return !isObject(); } + + offset *table() + { + return reinterpret_cast(reinterpret_cast(this) + tableOffset); + } + + const offset *table() const + { + return reinterpret_cast(reinterpret_cast(this) + tableOffset); + } + + uint reserveSpace(uint dataSize, uint posInTable, uint numItems, bool replace); +}; + +class Object : public Base +{ +public: + const Entry *entryAt(uint i) const + { + return reinterpret_cast(reinterpret_cast(this) + table()[i]); + } + + Entry *entryAt(uint i) + { + return reinterpret_cast(reinterpret_cast(this) + table()[i]); + } + + uint indexOf(QStringView key, bool *exists) const; + QJsonObject toJsonObject() const; + bool isValid(uint maxSize) const; +}; + +class Array : public Base +{ +public: + const Value *at(uint i) const { return reinterpret_cast(table() + i); } + Value *at(uint i) { return reinterpret_cast(table() + i); } + + QJsonArray toJsonArray() const; + bool isValid(uint maxSize) const; +}; + +class Value +{ +public: + enum { + MaxSize = (1 << 27) - 1 + }; + union { + uint _dummy; + qle_bitfield<0, 3> type; + qle_bitfield<3, 1> latinOrIntValue; + qle_bitfield<4, 1> latinKey; + qle_bitfield<5, 27> value; + qle_signedbitfield<5, 27> int_value; + }; + + inline const char *data(const Base *b) const + { + return reinterpret_cast(b) + value; + } + + uint usedStorage(const Base *b) const; + + bool toBoolean() const + { + Q_ASSERT(type == QJsonValue::Bool); + return value != 0; + } + + double toDouble(const Base *b) const + { + Q_ASSERT(type == QJsonValue::Double); + if (latinOrIntValue) + return int_value; + + auto i = qFromLittleEndian(reinterpret_cast(b) + value); + double d; + memcpy(&d, &i, sizeof(double)); + return d; + } + + QString toString(const Base *b) const + { + return latinOrIntValue + ? asLatin1String(b).toString() + : asString(b).toString(); + } + + String asString(const Base *b) const + { + Q_ASSERT(type == QJsonValue::String && !latinOrIntValue); + return String(data(b)); + } + + Latin1String asLatin1String(const Base *b) const + { + Q_ASSERT(type == QJsonValue::String && latinOrIntValue); + return Latin1String(data(b)); + } + + const Base *base(const Base *b) const + { + Q_ASSERT(type == QJsonValue::Array || type == QJsonValue::Object); + return reinterpret_cast(data(b)); + } + + QJsonValue toJsonValue(const Base *b) const; + bool isValid(const Base *b) const; + + static uint requiredStorage(const QBinaryJsonValue &v, bool *compressed); + static uint valueToStore(const QBinaryJsonValue &v, uint offset); + static void copyData(const QBinaryJsonValue &v, char *dest, bool compressed); +}; + +class Entry { +public: + Value value; + // key + // value data follows key + + uint size() const + { + uint s = sizeof(Entry); + if (value.latinKey) + s += shallowLatin1Key().byteSize(); + else + s += shallowKey().byteSize(); + return alignedSize(s); + } + + uint usedStorage(Base *b) const + { + return size() + value.usedStorage(b); + } + + String shallowKey() const + { + Q_ASSERT(!value.latinKey); + return String(reinterpret_cast(this) + sizeof(Entry)); + } + + Latin1String shallowLatin1Key() const + { + Q_ASSERT(value.latinKey); + return Latin1String(reinterpret_cast(this) + sizeof(Entry)); + } + + QString key() const + { + return value.latinKey + ? shallowLatin1Key().toString() + : shallowKey().toString(); + } + + bool isValid(uint maxSize) const + { + if (maxSize < sizeof(Entry)) + return false; + maxSize -= sizeof(Entry); + return value.latinKey + ? shallowLatin1Key().isValid(maxSize) + : shallowKey().isValid(maxSize); + } + + bool operator ==(QStringView key) const + { + return value.latinKey + ? (shallowLatin1Key().toQLatin1String() == key) + : (shallowKey() == key); + } + + bool operator >=(QStringView key) const + { + return value.latinKey + ? (shallowLatin1Key().toQLatin1String() >= key) + : (shallowKey().toString() >= key); + } +}; + +class Header { +public: + qle_uint tag; // 'qbjs' + qle_uint version; // 1 + Base *root() { return reinterpret_cast(this + 1); } + const Base *root() const { return reinterpret_cast(this + 1); } +}; + +class ConstData +{ + Q_DISABLE_COPY_MOVE(ConstData) +public: + const uint alloc; + union { + const char *rawData; + const Header *header; + }; + + ConstData(const char *raw, uint a) : alloc(a), rawData(raw) {} + bool isValid() const; + QJsonDocument toJsonDocument() const; +}; + +class MutableData +{ + Q_DISABLE_COPY_MOVE(MutableData) +public: + QAtomicInt ref; + uint alloc; + union { + char *rawData; + Header *header; + }; + uint compactionCounter : 31; + + MutableData(char *raw, uint a) + : alloc(a), rawData(raw), compactionCounter(0) + { + } + + MutableData(uint reserved, QJsonValue::Type valueType) + : rawData(nullptr), compactionCounter(0) + { + Q_ASSERT(valueType == QJsonValue::Array || valueType == QJsonValue::Object); + + alloc = sizeof(Header) + sizeof(Base) + reserved + sizeof(offset); + header = reinterpret_cast
(malloc(alloc)); + Q_CHECK_PTR(header); + header->tag = QJsonDocument::BinaryFormatTag; + header->version = 1; + Base *b = header->root(); + b->size = sizeof(Base); + b->is_object = (valueType == QJsonValue::Object); + b->tableOffset = sizeof(Base); + b->length = 0; + } + + ~MutableData() + { + free(rawData); + } + + MutableData *clone(const Base *b, uint reserve = 0) + { + uint size = sizeof(Header) + b->size; + if (b == header->root() && ref.loadRelaxed() == 1 && alloc >= size + reserve) + return this; + + if (reserve) { + if (reserve < 128) + reserve = 128; + size = qMax(size + reserve, qMin(size *2, uint(Value::MaxSize))); + if (size > Value::MaxSize) { + qWarning("QJson: Document too large to store in data structure"); + return nullptr; + } + } + char *raw = reinterpret_cast(malloc(size)); + Q_CHECK_PTR(raw); + memcpy(raw + sizeof(Header), b, b->size); + auto *h = reinterpret_cast
(raw); + h->tag = QJsonDocument::BinaryFormatTag; + h->version = 1; + auto *d = new MutableData(raw, size); + d->compactionCounter = (b == header->root()) ? compactionCounter : 0; + return d; + } + + char *takeRawData(uint *size) + { + *size = alloc; + char *result = rawData; + rawData = nullptr; + alloc = 0; + return result; + } + + void compact(); +}; + +} // namespace QBinaryJsonPrivate + +Q_DECLARE_TYPEINFO(QBinaryJsonPrivate::Value, Q_PRIMITIVE_TYPE); + +QT_END_NAMESPACE + +#endif // QBINARYJSON_P_H diff --git a/src/corelib/serialization/qbinaryjsonarray.cpp b/src/corelib/serialization/qbinaryjsonarray.cpp new file mode 100644 index 0000000000..68937fe17d --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonarray.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbinaryjsonarray_p.h" +#include "qbinaryjson_p.h" + +#include + +QT_BEGIN_NAMESPACE + +QBinaryJsonArray::~QBinaryJsonArray() +{ + if (d && !d->ref.deref()) + delete d; +} + +QBinaryJsonArray QBinaryJsonArray::fromJsonArray(const QJsonArray &array) +{ + QBinaryJsonArray binary; + for (const QJsonValue &value : array) + binary.append(QBinaryJsonValue::fromJsonValue(value)); + if (binary.d) // We want to compact it as it is a root item now + binary.d->compactionCounter++; + binary.compact(); + return binary; +} + +void QBinaryJsonArray::append(const QBinaryJsonValue &value) +{ + const uint i = a ? a->length : 0; + + bool compressed; + uint valueSize = QBinaryJsonPrivate::Value::requiredStorage(value, &compressed); + + if (!detach(valueSize + sizeof(QBinaryJsonPrivate::Value))) + return; + + if (!a->length) + a->tableOffset = sizeof(QBinaryJsonPrivate::Array); + + uint valueOffset = a->reserveSpace(valueSize, i, 1, false); + if (!valueOffset) + return; + + QBinaryJsonPrivate::Value *v = a->at(i); + v->type = (value.t == QJsonValue::Undefined ? QJsonValue::Null : value.t); + v->latinOrIntValue = compressed; + v->latinKey = false; + v->value = QBinaryJsonPrivate::Value::valueToStore(value, valueOffset); + if (valueSize) { + QBinaryJsonPrivate::Value::copyData(value, reinterpret_cast(a) + valueOffset, + compressed); + } +} + +char *QBinaryJsonArray::takeRawData(uint *size) +{ + if (d) + return d->takeRawData(size); + *size = 0; + return nullptr; +} + +bool QBinaryJsonArray::detach(uint reserve) +{ + if (!d) { + if (reserve >= QBinaryJsonPrivate::Value::MaxSize) { + qWarning("QBinaryJson: Document too large to store in data structure"); + return false; + } + d = new QBinaryJsonPrivate::MutableData(reserve, QJsonValue::Array); + a = static_cast(d->header->root()); + d->ref.ref(); + return true; + } + if (reserve == 0 && d->ref.loadRelaxed() == 1) + return true; + + QBinaryJsonPrivate::MutableData *x = d->clone(a, reserve); + if (!x) + return false; + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + a = static_cast(d->header->root()); + return true; +} + +void QBinaryJsonArray::compact() +{ + if (!d || !d->compactionCounter) + return; + + detach(); + d->compact(); + a = static_cast(d->header->root()); +} + +QT_END_NAMESPACE + diff --git a/src/corelib/serialization/qbinaryjsonarray_p.h b/src/corelib/serialization/qbinaryjsonarray_p.h new file mode 100644 index 0000000000..2bb8fed387 --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonarray_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBINARYJSONARRAY_P_H +#define QBINARYJSONARRAY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qbinaryjsonvalue_p.h" + +QT_REQUIRE_CONFIG(binaryjson); + +QT_BEGIN_NAMESPACE + +class QBinaryJsonArray +{ + Q_DISABLE_COPY(QBinaryJsonArray) +public: + QBinaryJsonArray() = default; + ~QBinaryJsonArray(); + + QBinaryJsonArray(QBinaryJsonArray &&other) noexcept + : d(other.d), + a(other.a) + { + other.d = nullptr; + other.a = nullptr; + } + + QBinaryJsonArray &operator =(QBinaryJsonArray &&other) noexcept + { + qSwap(d, other.d); + qSwap(a, other.a); + return *this; + } + + static QBinaryJsonArray fromJsonArray(const QJsonArray &array); + char *takeRawData(uint *size); + +private: + friend class QBinaryJsonValue; + + void append(const QBinaryJsonValue &value); + void compact(); + bool detach(uint reserve = 0); + + QBinaryJsonPrivate::MutableData *d = nullptr; + QBinaryJsonPrivate::Array *a = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QBINARYJSONARRAY_P_H diff --git a/src/corelib/serialization/qbinaryjsonobject.cpp b/src/corelib/serialization/qbinaryjsonobject.cpp new file mode 100644 index 0000000000..3186ab6087 --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonobject.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbinaryjsonobject_p.h" +#include "qbinaryjson_p.h" + +#include + +QT_BEGIN_NAMESPACE + +QBinaryJsonObject::~QBinaryJsonObject() +{ + if (d && !d->ref.deref()) + delete d; +} + +QBinaryJsonObject QBinaryJsonObject::fromJsonObject(const QJsonObject &object) +{ + QBinaryJsonObject binary; + for (auto it = object.begin(), end = object.end(); it != end; ++it) + binary.insert(it.key(), QBinaryJsonValue::fromJsonValue(it.value())); + if (binary.d) // We want to compact it as it is a root item now + binary.d->compactionCounter++; + binary.compact(); + return binary; +} + +void QBinaryJsonObject::insert(const QString &key, const QBinaryJsonValue &value) +{ + bool latinOrIntValue; + uint valueSize = QBinaryJsonPrivate::Value::requiredStorage(value, &latinOrIntValue); + + bool latinKey = QBinaryJsonPrivate::useCompressed(key); + uint valueOffset = sizeof(QBinaryJsonPrivate::Entry) + + QBinaryJsonPrivate::qStringSize(key, latinKey); + uint requiredSize = valueOffset + valueSize; + + if (!detach(requiredSize + sizeof(QBinaryJsonPrivate::offset))) // offset for the new index entry + return; + + if (!o->length) + o->tableOffset = sizeof(QBinaryJsonPrivate::Object); + + bool keyExists = false; + uint pos = o->indexOf(key, &keyExists); + if (keyExists) + ++d->compactionCounter; + + uint off = o->reserveSpace(requiredSize, pos, 1, keyExists); + if (!off) + return; + + QBinaryJsonPrivate::Entry *e = o->entryAt(pos); + e->value.type = value.t; + e->value.latinKey = latinKey; + e->value.latinOrIntValue = latinOrIntValue; + e->value.value = QBinaryJsonPrivate::Value::valueToStore( + value, reinterpret_cast(e) - reinterpret_cast(o) + valueOffset); + QBinaryJsonPrivate::copyString(reinterpret_cast(e + 1), key, latinKey); + if (valueSize) { + QBinaryJsonPrivate::Value::copyData(value, reinterpret_cast(e) + valueOffset, + latinOrIntValue); + } + + if (d->compactionCounter > 32U && d->compactionCounter >= unsigned(o->length) / 2U) + compact(); +} + +char *QBinaryJsonObject::takeRawData(uint *size) const +{ + if (d) + return d->takeRawData(size); + *size = 0; + return nullptr; +} + +bool QBinaryJsonObject::detach(uint reserve) +{ + if (!d) { + if (reserve >= QBinaryJsonPrivate::Value::MaxSize) { + qWarning("QBinaryJson: Document too large to store in data structure"); + return false; + } + d = new QBinaryJsonPrivate::MutableData(reserve, QJsonValue::Object); + o = static_cast(d->header->root()); + d->ref.ref(); + return true; + } + if (reserve == 0 && d->ref.loadRelaxed() == 1) + return true; + + QBinaryJsonPrivate::MutableData *x = d->clone(o, reserve); + if (!x) + return false; + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + o = static_cast(d->header->root()); + return true; +} + +void QBinaryJsonObject::compact() +{ + if (!d || !d->compactionCounter) + return; + + detach(); + d->compact(); + o = static_cast(d->header->root()); +} + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qbinaryjsonobject_p.h b/src/corelib/serialization/qbinaryjsonobject_p.h new file mode 100644 index 0000000000..c0991ac339 --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonobject_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBINARYJSONOBJECT_H +#define QBINARYJSONOBJECT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qbinaryjsonvalue_p.h" + +QT_REQUIRE_CONFIG(binaryjson); + +QT_BEGIN_NAMESPACE + +class QBinaryJsonObject +{ + Q_DISABLE_COPY(QBinaryJsonObject) +public: + QBinaryJsonObject() = default; + ~QBinaryJsonObject(); + + QBinaryJsonObject(QBinaryJsonObject &&other) noexcept + : d(other.d), o(other.o) + { + other.d = nullptr; + other.o = nullptr; + } + + QBinaryJsonObject &operator =(QBinaryJsonObject &&other) noexcept + { + qSwap(d, other.d); + qSwap(o, other.o); + return *this; + } + + static QBinaryJsonObject fromJsonObject(const QJsonObject &object); + char *takeRawData(uint *size) const; + +private: + friend class QBinaryJsonValue; + + void insert(const QString &key, const QBinaryJsonValue &value); + bool detach(uint reserve = 0); + void compact(); + + QBinaryJsonPrivate::MutableData *d = nullptr; + QBinaryJsonPrivate::Object *o = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QBINARYJSONOBJECT_P_H diff --git a/src/corelib/serialization/qbinaryjsonvalue.cpp b/src/corelib/serialization/qbinaryjsonvalue.cpp new file mode 100644 index 0000000000..5e3a01ad38 --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonvalue.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbinaryjsonobject_p.h" +#include "qbinaryjsonvalue_p.h" +#include "qbinaryjsonarray_p.h" +#include "qbinaryjson_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +QBinaryJsonValue::QBinaryJsonValue(QBinaryJsonPrivate::MutableData *data, + QBinaryJsonPrivate::Base *parent, + const QBinaryJsonPrivate::Value &v) + : t(QJsonValue::Type(uint(v.type))) +{ + switch (t) { + case QJsonValue::Undefined: + case QJsonValue::Null: + dbl = 0; + break; + case QJsonValue::Bool: + b = v.toBoolean(); + break; + case QJsonValue::Double: + dbl = v.toDouble(parent); + break; + case QJsonValue::String: { + QString s = v.toString(parent); + stringData = s.data_ptr(); + stringData->ref.ref(); + break; + } + case QJsonValue::Array: + case QJsonValue::Object: + d = data; + base = v.base(parent); + break; + } + if (d) + d->ref.ref(); +} + +QBinaryJsonValue::QBinaryJsonValue(QString string) + : stringData(*reinterpret_cast(&string)), t(QJsonValue::String) +{ + stringData->ref.ref(); +} + +QBinaryJsonValue::QBinaryJsonValue(const QBinaryJsonArray &a) + : base(a.a), d(a.d), t(QJsonValue::Array) +{ + if (d) + d->ref.ref(); +} + +QBinaryJsonValue::QBinaryJsonValue(const QBinaryJsonObject &o) + : base(o.o), d(o.d), t(QJsonValue::Object) +{ + if (d) + d->ref.ref(); +} + +QBinaryJsonValue::~QBinaryJsonValue() +{ + if (t == QJsonValue::String && stringData && !stringData->ref.deref()) + free(stringData); + + if (d && !d->ref.deref()) + delete d; +} + +QBinaryJsonValue QBinaryJsonValue::fromJsonValue(const QJsonValue &json) +{ + switch (json.type()) { + case QJsonValue::Bool: + return QBinaryJsonValue(json.toBool()); + case QJsonValue::Double: + return QBinaryJsonValue(json.toDouble()); + case QJsonValue::String: + return QBinaryJsonValue(json.toString()); + case QJsonValue::Array: + return QBinaryJsonArray::fromJsonArray(json.toArray()); + case QJsonValue::Object: + return QBinaryJsonObject::fromJsonObject(json.toObject()); + case QJsonValue::Null: + return QBinaryJsonValue(QJsonValue::Null); + case QJsonValue::Undefined: + return QBinaryJsonValue(QJsonValue::Undefined); + } + Q_UNREACHABLE(); + return QBinaryJsonValue(QJsonValue::Null); +} + +QString QBinaryJsonValue::toString() const +{ + if (t != QJsonValue::String) + return QString(); + stringData->ref.ref(); // the constructor below doesn't add a ref. + QStringDataPtr holder = { stringData }; + return QString(holder); +} + +void QBinaryJsonValue::detach() +{ + if (!d) + return; + + QBinaryJsonPrivate::MutableData *x = d->clone(base); + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + base = static_cast(d->header->root()); +} + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qbinaryjsonvalue_p.h b/src/corelib/serialization/qbinaryjsonvalue_p.h new file mode 100644 index 0000000000..498fc62ecd --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonvalue_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBINARYJSONVALUE_P_H +#define QBINARYJSONVALUE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_REQUIRE_CONFIG(binaryjson); + +QT_BEGIN_NAMESPACE + +class QBinaryJsonArray; +class QBinaryJsonObject; + +namespace QBinaryJsonPrivate { +class ConstData; +class MutableData; +class Base; +class Value; +class Object; +class Array; +} + +class Q_CORE_EXPORT QBinaryJsonValue +{ + Q_DISABLE_COPY(QBinaryJsonValue) +public: + explicit QBinaryJsonValue(QJsonValue::Type type) : ui(0), t(type) {} + explicit QBinaryJsonValue(bool b) : b(b), t(QJsonValue::Bool) {} + explicit QBinaryJsonValue(double n) : dbl(n), t(QJsonValue::Double) {} + explicit QBinaryJsonValue(QString s); + QBinaryJsonValue(const QBinaryJsonArray &a); + QBinaryJsonValue(const QBinaryJsonObject &o); + + ~QBinaryJsonValue(); + + QBinaryJsonValue(QBinaryJsonValue &&other) noexcept + : ui(other.ui), + d(other.d), + t(other.t) + { + other.ui = 0; + other.d = nullptr; + other.t = QJsonValue::Null; + } + + QBinaryJsonValue &operator =(QBinaryJsonValue &&other) noexcept + { + qSwap(ui, other.ui); + qSwap(d, other.d); + qSwap(t, other.t); + return *this; + } + + static QBinaryJsonValue fromJsonValue(const QJsonValue &json); + QJsonValue::Type type() const { return t; } + bool toBool() const { return (t == QJsonValue::Bool) && b; } + double toDouble() const { return (t == QJsonValue::Double) ? dbl : 0; } + QString toString() const; + +private: + friend class QBinaryJsonPrivate::Value; + friend class QBinaryJsonArray; + friend class QBinaryJsonObject; + + QBinaryJsonValue(QBinaryJsonPrivate::MutableData *d, QBinaryJsonPrivate::Base *parent, + const QBinaryJsonPrivate::Value &v); + + void detach(); + + union { + quint64 ui; + bool b; + double dbl; + QStringData *stringData; + const QBinaryJsonPrivate::Base *base; + }; + QBinaryJsonPrivate::MutableData *d = nullptr; // needed for Objects and Arrays + QJsonValue::Type t = QJsonValue::Null; +}; + +QT_END_NAMESPACE + +#endif // QBINARYJSONVALUE_P_H diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index 23595f4092..c2f670de06 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -3109,4 +3109,6 @@ QT_END_NAMESPACE #include "qcborarray.cpp" #include "qcbormap.cpp" +#ifndef QT_NO_QOBJECT #include "moc_qcborvalue.cpp" +#endif diff --git a/src/corelib/serialization/qcborvalue.h b/src/corelib/serialization/qcborvalue.h index accd0fae8a..9c613dfcfb 100644 --- a/src/corelib/serialization/qcborvalue.h +++ b/src/corelib/serialization/qcborvalue.h @@ -71,6 +71,8 @@ class QCborStreamReader; class QCborStreamWriter; class QDataStream; +namespace QJsonPrivate { class Value; } + struct QCborParserError { qint64 offset = 0; @@ -301,6 +303,8 @@ public: private: friend class QCborValueRef; friend class QCborContainerPrivate; + friend class QJsonPrivate::Value; + qint64 n = 0; QCborContainerPrivate *container = nullptr; Type t = Undefined; diff --git a/src/corelib/serialization/qjson.cpp b/src/corelib/serialization/qjson.cpp deleted file mode 100644 index 76f1eae1ce..0000000000 --- a/src/corelib/serialization/qjson.cpp +++ /dev/null @@ -1,451 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qjson_p.h" -#include - -QT_BEGIN_NAMESPACE - -namespace QJsonPrivate -{ - -static Q_CONSTEXPR Base emptyArray = { { qle_uint(sizeof(Base)) }, { 0 }, { qle_uint(0) } }; -static Q_CONSTEXPR Base emptyObject = { { qle_uint(sizeof(Base)) }, { qToLittleEndian(1u) }, { qle_uint(0) } }; - -void Data::compact() -{ - Q_ASSERT(sizeof(Value) == sizeof(offset)); - - if (!compactionCounter) - return; - - Base *base = header->root(); - int reserve = 0; - if (base->is_object) { - Object *o = static_cast(base); - for (int i = 0; i < (int)o->length; ++i) - reserve += o->entryAt(i)->usedStorage(o); - } else { - Array *a = static_cast(base); - for (int i = 0; i < (int)a->length; ++i) - reserve += (*a)[i].usedStorage(a); - } - - int size = sizeof(Base) + reserve + base->length*sizeof(offset); - int alloc = sizeof(Header) + size; - Header *h = (Header *) malloc(alloc); - Q_CHECK_PTR(h); - h->tag = QJsonDocument::BinaryFormatTag; - h->version = 1; - Base *b = h->root(); - b->size = size; - b->is_object = header->root()->is_object; - b->length = base->length; - b->tableOffset = reserve + sizeof(Array); - - int offset = sizeof(Base); - if (b->is_object) { - Object *o = static_cast(base); - Object *no = static_cast(b); - - for (int i = 0; i < (int)o->length; ++i) { - no->table()[i] = offset; - - const Entry *e = o->entryAt(i); - Entry *ne = no->entryAt(i); - int s = e->size(); - memcpy(ne, e, s); - offset += s; - int dataSize = e->value.usedStorage(o); - if (dataSize) { - memcpy((char *)no + offset, e->value.data(o), dataSize); - ne->value.value = offset; - offset += dataSize; - } - } - } else { - Array *a = static_cast(base); - Array *na = static_cast(b); - - for (int i = 0; i < (int)a->length; ++i) { - const Value &v = (*a)[i]; - Value &nv = (*na)[i]; - nv = v; - int dataSize = v.usedStorage(a); - if (dataSize) { - memcpy((char *)na + offset, v.data(a), dataSize); - nv.value = offset; - offset += dataSize; - } - } - } - Q_ASSERT(offset == (int)b->tableOffset); - - free(header); - header = h; - this->alloc = alloc; - compactionCounter = 0; -} - -bool Data::valid() const -{ - if (header->tag != QJsonDocument::BinaryFormatTag || header->version != 1u) - return false; - - bool res = false; - Base *root = header->root(); - int maxSize = alloc - sizeof(Header); - if (root->is_object) - res = static_cast(root)->isValid(maxSize); - else - res = static_cast(root)->isValid(maxSize); - - return res; -} - - -int Base::reserveSpace(uint dataSize, int posInTable, uint numItems, bool replace) -{ - Q_ASSERT(posInTable >= 0 && posInTable <= (int)length); - if (size + dataSize >= Value::MaxSize) { - qWarning("QJson: Document too large to store in data structure %d %d %d", (uint)size, dataSize, Value::MaxSize); - return 0; - } - - offset off = tableOffset; - // move table to new position - if (replace) { - memmove((char *)(table()) + dataSize, table(), length*sizeof(offset)); - } else { - memmove((char *)(table() + posInTable + numItems) + dataSize, table() + posInTable, (length - posInTable)*sizeof(offset)); - memmove((char *)(table()) + dataSize, table(), posInTable*sizeof(offset)); - } - tableOffset += dataSize; - for (int i = 0; i < (int)numItems; ++i) - table()[posInTable + i] = off; - size += dataSize; - if (!replace) { - length += numItems; - size += numItems * sizeof(offset); - } - return off; -} - -void Base::removeItems(int pos, int numItems) -{ - Q_ASSERT(pos >= 0 && pos <= (int)length); - if (pos + numItems < (int)length) - memmove(table() + pos, table() + pos + numItems, (length - pos - numItems)*sizeof(offset)); - length -= numItems; -} - -int Object::indexOf(QStringView key, bool *exists) const -{ - int min = 0; - int n = length; - while (n > 0) { - int half = n >> 1; - int middle = min + half; - if (*entryAt(middle) >= key) { - n = half; - } else { - min = middle + 1; - n -= half + 1; - } - } - if (min < (int)length && *entryAt(min) == key) { - *exists = true; - return min; - } - *exists = false; - return min; -} - -int Object::indexOf(QLatin1String key, bool *exists) const -{ - int min = 0; - int n = length; - while (n > 0) { - int half = n >> 1; - int middle = min + half; - if (*entryAt(middle) >= key) { - n = half; - } else { - min = middle + 1; - n -= half + 1; - } - } - if (min < (int)length && *entryAt(min) == key) { - *exists = true; - return min; - } - *exists = false; - return min; -} - -bool Object::isValid(int maxSize) const -{ - if (size > (uint)maxSize || tableOffset + length*sizeof(offset) > size) - return false; - - QString lastKey; - for (uint i = 0; i < length; ++i) { - offset entryOffset = table()[i]; - if (entryOffset + sizeof(Entry) >= tableOffset) - return false; - Entry *e = entryAt(i); - if (!e->isValid(tableOffset - table()[i])) - return false; - QString key = e->key(); - if (key < lastKey) - return false; - if (!e->value.isValid(this)) - return false; - lastKey = key; - } - return true; -} - - - -bool Array::isValid(int maxSize) const -{ - if (size > (uint)maxSize || tableOffset + length*sizeof(offset) > size) - return false; - - for (uint i = 0; i < length; ++i) { - if (!at(i).isValid(this)) - return false; - } - return true; -} - - -bool Entry::operator ==(QStringView key) const -{ - if (value.latinKey) - return (shallowLatin1Key() == key); - else - return (shallowKey() == key); -} - -bool Entry::operator==(QLatin1String key) const -{ - if (value.latinKey) - return shallowLatin1Key() == key; - else - return shallowKey() == QString(key); // ### conversion to QString -} - -bool Entry::operator ==(const Entry &other) const -{ - if (value.latinKey) { - if (other.value.latinKey) - return shallowLatin1Key() == other.shallowLatin1Key(); - return shallowLatin1Key() == other.shallowKey(); - } else if (other.value.latinKey) { - return shallowKey() == other.shallowLatin1Key(); - } - return shallowKey() == other.shallowKey(); -} - -bool Entry::operator >=(const Entry &other) const -{ - if (value.latinKey) { - if (other.value.latinKey) - return shallowLatin1Key() >= other.shallowLatin1Key(); - return shallowLatin1Key() >= other.shallowKey(); - } else if (other.value.latinKey) { - return shallowKey() >= other.shallowLatin1Key(); - } - return shallowKey() >= other.shallowKey(); -} - - -int Value::usedStorage(const Base *b) const -{ - int s = 0; - switch (type) { - case QJsonValue::Double: - if (latinOrIntValue) - break; - s = sizeof(double); - break; - case QJsonValue::String: { - char *d = data(b); - if (latinOrIntValue) - s = sizeof(ushort) + qFromLittleEndian(*(ushort *)d); - else - s = sizeof(int) + sizeof(ushort) * qFromLittleEndian(*(int *)d); - break; - } - case QJsonValue::Array: - case QJsonValue::Object: - s = base(b)->size; - break; - case QJsonValue::Null: - case QJsonValue::Bool: - default: - break; - } - return alignedSize(s); -} - -inline bool isValidValueOffset(uint offset, uint tableOffset) -{ - return offset >= sizeof(Base) - && offset + sizeof(uint) <= tableOffset; -} - -bool Value::isValid(const Base *b) const -{ - switch (type) { - case QJsonValue::Null: - case QJsonValue::Bool: - return true; - case QJsonValue::Double: - return latinOrIntValue || isValidValueOffset(value, b->tableOffset); - case QJsonValue::String: - if (!isValidValueOffset(value, b->tableOffset)) - return false; - if (latinOrIntValue) - return asLatin1String(b).isValid(b->tableOffset - value); - return asString(b).isValid(b->tableOffset - value); - case QJsonValue::Array: - return isValidValueOffset(value, b->tableOffset) - && static_cast(base(b))->isValid(b->tableOffset - value); - case QJsonValue::Object: - return isValidValueOffset(value, b->tableOffset) - && static_cast(base(b))->isValid(b->tableOffset - value); - default: - return false; - } -} - -/*! - \internal - */ -int Value::requiredStorage(QJsonValue &v, bool *compressed) -{ - *compressed = false; - switch (v.t) { - case QJsonValue::Double: - if (QJsonPrivate::compressedNumber(v.dbl) != INT_MAX) { - *compressed = true; - return 0; - } - return sizeof(double); - case QJsonValue::String: { - QString s = v.toString(); - *compressed = QJsonPrivate::useCompressed(s); - return QJsonPrivate::qStringSize(s, *compressed); - } - case QJsonValue::Array: - case QJsonValue::Object: - if (v.d && v.d->compactionCounter) { - v.detach(); - v.d->compact(); - v.base = static_cast(v.d->header->root()); - } - return v.base ? uint(v.base->size) : sizeof(QJsonPrivate::Base); - case QJsonValue::Undefined: - case QJsonValue::Null: - case QJsonValue::Bool: - break; - } - return 0; -} - -/*! - \internal - */ -uint Value::valueToStore(const QJsonValue &v, uint offset) -{ - switch (v.t) { - case QJsonValue::Undefined: - case QJsonValue::Null: - break; - case QJsonValue::Bool: - return v.b; - case QJsonValue::Double: { - int c = QJsonPrivate::compressedNumber(v.dbl); - if (c != INT_MAX) - return c; - } - Q_FALLTHROUGH(); - case QJsonValue::String: - case QJsonValue::Array: - case QJsonValue::Object: - return offset; - } - return 0; -} - -/*! - \internal - */ -void Value::copyData(const QJsonValue &v, char *dest, bool compressed) -{ - switch (v.t) { - case QJsonValue::Double: - if (!compressed) { - qToLittleEndian(v.ui, dest); - } - break; - case QJsonValue::String: { - QString str = v.toString(); - QJsonPrivate::copyString(dest, str, compressed); - break; - } - case QJsonValue::Array: - case QJsonValue::Object: { - const QJsonPrivate::Base *b = v.base; - if (!b) - b = (v.t == QJsonValue::Array ? &emptyArray : &emptyObject); - memcpy(dest, b, b->size); - break; - } - default: - break; - } -} - -} // namespace QJsonPrivate - -QT_END_NAMESPACE diff --git a/src/corelib/serialization/qjson_p.h b/src/corelib/serialization/qjson_p.h index 7978bed7da..d2bbf928d0 100644 --- a/src/corelib/serialization/qjson_p.h +++ b/src/corelib/serialization/qjson_p.h @@ -52,743 +52,170 @@ // We mean it. // -#include #include -#include -#include -#include -#include -#include -#include - -#include "private/qendian_p.h" -#include "private/qsimd_p.h" - -#include -#include -#include +#include +#include QT_BEGIN_NAMESPACE -// in qstring.cpp -void qt_to_latin1_unchecked(uchar *dst, const ushort *uc, qsizetype len); -void qt_from_latin1(ushort *dst, const char *str, size_t size) noexcept; - -/* - This defines a binary data structure for Json data. The data structure is optimised for fast reading - and minimum allocations. The whole data structure can be mmap'ed and used directly. - - In most cases the binary structure is not as space efficient as a utf8 encoded text representation, but - much faster to access. - - The size requirements are: - - String: - Latin1 data: 2 bytes header + string.length() - Full Unicode: 4 bytes header + 2*(string.length()) - - Values: 4 bytes + size of data (size can be 0 for some data) - bool: 0 bytes - double: 8 bytes (0 if integer with less than 27bits) - string: see above - array: size of array - object: size of object - Array: 12 bytes + 4*length + size of Value data - Object: 12 bytes + 8*length + size of Key Strings + size of Value data - - For an example such as - - { // object: 12 + 5*8 = 52 - "firstName": "John", // key 12, value 8 = 20 - "lastName" : "Smith", // key 12, value 8 = 20 - "age" : 25, // key 8, value 0 = 8 - "address" : // key 12, object below = 140 - { // object: 12 + 4*8 - "streetAddress": "21 2nd Street", // key 16, value 16 - "city" : "New York", // key 8, value 12 - "state" : "NY", // key 8, value 4 - "postalCode" : "10021" // key 12, value 8 - }, // object total: 128 - "phoneNumber": // key: 16, value array below = 172 - [ // array: 12 + 2*4 + values below: 156 - { // object 12 + 2*8 - "type" : "home", // key 8, value 8 - "number": "212 555-1234" // key 8, value 16 - }, // object total: 68 - { // object 12 + 2*8 - "type" : "fax", // key 8, value 8 - "number": "646 555-4567" // key 8, value 16 - } // object total: 68 - ] // array total: 156 - } // great total: 412 bytes - - The uncompressed text file used roughly 500 bytes, so in this case we end up using about - the same space as the text representation. - - Other measurements have shown a slightly bigger binary size than a compact text - representation where all possible whitespace was stripped out. -*/ -#define Q_DECLARE_JSONPRIVATE_TYPEINFO(Class, Flags) } Q_DECLARE_TYPEINFO(QJsonPrivate::Class, Flags); namespace QJsonPrivate { namespace QJsonPrivate { -class Array; -class Object; -class Value; -class Entry; - -template -using q_littleendian = QLEInteger; - -typedef q_littleendian qle_short; -typedef q_littleendian qle_ushort; -typedef q_littleendian qle_int; -typedef q_littleendian qle_uint; - -template -using qle_bitfield = QLEIntegerBitfield; - -template -using qle_signedbitfield = QLEIntegerBitfield; - -typedef qle_uint offset; - -// round the size up to the next 4 byte boundary -inline int alignedSize(int size) { return (size + 3) & ~3; } - -const int MaxLatin1Length = 0x7fff; - -static inline bool useCompressed(QStringView s) +template +struct ObjectIterator { - if (s.length() > MaxLatin1Length) - return false; - return QtPrivate::isLatin1(s); -} + using pointer = Element *; -static inline bool useCompressed(QLatin1String s) -{ - return s.size() <= MaxLatin1Length; -} + struct value_type; + struct reference { + reference(Element &ref) : m_key(&ref) {} -template -static inline int qStringSize(T string, bool compress) -{ - int l = 2 + string.size(); - if (!compress) - l *= 2; - return alignedSize(l); -} + reference() = delete; + ~reference() = default; -// returns INT_MAX if it can't compress it into 28 bits -static inline int compressedNumber(double d) -{ - // this relies on details of how ieee floats are represented - const int exponent_off = 52; - const quint64 fraction_mask = 0x000fffffffffffffull; - const quint64 exponent_mask = 0x7ff0000000000000ull; + reference(const reference &other) = default; + reference(reference &&other) = default; - quint64 val; - memcpy (&val, &d, sizeof(double)); - int exp = (int)((val & exponent_mask) >> exponent_off) - 1023; - if (exp < 0 || exp > 25) - return INT_MAX; - - quint64 non_int = val & (fraction_mask >> exp); - if (non_int) - return INT_MAX; - - bool neg = (val >> 63) != 0; - val &= fraction_mask; - val |= ((quint64)1 << 52); - int res = (int)(val >> (52 - exp)); - return neg ? -res : res; -} - -class Latin1String; - -class String -{ -public: - explicit String(const char *data) { d = (Data *)data; } - - struct Data { - qle_uint length; - qle_ushort utf16[1]; - }; - - Data *d; - - int byteSize() const { return sizeof(uint) + sizeof(ushort) * d->length; } - bool isValid(int maxSize) const { - // Check byteSize() <= maxSize, avoiding integer overflow - maxSize -= sizeof(uint); - return maxSize >= 0 && uint(d->length) <= maxSize / sizeof(ushort); - } - - inline String &operator=(QStringView str) - { - d->length = str.length(); - qToLittleEndian(str.utf16(), str.length(), d->utf16); - fillTrailingZeros(); - return *this; - } - - inline String &operator=(QLatin1String str) - { - d->length = str.size(); -#if Q_BYTE_ORDER == Q_BIG_ENDIAN - for (int i = 0; i < str.size(); ++i) - d->utf16[i] = str[i].unicode(); -#else - qt_from_latin1((ushort *)d->utf16, str.data(), str.size()); -#endif - fillTrailingZeros(); - return *this; - } - - void fillTrailingZeros() - { - if (d->length & 1) - d->utf16[d->length] = 0; - } - - inline bool operator ==(QStringView str) const { - int slen = str.length(); - int l = d->length; - if (slen != l) - return false; - const ushort *s = (const ushort *)str.utf16(); - const qle_ushort *a = d->utf16; - const ushort *b = s; - while (l-- && *a == *b) - a++,b++; - return (l == -1); - } - inline bool operator !=(QStringView str) const { - return !operator ==(str); - } - inline bool operator >=(QStringView str) const { - // ### - return toString() >= str; - } - - inline bool operator<(const Latin1String &str) const; - inline bool operator>=(const Latin1String &str) const { return !operator <(str); } - inline bool operator ==(const Latin1String &str) const; - - inline bool operator ==(const String &str) const { - if (d->length != str.d->length) - return false; - return !memcmp(d->utf16, str.d->utf16, d->length*sizeof(ushort)); - } - inline bool operator<(const String &other) const; - inline bool operator >=(const String &other) const { return !(*this < other); } - - inline QString toString() const { -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - return QString((QChar *)d->utf16, d->length); -#else - int l = d->length; - QString str(l, Qt::Uninitialized); - QChar *ch = str.data(); - for (int i = 0; i < l; ++i) - ch[i] = QChar(d->utf16[i]); - return str; -#endif - } - -}; - -class Latin1String -{ -public: - explicit Latin1String(const char *data) { d = (Data *)data; } - - struct Data { - qle_ushort length; - char latin1[1]; - }; - Data *d; - - int byteSize() const { return sizeof(ushort) + sizeof(char)*(d->length); } - bool isValid(int maxSize) const { - return byteSize() <= maxSize; - } - - inline Latin1String &operator=(QStringView str) - { - int len = d->length = str.length(); - uchar *l = (uchar *)d->latin1; - const ushort *uc = (const ushort *)str.utf16(); - qt_to_latin1_unchecked(l, uc, len); - - fillTrailingZeros(); - return *this; - } - - inline Latin1String &operator=(QLatin1String str) - { - int len = d->length = str.size(); - uchar *l = (uchar *)d->latin1; - memcpy(l, str.data(), len); - - fillTrailingZeros(); - return *this; - } - - void fillTrailingZeros() - { - uchar *l = (uchar *)d->latin1; - for (int len = d->length; (quintptr)(l + len) & 0x3; ++len) - l[len] = 0; - } - - QLatin1String toQLatin1String() const noexcept { - return QLatin1String(d->latin1, d->length); - } - - inline bool operator<(const String &str) const - { - const qle_ushort *uc = (qle_ushort *) str.d->utf16; - if (!uc || *uc == 0) - return false; - - const uchar *c = (uchar *)d->latin1; - const uchar *e = c + qMin((int)d->length, (int)str.d->length); - - while (c < e) { - if (*c != *uc) - break; - ++c; - ++uc; + reference &operator=(const value_type &value); + reference &operator=(const reference &other) + { + if (m_key != other.m_key) { + key() = other.key(); + value() = other.value(); + } + return *this; } - return (c == e ? (int)d->length < (int)str.d->length : *c < *uc); - } - inline bool operator ==(const String &str) const { - return (str == *this); - } - inline bool operator >=(const String &str) const { - return !(*this < str); - } + reference &operator=(reference &&other) + { + key() = other.key(); + value() = other.value(); + return *this; + } - inline QString toString() const { - return QString::fromLatin1(d->latin1, d->length); - } -}; + Element &key() { return *m_key; } + Element &value() { return *(m_key + 1); } -#define DEF_OP(op) \ - inline bool operator op(Latin1String lhs, Latin1String rhs) noexcept \ - { \ - return lhs.toQLatin1String() op rhs.toQLatin1String(); \ - } \ - inline bool operator op(QLatin1String lhs, Latin1String rhs) noexcept \ - { \ - return lhs op rhs.toQLatin1String(); \ - } \ - inline bool operator op(Latin1String lhs, QLatin1String rhs) noexcept \ - { \ - return lhs.toQLatin1String() op rhs; \ - } \ - inline bool operator op(QStringView lhs, Latin1String rhs) noexcept \ - { \ - return lhs op rhs.toQLatin1String(); \ - } \ - inline bool operator op(Latin1String lhs, QStringView rhs) noexcept \ - { \ - return lhs.toQLatin1String() op rhs; \ - } \ - /*end*/ -DEF_OP(==) -DEF_OP(!=) -DEF_OP(< ) -DEF_OP(> ) -DEF_OP(<=) -DEF_OP(>=) -#undef DEF_OP - -inline bool String::operator ==(const Latin1String &str) const -{ - if ((int)d->length != (int)str.d->length) - return false; - const qle_ushort *uc = d->utf16; - const qle_ushort *e = uc + d->length; - const uchar *c = (uchar *)str.d->latin1; - - while (uc < e) { - if (*uc != *c) - return false; - ++uc; - ++c; - } - return true; -} - -inline bool String::operator <(const String &other) const -{ - int alen = d->length; - int blen = other.d->length; - int l = qMin(alen, blen); - qle_ushort *a = d->utf16; - qle_ushort *b = other.d->utf16; - - while (l-- && *a == *b) - a++,b++; - if (l==-1) - return (alen < blen); - return (ushort)*a < (ushort)*b; -} - -inline bool String::operator<(const Latin1String &str) const -{ - const uchar *c = (uchar *) str.d->latin1; - if (!c || *c == 0) - return false; - - const qle_ushort *uc = d->utf16; - const qle_ushort *e = uc + qMin((int)d->length, (int)str.d->length); - - while (uc < e) { - if (*uc != *c) - break; - ++uc; - ++c; - } - return (uc == e ? (int)d->length < (int)str.d->length : (ushort)*uc < *c); - -} - -template -static inline void copyString(char *dest, T str, bool compress) -{ - if (compress) { - Latin1String string(dest); - string = str; - } else { - String string(dest); - string = str; - } -} + const Element &key() const { return *m_key; } + const Element &value() const { return *(m_key + 1); } -/* - Base is the base class for both Object and Array. Both classes work more or less the same way. - The class starts with a header (defined by the struct below), then followed by data (the data for - values in the Array case and Entry's (see below) for objects. - - After the data a table follows (tableOffset points to it) containing Value objects for Arrays, and - offsets from the beginning of the object to Entry's in the case of Object. - - Entry's in the Object's table are lexicographically sorted by key in the table(). This allows the usage - of a binary search over the keys in an Object. - */ -class Base -{ -public: - qle_uint size; - union { - uint _dummy; - qle_bitfield<0, 1> is_object; - qle_bitfield<1, 31> length; + private: + Element *m_key; }; - offset tableOffset; - // content follows here - inline bool isObject() const { return !!is_object; } - inline bool isArray() const { return !isObject(); } + struct value_type { + value_type(reference ref) : m_key(ref.key()), m_value(ref.value()) {} - inline offset *table() const { return (offset *) (((char *) this) + tableOffset); } + Element key() const { return m_key; } + Element value() const { return m_value; } + private: + Element m_key; + Element m_value; + }; - int reserveSpace(uint dataSize, int posInTable, uint numItems, bool replace); - void removeItems(int pos, int numItems); + using difference_type = typename QVector::difference_type; + using iterator_category = std::random_access_iterator_tag; + + ObjectIterator() = default; + ObjectIterator(ElementsIterator it) : it(it) {} + ElementsIterator elementsIterator() { return it; } + + ObjectIterator operator++(int) { ObjectIterator ret(it); it += 2; return ret; } + ObjectIterator &operator++() { it += 2; return *this; } + ObjectIterator &operator+=(difference_type n) { it += 2 * n; return *this; } + + ObjectIterator operator--(int) { ObjectIterator ret(it); it -= 2; return ret; } + ObjectIterator &operator--() { it -= 2; return *this; } + ObjectIterator &operator-=(difference_type n) { it -= 2 * n; return *this; } + + reference operator*() const { return *it; } + reference operator[](int n) const { return it[n * 2]; } + + bool operator<(ObjectIterator other) const { return it < other.it; } + bool operator>(ObjectIterator other) const { return it > other.it; } + bool operator<=(ObjectIterator other) const { return it <= other.it; } + bool operator>=(ObjectIterator other) const { return it >= other.it; } + +private: + ElementsIterator it; }; -class Object : public Base +template +inline ObjectIterator operator+( + ObjectIterator a, + typename ObjectIterator::difference_type n) { -public: - Entry *entryAt(int i) const { - return reinterpret_cast(((char *)this) + table()[i]); - } - int indexOf(QStringView key, bool *exists) const; - int indexOf(QLatin1String key, bool *exists) const; - - bool isValid(int maxSize) const; -}; - - -class Array : public Base + return {a.elementsIterator() + 2 * n}; +} +template +inline ObjectIterator operator+( + int n, ObjectIterator a) { -public: - inline Value at(int i) const; - inline Value &operator [](int i); + return {a.elementsIterator() + 2 * n}; +} +template +inline ObjectIterator operator-( + ObjectIterator a, + typename ObjectIterator::difference_type n) +{ + return {a.elementsIterator() - 2 * n}; +} +template +inline int operator-( + ObjectIterator a, + ObjectIterator b) +{ + return (a.elementsIterator() - b.elementsIterator()) / 2; +} +template +inline bool operator!=( + ObjectIterator a, + ObjectIterator b) +{ + return a.elementsIterator() != b.elementsIterator(); +} +template +inline bool operator==( + ObjectIterator a, + ObjectIterator b) +{ + return a.elementsIterator() == b.elementsIterator(); +} - bool isValid(int maxSize) const; -}; +using KeyIterator = ObjectIterator::iterator>; +using ConstKeyIterator = ObjectIterator::const_iterator>; +template<> +inline KeyIterator::reference &KeyIterator::reference::operator=(const KeyIterator::value_type &value) +{ + *m_key = value.key(); + *(m_key + 1) = value.value(); + return *this; +} + +inline void swap(KeyIterator::reference a, KeyIterator::reference b) +{ + KeyIterator::value_type t = a; + a = b; + b = t; +} class Value { public: - enum { - MaxSize = (1<<27) - 1 - }; - union { - uint _dummy; - qle_bitfield<0, 3> type; - qle_bitfield<3, 1> latinOrIntValue; - qle_bitfield<4, 1> latinKey; - qle_bitfield<5, 27> value; - qle_signedbitfield<5, 27> int_value; - }; + static QCborContainerPrivate *container(const QCborValue &v) { return v.container; } - inline char *data(const Base *b) const { return ((char *)b) + value; } - int usedStorage(const Base *b) const; - - bool toBoolean() const; - double toDouble(const Base *b) const; - QString toString(const Base *b) const; - String asString(const Base *b) const; - Latin1String asLatin1String(const Base *b) const; - Base *base(const Base *b) const; - - bool isValid(const Base *b) const; - - static int requiredStorage(QJsonValue &v, bool *compressed); - static uint valueToStore(const QJsonValue &v, uint offset); - static void copyData(const QJsonValue &v, char *dest, bool compressed); -}; -Q_DECLARE_JSONPRIVATE_TYPEINFO(Value, Q_PRIMITIVE_TYPE) - -inline Value Array::at(int i) const -{ - return *(Value *) (table() + i); -} - -inline Value &Array::operator [](int i) -{ - return *(Value *) (table() + i); -} - - - -class Entry { -public: - Value value; - // key - // value data follows key - - uint size() const { - int s = sizeof(Entry); - if (value.latinKey) - s += shallowLatin1Key().byteSize(); - else - s += shallowKey().byteSize(); - return alignedSize(s); - } - - int usedStorage(Base *b) const { - return size() + value.usedStorage(b); - } - - String shallowKey() const + static QJsonValue fromTrustedCbor(const QCborValue &v) { - Q_ASSERT(!value.latinKey); - return String((const char *)this + sizeof(Entry)); + QJsonValue result; + result.d = v.container; + result.n = v.n; + result.t = v.t; + return result; } - Latin1String shallowLatin1Key() const - { - Q_ASSERT(value.latinKey); - return Latin1String((const char *)this + sizeof(Entry)); - } - QString key() const - { - if (value.latinKey) { - return shallowLatin1Key().toString(); - } - return shallowKey().toString(); - } - - bool isValid(int maxSize) const { - if (maxSize < (int)sizeof(Entry)) - return false; - maxSize -= sizeof(Entry); - if (value.latinKey) - return shallowLatin1Key().isValid(maxSize); - return shallowKey().isValid(maxSize); - } - - bool operator ==(QStringView key) const; - inline bool operator !=(QStringView key) const { return !operator ==(key); } - inline bool operator >=(QStringView key) const; - - bool operator==(QLatin1String key) const; - inline bool operator!=(QLatin1String key) const { return !operator ==(key); } - inline bool operator>=(QLatin1String key) const; - - bool operator ==(const Entry &other) const; - bool operator >=(const Entry &other) const; }; -inline bool Entry::operator >=(QStringView key) const -{ - if (value.latinKey) - return (shallowLatin1Key() >= key); - else - return (shallowKey() >= key); -} - -inline bool Entry::operator >=(QLatin1String key) const -{ - if (value.latinKey) - return shallowLatin1Key() >= key; - else - return shallowKey() >= QString(key); // ### conversion to QString -} - -inline bool operator <(QStringView key, const Entry &e) -{ return e >= key; } - -inline bool operator<(QLatin1String key, const Entry &e) -{ return e >= key; } - - -class Header { -public: - qle_uint tag; // 'qbjs' - qle_uint version; // 1 - Base *root() { return (Base *)(this + 1); } -}; - - -inline bool Value::toBoolean() const -{ - Q_ASSERT(type == QJsonValue::Bool); - return value != 0; -} - -inline double Value::toDouble(const Base *b) const -{ - Q_ASSERT(type == QJsonValue::Double); - if (latinOrIntValue) - return int_value; - - quint64 i = qFromLittleEndian((const uchar *)b + value); - double d; - memcpy(&d, &i, sizeof(double)); - return d; -} - -inline String Value::asString(const Base *b) const -{ - Q_ASSERT(type == QJsonValue::String && !latinOrIntValue); - return String(data(b)); -} - -inline Latin1String Value::asLatin1String(const Base *b) const -{ - Q_ASSERT(type == QJsonValue::String && latinOrIntValue); - return Latin1String(data(b)); -} - -inline QString Value::toString(const Base *b) const -{ - if (latinOrIntValue) - return asLatin1String(b).toString(); - else - return asString(b).toString(); -} - -inline Base *Value::base(const Base *b) const -{ - Q_ASSERT(type == QJsonValue::Array || type == QJsonValue::Object); - return reinterpret_cast(data(b)); -} - -class Data { -public: - enum Validation { - Unchecked, - Validated, - Invalid - }; - - QAtomicInt ref; - int alloc; - union { - char *rawData; - Header *header; - }; - uint compactionCounter : 31; - uint ownsData : 1; - - inline Data(char *raw, int a) - : alloc(a), rawData(raw), compactionCounter(0), ownsData(true) - { - } - inline Data(int reserved, QJsonValue::Type valueType) - : rawData(nullptr), compactionCounter(0), ownsData(true) - { - Q_ASSERT(valueType == QJsonValue::Array || valueType == QJsonValue::Object); - - alloc = sizeof(Header) + sizeof(Base) + reserved + sizeof(offset); - header = (Header *)malloc(alloc); - Q_CHECK_PTR(header); - header->tag = QJsonDocument::BinaryFormatTag; - header->version = 1; - Base *b = header->root(); - b->size = sizeof(Base); - b->is_object = (valueType == QJsonValue::Object); - b->tableOffset = sizeof(Base); - b->length = 0; - } - inline ~Data() - { if (ownsData) free(rawData); } - - uint offsetOf(const void *ptr) const { return (uint)(((char *)ptr - rawData)); } - - QJsonObject toObject(Object *o) const - { - return QJsonObject(const_cast(this), o); - } - - QJsonArray toArray(Array *a) const - { - return QJsonArray(const_cast(this), a); - } - - Data *clone(Base *b, int reserve = 0) - { - int size = sizeof(Header) + b->size; - if (b == header->root() && ref.loadRelaxed() == 1 && alloc >= size + reserve) - return this; - - if (reserve) { - if (reserve < 128) - reserve = 128; - size = qMax(size + reserve, qMin(size *2, (int)Value::MaxSize)); - if (size > Value::MaxSize) { - qWarning("QJson: Document too large to store in data structure"); - return nullptr; - } - } - char *raw = (char *)malloc(size); - Q_CHECK_PTR(raw); - memcpy(raw + sizeof(Header), b, b->size); - Header *h = (Header *)raw; - h->tag = QJsonDocument::BinaryFormatTag; - h->version = 1; - Data *d = new Data(raw, size); - d->compactionCounter = (b == header->root()) ? compactionCounter : 0; - return d; - } - - void compact(); - bool valid() const; - -private: - Q_DISABLE_COPY_MOVE(Data) -}; - -} +} // namespace QJsonPrivate QT_END_NAMESPACE diff --git a/src/corelib/serialization/qjsonarray.cpp b/src/corelib/serialization/qjsonarray.cpp index 9636ac5856..c5d4bf0315 100644 --- a/src/corelib/serialization/qjsonarray.cpp +++ b/src/corelib/serialization/qjsonarray.cpp @@ -40,12 +40,16 @@ #include #include #include +#include #include +#include #include #include +#include +#include + #include "qjsonwriter_p.h" -#include "qjson_p.h" QT_BEGIN_NAMESPACE @@ -131,10 +135,7 @@ QT_BEGIN_NAMESPACE /*! Creates an empty array. */ -QJsonArray::QJsonArray() - : d(nullptr), a(nullptr) -{ -} +QJsonArray::QJsonArray() = default; /*! \fn QJsonArray::QJsonArray(std::initializer_list args) @@ -151,12 +152,10 @@ QJsonArray::QJsonArray() /*! \internal */ -QJsonArray::QJsonArray(QJsonPrivate::Data *data, QJsonPrivate::Array *array) - : d(data), a(array) +QJsonArray::QJsonArray(QCborContainerPrivate *array) + : a(array) { - Q_ASSERT(data); Q_ASSERT(array); - d->ref.ref(); } /*! @@ -168,18 +167,13 @@ QJsonArray::QJsonArray(QJsonPrivate::Data *data, QJsonPrivate::Array *array) */ void QJsonArray::initialize() { - d = nullptr; a = nullptr; } /*! Deletes the array. */ -QJsonArray::~QJsonArray() -{ - if (d && !d->ref.deref()) - delete d; -} +QJsonArray::~QJsonArray() = default; /*! Creates a copy of \a other. @@ -187,12 +181,22 @@ QJsonArray::~QJsonArray() Since QJsonArray is implicitly shared, the copy is shallow as long as the object doesn't get modified. */ +QJsonArray::QJsonArray(std::initializer_list args) +{ + initialize(); + for (const auto & arg : args) + append(arg); +} + QJsonArray::QJsonArray(const QJsonArray &other) { - d = other.d; a = other.a; - if (d) - d->ref.ref(); +} + +QJsonArray::QJsonArray(QJsonArray &&other) noexcept + : a(other.a) +{ + other.a = nullptr; } /*! @@ -200,15 +204,7 @@ QJsonArray::QJsonArray(const QJsonArray &other) */ QJsonArray &QJsonArray::operator =(const QJsonArray &other) { - if (d != other.d) { - if (d && !d->ref.deref()) - delete d; - d = other.d; - if (d) - d->ref.ref(); - } a = other.a; - return *this; } @@ -282,48 +278,7 @@ QJsonArray QJsonArray::fromStringList(const QStringList &list) */ QJsonArray QJsonArray::fromVariantList(const QVariantList &list) { - QJsonArray array; - if (list.isEmpty()) - return array; - - array.detach2(1024); - - QVector values; - values.resize(list.size()); - QJsonPrivate::Value *valueData = values.data(); - uint currentOffset = sizeof(QJsonPrivate::Base); - - for (int i = 0; i < list.size(); ++i) { - QJsonValue val = QJsonValue::fromVariant(list.at(i)); - - bool latinOrIntValue; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &latinOrIntValue); - - if (!array.detach2(valueSize)) - return QJsonArray(); - - QJsonPrivate::Value *v = valueData + i; - v->type = (val.t == QJsonValue::Undefined ? QJsonValue::Null : val.t); - v->latinOrIntValue = latinOrIntValue; - v->latinKey = false; - v->value = QJsonPrivate::Value::valueToStore(val, currentOffset); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)array.a + currentOffset, latinOrIntValue); - - currentOffset += valueSize; - array.a->size = currentOffset; - } - - // write table - array.a->tableOffset = currentOffset; - if (!array.detach2(sizeof(QJsonPrivate::offset)*values.size())) - return QJsonArray(); - memcpy(static_cast(array.a->table()), - static_cast(values.constData()), values.size()*sizeof(uint)); - array.a->length = values.size(); - array.a->size = currentOffset + sizeof(QJsonPrivate::offset)*values.size(); - - return array; + return QCborArray::fromVariantList(list).toJsonArray(); } /*! @@ -333,14 +288,7 @@ QJsonArray QJsonArray::fromVariantList(const QVariantList &list) */ QVariantList QJsonArray::toVariantList() const { - QVariantList list; - - if (a) { - list.reserve(a->length); - for (int i = 0; i < (int)a->length; ++i) - list.append(QJsonValue(d, a, a->at(i)).toVariant()); - } - return list; + return QCborArray::fromJsonArray(*this).toVariantList(); } @@ -349,10 +297,7 @@ QVariantList QJsonArray::toVariantList() const */ int QJsonArray::size() const { - if (!d) - return 0; - - return (int)a->length; + return a ? a->elements.size() : 0; } /*! @@ -370,10 +315,7 @@ int QJsonArray::size() const */ bool QJsonArray::isEmpty() const { - if (!d) - return true; - - return !a->length; + return a == nullptr || a->elements.isEmpty(); } /*! @@ -384,10 +326,10 @@ bool QJsonArray::isEmpty() const */ QJsonValue QJsonArray::at(int i) const { - if (!a || i < 0 || i >= (int)a->length) + if (!a || i < 0 || i >= a->elements.size()) return QJsonValue(QJsonValue::Undefined); - return QJsonValue(d, a, a->at(i)); + return QJsonPrivate::Value::fromTrustedCbor(a->valueAt(i)); } /*! @@ -411,7 +353,7 @@ QJsonValue QJsonArray::first() const */ QJsonValue QJsonArray::last() const { - return at(a ? (a->length - 1) : 0); + return at(a ? (a->elements.size() - 1) : 0); } /*! @@ -433,7 +375,7 @@ void QJsonArray::prepend(const QJsonValue &value) */ void QJsonArray::append(const QJsonValue &value) { - insert(a ? (int)a->length : 0, value); + insert(a ? a->elements.size() : 0, value); } /*! @@ -444,14 +386,10 @@ void QJsonArray::append(const QJsonValue &value) */ void QJsonArray::removeAt(int i) { - if (!a || i < 0 || i >= (int)a->length) + if (!a || i < 0 || i >= a->elements.length()) return; - detach2(); - a->removeItems(i, 1); - ++d->compactionCounter; - if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u) - compact(); + a->removeAt(i); } /*! \fn void QJsonArray::removeFirst() @@ -484,11 +422,12 @@ void QJsonArray::removeAt(int i) */ QJsonValue QJsonArray::takeAt(int i) { - if (!a || i < 0 || i >= (int)a->length) + if (!a || i < 0 || i >= a->elements.length()) return QJsonValue(QJsonValue::Undefined); - QJsonValue v(d, a, a->at(i)); - removeAt(i); // detaches + detach2(); + const QJsonValue v = QJsonPrivate::Value::fromTrustedCbor(a->extractAt(i)); + a->removeAt(i); return v; } @@ -501,29 +440,14 @@ QJsonValue QJsonArray::takeAt(int i) */ void QJsonArray::insert(int i, const QJsonValue &value) { - Q_ASSERT (i >= 0 && i <= (a ? (int)a->length : 0)); - QJsonValue val = value; + if (a) + detach2(a->elements.length() + 1); + else + a = new QCborContainerPrivate; - bool compressed; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &compressed); - - if (!detach2(valueSize + sizeof(QJsonPrivate::Value))) - return; - - if (!a->length) - a->tableOffset = sizeof(QJsonPrivate::Array); - - int valueOffset = a->reserveSpace(valueSize, i, 1, false); - if (!valueOffset) - return; - - QJsonPrivate::Value &v = (*a)[i]; - v.type = (val.t == QJsonValue::Undefined ? QJsonValue::Null : val.t); - v.latinOrIntValue = compressed; - v.latinKey = false; - v.value = QJsonPrivate::Value::valueToStore(val, valueOffset); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)a + valueOffset, compressed); + Q_ASSERT (i >= 0 && i <= a->elements.length()); + a->insertAt(i, value.type() == QJsonValue::Undefined ? QCborValue(nullptr) + : QCborValue::fromJsonValue(value)); } /*! @@ -552,33 +476,9 @@ void QJsonArray::insert(int i, const QJsonValue &value) */ void QJsonArray::replace(int i, const QJsonValue &value) { - Q_ASSERT (a && i >= 0 && i < (int)(a->length)); - QJsonValue val = value; - - bool compressed; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &compressed); - - if (!detach2(valueSize)) - return; - - if (!a->length) - a->tableOffset = sizeof(QJsonPrivate::Array); - - int valueOffset = a->reserveSpace(valueSize, i, 1, true); - if (!valueOffset) - return; - - QJsonPrivate::Value &v = (*a)[i]; - v.type = (val.t == QJsonValue::Undefined ? QJsonValue::Null : val.t); - v.latinOrIntValue = compressed; - v.latinKey = false; - v.value = QJsonPrivate::Value::valueToStore(val, valueOffset); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)a + valueOffset, compressed); - - ++d->compactionCounter; - if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u) - compact(); + Q_ASSERT (a && i >= 0 && i < a->elements.length()); + detach2(); + a->replaceAt(i, QCborValue::fromJsonValue(value)); } /*! @@ -610,7 +510,7 @@ bool QJsonArray::contains(const QJsonValue &value) const */ QJsonValueRef QJsonArray::operator [](int i) { - Q_ASSERT(a && i >= 0 && i < (int)a->length); + Q_ASSERT(a && i >= 0 && i < a->elements.length()); return QJsonValueRef(this, i); } @@ -633,14 +533,14 @@ bool QJsonArray::operator==(const QJsonArray &other) const return true; if (!a) - return !other.a->length; + return !other.a->elements.length(); if (!other.a) - return !a->length; - if (a->length != other.a->length) + return !a->elements.length(); + if (a->elements.length() != other.a->elements.length()) return false; - for (int i = 0; i < (int)a->length; ++i) { - if (QJsonValue(d, a, a->at(i)) != QJsonValue(other.d, other.a, other.a->at(i))) + for (int i = 0; i < a->elements.length(); ++i) { + if (a->valueAt(i) != other.a->valueAt(i)) return false; } return true; @@ -1216,28 +1116,10 @@ void QJsonArray::detach(uint reserve) */ bool QJsonArray::detach2(uint reserve) { - if (!d) { - if (reserve >= QJsonPrivate::Value::MaxSize) { - qWarning("QJson: Document too large to store in data structure"); - return false; - } - d = new QJsonPrivate::Data(reserve, QJsonValue::Array); - a = static_cast(d->header->root()); - d->ref.ref(); + if (!a) return true; - } - if (reserve == 0 && d->ref.loadRelaxed() == 1) - return true; - - QJsonPrivate::Data *x = d->clone(a, reserve); - if (!x) - return false; - x->ref.ref(); - if (!d->ref.deref()) - delete d; - d = x; - a = static_cast(d->header->root()); - return true; + a = a->detach(a.data(), reserve ? reserve : size()); + return a; } /*! @@ -1245,12 +1127,7 @@ bool QJsonArray::detach2(uint reserve) */ void QJsonArray::compact() { - if (!d || !d->compactionCounter) - return; - - detach2(); - d->compact(); - a = static_cast(d->header->root()); + a->compact(a->elements.size()); } uint qHash(const QJsonArray &array, uint seed) @@ -1267,7 +1144,7 @@ QDebug operator<<(QDebug dbg, const QJsonArray &a) return dbg; } QByteArray json; - QJsonPrivate::Writer::arrayToJson(a.a, json, 0, true); + QJsonPrivate::Writer::arrayToJson(a.a.data(), json, 0, true); dbg.nospace() << "QJsonArray(" << json.constData() // print as utf-8 string without extra quotation marks << ")"; diff --git a/src/corelib/serialization/qjsonarray.h b/src/corelib/serialization/qjsonarray.h index 287671419f..9b7e10766f 100644 --- a/src/corelib/serialization/qjsonarray.h +++ b/src/corelib/serialization/qjsonarray.h @@ -42,6 +42,7 @@ #include #include +#include #include QT_BEGIN_NAMESPACE @@ -55,25 +56,14 @@ class Q_CORE_EXPORT QJsonArray public: QJsonArray(); - QJsonArray(std::initializer_list args) - { - initialize(); - for (std::initializer_list::const_iterator i = args.begin(); i != args.end(); ++i) - append(*i); - } + QJsonArray(std::initializer_list args); ~QJsonArray(); QJsonArray(const QJsonArray &other); QJsonArray &operator =(const QJsonArray &other); - QJsonArray(QJsonArray &&other) noexcept - : d(other.d), - a(other.a) - { - other.d = nullptr; - other.a = nullptr; - } + QJsonArray(QJsonArray &&other) noexcept; QJsonArray &operator =(QJsonArray &&other) noexcept { @@ -112,7 +102,6 @@ public: void swap(QJsonArray &other) noexcept { - qSwap(d, other.d); qSwap(a, other.a); } @@ -244,20 +233,21 @@ public: typedef int difference_type; private: - friend class QJsonPrivate::Data; friend class QJsonValue; friend class QJsonDocument; + friend class QCborArray; friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonArray &); - QJsonArray(QJsonPrivate::Data *data, QJsonPrivate::Array *array); + QJsonArray(QCborContainerPrivate *array); void initialize(); void compact(); // ### Qt 6: remove me and merge with detach2 void detach(uint reserve = 0); bool detach2(uint reserve = 0); - QJsonPrivate::Data *d; - QJsonPrivate::Array *a; + // ### Qt 6: remove + void *dead = nullptr; + QExplicitlySharedDataPointer a; }; Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QJsonArray) diff --git a/src/corelib/serialization/qjsoncbor.cpp b/src/corelib/serialization/qjsoncbor.cpp index d6a0c8c48a..7136a163ee 100644 --- a/src/corelib/serialization/qjsoncbor.cpp +++ b/src/corelib/serialization/qjsoncbor.cpp @@ -42,6 +42,10 @@ #include "qcborarray.h" #include "qcbormap.h" + +#include "qjsonarray.h" +#include "qjsonobject.h" +#include "qjsondocument.h" #include "qjson_p.h" #include @@ -149,7 +153,12 @@ static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizety case QCborValue::Array: case QCborValue::Map: +#if defined(QT_JSON_READONLY) || defined(QT_BOOTSTRAPPED) + qFatal("Writing JSON is disabled."); + return QString(); +#else return d->valueAt(idx).toDiagnosticNotation(QCborValue::Compact); +#endif case QCborValue::SimpleType: break; @@ -181,30 +190,11 @@ static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizety return simpleTypeString(e.type); } -static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx); +QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx); -static QJsonArray convertToJsonArray(const QCborContainerPrivate *d) -{ - QJsonArray a; - if (d) { - for (qsizetype idx = 0; idx < d->elements.size(); ++idx) - a.append(convertToJson(d, idx)); - } - return a; -} - -static QJsonObject convertToJsonObject(const QCborContainerPrivate *d) -{ - QJsonObject o; - if (d) { - for (qsizetype idx = 0; idx < d->elements.size(); idx += 2) - o.insert(makeString(d, idx), convertToJson(d, idx + 1)); - } - return o; -} - -static QJsonValue convertExtendedTypeToJson(const QCborContainerPrivate *d) +static QJsonValue convertExtendedTypeToJson(QCborContainerPrivate *d) { +#ifndef QT_BUILD_QMAKE qint64 tag = d->elements.at(0).value; switch (tag) { @@ -225,12 +215,36 @@ static QJsonValue convertExtendedTypeToJson(const QCborContainerPrivate *d) return s; } } +#endif // for all other tags, ignore it and return the converted tagged item - return convertToJson(d, 1); + return qt_convertToJson(d, 1); } -static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) +// We need to do this because sub-objects may need conversion. +static QJsonArray convertToJsonArray(QCborContainerPrivate *d) +{ + QJsonArray a; + if (d) { + for (qsizetype idx = 0; idx < d->elements.size(); ++idx) + a.append(qt_convertToJson(d, idx)); + } + return a; +} + +// We need to do this because the keys need to be sorted and converted to strings +// and sub-objects may need recursive conversion. +static QJsonObject convertToJsonObject(QCborContainerPrivate *d) +{ + QJsonObject o; + if (d) { + for (qsizetype idx = 0; idx < d->elements.size(); idx += 2) + o.insert(makeString(d, idx), qt_convertToJson(d, idx + 1)); + } + return o; +} + +QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx) { // encoding the container itself if (idx == -QCborValue::Array) @@ -248,7 +262,7 @@ static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) const auto &e = d->elements.at(idx); switch (e.type) { case QCborValue::Integer: - return qint64(e.value); + return QJsonPrivate::Value::fromTrustedCbor(e.value); case QCborValue::ByteArray: case QCborValue::String: @@ -264,14 +278,14 @@ static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) case QCborValue::RegularExpression: case QCborValue::Uuid: // recurse - return convertToJson(e.flags & Element::IsContainer ? e.container : nullptr, -e.type); + return qt_convertToJson(e.flags & Element::IsContainer ? e.container : nullptr, -e.type); case QCborValue::Null: return QJsonValue(); case QCborValue::Undefined: case QCborValue::Invalid: - return QJsonValue(QJsonValue::Undefined); + return QJsonValue::Undefined; case QCborValue::False: return false; @@ -283,7 +297,7 @@ static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) return fpToJson(e.fpvalue()); } - return makeString(d, idx); + return QJsonPrivate::Value::fromTrustedCbor(makeString(d, idx)); } /*! @@ -348,22 +362,22 @@ static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) QJsonValue QCborValue::toJsonValue() const { if (container) - return convertToJson(container, n < 0 ? -type() : n); + return qt_convertToJson(container, n < 0 ? -type() : n); // simple values switch (type()) { - case Integer: - return n; - - case Null: - return QJsonValue(); - case False: return false; + case Integer: + return QJsonPrivate::Value::fromTrustedCbor(n); + case True: return true; + case Null: + return QJsonValue(); + case Double: return fpToJson(fp_helper()); @@ -372,12 +386,12 @@ QJsonValue QCborValue::toJsonValue() const case Undefined: case Invalid: - return QJsonValue(QJsonValue::Undefined); + return QJsonValue::Undefined; case ByteArray: case String: // empty strings - return QString(); + return QJsonValue::String; case Array: // empty array @@ -392,16 +406,16 @@ QJsonValue QCborValue::toJsonValue() const case Url: case RegularExpression: case Uuid: - Q_UNREACHABLE(); + // Reachable, but invalid in Json return QJsonValue::Undefined; } - return simpleTypeString(type()); + return QJsonPrivate::Value::fromTrustedCbor(simpleTypeString(type())); } QJsonValue QCborValueRef::toJsonValue() const { - return convertToJson(d, i); + return qt_convertToJson(d, i); } /*! @@ -540,8 +554,10 @@ QVariant QCborValue::toVariant() const case DateTime: return toDateTime(); +#ifndef QT_BOOTSTRAPPED case Url: return toUrl(); +#endif #if QT_CONFIG(regularexpression) case RegularExpression: @@ -597,12 +613,13 @@ QCborValue QCborValue::fromJsonValue(const QJsonValue &v) { switch (v.type()) { case QJsonValue::Bool: - return v.b; + return v.toBool(); case QJsonValue::Double: { qint64 i; - if (convertDoubleTo(v.dbl, &i)) + const double dbl = v.toDouble(); + if (convertDoubleTo(dbl, &i)) return i; - return v.dbl; + return dbl; } case QJsonValue::String: return v.toString(); @@ -710,8 +727,10 @@ QCborValue QCborValue::fromVariant(const QVariant &variant) return variant.toByteArray(); case QVariant::DateTime: return QCborValue(variant.toDateTime()); +#ifndef QT_BOOTSTRAPPED case QVariant::Url: return QCborValue(variant.toUrl()); +#endif case QVariant::Uuid: return QCborValue(variant.toUuid()); case QVariant::List: @@ -824,15 +843,9 @@ QCborArray QCborArray::fromVariantList(const QVariantList &list) */ QCborArray QCborArray::fromJsonArray(const QJsonArray &array) { - QCborArray a; - a.detach(array.size()); - for (const QJsonValue &v : array) { - if (v.isString()) - a.d->append(v.toString()); - else - a.d->append(QCborValue::fromJsonValue(v)); - } - return a; + QCborArray result; + result.d = array.a; + return result; } /*! @@ -944,20 +957,9 @@ QCborMap QCborMap::fromVariantHash(const QVariantHash &hash) */ QCborMap QCborMap::fromJsonObject(const QJsonObject &obj) { - QCborMap m; - m.detach(obj.size()); - QCborContainerPrivate *d = m.d.data(); - - auto it = obj.begin(); - auto end = obj.end(); - for ( ; it != end; ++it) { - d->append(it.key()); - if (it.value().isString()) - d->append(it.value().toString()); - else - d->append(QCborValue::fromJsonValue(it.value())); - } - return m; + QCborMap result; + result.d = obj.o; + return result; } QT_END_NAMESPACE diff --git a/src/corelib/serialization/qjsondocument.cpp b/src/corelib/serialization/qjsondocument.cpp index 8c3818caff..b9b1902f34 100644 --- a/src/corelib/serialization/qjsondocument.cpp +++ b/src/corelib/serialization/qjsondocument.cpp @@ -44,11 +44,22 @@ #include #include #include +#include +#include +#include "qcborvalue_p.h" #include "qjsonwriter_p.h" #include "qjsonparser_p.h" #include "qjson_p.h" #include "qdatastream.h" +#if QT_CONFIG(binaryjson) +#include "qbinaryjson_p.h" +#include "qbinaryjsonobject_p.h" +#include "qbinaryjsonarray_p.h" +#endif + +#include + QT_BEGIN_NAMESPACE /*! \class QJsonDocument @@ -80,6 +91,33 @@ QT_BEGIN_NAMESPACE \sa {JSON Support in Qt}, {JSON Save Game Example} */ + +class QJsonDocumentPrivate +{ + Q_DISABLE_COPY_MOVE(QJsonDocumentPrivate); +public: + QJsonDocumentPrivate() = default; + QJsonDocumentPrivate(QCborValue data) : value(std::move(data)) {} + ~QJsonDocumentPrivate() + { + if (rawData) + free(rawData); + } + + QCborValue value; + char *rawData = nullptr; + uint rawDataSize = 0; + + void clearRawData() + { + if (rawData) { + free(rawData); + rawData = nullptr; + rawDataSize = 0; + } + } +}; + /*! * Constructs an empty and invalid document. */ @@ -109,11 +147,10 @@ QJsonDocument::QJsonDocument(const QJsonArray &array) /*! \internal */ -QJsonDocument::QJsonDocument(QJsonPrivate::Data *data) - : d(data) +QJsonDocument::QJsonDocument(const QCborValue &data) + : d(qt_make_unique(data)) { Q_ASSERT(d); - d->ref.ref(); } /*! @@ -121,20 +158,30 @@ QJsonDocument::QJsonDocument(QJsonPrivate::Data *data) Binary data set with fromRawData is not freed. */ -QJsonDocument::~QJsonDocument() -{ - if (d && !d->ref.deref()) - delete d; -} +QJsonDocument::~QJsonDocument() = default; /*! * Creates a copy of the \a other document. */ QJsonDocument::QJsonDocument(const QJsonDocument &other) { - d = other.d; - if (d) - d->ref.ref(); + if (other.d) { + if (!d) + d = qt_make_unique(); + d->value = other.d->value; + } else { + d.reset(); + } +} + +QJsonDocument::QJsonDocument(QJsonDocument &&other) noexcept + : d(std::move(other.d)) +{ +} + +void QJsonDocument::swap(QJsonDocument &other) noexcept +{ + qSwap(d, other.d); } /*! @@ -143,14 +190,17 @@ QJsonDocument::QJsonDocument(const QJsonDocument &other) */ QJsonDocument &QJsonDocument::operator =(const QJsonDocument &other) { - if (d != other.d) { - if (d && !d->ref.deref()) - delete d; - d = other.d; - if (d) - d->ref.ref(); + if (this != &other) { + if (other.d) { + if (!d) + d = qt_make_unique(); + else + d->clearRawData(); + d->value = other.d->value; + } else { + d.reset(); + } } - return *this; } @@ -187,12 +237,13 @@ QJsonDocument &QJsonDocument::operator =(const QJsonDocument &other) the application. */ +#if QT_CONFIG(binaryjson) /*! Creates a QJsonDocument that uses the first \a size bytes from \a data. It assumes \a data contains a binary encoded JSON document. - The created document does not take ownership of \a data and the caller - has to guarantee that \a data will not be deleted or modified as long as - any QJsonDocument, QJsonObject or QJsonArray still references the data. + The created document does not take ownership of \a data. The data is + copied into a different data structure, and the original data can be + deleted or modified afterwards. \a data has to be aligned to a 4 byte boundary. @@ -202,7 +253,18 @@ QJsonDocument &QJsonDocument::operator =(const QJsonDocument &other) Returns a QJsonDocument representing the data. - \sa rawData(), fromBinaryData(), isNull(), DataValidation + \deprecated in Qt 5.15. The binary JSON encoding is only retained for backwards + compatibility. It is undocumented and restrictive in the maximum size of JSON + documents that can be encoded. Qt JSON types can be converted to Qt CBOR types, + which can in turn be serialized into the CBOR binary format and vice versa. The + CBOR format is a well-defined and less restrictive binary representation for a + superset of JSON. + + \note Before Qt 5.15, the caller had to guarantee that \a data would not be + deleted or modified as long as any QJsonDocument, QJsonObject or QJsonArray + still referenced the data. From Qt 5.15 on, this is not necessary anymore. + + \sa rawData(), fromBinaryData(), isNull(), DataValidation, QCborValue */ QJsonDocument QJsonDocument::fromRawData(const char *data, int size, DataValidation validation) { @@ -211,18 +273,15 @@ QJsonDocument QJsonDocument::fromRawData(const char *data, int size, DataValidat return QJsonDocument(); } - if (size < (int)(sizeof(QJsonPrivate::Header) + sizeof(QJsonPrivate::Base))) + if (size < 0 || uint(size) < sizeof(QBinaryJsonPrivate::Header) + sizeof(QBinaryJsonPrivate::Base)) return QJsonDocument(); - QJsonPrivate::Data *d = new QJsonPrivate::Data((char *)data, size); - d->ownsData = false; + std::unique_ptr binaryData + = qt_make_unique(data, size); - if (validation != BypassValidation && !d->valid()) { - delete d; - return QJsonDocument(); - } - - return QJsonDocument(d); + return (validation == BypassValidation || binaryData->isValid()) + ? binaryData->toJsonDocument() + : QJsonDocument(); } /*! @@ -230,7 +289,16 @@ QJsonDocument QJsonDocument::fromRawData(const char *data, int size, DataValidat \a size will contain the size of the returned data. This method is useful to e.g. stream the JSON document - in it's binary form to a file. + in its binary form to a file. + + \deprecated in Qt 5.15. The binary JSON encoding is only retained for backwards + compatibility. It is undocumented and restrictive in the maximum size of JSON + documents that can be encoded. Qt JSON types can be converted to Qt CBOR types, + which can in turn be serialized into the CBOR binary format and vice versa. The + CBOR format is a well-defined and less restrictive binary representation for a + superset of JSON. + + \sa QCborValue */ const char *QJsonDocument::rawData(int *size) const { @@ -238,7 +306,21 @@ const char *QJsonDocument::rawData(int *size) const *size = 0; return nullptr; } - *size = d->alloc; + + if (!d->rawData) { + if (isObject()) { + QBinaryJsonObject o = QBinaryJsonObject::fromJsonObject(object()); + d->rawData = o.takeRawData(&(d->rawDataSize)); + } else { + QBinaryJsonArray a = QBinaryJsonArray::fromJsonArray(array()); + d->rawData = a.takeRawData(&(d->rawDataSize)); + } + } + + // It would be quite miraculous if not, as we should have hit the 128MB limit then. + Q_ASSERT(d->rawDataSize <= uint(std::numeric_limits::max())); + + *size = d->rawDataSize; return d->rawData; } @@ -249,39 +331,65 @@ const char *QJsonDocument::rawData(int *size) const By default the data is validated. If the \a data is not valid, the method returns a null document. - \sa toBinaryData(), fromRawData(), isNull(), DataValidation + \deprecated in Qt 5.15. The binary JSON encoding is only retained for backwards + compatibility. It is undocumented and restrictive in the maximum size of JSON + documents that can be encoded. Qt JSON types can be converted to Qt CBOR types, + which can in turn be serialized into the CBOR binary format and vice versa. The + CBOR format is a well-defined and less restrictive binary representation for a + superset of JSON. + + \sa toBinaryData(), fromRawData(), isNull(), DataValidation, QCborValue */ QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data, DataValidation validation) { - if (data.size() < (int)(sizeof(QJsonPrivate::Header) + sizeof(QJsonPrivate::Base))) + if (uint(data.size()) < sizeof(QBinaryJsonPrivate::Header) + sizeof(QBinaryJsonPrivate::Base)) return QJsonDocument(); - QJsonPrivate::Header h; - memcpy(&h, data.constData(), sizeof(QJsonPrivate::Header)); - QJsonPrivate::Base root; - memcpy(&root, data.constData() + sizeof(QJsonPrivate::Header), sizeof(QJsonPrivate::Base)); + QBinaryJsonPrivate::Header h; + memcpy(&h, data.constData(), sizeof(QBinaryJsonPrivate::Header)); + QBinaryJsonPrivate::Base root; + memcpy(&root, data.constData() + sizeof(QBinaryJsonPrivate::Header), + sizeof(QBinaryJsonPrivate::Base)); - // do basic checks here, so we don't try to allocate more memory than we can. - if (h.tag != QJsonDocument::BinaryFormatTag || h.version != 1u || - sizeof(QJsonPrivate::Header) + root.size > (uint)data.size()) + const uint size = sizeof(QBinaryJsonPrivate::Header) + root.size; + if (h.tag != QJsonDocument::BinaryFormatTag || h.version != 1U || size > uint(data.size())) return QJsonDocument(); - const uint size = sizeof(QJsonPrivate::Header) + root.size; - char *raw = (char *)malloc(size); - if (!raw) - return QJsonDocument(); + std::unique_ptr d + = qt_make_unique(data.constData(), size); - memcpy(raw, data.constData(), size); - QJsonPrivate::Data *d = new QJsonPrivate::Data(raw, size); - - if (validation != BypassValidation && !d->valid()) { - delete d; - return QJsonDocument(); - } - - return QJsonDocument(d); + return (validation == BypassValidation || d->isValid()) + ? d->toJsonDocument() + : QJsonDocument(); } +/*! + Returns a binary representation of the document. + + The binary representation is also the native format used internally in Qt, + and is very efficient and fast to convert to and from. + + The binary format can be stored on disk and interchanged with other applications + or computers. fromBinaryData() can be used to convert it back into a + JSON document. + + \deprecated in Qt 5.15. The binary JSON encoding is only retained for backwards + compatibility. It is undocumented and restrictive in the maximum size of JSON + documents that can be encoded. Qt JSON types can be converted to Qt CBOR types, + which can in turn be serialized into the CBOR binary format and vice versa. The + CBOR format is a well-defined and less restrictive binary representation for a + superset of JSON. + + \sa fromBinaryData(), QCborValue + */ +QByteArray QJsonDocument::toBinaryData() const +{ + int size = 0; + const char *raw = rawData(&size); + return QByteArray(raw, size); +} +#endif // QT_CONFIG(binaryjson) + /*! Creates a QJsonDocument from the QVariant \a variant. @@ -293,6 +401,7 @@ QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data, DataValidati QJsonDocument QJsonDocument::fromVariant(const QVariant &variant) { QJsonDocument doc; + switch (variant.type()) { case QVariant::Map: doc.setObject(QJsonObject::fromVariantMap(variant.toMap())); @@ -304,7 +413,8 @@ QJsonDocument QJsonDocument::fromVariant(const QVariant &variant) doc.setArray(QJsonArray::fromVariantList(variant.toList())); break; case QVariant::StringList: - doc.setArray(QJsonArray::fromStringList(variant.toStringList())); + doc.d = qt_make_unique(); + doc.d->value = QCborArray::fromStringList(variant.toStringList()); break; default: break; @@ -325,10 +435,10 @@ QVariant QJsonDocument::toVariant() const if (!d) return QVariant(); - if (d->header->root()->isArray()) - return QJsonArray(d, static_cast(d->header->root())).toVariantList(); - else - return QJsonObject(d, static_cast(d->header->root())).toVariantMap(); + QCborContainerPrivate *container = QJsonPrivate::Value::container(d->value); + if (d->value.isArray()) + return QJsonArray(container).toVariantList(); + return QJsonObject(container).toVariantMap(); } /*! @@ -370,10 +480,11 @@ QByteArray QJsonDocument::toJson(JsonFormat format) const if (!d) return json; - if (d->header->root()->isArray()) - QJsonPrivate::Writer::arrayToJson(static_cast(d->header->root()), json, 0, (format == Compact)); + const QCborContainerPrivate *container = QJsonPrivate::Value::container(d->value); + if (d->value.isArray()) + QJsonPrivate::Writer::arrayToJson(container, json, 0, (format == Compact)); else - QJsonPrivate::Writer::objectToJson(static_cast(d->header->root()), json, 0, (format == Compact)); + QJsonPrivate::Writer::objectToJson(container, json, 0, (format == Compact)); return json; } @@ -392,7 +503,13 @@ QByteArray QJsonDocument::toJson(JsonFormat format) const QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *error) { QJsonPrivate::Parser parser(json.constData(), json.length()); - return parser.parse(error); + QJsonDocument result; + const QCborValue val = parser.parse(error); + if (val.isArray() || val.isMap()) { + result.d = qt_make_unique(); + result.d->value = val; + } + return result; } /*! @@ -406,26 +523,6 @@ bool QJsonDocument::isEmpty() const return false; } -/*! - Returns a binary representation of the document. - - The binary representation is also the native format used internally in Qt, - and is very efficient and fast to convert to and from. - - The binary format can be stored on disk and interchanged with other applications - or computers. fromBinaryData() can be used to convert it back into a - JSON document. - - \sa fromBinaryData() - */ -QByteArray QJsonDocument::toBinaryData() const -{ - if (!d || !d->rawData) - return QByteArray(); - - return QByteArray(d->rawData, d->header->root()->size + sizeof(QJsonPrivate::Header)); -} - /*! Returns \c true if the document contains an array. @@ -436,8 +533,7 @@ bool QJsonDocument::isArray() const if (!d) return false; - QJsonPrivate::Header *h = (QJsonPrivate::Header *)d->rawData; - return h->root()->isArray(); + return d->value.isArray(); } /*! @@ -450,8 +546,7 @@ bool QJsonDocument::isObject() const if (!d) return false; - QJsonPrivate::Header *h = (QJsonPrivate::Header *)d->rawData; - return h->root()->isObject(); + return d->value.isMap(); } /*! @@ -464,10 +559,9 @@ bool QJsonDocument::isObject() const */ QJsonObject QJsonDocument::object() const { - if (d) { - QJsonPrivate::Base *b = d->header->root(); - if (b->isObject()) - return QJsonObject(d, static_cast(b)); + if (isObject()) { + if (auto container = QJsonPrivate::Value::container(d->value)) + return QJsonObject(container); } return QJsonObject(); } @@ -482,10 +576,9 @@ QJsonObject QJsonDocument::object() const */ QJsonArray QJsonDocument::array() const { - if (d) { - QJsonPrivate::Base *b = d->header->root(); - if (b->isArray()) - return QJsonArray(d, static_cast(b)); + if (isArray()) { + if (auto container = QJsonPrivate::Value::container(d->value)) + return QJsonArray(container); } return QJsonArray(); } @@ -497,24 +590,12 @@ QJsonArray QJsonDocument::array() const */ void QJsonDocument::setObject(const QJsonObject &object) { - if (d && !d->ref.deref()) - delete d; + if (!d) + d = qt_make_unique(); + else + d->clearRawData(); - d = object.d; - - if (!d) { - d = new QJsonPrivate::Data(0, QJsonValue::Object); - } else if (d->compactionCounter || object.o != d->header->root()) { - QJsonObject o(object); - if (d->compactionCounter) - o.compact(); - else - o.detach2(); - d = o.d; - d->ref.ref(); - return; - } - d->ref.ref(); + d->value = QCborValue::fromJsonValue(object); } /*! @@ -524,24 +605,12 @@ void QJsonDocument::setObject(const QJsonObject &object) */ void QJsonDocument::setArray(const QJsonArray &array) { - if (d && !d->ref.deref()) - delete d; + if (!d) + d = qt_make_unique(); + else + d->clearRawData(); - d = array.d; - - if (!d) { - d = new QJsonPrivate::Data(0, QJsonValue::Array); - } else if (d->compactionCounter || array.a != d->header->root()) { - QJsonArray a(array); - if (d->compactionCounter) - a.compact(); - else - a.detach2(); - d = a.d; - d->ref.ref(); - return; - } - d->ref.ref(); + d->value = QCborValue::fromJsonValue(array); } #if QT_STRINGVIEW_LEVEL < 2 @@ -572,7 +641,7 @@ const QJsonValue QJsonDocument::operator[](QStringView key) const if (!isObject()) return QJsonValue(QJsonValue::Undefined); - return object().value(key); + return QJsonPrivate::Value::fromTrustedCbor(d->value.toMap().value(key)); } /*! @@ -584,7 +653,7 @@ const QJsonValue QJsonDocument::operator[](QLatin1String key) const if (!isObject()) return QJsonValue(QJsonValue::Undefined); - return object().value(key); + return QJsonPrivate::Value::fromTrustedCbor(d->value.toMap().value(key)); } /*! @@ -604,7 +673,7 @@ const QJsonValue QJsonDocument::operator[](int i) const if (!isArray()) return QJsonValue(QJsonValue::Undefined); - return array().at(i); + return QJsonPrivate::Value::fromTrustedCbor(d->value.toArray().at(i)); } /*! @@ -612,21 +681,7 @@ const QJsonValue QJsonDocument::operator[](int i) const */ bool QJsonDocument::operator==(const QJsonDocument &other) const { - if (d == other.d) - return true; - - if (!d || !other.d) - return false; - - if (d->header->root()->isArray() != other.d->header->root()->isArray()) - return false; - - if (d->header->root()->isObject()) - return QJsonObject(d, static_cast(d->header->root())) - == QJsonObject(other.d, static_cast(other.d->header->root())); - else - return QJsonArray(d, static_cast(d->header->root())) - == QJsonArray(other.d, static_cast(other.d->header->root())); + return (!d) ? (!other.d) : (d->value == other.d->value); } /*! @@ -658,10 +713,11 @@ QDebug operator<<(QDebug dbg, const QJsonDocument &o) return dbg; } QByteArray json; - if (o.d->header->root()->isArray()) - QJsonPrivate::Writer::arrayToJson(static_cast(o.d->header->root()), json, 0, true); + const QCborContainerPrivate *container = QJsonPrivate::Value::container(o.d->value); + if (o.d->value.isArray()) + QJsonPrivate::Writer::arrayToJson(container, json, 0, true); else - QJsonPrivate::Writer::objectToJson(static_cast(o.d->header->root()), json, 0, true); + QJsonPrivate::Writer::objectToJson(container, json, 0, true); dbg.nospace() << "QJsonDocument(" << json.constData() // print as utf-8 string without extra quotation marks << ')'; diff --git a/src/corelib/serialization/qjsondocument.h b/src/corelib/serialization/qjsondocument.h index a8e7485f5d..325e47b531 100644 --- a/src/corelib/serialization/qjsondocument.h +++ b/src/corelib/serialization/qjsondocument.h @@ -41,14 +41,16 @@ #define QJSONDOCUMENT_H #include +#include + +#include QT_BEGIN_NAMESPACE class QDebug; +class QCborValue; -namespace QJsonPrivate { - class Parser; -} +namespace QJsonPrivate { class Parser; } struct Q_CORE_EXPORT QJsonParseError { @@ -76,6 +78,7 @@ struct Q_CORE_EXPORT QJsonParseError ParseError error; }; +class QJsonDocumentPrivate; class Q_CORE_EXPORT QJsonDocument { public: @@ -93,11 +96,7 @@ public: QJsonDocument(const QJsonDocument &other); QJsonDocument &operator =(const QJsonDocument &other); - QJsonDocument(QJsonDocument &&other) noexcept - : d(other.d) - { - other.d = nullptr; - } + QJsonDocument(QJsonDocument &&other) noexcept; QJsonDocument &operator =(QJsonDocument &&other) noexcept { @@ -105,21 +104,20 @@ public: return *this; } - void swap(QJsonDocument &other) noexcept - { - qSwap(d, other.d); - } + void swap(QJsonDocument &other) noexcept; enum DataValidation { Validate, BypassValidation }; +#if QT_CONFIG(binaryjson) static QJsonDocument fromRawData(const char *data, int size, DataValidation validation = Validate); const char *rawData(int *size) const; static QJsonDocument fromBinaryData(const QByteArray &data, DataValidation validation = Validate); QByteArray toBinaryData() const; +#endif // QT_CONFIG(binaryjson) static QJsonDocument fromVariant(const QVariant &variant); QVariant toVariant() const; @@ -160,13 +158,12 @@ public: private: friend class QJsonValue; - friend class QJsonPrivate::Data; friend class QJsonPrivate::Parser; friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonDocument &); - QJsonDocument(QJsonPrivate::Data *data); + QJsonDocument(const QCborValue &data); - QJsonPrivate::Data *d; + std::unique_ptr d; }; Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QJsonDocument) diff --git a/src/corelib/serialization/qjsonobject.cpp b/src/corelib/serialization/qjsonobject.cpp index 8b095db915..ae37481f31 100644 --- a/src/corelib/serialization/qjsonobject.cpp +++ b/src/corelib/serialization/qjsonobject.cpp @@ -40,11 +40,17 @@ #include #include #include +#include #include #include #include -#include "qjson_p.h" +#include + +#include #include "qjsonwriter_p.h" +#include "qjson_p.h" + +#include QT_BEGIN_NAMESPACE @@ -109,10 +115,7 @@ QT_BEGIN_NAMESPACE \sa isEmpty() */ -QJsonObject::QJsonObject() - : d(nullptr), o(nullptr) -{ -} +QJsonObject::QJsonObject() = default; /*! \fn QJsonObject::QJsonObject(std::initializer_list > args) @@ -131,12 +134,10 @@ QJsonObject::QJsonObject() /*! \internal */ -QJsonObject::QJsonObject(QJsonPrivate::Data *data, QJsonPrivate::Object *object) - : d(data), o(object) +QJsonObject::QJsonObject(QCborContainerPrivate *object) + : o(object) { - Q_ASSERT(d); Q_ASSERT(o); - d->ref.ref(); } /*! @@ -149,17 +150,19 @@ QJsonObject::QJsonObject(QJsonPrivate::Data *data, QJsonPrivate::Object *object) void QJsonObject::initialize() { - d = nullptr; o = nullptr; } /*! Destroys the object. */ -QJsonObject::~QJsonObject() +QJsonObject::~QJsonObject() = default; + +QJsonObject::QJsonObject(std::initializer_list > args) { - if (d && !d->ref.deref()) - delete d; + initialize(); + for (const auto &arg : args) + insert(arg.first, arg.second); } /*! @@ -170,10 +173,13 @@ QJsonObject::~QJsonObject() */ QJsonObject::QJsonObject(const QJsonObject &other) { - d = other.d; o = other.o; - if (d) - d->ref.ref(); +} + +QJsonObject::QJsonObject(QJsonObject &&other) noexcept + : o(other.o) +{ + other.o = nullptr; } /*! @@ -181,15 +187,7 @@ QJsonObject::QJsonObject(const QJsonObject &other) */ QJsonObject &QJsonObject::operator =(const QJsonObject &other) { - if (d != other.d) { - if (d && !d->ref.deref()) - delete d; - d = other.d; - if (d) - d->ref.ref(); - } o = other.o; - return *this; } @@ -225,55 +223,7 @@ QJsonObject &QJsonObject::operator =(const QJsonObject &other) */ QJsonObject QJsonObject::fromVariantMap(const QVariantMap &map) { - QJsonObject object; - if (map.isEmpty()) - return object; - - object.detach2(1024); - - QVector offsets; - QJsonPrivate::offset currentOffset; - currentOffset = sizeof(QJsonPrivate::Base); - - // the map is already sorted, so we can simply append one entry after the other and - // write the offset table at the end - for (QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it) { - QString key = it.key(); - QJsonValue val = QJsonValue::fromVariant(it.value()); - - bool latinOrIntValue; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &latinOrIntValue); - - bool latinKey = QJsonPrivate::useCompressed(key); - int valueOffset = sizeof(QJsonPrivate::Entry) + QJsonPrivate::qStringSize(key, latinKey); - int requiredSize = valueOffset + valueSize; - - if (!object.detach2(requiredSize + sizeof(QJsonPrivate::offset))) // offset for the new index entry - return QJsonObject(); - - QJsonPrivate::Entry *e = reinterpret_cast(reinterpret_cast(object.o) + currentOffset); - e->value.type = val.t; - e->value.latinKey = latinKey; - e->value.latinOrIntValue = latinOrIntValue; - e->value.value = QJsonPrivate::Value::valueToStore(val, (char *)e - (char *)object.o + valueOffset); - QJsonPrivate::copyString((char *)(e + 1), key, latinKey); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)e + valueOffset, latinOrIntValue); - - offsets << currentOffset; - currentOffset += requiredSize; - object.o->size = currentOffset; - } - - // write table - object.o->tableOffset = currentOffset; - if (!object.detach2(sizeof(QJsonPrivate::offset)*offsets.size())) - return QJsonObject(); - memcpy(object.o->table(), offsets.constData(), offsets.size()*sizeof(uint)); - object.o->length = offsets.size(); - object.o->size = currentOffset + sizeof(QJsonPrivate::offset)*offsets.size(); - - return object; + return QCborMap::fromVariantMap(map).toJsonObject(); } /*! @@ -285,14 +235,7 @@ QJsonObject QJsonObject::fromVariantMap(const QVariantMap &map) */ QVariantMap QJsonObject::toVariantMap() const { - QVariantMap map; - if (o) { - for (uint i = 0; i < o->length; ++i) { - QJsonPrivate::Entry *e = o->entryAt(i); - map.insert(e->key(), QJsonValue(d, o, e->value).toVariant()); - } - } - return map; + return QCborMap::fromJsonObject(*this).toVariantMap(); } /*! @@ -324,15 +267,7 @@ QJsonObject QJsonObject::fromVariantHash(const QVariantHash &hash) */ QVariantHash QJsonObject::toVariantHash() const { - QVariantHash hash; - if (o) { - hash.reserve(o->length); - for (uint i = 0; i < o->length; ++i) { - QJsonPrivate::Entry *e = o->entryAt(i); - hash.insert(e->key(), QJsonValue(d, o, e->value).toVariant()); - } - } - return hash; + return QCborMap::fromJsonObject(*this).toVariantHash(); } /*! @@ -344,11 +279,9 @@ QStringList QJsonObject::keys() const { QStringList keys; if (o) { - keys.reserve(o->length); - for (uint i = 0; i < o->length; ++i) { - QJsonPrivate::Entry *e = o->entryAt(i); - keys.append(e->key()); - } + keys.reserve(o->elements.length() / 2); + for (int i = 0, end = o->elements.length(); i < end; i += 2) + keys.append(o->stringAt(i)); } return keys; } @@ -358,10 +291,7 @@ QStringList QJsonObject::keys() const */ int QJsonObject::size() const { - if (!d) - return 0; - - return o->length; + return o ? o->elements.length() / 2 : 0; } /*! @@ -371,10 +301,24 @@ int QJsonObject::size() const */ bool QJsonObject::isEmpty() const { - if (!d) - return true; + return !o || o->elements.isEmpty(); +} - return !o->length; +template +static int indexOf(const QExplicitlySharedDataPointer &o, + String key, bool *keyExists) +{ + const auto begin = QJsonPrivate::ConstKeyIterator(o->elements.constBegin()); + const auto end = QJsonPrivate::ConstKeyIterator(o->elements.constEnd()); + + const auto it = std::lower_bound( + begin, end, key, + [&](const QJsonPrivate::ConstKeyIterator::value_type &e, const String &key) { + return o->stringCompareElement(e.key(), key) < 0; + }); + + *keyExists = (it != end) && o->stringEqualsElement((*it).key(), key); + return (it - begin) * 2; } #if QT_STRINGVIEW_LEVEL < 2 @@ -415,14 +359,14 @@ QJsonValue QJsonObject::value(QLatin1String key) const template QJsonValue QJsonObject::valueImpl(T key) const { - if (!d) + if (!o) return QJsonValue(QJsonValue::Undefined); bool keyExists; - int i = o->indexOf(key, &keyExists); + int i = indexOf(o, key, &keyExists); if (!keyExists) return QJsonValue(QJsonValue::Undefined); - return QJsonValue(d, o, o->entryAt(i)->value); + return QJsonPrivate::Value::fromTrustedCbor(o->valueAt(i + 1)); } #if QT_STRINGVIEW_LEVEL < 2 @@ -497,13 +441,16 @@ QJsonValueRef QJsonObject::operator [](QLatin1String key) template QJsonValueRef QJsonObject::atImpl(T key) { + if (!o) + o = new QCborContainerPrivate; + bool keyExists = false; - int index = o ? o->indexOf(key, &keyExists) : 0; + int index = indexOf(o, key, &keyExists); if (!keyExists) { - iterator i = insertAt(index, key, QJsonValue(), false); - index = i.i; + o->insertAt(index, key); + o->insertAt(index + 1, QCborValue::fromJsonValue(QJsonValue())); } - return QJsonValueRef(this, index); + return QJsonValueRef(this, index / 2); } #if QT_STRINGVIEW_LEVEL < 2 @@ -550,12 +497,12 @@ QJsonObject::iterator QJsonObject::insert(QLatin1String key, const QJsonValue &v template QJsonObject::iterator QJsonObject::insertImpl(T key, const QJsonValue &value) { - if (value.t == QJsonValue::Undefined) { + if (value.type() == QJsonValue::Undefined) { remove(key); return end(); } bool keyExists = false; - int pos = o ? o->indexOf(key, &keyExists) : 0; + int pos = o ? indexOf(o, key, &keyExists) : 0; return insertAt(pos, key, value, keyExists); } @@ -565,40 +512,18 @@ QJsonObject::iterator QJsonObject::insertImpl(T key, const QJsonValue &value) template QJsonObject::iterator QJsonObject::insertAt(int pos, T key, const QJsonValue &value, bool keyExists) { - QJsonValue val = value; + if (o) + detach2(o->elements.length() / 2 + (keyExists ? 0 : 1)); + else + o = new QCborContainerPrivate; - bool latinOrIntValue; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &latinOrIntValue); - - bool latinKey = QJsonPrivate::useCompressed(key); - int valueOffset = sizeof(QJsonPrivate::Entry) + QJsonPrivate::qStringSize(key, latinKey); - int requiredSize = valueOffset + valueSize; - - if (!detach2(requiredSize + sizeof(QJsonPrivate::offset))) // offset for the new index entry - return iterator(); - - if (!o->length) - o->tableOffset = sizeof(QJsonPrivate::Object); - - if (keyExists) - ++d->compactionCounter; - - uint off = o->reserveSpace(requiredSize, pos, 1, keyExists); - if (!off) - return end(); - - QJsonPrivate::Entry *e = o->entryAt(pos); - e->value.type = val.t; - e->value.latinKey = latinKey; - e->value.latinOrIntValue = latinOrIntValue; - e->value.value = QJsonPrivate::Value::valueToStore(val, (char *)e - (char *)o + valueOffset); - QJsonPrivate::copyString((char *)(e + 1), key, latinKey); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)e + valueOffset, latinOrIntValue); - - compactIfNeeded(); - - return iterator(this, pos); + if (keyExists) { + o->replaceAt(pos + 1, QCborValue::fromJsonValue(value)); + } else { + o->insertAt(pos, key); + o->insertAt(pos + 1, QCborValue::fromJsonValue(value)); + } + return {this, pos / 2}; } #if QT_STRINGVIEW_LEVEL < 2 @@ -637,11 +562,11 @@ void QJsonObject::remove(QLatin1String key) template void QJsonObject::removeImpl(T key) { - if (!d) + if (!o) return; bool keyExists; - int index = o->indexOf(key, &keyExists); + int index = indexOf(o, key, &keyExists); if (!keyExists) return; @@ -692,13 +617,12 @@ QJsonValue QJsonObject::takeImpl(T key) return QJsonValue(QJsonValue::Undefined); bool keyExists; - int index = o->indexOf(key, &keyExists); + int index = indexOf(o, key, &keyExists); if (!keyExists) return QJsonValue(QJsonValue::Undefined); - QJsonValue v(d, o, o->entryAt(index)->value); + const QJsonValue v = QJsonPrivate::Value::fromTrustedCbor(o->extractAt(index + 1)); removeAt(index); - return v; } @@ -742,7 +666,7 @@ bool QJsonObject::containsImpl(T key) const return false; bool keyExists; - o->indexOf(key, &keyExists); + indexOf(o, key, &keyExists); return keyExists; } @@ -755,16 +679,14 @@ bool QJsonObject::operator==(const QJsonObject &other) const return true; if (!o) - return !other.o->length; + return !other.o->elements.length(); if (!other.o) - return !o->length; - if (o->length != other.o->length) + return !o->elements.length(); + if (o->elements.length() != other.o->elements.length()) return false; - for (uint i = 0; i < o->length; ++i) { - QJsonPrivate::Entry *e = o->entryAt(i); - QJsonValue v(d, o, e->value); - if (other.value(e->key()) != v) + for (int i = 0, end = o->elements.length(); i < end; ++i) { + if (o->valueAt(i) != other.o->valueAt(i)) return false; } @@ -788,9 +710,8 @@ bool QJsonObject::operator!=(const QJsonObject &other) const */ QJsonObject::iterator QJsonObject::erase(QJsonObject::iterator it) { - Q_ASSERT(d && d->ref.loadRelaxed() == 1); - if (it.o != this || it.i < 0 || it.i >= (int)o->length) - return iterator(this, o->length); + if (it.o != this || it.i < 0 || it.i >= o->elements.length()) + return {this, o->elements.length()}; int index = it.i; @@ -839,11 +760,11 @@ template QJsonObject::iterator QJsonObject::findImpl(T key) { bool keyExists = false; - int index = o ? o->indexOf(key, &keyExists) : 0; + int index = o ? indexOf(o, key, &keyExists) : 0; if (!keyExists) return end(); detach2(); - return iterator(this, index); + return {this, index / 2}; } #if QT_STRINGVIEW_LEVEL < 2 @@ -904,10 +825,10 @@ template QJsonObject::const_iterator QJsonObject::constFindImpl(T key) const { bool keyExists = false; - int index = o ? o->indexOf(key, &keyExists) : 0; + int index = o ? indexOf(o, key, &keyExists) : 0; if (!keyExists) return end(); - return const_iterator(this, index); + return {this, index / 2}; } /*! \fn int QJsonObject::count() const @@ -1500,28 +1421,10 @@ void QJsonObject::detach(uint reserve) bool QJsonObject::detach2(uint reserve) { - if (!d) { - if (reserve >= QJsonPrivate::Value::MaxSize) { - qWarning("QJson: Document too large to store in data structure"); - return false; - } - d = new QJsonPrivate::Data(reserve, QJsonValue::Object); - o = static_cast(d->header->root()); - d->ref.ref(); + if (!o) return true; - } - if (reserve == 0 && d->ref.loadRelaxed() == 1) - return true; - - QJsonPrivate::Data *x = d->clone(o, reserve); - if (!x) - return false; - x->ref.ref(); - if (!d->ref.deref()) - delete d; - d = x; - o = static_cast(d->header->root()); - return true; + o = QCborContainerPrivate::detach(o.data(), reserve ? reserve * 2 : o->elements.length()); + return o; } /*! @@ -1529,21 +1432,11 @@ bool QJsonObject::detach2(uint reserve) */ void QJsonObject::compact() { - if (!d || !d->compactionCounter) + if (!o) return; detach2(); - d->compact(); - o = static_cast(d->header->root()); -} - -/*! - \internal - */ -void QJsonObject::compactIfNeeded() -{ - if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u) - compact(); + o->compact(o->elements.length()); } /*! @@ -1551,10 +1444,8 @@ void QJsonObject::compactIfNeeded() */ QString QJsonObject::keyAt(int i) const { - Q_ASSERT(o && i >= 0 && i < (int)o->length); - - QJsonPrivate::Entry *e = o->entryAt(i); - return e->key(); + Q_ASSERT(o && i >= 0 && i * 2 < o->elements.length()); + return o->stringAt(i * 2); } /*! @@ -1562,11 +1453,9 @@ QString QJsonObject::keyAt(int i) const */ QJsonValue QJsonObject::valueAt(int i) const { - if (!o || i < 0 || i >= (int)o->length) + if (!o || i < 0 || 2 * i + 1 >= o->elements.length()) return QJsonValue(QJsonValue::Undefined); - - QJsonPrivate::Entry *e = o->entryAt(i); - return QJsonValue(d, o, e->value); + return QJsonPrivate::Value::fromTrustedCbor(o->valueAt(2 * i + 1)); } /*! @@ -1574,13 +1463,13 @@ QJsonValue QJsonObject::valueAt(int i) const */ void QJsonObject::setValueAt(int i, const QJsonValue &val) { - Q_ASSERT(o && i >= 0 && i < (int)o->length); - - QJsonPrivate::Entry *e = o->entryAt(i); - if (val.t == QJsonValue::Undefined) - removeAt(i); - else - insertAt(i, e->key(), val, true); + Q_ASSERT(o && i >= 0 && 2 * i + 1 < o->elements.length()); + if (val.isUndefined()) { + o->removeAt(2 * i + 1); + o->removeAt(2 * i); + } else { + o->replaceAt(2 * i + 1, QCborValue::fromJsonValue(val)); + } } /*! @@ -1589,9 +1478,8 @@ void QJsonObject::setValueAt(int i, const QJsonValue &val) void QJsonObject::removeAt(int index) { detach2(); - o->removeItems(index, 1); - ++d->compactionCounter; - compactIfNeeded(); + o->removeAt(index + 1); + o->removeAt(index); } uint qHash(const QJsonObject &object, uint seed) @@ -1614,7 +1502,7 @@ QDebug operator<<(QDebug dbg, const QJsonObject &o) return dbg; } QByteArray json; - QJsonPrivate::Writer::objectToJson(o.o, json, 0, true); + QJsonPrivate::Writer::objectToJson(o.o.data(), json, 0, true); dbg.nospace() << "QJsonObject(" << json.constData() // print as utf-8 string without extra quotation marks << ")"; diff --git a/src/corelib/serialization/qjsonobject.h b/src/corelib/serialization/qjsonobject.h index 53db1e1c08..cfb44c9c55 100644 --- a/src/corelib/serialization/qjsonobject.h +++ b/src/corelib/serialization/qjsonobject.h @@ -43,6 +43,7 @@ #include #include #include +#include #include QT_BEGIN_NAMESPACE @@ -53,29 +54,21 @@ typedef QMap QVariantMap; template class QHash; typedef QHash QVariantHash; +class QCborContainerPrivate; + class Q_CORE_EXPORT QJsonObject { public: QJsonObject(); - QJsonObject(std::initializer_list > args) - { - initialize(); - for (std::initializer_list >::const_iterator i = args.begin(); i != args.end(); ++i) - insert(i->first, i->second); - } + QJsonObject(std::initializer_list > args); ~QJsonObject(); QJsonObject(const QJsonObject &other); QJsonObject &operator =(const QJsonObject &other); - QJsonObject(QJsonObject &&other) noexcept - : d(other.d), o(other.o) - { - other.d = nullptr; - other.o = nullptr; - } + QJsonObject(QJsonObject &&other) noexcept; QJsonObject &operator =(QJsonObject &&other) noexcept { @@ -85,7 +78,6 @@ public: void swap(QJsonObject &other) noexcept { - qSwap(d, other.d); qSwap(o, other.o); } @@ -275,20 +267,18 @@ public: inline bool empty() const { return isEmpty(); } private: - friend class QJsonPrivate::Data; friend class QJsonValue; friend class QJsonDocument; friend class QJsonValueRef; - + friend class QCborMap; friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonObject &); - QJsonObject(QJsonPrivate::Data *data, QJsonPrivate::Object *object); + QJsonObject(QCborContainerPrivate *object); void initialize(); // ### Qt 6: remove me and merge with detach2 void detach(uint reserve = 0); bool detach2(uint reserve = 0); void compact(); - void compactIfNeeded(); template QJsonValue valueImpl(T key) const; template QJsonValueRef atImpl(T key); @@ -305,8 +295,9 @@ private: void removeAt(int i); template iterator insertAt(int i, T key, const QJsonValue &val, bool exists); - QJsonPrivate::Data *d; - QJsonPrivate::Object *o; + // ### Qt 6: remove + void *dead = nullptr; + QExplicitlySharedDataPointer o; }; Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QJsonObject) diff --git a/src/corelib/serialization/qjsonparser.cpp b/src/corelib/serialization/qjsonparser.cpp index cd36bd5a5b..6d0a92e094 100644 --- a/src/corelib/serialization/qjsonparser.cpp +++ b/src/corelib/serialization/qjsonparser.cpp @@ -45,6 +45,8 @@ #include "qjsonparser_p.h" #include "qjson_p.h" #include "private/qutfcodec_p.h" +#include "private/qcborvalue_p.h" +#include "private/qnumeric_p.h" //#define PARSER_DEBUG #ifdef PARSER_DEBUG @@ -197,9 +199,32 @@ QString QJsonParseError::errorString() const using namespace QJsonPrivate; +class StashedContainer +{ + Q_DISABLE_COPY_MOVE(StashedContainer) +public: + StashedContainer(QExplicitlySharedDataPointer *container, + QCborValue::Type type) + : type(type), stashed(std::move(*container)), current(container) + { + } + + ~StashedContainer() + { + stashed->append(QCborContainerPrivate::makeValue(type, -1, current->take(), + QCborContainerPrivate::MoveContainer)); + *current = std::move(stashed); + } + +private: + QCborValue::Type type; + QExplicitlySharedDataPointer stashed; + QExplicitlySharedDataPointer *current; +}; + Parser::Parser(const char *json, int length) - : head(json), json(json), data(nullptr) - , dataLength(0), current(0), nestingLevel(0) + : head(json), json(json) + , nestingLevel(0) , lastError(QJsonParseError::NoError) { end = json + length; @@ -297,34 +322,30 @@ char Parser::nextToken() /* JSON-text = object / array */ -QJsonDocument Parser::parse(QJsonParseError *error) +QCborValue Parser::parse(QJsonParseError *error) { #ifdef PARSER_DEBUG indent = 0; qDebug(">>>>> parser begin"); #endif - // allocate some space - dataLength = qMax(end - json, (ptrdiff_t) 256); - data = (char *)malloc(dataLength); - Q_CHECK_PTR(data); - - // fill in Header data - QJsonPrivate::Header *h = (QJsonPrivate::Header *)data; - h->tag = QJsonDocument::BinaryFormatTag; - h->version = 1u; - - current = sizeof(QJsonPrivate::Header); - eatBOM(); char token = nextToken(); + QCborValue data; + DEBUG << Qt::hex << (uint)token; if (token == BeginArray) { + container = new QCborContainerPrivate; if (!parseArray()) goto error; + data = QCborContainerPrivate::makeValue(QCborValue::Array, -1, container.take(), + QCborContainerPrivate::MoveContainer); } else if (token == BeginObject) { + container = new QCborContainerPrivate; if (!parseObject()) goto error; + data = QCborContainerPrivate::makeValue(QCborValue::Map, -1, container.take(), + QCborContainerPrivate::MoveContainer); } else { lastError = QJsonParseError::IllegalValue; goto error; @@ -342,44 +363,95 @@ QJsonDocument Parser::parse(QJsonParseError *error) error->offset = 0; error->error = QJsonParseError::NoError; } - QJsonPrivate::Data *d = new QJsonPrivate::Data(data, current); - return QJsonDocument(d); + + return data; } error: #ifdef PARSER_DEBUG qDebug(">>>>> parser error"); #endif + container.reset(); if (error) { error->offset = json - head; error->error = lastError; } - free(data); - return QJsonDocument(); + return QCborValue(); } -void Parser::ParsedObject::insert(uint offset) { - const QJsonPrivate::Entry *newEntry = reinterpret_cast(parser->data + objectPosition + offset); - int min = 0; - int n = offsets.size(); - while (n > 0) { - int half = n >> 1; - int middle = min + half; - if (*entryAt(middle) >= *newEntry) { - n = half; - } else { - min = middle + 1; - n -= half + 1; + +static void sortContainer(QCborContainerPrivate *container) +{ + using Forward = QJsonPrivate::KeyIterator; + using Reverse = std::reverse_iterator; + using Value = Forward::value_type; + + auto compare = [container](const Value &a, const Value &b) + { + const auto &aKey = a.key(); + const auto &bKey = b.key(); + + Q_ASSERT(aKey.flags & QtCbor::Element::HasByteData); + Q_ASSERT(bKey.flags & QtCbor::Element::HasByteData); + + const QtCbor::ByteData *aData = container->byteData(aKey); + const QtCbor::ByteData *bData = container->byteData(bKey); + + if (!aData) + return bData ? -1 : 0; + if (!bData) + return 1; + + // If StringIsAscii is set, we can use either the UTF-8 or the latin1 comparison + // for the string as ASCII is a subset of both. If nothing is set, that means UTF-8. + + // We are currently missing an efficient comparison between UTF-8 and UTF-16 strings. + // Therefore, we need to convert the UTF-8 string if we encounter such a case. + + if (aKey.flags & QtCbor::Element::StringIsAscii) { + if (bKey.flags & QtCbor::Element::StringIsAscii) + return QtPrivate::compareStrings(aData->asLatin1(), bData->asLatin1()); + if (bKey.flags & QtCbor::Element::StringIsUtf16) + return QtPrivate::compareStrings(aData->asLatin1(), bData->asStringView()); + + return QCborContainerPrivate::compareUtf8(aData, bData->asLatin1()); } - } - if (min < offsets.size() && *entryAt(min) == *newEntry) { - offsets[min] = offset; - } else { - offsets.insert(min, offset); - } + + if (aKey.flags & QtCbor::Element::StringIsUtf16) { + if (bKey.flags & QtCbor::Element::StringIsAscii) + return QtPrivate::compareStrings(aData->asStringView(), bData->asLatin1()); + if (bKey.flags & QtCbor::Element::StringIsUtf16) + return QtPrivate::compareStrings(aData->asStringView(), bData->asStringView()); + + // Nasty case. a is UTF-16 and b is UTF-8 + return QtPrivate::compareStrings(aData->asStringView(), bData->toUtf8String()); + } + + if (bKey.flags & QtCbor::Element::StringIsAscii) + return QCborContainerPrivate::compareUtf8(aData, bData->asLatin1()); + + // Nasty case. a is UTF-8 and b is UTF-16 + if (bKey.flags & QtCbor::Element::StringIsUtf16) + return QtPrivate::compareStrings(aData->toUtf8String(), bData->asStringView()); + + return QCborContainerPrivate::compareUtf8(aData, bData->asLatin1()); + }; + + std::sort(Forward(container->elements.begin()), Forward(container->elements.end()), + [&compare](const Value &a, const Value &b) { return compare(a, b) < 0; }); + + // We need to retain the _last_ value for any duplicate keys. Therefore the reverse dance here. + auto it = std::unique(Reverse(container->elements.end()), Reverse(container->elements.begin()), + [&compare](const Value &a, const Value &b) { + return compare(a, b) == 0; + }).base().elementsIterator(); + + // The erase from beginning is expensive but hopefully rare. + container->elements.erase(container->elements.begin(), it); } + /* object = begin-object [ member *( value-separator member ) ] end-object @@ -392,19 +464,14 @@ bool Parser::parseObject() return false; } - int objectOffset = reserveSpace(sizeof(QJsonPrivate::Object)); - if (objectOffset < 0) - return false; - BEGIN << "parseObject pos=" << objectOffset << current << json; - - ParsedObject parsedObject(this, objectOffset); + BEGIN << "parseObject" << json; char token = nextToken(); while (token == Quote) { - int off = current - objectOffset; - if (!parseMember(objectOffset)) + if (!container) + container = new QCborContainerPrivate; + if (!parseMember()) return false; - parsedObject.insert(off); token = nextToken(); if (token != ValueSeparator) break; @@ -421,50 +488,23 @@ bool Parser::parseObject() return false; } - DEBUG << "numEntries" << parsedObject.offsets.size(); - int table = objectOffset; - // finalize the object - if (parsedObject.offsets.size()) { - int tableSize = parsedObject.offsets.size()*sizeof(uint); - table = reserveSpace(tableSize); - if (table < 0) - return false; - -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - memcpy(data + table, parsedObject.offsets.constData(), tableSize); -#else - offset *o = (offset *)(data + table); - for (int i = 0; i < parsedObject.offsets.size(); ++i) - o[i] = parsedObject.offsets[i]; - -#endif - } - - QJsonPrivate::Object *o = (QJsonPrivate::Object *)(data + objectOffset); - o->tableOffset = table - objectOffset; - o->size = current - objectOffset; - o->is_object = true; - o->length = parsedObject.offsets.size(); - - DEBUG << "current=" << current; END; --nestingLevel; + + if (container) + sortContainer(container.data()); return true; } /* member = string name-separator value */ -bool Parser::parseMember(int baseOffset) +bool Parser::parseMember() { - int entryOffset = reserveSpace(sizeof(QJsonPrivate::Entry)); - if (entryOffset < 0) - return false; - BEGIN << "parseMember pos=" << entryOffset; + BEGIN << "parseMember"; - bool latin1; - if (!parseString(&latin1)) + if (!parseString()) return false; char token = nextToken(); if (token != NameSeparator) { @@ -475,56 +515,13 @@ bool Parser::parseMember(int baseOffset) lastError = QJsonParseError::UnterminatedObject; return false; } - QJsonPrivate::Value val; - if (!parseValue(&val, baseOffset)) + if (!parseValue()) return false; - // finalize the entry - QJsonPrivate::Entry *e = (QJsonPrivate::Entry *)(data + entryOffset); - e->value = val; - e->value.latinKey = latin1; - END; return true; } -namespace { - struct ValueArray { - static const int prealloc = 128; - ValueArray() : data(stackValues), alloc(prealloc), size(0) {} - ~ValueArray() { if (data != stackValues) free(data); } - - inline bool grow() { - alloc *= 2; - if (data == stackValues) { - QJsonPrivate::Value *newValues = static_cast(malloc(alloc*sizeof(QJsonPrivate::Value))); - if (!newValues) - return false; - memcpy(newValues, data, size*sizeof(QJsonPrivate::Value)); - data = newValues; - } else { - void *newValues = realloc(data, alloc * sizeof(QJsonPrivate::Value)); - if (!newValues) - return false; - data = static_cast(newValues); - } - return true; - } - bool append(const QJsonPrivate::Value &v) { - if (alloc == size && !grow()) - return false; - data[size] = v; - ++size; - return true; - } - - QJsonPrivate::Value stackValues[prealloc]; - QJsonPrivate::Value *data; - int alloc; - int size; - }; -} - /* array = begin-array [ value *( value-separator value ) ] end-array */ @@ -537,12 +534,6 @@ bool Parser::parseArray() return false; } - int arrayOffset = reserveSpace(sizeof(QJsonPrivate::Array)); - if (arrayOffset < 0) - return false; - - ValueArray values; - if (!eatSpace()) { lastError = QJsonParseError::UnterminatedArray; return false; @@ -555,13 +546,10 @@ bool Parser::parseArray() lastError = QJsonParseError::UnterminatedArray; return false; } - QJsonPrivate::Value val; - if (!parseValue(&val, arrayOffset)) + if (!container) + container = new QCborContainerPrivate; + if (!parseValue()) return false; - if (!values.append(val)) { - lastError = QJsonParseError::DocumentTooLarge; - return false; - } char token = nextToken(); if (token == EndArray) break; @@ -575,27 +563,11 @@ bool Parser::parseArray() } } - DEBUG << "size =" << values.size; - int table = arrayOffset; - // finalize the object - if (values.size) { - int tableSize = values.size*sizeof(QJsonPrivate::Value); - table = reserveSpace(tableSize); - if (table < 0) - return false; - memcpy(data + table, values.data, tableSize); - } - - QJsonPrivate::Array *a = (QJsonPrivate::Array *)(data + arrayOffset); - a->tableOffset = table - arrayOffset; - a->size = current - arrayOffset; - a->is_object = false; - a->length = values.size; - - DEBUG << "current=" << current; + DEBUG << "size =" << (container ? container->elements.length() : 0); END; --nestingLevel; + return true; } @@ -604,10 +576,9 @@ value = false / null / true / object / array / number / string */ -bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) +bool Parser::parseValue() { BEGIN << "parse Value" << json; - val->_dummy = 0; switch (*json++) { case 'n': @@ -618,7 +589,7 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) if (*json++ == 'u' && *json++ == 'l' && *json++ == 'l') { - val->type = QJsonValue::Null; + container->append(QCborValue(QCborValue::Null)); DEBUG << "value: null"; END; return true; @@ -633,8 +604,7 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) if (*json++ == 'r' && *json++ == 'u' && *json++ == 'e') { - val->type = QJsonValue::Bool; - val->value = true; + container->append(QCborValue(true)); DEBUG << "value: true"; END; return true; @@ -650,8 +620,7 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) *json++ == 'l' && *json++ == 's' && *json++ == 'e') { - val->type = QJsonValue::Bool; - val->value = false; + container->append(QCborValue(false)); DEBUG << "value: false"; END; return true; @@ -659,44 +628,28 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) lastError = QJsonParseError::IllegalValue; return false; case Quote: { - val->type = QJsonValue::String; - if (current - baseOffset >= Value::MaxSize) { - lastError = QJsonParseError::DocumentTooLarge; + if (!parseString()) return false; - } - val->value = current - baseOffset; - bool latin1; - if (!parseString(&latin1)) - return false; - val->latinOrIntValue = latin1; DEBUG << "value: string"; END; return true; } - case BeginArray: - val->type = QJsonValue::Array; - if (current - baseOffset >= Value::MaxSize) { - lastError = QJsonParseError::DocumentTooLarge; - return false; - } - val->value = current - baseOffset; + case BeginArray: { + StashedContainer stashedContainer(&container, QCborValue::Array); if (!parseArray()) return false; DEBUG << "value: array"; END; return true; - case BeginObject: - val->type = QJsonValue::Object; - if (current - baseOffset >= Value::MaxSize) { - lastError = QJsonParseError::DocumentTooLarge; - return false; - } - val->value = current - baseOffset; + } + case BeginObject: { + StashedContainer stashedContainer(&container, QCborValue::Map); if (!parseObject()) return false; DEBUG << "value: object"; END; return true; + } case ValueSeparator: // Essentially missing value, but after a colon, not after a comma // like the other MissingObject errors. @@ -708,7 +661,7 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) return false; default: --json; - if (!parseNumber(val, baseOffset)) + if (!parseNumber()) return false; DEBUG << "value: number"; END; @@ -735,10 +688,9 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) */ -bool Parser::parseNumber(QJsonPrivate::Value *val, int baseOffset) +bool Parser::parseNumber() { BEGIN << "parseNumber" << json; - val->type = QJsonValue::Double; const char *start = json; bool isInt = true; @@ -778,42 +730,32 @@ bool Parser::parseNumber(QJsonPrivate::Value *val, int baseOffset) return false; } - QByteArray number(start, json - start); + const QByteArray number = QByteArray::fromRawData(start, json - start); DEBUG << "numberstring" << number; if (isInt) { bool ok; - int n = number.toInt(&ok); - if (ok && n < (1<<25) && n > -(1<<25)) { - val->int_value = n; - val->latinOrIntValue = true; + qlonglong n = number.toLongLong(&ok); + if (ok) { + container->append(QCborValue(n)); END; return true; } } bool ok; - union { - quint64 ui; - double d; - }; - d = number.toDouble(&ok); + double d = number.toDouble(&ok); if (!ok) { lastError = QJsonParseError::IllegalNumber; return false; } - int pos = reserveSpace(sizeof(double)); - if (pos < 0) - return false; - qToLittleEndian(ui, data + pos); - if (current - baseOffset >= Value::MaxSize) { - lastError = QJsonParseError::DocumentTooLarge; - return false; - } - val->value = pos - baseOffset; - val->latinOrIntValue = false; + qint64 n; + if (convertDoubleTo(d, &n)) + container->append(QCborValue(n)); + else + container->append(QCborValue(d)); END; return true; @@ -902,58 +844,45 @@ static inline bool scanEscapeSequence(const char *&json, const char *end, uint * static inline bool scanUtf8Char(const char *&json, const char *end, uint *result) { - const uchar *&src = reinterpret_cast(json); - const uchar *uend = reinterpret_cast(end); - uchar b = *src++; - int res = QUtf8Functions::fromUtf8(b, result, src, uend); - if (res < 0) { - // decoding error, backtrack the character we read above - --json; + const auto *usrc = reinterpret_cast(json); + const auto *uend = reinterpret_cast(end); + const uchar b = *usrc++; + int res = QUtf8Functions::fromUtf8(b, result, usrc, uend); + if (res < 0) return false; - } + json = reinterpret_cast(usrc); return true; } -bool Parser::parseString(bool *latin1) +bool Parser::parseString() { - *latin1 = true; - const char *start = json; - int outStart = current; - // try to write out a latin1 string + // try to parse a utf-8 string without escape sequences, and note whether it's 7bit ASCII. - int stringPos = reserveSpace(2); - if (stringPos < 0) - return false; - - BEGIN << "parse string stringPos=" << stringPos << json; + BEGIN << "parse string" << json; + bool isUtf8 = true; + bool isAscii = true; while (json < end) { uint ch = 0; if (*json == '"') break; - else if (*json == '\\') { - if (!scanEscapeSequence(json, end, &ch)) { - lastError = QJsonParseError::IllegalEscapeSequence; - return false; - } - } else { - if (!scanUtf8Char(json, end, &ch)) { - lastError = QJsonParseError::IllegalUTF8String; - return false; - } - } - // bail out if the string is not pure latin1 or too long to hold as a latin1string (which has only 16 bit for the length) - if (ch > 0xff || json - start >= 0x8000) { - *latin1 = false; + if (*json == '\\') { + isAscii = false; + // If we find escape sequences, we store UTF-16 as there are some + // escape sequences which are hard to represent in UTF-8. + // (plain "\\ud800" for example) + isUtf8 = false; break; } - int pos = reserveSpace(1); - if (pos < 0) + if (!scanUtf8Char(json, end, &ch)) { + lastError = QJsonParseError::IllegalUTF8String; return false; - DEBUG << " " << ch << (char)ch; - data[pos] = (uchar)ch; + } + if (ch > 0x7f) + isAscii = false; + DEBUG << " " << ch << char(ch); } ++json; DEBUG << "end of string"; @@ -962,25 +891,20 @@ bool Parser::parseString(bool *latin1) return false; } - // no unicode string, we are done - if (*latin1) { - // write string length - *(QJsonPrivate::qle_ushort *)(data + stringPos) = ushort(current - outStart - sizeof(ushort)); - int pos = reserveSpace((4 - current) & 3); - if (pos < 0) - return false; - while (pos & 3) - data[pos++] = 0; + // no escape sequences, we are done + if (isUtf8) { + container->appendByteData(start, json - start - 1, QCborValue::String, + isAscii ? QtCbor::Element::StringIsAscii + : QtCbor::Element::ValueFlags {}); END; return true; } - *latin1 = false; - DEBUG << "not latin"; + DEBUG << "has escape sequences"; json = start; - current = outStart + sizeof(int); + QString ucs4; while (json < end) { uint ch = 0; if (*json == '"') @@ -997,16 +921,10 @@ bool Parser::parseString(bool *latin1) } } if (QChar::requiresSurrogates(ch)) { - int pos = reserveSpace(4); - if (pos < 0) - return false; - *(QJsonPrivate::qle_ushort *)(data + pos) = QChar::highSurrogate(ch); - *(QJsonPrivate::qle_ushort *)(data + pos + 2) = QChar::lowSurrogate(ch); + ucs4.append(QChar::highSurrogate(ch)); + ucs4.append(QChar::lowSurrogate(ch)); } else { - int pos = reserveSpace(2); - if (pos < 0) - return false; - *(QJsonPrivate::qle_ushort *)(data + pos) = (ushort)ch; + ucs4.append(QChar(ushort(ch))); } } ++json; @@ -1016,13 +934,8 @@ bool Parser::parseString(bool *latin1) return false; } - // write string length - *(QJsonPrivate::qle_int *)(data + stringPos) = (current - outStart - sizeof(int))/2; - int pos = reserveSpace((4 - current) & 3); - if (pos < 0) - return false; - while (pos & 3) - data[pos++] = 0; + container->appendByteData(reinterpret_cast(ucs4.utf16()), ucs4.size() * 2, + QCborValue::String, QtCbor::Element::StringIsUtf16); END; return true; } diff --git a/src/corelib/serialization/qjsonparser_p.h b/src/corelib/serialization/qjsonparser_p.h index 379256847f..14d9705447 100644 --- a/src/corelib/serialization/qjsonparser_p.h +++ b/src/corelib/serialization/qjsonparser_p.h @@ -52,8 +52,8 @@ // #include -#include -#include +#include +#include QT_BEGIN_NAMESPACE @@ -64,25 +64,7 @@ class Parser public: Parser(const char *json, int length); - QJsonDocument parse(QJsonParseError *error); - - class ParsedObject - { - public: - ParsedObject(Parser *p, int pos) : parser(p), objectPosition(pos) { - offsets.reserve(64); - } - void insert(uint offset); - - Parser *parser; - int objectPosition; - QVector offsets; - - inline QJsonPrivate::Entry *entryAt(int i) const { - return reinterpret_cast(parser->data + objectPosition + offsets[i]); - } - }; - + QCborValue parse(QJsonParseError *error); private: inline void eatBOM(); @@ -91,34 +73,17 @@ private: bool parseObject(); bool parseArray(); - bool parseMember(int baseOffset); - bool parseString(bool *latin1); - bool parseValue(QJsonPrivate::Value *val, int baseOffset); - bool parseNumber(QJsonPrivate::Value *val, int baseOffset); + bool parseMember(); + bool parseString(); + bool parseValue(); + bool parseNumber(); const char *head; const char *json; const char *end; - char *data; - int dataLength; - int current; int nestingLevel; QJsonParseError::ParseError lastError; - - inline int reserveSpace(int space) { - if (current + space >= dataLength) { - dataLength = 2*dataLength + space; - char *newData = (char *)realloc(data, dataLength); - if (!newData) { - lastError = QJsonParseError::DocumentTooLarge; - return -1; - } - data = newData; - } - int pos = current; - current += space; - return pos; - } + QExplicitlySharedDataPointer container; }; } diff --git a/src/corelib/serialization/qjsonvalue.cpp b/src/corelib/serialization/qjsonvalue.cpp index 5f07a6a03e..033e438580 100644 --- a/src/corelib/serialization/qjsonvalue.cpp +++ b/src/corelib/serialization/qjsonvalue.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -47,10 +48,11 @@ #include #include "qdatastream.h" -#ifndef QT_BOOTSTRAPPED -# include -# include -#endif +#include +#include + +#include +#include #include "qjson_p.h" @@ -112,70 +114,61 @@ QT_BEGIN_NAMESPACE The default is to create a Null value. */ QJsonValue::QJsonValue(Type type) - : ui(0), d(nullptr), t(type) + : d(nullptr), t(QCborValue::Undefined) { -} - -/*! - \internal - */ -QJsonValue::QJsonValue(QJsonPrivate::Data *data, QJsonPrivate::Base *base, const QJsonPrivate::Value &v) - : d(nullptr) -{ - t = (Type)(uint)v.type; - switch (t) { - case Undefined: + switch (type) { case Null: - dbl = 0; + t = QCborValue::Null; break; case Bool: - b = v.toBoolean(); + t = QCborValue::False; break; case Double: - dbl = v.toDouble(base); + t = QCborValue::Double; break; - case String: { - QString s = v.toString(base); - stringData = s.data_ptr(); - stringData->ref.ref(); + case String: + t = QCborValue::String; break; - } case Array: + t = QCborValue::Array; + break; case Object: - d = data; - this->base = v.base(base); + t = QCborValue::Map; + break; + case Undefined: break; } - if (d) - d->ref.ref(); } /*! Creates a value of type Bool, with value \a b. */ QJsonValue::QJsonValue(bool b) - : d(nullptr), t(Bool) + : t(b ? QCborValue::True : QCborValue::False) { - this->b = b; } /*! Creates a value of type Double, with value \a n. */ -QJsonValue::QJsonValue(double n) - : d(nullptr), t(Double) +QJsonValue::QJsonValue(double v) + : d(nullptr) { - this->dbl = n; + if (convertDoubleTo(v, &n)) { + t = QCborValue::Integer; + } else { + memcpy(&n, &v, sizeof(n)); + t = QCborValue::Double; + } } /*! \overload Creates a value of type Double, with value \a n. */ -QJsonValue::QJsonValue(int n) - : d(nullptr), t(Double) +QJsonValue::QJsonValue(int v) + : n(v), t(QCborValue::Integer) { - this->dbl = n; } /*! @@ -184,19 +177,17 @@ QJsonValue::QJsonValue(int n) NOTE: the integer limits for IEEE 754 double precision data is 2^53 (-9007199254740992 to +9007199254740992). If you pass in values outside this range expect a loss of precision to occur. */ -QJsonValue::QJsonValue(qint64 n) - : d(nullptr), t(Double) +QJsonValue::QJsonValue(qint64 v) + : n(v), t(QCborValue::Integer) { - this->dbl = double(n); } /*! Creates a value of type String, with value \a s. */ QJsonValue::QJsonValue(const QString &s) - : d(nullptr), t(String) + : QJsonValue(QJsonPrivate::Value::fromTrustedCbor(s)) { - stringDataFromQStringHelper(s); } /*! @@ -211,71 +202,50 @@ QJsonValue::QJsonValue(const QString &s) \since 5.3 */ +// ### Qt6: remove void QJsonValue::stringDataFromQStringHelper(const QString &string) { - stringData = *(QStringData **)(&string); - stringData->ref.ref(); + *this = QJsonValue(string); } /*! Creates a value of type String, with value \a s. */ QJsonValue::QJsonValue(QLatin1String s) - : d(nullptr), t(String) + : QJsonValue(QJsonPrivate::Value::fromTrustedCbor(s)) { - // ### FIXME: Avoid creating the temp QString below - QString str(s); - stringDataFromQStringHelper(str); } /*! Creates a value of type Array, with value \a a. */ QJsonValue::QJsonValue(const QJsonArray &a) - : d(a.d), t(Array) + : n(-1), d(a.a), t(QCborValue::Array) { - base = a.a; - if (d) - d->ref.ref(); } /*! Creates a value of type Object, with value \a o. */ QJsonValue::QJsonValue(const QJsonObject &o) - : d(o.d), t(Object) + : n(-1), d(o.o), t(QCborValue::Map) { - base = o.o; - if (d) - d->ref.ref(); } /*! Destroys the value. */ -QJsonValue::~QJsonValue() -{ - if (t == String && stringData && !stringData->ref.deref()) - free(stringData); - - if (d && !d->ref.deref()) - delete d; -} +QJsonValue::~QJsonValue() = default; /*! Creates a copy of \a other. */ QJsonValue::QJsonValue(const QJsonValue &other) { + n = other.n; t = other.t; d = other.d; - ui = other.ui; - if (d) - d->ref.ref(); - - if (t == String && stringData) - stringData->ref.ref(); } /*! @@ -288,6 +258,23 @@ QJsonValue &QJsonValue::operator =(const QJsonValue &other) return *this; } +QJsonValue::QJsonValue(QJsonValue &&other) noexcept : + n(other.n), + d(other.d), + t(other.t) +{ + other.n = 0; + other.d = nullptr; + other.t = QCborValue::Null; +} + +void QJsonValue::swap(QJsonValue &other) noexcept +{ + qSwap(n, other.n); + qSwap(d, other.d); + qSwap(t, other.t); +} + /*! \fn QJsonValue::QJsonValue(QJsonValue &&other) \since 5.10 @@ -528,23 +515,27 @@ QJsonValue QJsonValue::fromVariant(const QVariant &variant) QVariant QJsonValue::toVariant() const { switch (t) { - case Bool: - return b; - case Double: - return dbl; - case String: + case QCborValue::True: + return true; + case QCborValue::False: + return false; + case QCborValue::Integer: + case QCborValue::Double: + return toDouble(); + case QCborValue::String: return toString(); - case Array: + case QCborValue::Array: return d ? - QJsonArray(d, static_cast(base)).toVariantList() : + QJsonArray(d.data()).toVariantList() : QVariantList(); - case Object: + case QCborValue::Map: return d ? - QJsonObject(d, static_cast(base)).toVariantMap() : + QJsonObject(d.data()).toVariantMap() : QVariantMap(); - case Null: + case QCborValue::Null: return QVariant::fromValue(nullptr); - case Undefined: + case QCborValue::Undefined: + default: break; } return QVariant(); @@ -573,7 +564,25 @@ QVariant QJsonValue::toVariant() const */ QJsonValue::Type QJsonValue::type() const { - return t; + switch (t) { + case QCborValue::Null: + return QJsonValue::Null; + case QCborValue::True: + case QCborValue::False: + return QJsonValue::Bool; + case QCborValue::Double: + case QCborValue::Integer: + return QJsonValue::Double; + case QCborValue::String: + return QJsonValue::String; + case QCborValue::Array: + return QJsonValue::Array; + case QCborValue::Map: + return QJsonValue::Object; + case QCborValue::Undefined: + default: + return QJsonValue::Undefined; + } } /*! @@ -583,9 +592,14 @@ QJsonValue::Type QJsonValue::type() const */ bool QJsonValue::toBool(bool defaultValue) const { - if (t != Bool) + switch (t) { + case QCborValue::True: + return true; + case QCborValue::False: + return false; + default: return defaultValue; - return b; + } } /*! @@ -597,9 +611,20 @@ bool QJsonValue::toBool(bool defaultValue) const */ int QJsonValue::toInt(int defaultValue) const { - if (t == Double && int(dbl) == dbl) - return int(dbl); - return defaultValue; + switch (t) { + case QCborValue::Double: { + const double dbl = toDouble(); + int dblInt; + convertDoubleTo(dbl, &dblInt); + return dbl == dblInt ? dblInt : defaultValue; + } + case QCborValue::Integer: + return (n <= qint64(std::numeric_limits::max()) + && n >= qint64(std::numeric_limits::min())) + ? n : defaultValue; + default: + return defaultValue; + } } /*! @@ -609,9 +634,17 @@ int QJsonValue::toInt(int defaultValue) const */ double QJsonValue::toDouble(double defaultValue) const { - if (t != Double) + switch (t) { + case QCborValue::Double: { + double d; + memcpy(&d, &n, sizeof(d)); + return d; + } + case QCborValue::Integer: + return n; + default: return defaultValue; - return dbl; + } } /*! @@ -621,11 +654,7 @@ double QJsonValue::toDouble(double defaultValue) const */ QString QJsonValue::toString(const QString &defaultValue) const { - if (t != String) - return defaultValue; - stringData->ref.ref(); // the constructor below doesn't add a ref. - QStringDataPtr holder = { stringData }; - return QString(holder); + return (t == QCborValue::String && d) ? d->stringAt(n) : defaultValue; } /*! @@ -637,11 +666,7 @@ QString QJsonValue::toString(const QString &defaultValue) const */ QString QJsonValue::toString() const { - if (t != String) - return QString(); - stringData->ref.ref(); // the constructor below doesn't add a ref. - QStringDataPtr holder = { stringData }; - return QString(holder); + return (t == QCborValue::String && d) ? d->stringAt(n) : QString(); } /*! @@ -651,10 +676,10 @@ QString QJsonValue::toString() const */ QJsonArray QJsonValue::toArray(const QJsonArray &defaultValue) const { - if (!d || t != Array) + if (t != QCborValue::Array || n >= 0 || !d) return defaultValue; - return QJsonArray(d, static_cast(base)); + return QJsonArray(d.data()); } /*! @@ -676,10 +701,10 @@ QJsonArray QJsonValue::toArray() const */ QJsonObject QJsonValue::toObject(const QJsonObject &defaultValue) const { - if (!d || t != Object) + if (t != QCborValue::Map || n >= 0 || !d) return defaultValue; - return QJsonObject(d, static_cast(base)); + return QJsonObject(d.data()); } /*! @@ -766,33 +791,31 @@ bool QJsonValue::operator==(const QJsonValue &other) const return false; switch (t) { - case Undefined: - case Null: + case QCborValue::Undefined: + case QCborValue::Null: + case QCborValue::True: + case QCborValue::False: break; - case Bool: - return b == other.b; - case Double: - return dbl == other.dbl; - case String: + case QCborValue::Double: + return toDouble() == other.toDouble(); + case QCborValue::Integer: + return n == other.n; + case QCborValue::String: return toString() == other.toString(); - case Array: - if (base == other.base) - return true; - if (!base) - return !other.base->length; - if (!other.base) - return !base->length; - return QJsonArray(d, static_cast(base)) - == QJsonArray(other.d, static_cast(other.base)); - case Object: - if (base == other.base) - return true; - if (!base) - return !other.base->length; - if (!other.base) - return !base->length; - return QJsonObject(d, static_cast(base)) - == QJsonObject(other.d, static_cast(other.base)); + case QCborValue::Array: + if (!d) + return !other.d || other.d->elements.length() == 0; + if (!other.d) + return d->elements.length() == 0; + return QJsonArray(d.data()) == QJsonArray(other.d.data()); + case QCborValue::Map: + if (!d) + return !other.d || other.d->elements.length() == 0; + if (!other.d) + return d->elements.length() == 0; + return QJsonObject(d.data()) == QJsonObject(other.d.data()); + default: + return false; } return true; } @@ -810,15 +833,7 @@ bool QJsonValue::operator!=(const QJsonValue &other) const */ void QJsonValue::detach() { - if (!d) - return; - - QJsonPrivate::Data *x = d->clone(base); - x->ref.ref(); - if (!d->ref.deref()) - delete d; - d = x; - base = static_cast(d->header->root()); + d.detach(); } @@ -914,7 +929,7 @@ uint qHash(const QJsonValue &value, uint seed) QDebug operator<<(QDebug dbg, const QJsonValue &o) { QDebugStateSaver saver(dbg); - switch (o.t) { + switch (o.type()) { case QJsonValue::Undefined: dbg << "QJsonValue(undefined)"; break; @@ -948,7 +963,7 @@ QDebug operator<<(QDebug dbg, const QJsonValue &o) #ifndef QT_NO_DATASTREAM QDataStream &operator<<(QDataStream &stream, const QJsonValue &v) { - quint8 type = v.t; + quint8 type = v.type(); stream << type; switch (type) { case QJsonValue::Undefined: diff --git a/src/corelib/serialization/qjsonvalue.h b/src/corelib/serialization/qjsonvalue.h index 8ade18509b..5adcd64176 100644 --- a/src/corelib/serialization/qjsonvalue.h +++ b/src/corelib/serialization/qjsonvalue.h @@ -42,22 +42,18 @@ #include #include +#include +#include QT_BEGIN_NAMESPACE -class QDebug; class QVariant; class QJsonArray; class QJsonObject; +class QCborContainerPrivate; namespace QJsonPrivate { - class Data; - class Base; - class Object; - class Header; - class Array; - class Value; - class Entry; +class Value; } class Q_CORE_EXPORT QJsonValue @@ -77,12 +73,12 @@ public: QJsonValue(bool b); QJsonValue(double n); QJsonValue(int n); - QJsonValue(qint64 n); + QJsonValue(qint64 v); QJsonValue(const QString &s); QJsonValue(QLatin1String s); #ifndef QT_NO_CAST_FROM_ASCII inline QT_ASCII_CAST_WARN QJsonValue(const char *s) - : d(nullptr), t(String) { stringDataFromQStringHelper(QString::fromUtf8(s)); } + : QJsonValue(QString::fromUtf8(s)) {} #endif QJsonValue(const QJsonArray &a); QJsonValue(const QJsonObject &o); @@ -92,15 +88,7 @@ public: QJsonValue(const QJsonValue &other); QJsonValue &operator =(const QJsonValue &other); - QJsonValue(QJsonValue &&other) noexcept - : ui(other.ui), - d(other.d), - t(other.t) - { - other.ui = 0; - other.d = nullptr; - other.t = Null; - } + QJsonValue(QJsonValue &&other) noexcept; QJsonValue &operator =(QJsonValue &&other) noexcept { @@ -108,12 +96,7 @@ public: return *this; } - void swap(QJsonValue &other) noexcept - { - qSwap(ui, other.ui); - qSwap(d, other.d); - qSwap(t, other.t); - } + void swap(QJsonValue &other) noexcept; static QJsonValue fromVariant(const QVariant &variant); QVariant toVariant() const; @@ -149,7 +132,7 @@ public: private: // avoid implicit conversions from char * to bool - inline QJsonValue(const void *) {} + QJsonValue(const void *) = delete; friend class QJsonPrivate::Value; friend class QJsonArray; friend class QJsonObject; @@ -157,20 +140,19 @@ private: friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonValue &); friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QJsonValue &); - QJsonValue(QJsonPrivate::Data *d, QJsonPrivate::Base *b, const QJsonPrivate::Value& v); + // ### Qt6: Remove this. void stringDataFromQStringHelper(const QString &string); void detach(); - union { - quint64 ui; - bool b; - double dbl; - QStringData *stringData; - QJsonPrivate::Base *base; - }; - QJsonPrivate::Data *d; // needed for Objects and Arrays - Type t; + // ### Qt6: change to an actual QCborValue + qint64 n = 0; + QExplicitlySharedDataPointer d; // needed for Objects, Arrays, Strings + QCborValue::Type t; + + // Assert binary compatibility with pre-5.15 QJsonValue + Q_STATIC_ASSERT(sizeof(QExplicitlySharedDataPointer) == sizeof(void *)); + Q_STATIC_ASSERT(sizeof(QCborValue::Type) == sizeof(QJsonValue::Type)); }; class Q_CORE_EXPORT QJsonValueRef diff --git a/src/corelib/serialization/qjsonwriter.cpp b/src/corelib/serialization/qjsonwriter.cpp index 012d3bea86..627d1bbd62 100644 --- a/src/corelib/serialization/qjsonwriter.cpp +++ b/src/corelib/serialization/qjsonwriter.cpp @@ -44,13 +44,14 @@ #include "qjson_p.h" #include "private/qutfcodec_p.h" #include +#include QT_BEGIN_NAMESPACE using namespace QJsonPrivate; -static void objectContentToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact); -static void arrayContentToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact); +static void objectContentToJson(const QCborContainerPrivate *o, QByteArray &json, int indent, bool compact); +static void arrayContentToJson(const QCborContainerPrivate *a, QByteArray &json, int indent, bool compact); static inline uchar hexdig(uint u) { @@ -126,16 +127,20 @@ static QByteArray escapedString(const QString &s) return ba; } -static void valueToJson(const QJsonPrivate::Base *b, const QJsonPrivate::Value &v, QByteArray &json, int indent, bool compact) +static void valueToJson(const QCborValue &v, QByteArray &json, int indent, bool compact) { - QJsonValue::Type type = (QJsonValue::Type)(uint)v.type; + QCborValue::Type type = v.type(); switch (type) { - case QJsonValue::Bool: - json += v.toBoolean() ? "true" : "false"; + case QCborValue::True: + json += "true"; break; - case QJsonValue::Double: { - const double d = v.toDouble(b); - if (qIsFinite(d)) { // +2 to format to ensure the expected precision + case QCborValue::False: + json += "false"; + break; + case QCborValue::Integer: + case QCborValue::Double: { + const double d = v.toDouble(); + if (qIsFinite(d)) { quint64 absInt; json += QByteArray::number(d, convertDoubleTo(std::abs(d), &absInt) ? 'f' : 'g', QLocale::FloatingPointShortest); @@ -144,42 +149,44 @@ static void valueToJson(const QJsonPrivate::Base *b, const QJsonPrivate::Value & } break; } - case QJsonValue::String: + case QCborValue::String: json += '"'; - json += escapedString(v.toString(b)); + json += escapedString(v.toString()); json += '"'; break; - case QJsonValue::Array: + case QCborValue::Array: json += compact ? "[" : "[\n"; - arrayContentToJson(static_cast(v.base(b)), json, indent + (compact ? 0 : 1), compact); + arrayContentToJson( + QJsonPrivate::Value::container(v), json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); json += ']'; break; - case QJsonValue::Object: + case QCborValue::Map: json += compact ? "{" : "{\n"; - objectContentToJson(static_cast(v.base(b)), json, indent + (compact ? 0 : 1), compact); + objectContentToJson( + QJsonPrivate::Value::container(v), json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); json += '}'; break; - case QJsonValue::Null: + case QCborValue::Null: default: json += "null"; } } -static void arrayContentToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact) +static void arrayContentToJson(const QCborContainerPrivate *a, QByteArray &json, int indent, bool compact) { - if (!a || !a->length) + if (!a || a->elements.empty()) return; QByteArray indentString(4*indent, ' '); - uint i = 0; - while (1) { + qsizetype i = 0; + while (true) { json += indentString; - valueToJson(a, a->at(i), json, indent, compact); + valueToJson(a->valueAt(i), json, indent, compact); - if (++i == a->length) { + if (++i == a->elements.size()) { if (!compact) json += '\n'; break; @@ -190,23 +197,23 @@ static void arrayContentToJson(const QJsonPrivate::Array *a, QByteArray &json, i } -static void objectContentToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact) +static void objectContentToJson(const QCborContainerPrivate *o, QByteArray &json, int indent, bool compact) { - if (!o || !o->length) + if (!o || o->elements.empty()) return; QByteArray indentString(4*indent, ' '); - uint i = 0; - while (1) { - QJsonPrivate::Entry *e = o->entryAt(i); + qsizetype i = 0; + while (true) { + QCborValue e = o->valueAt(i); json += indentString; json += '"'; - json += escapedString(e->key()); + json += escapedString(o->valueAt(i).toString()); json += compact ? "\":" : "\": "; - valueToJson(o, e->value, json, indent, compact); + valueToJson(o->valueAt(i + 1), json, indent, compact); - if (++i == o->length) { + if ((i += 2) == o->elements.size()) { if (!compact) json += '\n'; break; @@ -216,18 +223,18 @@ static void objectContentToJson(const QJsonPrivate::Object *o, QByteArray &json, } } -void Writer::objectToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact) +void Writer::objectToJson(const QCborContainerPrivate *o, QByteArray &json, int indent, bool compact) { - json.reserve(json.size() + (o ? (int)o->size : 16)); + json.reserve(json.size() + (o ? (int)o->elements.size() : 16)); json += compact ? "{" : "{\n"; objectContentToJson(o, json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); json += compact ? "}" : "}\n"; } -void Writer::arrayToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact) +void Writer::arrayToJson(const QCborContainerPrivate *a, QByteArray &json, int indent, bool compact) { - json.reserve(json.size() + (a ? (int)a->size : 16)); + json.reserve(json.size() + (a ? (int)a->elements.size() : 16)); json += compact ? "[" : "[\n"; arrayContentToJson(a, json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); diff --git a/src/corelib/serialization/qjsonwriter_p.h b/src/corelib/serialization/qjsonwriter_p.h index 76a8460449..8c263bb7c3 100644 --- a/src/corelib/serialization/qjsonwriter_p.h +++ b/src/corelib/serialization/qjsonwriter_p.h @@ -62,8 +62,8 @@ namespace QJsonPrivate class Writer { public: - static void objectToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact = false); - static void arrayToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact = false); + static void objectToJson(const QCborContainerPrivate *o, QByteArray &json, int indent, bool compact = false); + static void arrayToJson(const QCborContainerPrivate *a, QByteArray &json, int indent, bool compact = false); }; } diff --git a/src/corelib/serialization/serialization.pri b/src/corelib/serialization/serialization.pri index 5310fddd67..7407e20d9e 100644 --- a/src/corelib/serialization/serialization.pri +++ b/src/corelib/serialization/serialization.pri @@ -25,7 +25,6 @@ SOURCES += \ serialization/qcbordiagnostic.cpp \ serialization/qcborvalue.cpp \ serialization/qdatastream.cpp \ - serialization/qjson.cpp \ serialization/qjsoncbor.cpp \ serialization/qjsondocument.cpp \ serialization/qjsonobject.cpp \ @@ -45,6 +44,20 @@ qtConfig(cborstream): { serialization/qcborstream.h } +qtConfig(binaryjson): { + HEADERS += \ + serialization/qbinaryjson_p.h \ + serialization/qbinaryjsonarray_p.h \ + serialization/qbinaryjsonobject_p.h \ + serialization/qbinaryjsonvalue_p.h + + SOURCES += \ + serialization/qbinaryjson.cpp \ + serialization/qbinaryjsonarray.cpp \ + serialization/qbinaryjsonobject.cpp \ + serialization/qbinaryjsonvalue.cpp \ +} + false: SOURCES += \ serialization/qcborarray.cpp \ serialization/qcbormap.cpp diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index 0ed92514f9..3c03cf40e4 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -1498,7 +1498,7 @@ void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractSta case QStateMachine::StateMachineChildModeSetToParallelError: Q_ASSERT(currentContext != nullptr); - errorString = QStateMachine::tr("Child mode of state machine '%1' is not 'ExclusiveStates'!") + errorString = QStateMachine::tr("Child mode of state machine '%1' is not 'ExclusiveStates'.") .arg(currentContext->objectName()); break; @@ -2469,7 +2469,7 @@ QStateMachine::QStateMachine(QObject *parent) and \a parent. \warning Do not set the \a childMode to anything else than \l{ExclusiveStates}, otherwise the - state machine is invalid, and might work incorrectly! + state machine is invalid, and might work incorrectly. */ QStateMachine::QStateMachine(QState::ChildMode childMode, QObject *parent) : QState(*new QStateMachinePrivate, /*parentState=*/0) diff --git a/src/corelib/tools/qsharedpointer_impl.h b/src/corelib/tools/qsharedpointer_impl.h index 198cc58c38..362d57fb9a 100644 --- a/src/corelib/tools/qsharedpointer_impl.h +++ b/src/corelib/tools/qsharedpointer_impl.h @@ -71,21 +71,6 @@ QT_END_NAMESPACE QT_BEGIN_NAMESPACE - -// Macro QSHAREDPOINTER_VERIFY_AUTO_CAST -// generates a compiler error if the following construct isn't valid: -// T *ptr1; -// X *ptr2 = ptr1; -// -#ifdef QT_NO_DEBUG -# define QSHAREDPOINTER_VERIFY_AUTO_CAST(T, X) qt_noop() -#else - -template inline void qt_sharedpointer_cast_check(T *) { } -# define QSHAREDPOINTER_VERIFY_AUTO_CAST(T, X) \ - qt_sharedpointer_cast_check(static_cast(0)) -#endif - // // forward declarations // @@ -299,6 +284,9 @@ template class QSharedPointer { typedef T *QSharedPointer:: *RestrictedBool; typedef QtSharedPointer::ExternalRefCountData Data; + template + using IfCompatible = typename std::enable_if::value, bool>::type; + public: typedef T Type; typedef T element_type; @@ -322,11 +310,11 @@ public: Q_DECL_CONSTEXPR QSharedPointer(std::nullptr_t) noexcept : value(nullptr), d(nullptr) { } - template + template = true> inline explicit QSharedPointer(X *ptr) : value(ptr) // noexcept { internalConstruct(ptr, QtSharedPointer::NormalDeleter()); } - template + template = true> inline QSharedPointer(X *ptr, Deleter deleter) : value(ptr) // throws { internalConstruct(ptr, deleter); } @@ -354,7 +342,7 @@ public: return *this; } - template + template = true> QSharedPointer(QSharedPointer &&other) noexcept : value(other.value), d(other.d) { @@ -362,7 +350,7 @@ public: other.value = nullptr; } - template + template = true> QSharedPointer &operator=(QSharedPointer &&other) noexcept { QSharedPointer moved(std::move(other)); @@ -370,11 +358,11 @@ public: return *this; } - template + template = true> QSharedPointer(const QSharedPointer &other) noexcept : value(other.value), d(other.d) { if (d) ref(); } - template + template = true> inline QSharedPointer &operator=(const QSharedPointer &other) { QSharedPointer copy(other); @@ -382,11 +370,11 @@ public: return *this; } - template + template = true> inline QSharedPointer(const QWeakPointer &other) : value(nullptr), d(nullptr) { *this = other; } - template + template = true> inline QSharedPointer &operator=(const QWeakPointer &other) { internalSet(other.d, other.value); return *this; } @@ -556,6 +544,8 @@ class QWeakPointer { typedef T *QWeakPointer:: *RestrictedBool; typedef QtSharedPointer::ExternalRefCountData Data; + template + using IfCompatible = typename std::enable_if::value, bool>::type; public: typedef T element_type; @@ -581,14 +571,14 @@ public: #ifndef QT_NO_QOBJECT // special constructor that is enabled only if X derives from QObject #if QT_DEPRECATED_SINCE(5, 0) - template + template = true> QT_DEPRECATED inline QWeakPointer(X *ptr) : d(ptr ? Data::getAndRef(ptr) : nullptr), value(ptr) { } #endif #endif #if QT_DEPRECATED_SINCE(5, 0) - template + template = true> QT_DEPRECATED inline QWeakPointer &operator=(X *ptr) { return *this = QWeakPointer(ptr); } #endif @@ -624,11 +614,11 @@ public: return *this; } - template + template = true> inline QWeakPointer(const QWeakPointer &o) : d(nullptr), value(nullptr) { *this = o; } - template + template = true> inline QWeakPointer &operator=(const QWeakPointer &o) { // conversion between X and T could require access to the virtual table @@ -645,14 +635,13 @@ public: bool operator!=(const QWeakPointer &o) const noexcept { return !(*this == o); } - template + template = true> inline QWeakPointer(const QSharedPointer &o) : d(nullptr), value(nullptr) { *this = o; } - template + template = true> inline QWeakPointer &operator=(const QSharedPointer &o) { - QSHAREDPOINTER_VERIFY_AUTO_CAST(T, X); // if you get an error in this line, the cast is invalid internalSet(o.d, o.data()); return *this; } @@ -689,7 +678,7 @@ public: { return *this = QWeakPointer(ptr, true); } #ifndef QT_NO_QOBJECT - template + template = true> inline QWeakPointer(X *ptr, bool) : d(ptr ? Data::getAndRef(ptr) : nullptr), value(ptr) { } #endif diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index dda407181a..2779b97fbd 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -4145,11 +4145,11 @@ QPaintEngine *QImage::paintEngine() const if (!d->paintEngine) { QPaintDevice *paintDevice = const_cast(this); - QPaintEngine *paintEngine = 0; QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration(); if (platformIntegration) - paintEngine = platformIntegration->createImagePaintEngine(paintDevice); - d->paintEngine = paintEngine ? paintEngine : new QRasterPaintEngine(paintDevice); + d->paintEngine = platformIntegration->createImagePaintEngine(paintDevice); + if (!d->paintEngine) + d->paintEngine = new QRasterPaintEngine(paintDevice); } return d->paintEngine; diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index fa4c419ef0..f4a5c0e104 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -1869,7 +1869,20 @@ bool QGuiApplication::event(QEvent *e) { if(e->type() == QEvent::LanguageChange) { setLayoutDirection(qt_detectRTLLanguage()?Qt::RightToLeft:Qt::LeftToRight); + } else if (e->type() == QEvent::Quit) { + // Close open windows. This is done in order to deliver de-expose + // events while the event loop is still running. + for (QWindow *topLevelWindow : QGuiApplication::topLevelWindows()) { + // Already closed windows will not have a platform window, skip those + if (!topLevelWindow->handle()) + continue; + if (!topLevelWindow->close()) { + e->ignore(); + return true; + } + } } + return QCoreApplication::event(e); } @@ -1946,6 +1959,9 @@ void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePriv QWindowSystemInterfacePrivate::ApplicationStateChangedEvent * changeEvent = static_cast(e); QGuiApplicationPrivate::setApplicationState(changeEvent->newState, changeEvent->forcePropagate); } break; + case QWindowSystemInterfacePrivate::ApplicationTermination: + QGuiApplicationPrivate::processApplicationTermination(e); + break; case QWindowSystemInterfacePrivate::FlushEvents: { QWindowSystemInterfacePrivate::FlushEventsEvent *flushEventsEvent = static_cast(e); QWindowSystemInterface::deferredFlushWindowSystemEvents(flushEventsEvent->flags); } @@ -3495,6 +3511,13 @@ bool QGuiApplicationPrivate::tryCloseRemainingWindows(QWindowList processedWindo return true; } +void QGuiApplicationPrivate::processApplicationTermination(QWindowSystemInterfacePrivate::WindowSystemEvent *windowSystemEvent) +{ + QEvent event(QEvent::Quit); + QGuiApplication::sendSpontaneousEvent(QGuiApplication::instance(), &event); + windowSystemEvent->eventAccepted = event.isAccepted(); +} + /*! \since 5.2 \fn Qt::ApplicationState QGuiApplication::applicationState() diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h index 73d137619e..56a3be1122 100644 --- a/src/gui/kernel/qguiapplication_p.h +++ b/src/gui/kernel/qguiapplication_p.h @@ -144,6 +144,8 @@ public: static void processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e); + static void processApplicationTermination(QWindowSystemInterfacePrivate::WindowSystemEvent *e); + static void updateFilteredScreenOrientation(QScreen *screen); static void reportScreenOrientationChange(QScreen *screen); static void processScreenOrientationChange(QWindowSystemInterfacePrivate::ScreenOrientationEvent *e); diff --git a/src/gui/kernel/qkeysequence.cpp b/src/gui/kernel/qkeysequence.cpp index 25766467eb..d5bdf1f15b 100644 --- a/src/gui/kernel/qkeysequence.cpp +++ b/src/gui/kernel/qkeysequence.cpp @@ -490,6 +490,8 @@ static const struct { { Qt::Key_LaunchD, QT_TRANSLATE_NOOP("QShortcut", "Launch (D)") }, { Qt::Key_LaunchE, QT_TRANSLATE_NOOP("QShortcut", "Launch (E)") }, { Qt::Key_LaunchF, QT_TRANSLATE_NOOP("QShortcut", "Launch (F)") }, + { Qt::Key_LaunchG, QT_TRANSLATE_NOOP("QShortcut", "Launch (G)") }, + { Qt::Key_LaunchH, QT_TRANSLATE_NOOP("QShortcut", "Launch (H)") }, { Qt::Key_MonBrightnessUp, QT_TRANSLATE_NOOP("QShortcut", "Monitor Brightness Up") }, { Qt::Key_MonBrightnessDown, QT_TRANSLATE_NOOP("QShortcut", "Monitor Brightness Down") }, { Qt::Key_KeyboardLightOnOff, QT_TRANSLATE_NOOP("QShortcut", "Keyboard Light On/Off") }, @@ -516,9 +518,11 @@ static const struct { { Qt::Key_Book, QT_TRANSLATE_NOOP("QShortcut", "Book") }, { Qt::Key_CD, QT_TRANSLATE_NOOP("QShortcut", "CD") }, { Qt::Key_Calculator, QT_TRANSLATE_NOOP("QShortcut", "Calculator") }, + { Qt::Key_Calendar, QT_TRANSLATE_NOOP("QShortcut", "Calendar") }, { Qt::Key_Clear, QT_TRANSLATE_NOOP("QShortcut", "Clear") }, { Qt::Key_ClearGrab, QT_TRANSLATE_NOOP("QShortcut", "Clear Grab") }, { Qt::Key_Close, QT_TRANSLATE_NOOP("QShortcut", "Close") }, + { Qt::Key_ContrastAdjust, QT_TRANSLATE_NOOP("QShortcut", "Adjust contrast") }, { Qt::Key_Copy, QT_TRANSLATE_NOOP("QShortcut", "Copy") }, { Qt::Key_Cut, QT_TRANSLATE_NOOP("QShortcut", "Cut") }, { Qt::Key_Display, QT_TRANSLATE_NOOP("QShortcut", "Display") }, @@ -532,6 +536,7 @@ static const struct { { Qt::Key_LogOff, QT_TRANSLATE_NOOP("QShortcut", "Logoff") }, { Qt::Key_Market, QT_TRANSLATE_NOOP("QShortcut", "Market") }, { Qt::Key_Meeting, QT_TRANSLATE_NOOP("QShortcut", "Meeting") }, + { Qt::Key_Memo, QT_TRANSLATE_NOOP("QShortcut", "Memo") }, { Qt::Key_MenuKB, QT_TRANSLATE_NOOP("QShortcut", "Keyboard Menu") }, { Qt::Key_MenuPB, QT_TRANSLATE_NOOP("QShortcut", "Menu PB") }, { Qt::Key_MySites, QT_TRANSLATE_NOOP("QShortcut", "My Sites") }, @@ -552,6 +557,7 @@ static const struct { { Qt::Key_Support, QT_TRANSLATE_NOOP("QShortcut", "Support") }, { Qt::Key_TaskPane, QT_TRANSLATE_NOOP("QShortcut", "Task Panel") }, { Qt::Key_Terminal, QT_TRANSLATE_NOOP("QShortcut", "Terminal") }, + { Qt::Key_ToDoList, QT_TRANSLATE_NOOP("QShortcut", "To-do list") }, { Qt::Key_Tools, QT_TRANSLATE_NOOP("QShortcut", "Tools") }, { Qt::Key_Travel, QT_TRANSLATE_NOOP("QShortcut", "Travel") }, { Qt::Key_Video, QT_TRANSLATE_NOOP("QShortcut", "Video") }, diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp index 5ae8013b82..71780ed609 100644 --- a/src/gui/kernel/qwindowsysteminterface.cpp +++ b/src/gui/kernel/qwindowsysteminterface.cpp @@ -285,6 +285,12 @@ QT_DEFINE_QPA_EVENT_HANDLER(void, handleApplicationStateChanged, Qt::Application QWindowSystemInterfacePrivate::handleWindowSystemEvent(e); } +QT_DEFINE_QPA_EVENT_HANDLER(bool, handleApplicationTermination) +{ + auto *e = new QWindowSystemInterfacePrivate::WindowSystemEvent(QWindowSystemInterfacePrivate::ApplicationTermination); + return QWindowSystemInterfacePrivate::handleWindowSystemEvent(e); +} + QWindowSystemInterfacePrivate::GeometryChangeEvent::GeometryChangeEvent(QWindow *window, const QRect &newGeometry) : WindowSystemEvent(GeometryChange) , window(window) diff --git a/src/gui/kernel/qwindowsysteminterface.h b/src/gui/kernel/qwindowsysteminterface.h index 4a0bc858a9..d5a4ad30d8 100644 --- a/src/gui/kernel/qwindowsysteminterface.h +++ b/src/gui/kernel/qwindowsysteminterface.h @@ -215,6 +215,9 @@ public: template static void handleApplicationStateChanged(Qt::ApplicationState newState, bool forcePropagate = false); + template + static bool handleApplicationTermination(); + #if QT_CONFIG(draganddrop) #if QT_DEPRECATED_SINCE(5, 11) QT_DEPRECATED static QPlatformDragQtResponse handleDrag(QWindow *window, const QMimeData *dropData, diff --git a/src/gui/kernel/qwindowsysteminterface_p.h b/src/gui/kernel/qwindowsysteminterface_p.h index 55fd181ef0..6e4bce607e 100644 --- a/src/gui/kernel/qwindowsysteminterface_p.h +++ b/src/gui/kernel/qwindowsysteminterface_p.h @@ -99,7 +99,8 @@ public: ApplicationStateChanged = 0x19, FlushEvents = 0x20, WindowScreenChanged = 0x21, - SafeAreaMarginsChanged = 0x22 + SafeAreaMarginsChanged = 0x22, + ApplicationTermination = 0x23 }; class WindowSystemEvent { diff --git a/src/gui/opengl/qopenglfunctions_4_2_compatibility.h b/src/gui/opengl/qopenglfunctions_4_2_compatibility.h index 6726d5fc44..a48d581c2d 100644 --- a/src/gui/opengl/qopenglfunctions_4_2_compatibility.h +++ b/src/gui/opengl/qopenglfunctions_4_2_compatibility.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_2_Compatibility : public QAbstractOpenGLFunctions @@ -5632,6 +5638,10 @@ inline void QOpenGLFunctions_4_2_Compatibility::glVertexAttribI1i(GLuint index, QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_2_core.h b/src/gui/opengl/qopenglfunctions_4_2_core.h index a921329741..5ca98e9808 100644 --- a/src/gui/opengl/qopenglfunctions_4_2_core.h +++ b/src/gui/opengl/qopenglfunctions_4_2_core.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_2_Core : public QAbstractOpenGLFunctions @@ -3027,6 +3033,10 @@ inline void QOpenGLFunctions_4_2_Core::glDrawArraysInstancedBaseInstance(GLenum QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_3_compatibility.h b/src/gui/opengl/qopenglfunctions_4_3_compatibility.h index b9d4eb1d6f..d969f5b3b4 100644 --- a/src/gui/opengl/qopenglfunctions_4_3_compatibility.h +++ b/src/gui/opengl/qopenglfunctions_4_3_compatibility.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_3_Compatibility : public QAbstractOpenGLFunctions @@ -5839,6 +5845,10 @@ inline void QOpenGLFunctions_4_3_Compatibility::glVertexAttribI1i(GLuint index, QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_3_core.h b/src/gui/opengl/qopenglfunctions_4_3_core.h index da552d64af..13675caf62 100644 --- a/src/gui/opengl/qopenglfunctions_4_3_core.h +++ b/src/gui/opengl/qopenglfunctions_4_3_core.h @@ -57,6 +57,13 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_3_Core : public QAbstractOpenGLFunctions @@ -3230,6 +3237,10 @@ inline void QOpenGLFunctions_4_3_Core::glClearBufferData(GLenum target, GLenum i QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_4_compatibility.h b/src/gui/opengl/qopenglfunctions_4_4_compatibility.h index 7a05bd802d..0acab349a1 100644 --- a/src/gui/opengl/qopenglfunctions_4_4_compatibility.h +++ b/src/gui/opengl/qopenglfunctions_4_4_compatibility.h @@ -59,6 +59,12 @@ QT_BEGIN_NAMESPACE +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + class Q_GUI_EXPORT QOpenGLFunctions_4_4_Compatibility : public QAbstractOpenGLFunctions { public: @@ -5961,6 +5967,10 @@ inline void QOpenGLFunctions_4_4_Compatibility::glVertexP2ui(GLenum type, GLuint QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_4_core.h b/src/gui/opengl/qopenglfunctions_4_4_core.h index 6b29a9659b..1ad6f40214 100644 --- a/src/gui/opengl/qopenglfunctions_4_4_core.h +++ b/src/gui/opengl/qopenglfunctions_4_4_core.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_4_Core : public QAbstractOpenGLFunctions @@ -3415,6 +3421,10 @@ inline void QOpenGLFunctions_4_4_Core::glBufferStorage(GLenum target, GLsizeiptr QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_5_compatibility.h b/src/gui/opengl/qopenglfunctions_4_5_compatibility.h index a809c1c90b..9d9d14548b 100644 --- a/src/gui/opengl/qopenglfunctions_4_5_compatibility.h +++ b/src/gui/opengl/qopenglfunctions_4_5_compatibility.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_5_Compatibility : public QAbstractOpenGLFunctions @@ -6679,6 +6685,10 @@ inline void QOpenGLFunctions_4_5_Compatibility::glGetnMapdv(GLenum target, GLenu QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_5_core.h b/src/gui/opengl/qopenglfunctions_4_5_core.h index bb1b17f7b1..bf872c628b 100644 --- a/src/gui/opengl/qopenglfunctions_4_5_core.h +++ b/src/gui/opengl/qopenglfunctions_4_5_core.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_5_Core : public QAbstractOpenGLFunctions @@ -4056,6 +4062,11 @@ inline void QOpenGLFunctions_4_5_Core::glClipControl(GLenum origin, GLenum depth QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglversionfunctions.h b/src/gui/opengl/qopenglversionfunctions.h index aa6e49b9ae..4835ea4871 100644 --- a/src/gui/opengl/qopenglversionfunctions.h +++ b/src/gui/opengl/qopenglversionfunctions.h @@ -61,6 +61,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class QOpenGLContext; @@ -1897,6 +1903,10 @@ public: QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL #endif diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index dabad35688..8ef98d2e42 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -3518,14 +3518,36 @@ QRhiResource::Type QRhiSwapChain::resourceType() const \c{currentPixelSize() != surfacePixelSize()} then the swapchain needs to be resized. + \note Typical rendering logic will call this function to get the output + size when starting to prepare a new frame, and base dependent calculations + (such as, the viewport) on the size returned from this function. + + While in many cases the value is the same as \c{QWindow::size() * + QWindow::devicePixelRatio()}, relying on the QWindow-reported size is not + guaranteed to be correct on all platforms and graphics API implementations. + Using this function is therefore strongly recommended whenever there is a + need to identify the dimensions, in pixels, of the output layer or surface. + + This also has the added benefit of avoiding potential data races when QRhi + is used on a dedicated rendering thread, because the need to call QWindow + functions, that may then access data updated on the main thread, is + avoided. + \sa surfacePixelSize() */ /*! \fn QSize QRhiSwapChain::surfacePixelSize() - \return The size of the window's associated surface or layer. Do not assume - this is the same as QWindow::size() * QWindow::devicePixelRatio(). + \return The size of the window's associated surface or layer. + + \warning Do not assume this is the same as \c{QWindow::size() * + QWindow::devicePixelRatio()}. With some graphics APIs and windowing system + interfaces (for example, Vulkan) there is a theoretical possibility for a + surface to assume a size different from the associated window. To support + these cases, rendering logic must always base size-derived calculations + (such as, viewports) on the size reported from QRhiSwapChain, and never on + the size queried from QWindow. \note Can also be called before buildOrResize(), if at least window() is already set) This in combination with currentPixelSize() allows to detect diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index dec28cac9b..abee843a74 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -2961,7 +2961,7 @@ bool QRhiGles2::isProgramBinaryDiskCacheEnabled() const return checker.get(ctx)->isSupported(); } -static QOpenGLProgramBinaryCache qrhi_programBinaryCache; +Q_GLOBAL_STATIC(QOpenGLProgramBinaryCache, qrhi_programBinaryCache); static inline QShader::Stage toShaderStage(QRhiShaderStage::Type type) { @@ -2995,7 +2995,7 @@ QRhiGles2::DiskCacheResult QRhiGles2::tryLoadFromDiskCache(const QRhiShaderStage } diskCacheKey = binaryProgram.cacheKey(); - if (qrhi_programBinaryCache.load(diskCacheKey, program)) { + if (qrhi_programBinaryCache()->load(diskCacheKey, program)) { qCDebug(lcOpenGLProgramDiskCache, "Program binary received from cache, program %u, key %s", program, diskCacheKey.constData()); result = QRhiGles2::DiskCacheHit; @@ -3013,7 +3013,7 @@ void QRhiGles2::trySaveToDiskCache(GLuint program, const QByteArray &cacheKey) if (isProgramBinaryDiskCacheEnabled()) { qCDebug(lcOpenGLProgramDiskCache, "Saving program binary, program %u, key %s", program, cacheKey.constData()); - qrhi_programBinaryCache.save(cacheKey, program); + qrhi_programBinaryCache()->save(cacheKey, program); } } diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 5f14d917b8..131b2da802 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -35,8 +35,6 @@ ****************************************************************************/ #include "qrhimetal_p_p.h" -#include "qshader_p.h" -#include "qshaderdescription_p.h" #include #include #include @@ -143,8 +141,10 @@ struct QMetalShader id lib = nil; id func = nil; std::array localSize; + QShader::NativeResourceBindingMap nativeResourceBindingMap; void release() { + nativeResourceBindingMap.clear(); [lib release]; lib = nil; [func release]; @@ -164,7 +164,7 @@ struct QRhiMetalData const QRhiDepthStencilClearValue &depthStencilClearValue, int colorAttCount); id createMetalLib(const QShader &shader, QShader::Variant shaderVariant, - QString *error, QByteArray *entryPoint); + QString *error, QByteArray *entryPoint, QShaderKey *activeKey); id createMSLShaderFunction(id lib, const QByteArray &entryPoint); struct DeferredReleaseEntry { @@ -653,18 +653,40 @@ QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings() return new QMetalShaderResourceBindings(this); } -void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, +enum class BindingType { + Buffer, + Texture, + Sampler +}; + +static inline int mapBinding(int binding, + int stageIndex, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[], + BindingType type) +{ + const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex]; + if (map) { + auto it = map->constFind(binding); + if (it != map->cend()) + return type == BindingType::Sampler ? it->second : it->first; + } + return binding; +} + +void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, + QMetalCommandBuffer *cbD, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, - bool offsetOnlyChange) + bool offsetOnlyChange, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]) { - static const int KNOWN_STAGES = 3; struct { QRhiBatchedBindings > buffers; QRhiBatchedBindings bufferOffsets; QRhiBatchedBindings > textures; QRhiBatchedBindings > samplers; - } res[KNOWN_STAGES]; + } res[SUPPORTED_STAGES]; + enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2 }; for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) { const QRhiShaderResourceBinding::Data *b = binding.data(); @@ -682,16 +704,16 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } } if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].buffers.feed(b->binding, mtlbuf); - res[0].bufferOffsets.feed(b->binding, offset); + res[VERTEX].buffers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[VERTEX].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].buffers.feed(b->binding, mtlbuf); - res[1].bufferOffsets.feed(b->binding, offset); + res[FRAGMENT].buffers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[FRAGMENT].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].buffers.feed(b->binding, mtlbuf); - res[2].bufferOffsets.feed(b->binding, offset); + res[COMPUTE].buffers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[COMPUTE].bufferOffsets.feed(b->binding, offset); } } break; @@ -700,16 +722,16 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].textures.feed(b->binding, texD->d->tex); - res[0].samplers.feed(b->binding, samplerD->d->samplerState); + res[VERTEX].textures.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[VERTEX].samplers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].textures.feed(b->binding, texD->d->tex); - res[1].samplers.feed(b->binding, samplerD->d->samplerState); + res[FRAGMENT].textures.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[FRAGMENT].samplers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].textures.feed(b->binding, texD->d->tex); - res[2].samplers.feed(b->binding, samplerD->d->samplerState); + res[COMPUTE].textures.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[COMPUTE].samplers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } } break; @@ -722,11 +744,11 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); id t = texD->d->viewForLevel(b->u.simage.level); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) - res[0].textures.feed(b->binding, t); + res[VERTEX].textures.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture), t); if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) - res[1].textures.feed(b->binding, t); + res[FRAGMENT].textures.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture), t); if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) - res[2].textures.feed(b->binding, t); + res[COMPUTE].textures.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture), t); } break; case QRhiShaderResourceBinding::BufferLoad: @@ -739,16 +761,16 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD id mtlbuf = bufD->d->buf[0]; uint offset = uint(b->u.sbuf.offset); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].buffers.feed(b->binding, mtlbuf); - res[0].bufferOffsets.feed(b->binding, offset); + res[VERTEX].buffers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[VERTEX].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].buffers.feed(b->binding, mtlbuf); - res[1].bufferOffsets.feed(b->binding, offset); + res[FRAGMENT].buffers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[FRAGMENT].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].buffers.feed(b->binding, mtlbuf); - res[2].bufferOffsets.feed(b->binding, offset); + res[COMPUTE].buffers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[COMPUTE].bufferOffsets.feed(b->binding, offset); } } break; @@ -758,25 +780,30 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } } - for (int idx = 0; idx < KNOWN_STAGES; ++idx) { - res[idx].buffers.finish(); - res[idx].bufferOffsets.finish(); + for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) { + if (cbD->recordingPass != QMetalCommandBuffer::RenderPass && (stage == VERTEX || stage == FRAGMENT)) + continue; + if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && stage == COMPUTE) + continue; - for (int i = 0, ie = res[idx].buffers.batches.count(); i != ie; ++i) { - const auto &bufferBatch(res[idx].buffers.batches[i]); - const auto &offsetBatch(res[idx].bufferOffsets.batches[i]); - switch (idx) { - case 0: + res[stage].buffers.finish(); + res[stage].bufferOffsets.finish(); + + for (int i = 0, ie = res[stage].buffers.batches.count(); i != ie; ++i) { + const auto &bufferBatch(res[stage].buffers.batches[i]); + const auto &offsetBatch(res[stage].bufferOffsets.batches[i]); + switch (stage) { + case VERTEX: [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData() offsets: offsetBatch.resources.constData() withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))]; break; - case 1: + case FRAGMENT: [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData() offsets: offsetBatch.resources.constData() withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))]; break; - case 2: + case COMPUTE: [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData() offsets: offsetBatch.resources.constData() withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))]; @@ -790,21 +817,21 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD if (offsetOnlyChange) continue; - res[idx].textures.finish(); - res[idx].samplers.finish(); + res[stage].textures.finish(); + res[stage].samplers.finish(); - for (int i = 0, ie = res[idx].textures.batches.count(); i != ie; ++i) { - const auto &batch(res[idx].textures.batches[i]); - switch (idx) { - case 0: + for (int i = 0, ie = res[stage].textures.batches.count(); i != ie; ++i) { + const auto &batch(res[stage].textures.batches[i]); + switch (stage) { + case VERTEX: [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; - case 1: + case FRAGMENT: [cbD->d->currentRenderPassEncoder setFragmentTextures: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; - case 2: + case COMPUTE: [cbD->d->currentComputePassEncoder setTextures: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; @@ -813,18 +840,18 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD break; } } - for (int i = 0, ie = res[idx].samplers.batches.count(); i != ie; ++i) { - const auto &batch(res[idx].samplers.batches[i]); - switch (idx) { - case 0: + for (int i = 0, ie = res[stage].samplers.batches.count(); i != ie; ++i) { + const auto &batch(res[stage].samplers.batches[i]); + switch (stage) { + case VERTEX: [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; - case 1: + case FRAGMENT: [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; - case 2: + case COMPUTE: [cbD->d->currentComputePassEncoder setSamplerStates: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; @@ -973,18 +1000,22 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind // dynamic uniform buffer offsets always trigger a rebind if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) { + const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr }; if (gfxPsD) { cbD->currentGraphicsSrb = srb; cbD->currentComputeSrb = nullptr; + resBindMaps[0] = &gfxPsD->d->vs.nativeResourceBindingMap; + resBindMaps[1] = &gfxPsD->d->fs.nativeResourceBindingMap; } else { cbD->currentGraphicsSrb = nullptr; cbD->currentComputeSrb = srb; + resBindMaps[2] = &compPsD->d->cs.nativeResourceBindingMap; } cbD->currentSrbGeneration = srbD->generation; cbD->currentResSlot = resSlot; const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt; - enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange); + enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange, resBindMaps); } } @@ -3081,9 +3112,10 @@ static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c) } id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant, - QString *error, QByteArray *entryPoint) + QString *error, QByteArray *entryPoint, QShaderKey *activeKey) { - QShaderCode mtllib = shader.shader({ QShader::MetalLibShader, 12, shaderVariant }); + QShaderKey key = { QShader::MetalLibShader, 12, shaderVariant }; + QShaderCode mtllib = shader.shader(key); if (!mtllib.shader().isEmpty()) { dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(), size_t(mtllib.shader().size()), @@ -3094,6 +3126,7 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var dispatch_release(data); if (!err) { *entryPoint = mtllib.entryPoint(); + *activeKey = key; return lib; } else { const QString msg = QString::fromNSString(err.localizedDescription); @@ -3101,7 +3134,8 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var } } - QShaderCode mslSource = shader.shader({ QShader::MslShader, 12, shaderVariant }); + key = { QShader::MslShader, 12, shaderVariant }; + QShaderCode mslSource = shader.shader(key); if (mslSource.shader().isEmpty()) { qWarning() << "No MSL 1.2 code found in baked shader" << shader; return nil; @@ -3122,6 +3156,7 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var } *entryPoint = mslSource.entryPoint(); + *activeKey = key; return lib; } @@ -3195,9 +3230,12 @@ bool QMetalGraphicsPipeline::build() break; } } else { + const QShader shader = shaderStage.shader(); QString error; QByteArray entryPoint; - id lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint); + QShaderKey activeKey; + id lib = rhiD->d->createMetalLib(shader, shaderStage.shaderVariant(), + &error, &entryPoint, &activeKey); if (!lib) { qWarning("MSL shader compilation failed: %s", qPrintable(error)); return false; @@ -3218,6 +3256,8 @@ bool QMetalGraphicsPipeline::build() case QRhiShaderStage::Vertex: d->vs.lib = lib; d->vs.func = func; + if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey)) + d->vs.nativeResourceBindingMap = *map; rhiD->d->shaderCache.insert(shaderStage, d->vs); [d->vs.lib retain]; [d->vs.func retain]; @@ -3226,6 +3266,8 @@ bool QMetalGraphicsPipeline::build() case QRhiShaderStage::Fragment: d->fs.lib = lib; d->fs.func = func; + if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey)) + d->fs.nativeResourceBindingMap = *map; rhiD->d->shaderCache.insert(shaderStage, d->fs); [d->fs.lib retain]; [d->fs.func retain]; @@ -3360,8 +3402,9 @@ bool QMetalComputePipeline::build() const QShader shader = m_shaderStage.shader(); QString error; QByteArray entryPoint; + QShaderKey activeKey; id lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(), - &error, &entryPoint); + &error, &entryPoint, &activeKey); if (!lib) { qWarning("MSL shader compilation failed: %s", qPrintable(error)); return false; @@ -3375,6 +3418,8 @@ bool QMetalComputePipeline::build() d->cs.lib = lib; d->cs.func = func; d->cs.localSize = shader.description().computeShaderLocalSize(); + if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey)) + d->cs.nativeResourceBindingMap = *map; if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) { for (QMetalShader &s : rhiD->d->shaderCache) diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index 688fec8147..2be86db5c8 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -433,10 +433,13 @@ public: qsizetype *curOfs); void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD); - void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, + static const int SUPPORTED_STAGES = 3; + void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, + QMetalCommandBuffer *cbD, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, - bool offsetOnlyChange); + bool offsetOnlyChange, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]); int effectiveSampleCount(int sampleCount) const; bool importedDevice = false; diff --git a/src/gui/rhi/qshader.cpp b/src/gui/rhi/qshader.cpp index 6a2c596557..c22b029dc8 100644 --- a/src/gui/rhi/qshader.cpp +++ b/src/gui/rhi/qshader.cpp @@ -214,7 +214,8 @@ QT_BEGIN_NAMESPACE QShader, it indicates no shader code was found for the requested key. */ -static const int QSB_VERSION = 1; +static const int QSB_VERSION = 2; +static const int QSB_VERSION_WITHOUT_BINDINGS = 1; /*! Constructs a new, empty (and thus invalid) QShader instance. @@ -345,6 +346,14 @@ void QShader::removeShader(const QShaderKey &key) d->shaders.erase(it); } +static void writeShaderKey(QDataStream *ds, const QShaderKey &k) +{ + *ds << k.source(); + *ds << k.sourceVersion().version(); + *ds << k.sourceVersion().flags(); + *ds << k.sourceVariant(); +} + /*! \return a serialized binary version of all the data held by the QShader, suitable for writing to files or other I/O devices. @@ -365,18 +374,42 @@ QByteArray QShader::serialized() const ds << d->shaders.count(); for (auto it = d->shaders.cbegin(), itEnd = d->shaders.cend(); it != itEnd; ++it) { const QShaderKey &k(it.key()); - ds << k.source(); - ds << k.sourceVersion().version(); - ds << k.sourceVersion().flags(); - ds << k.sourceVariant(); + writeShaderKey(&ds, k); const QShaderCode &shader(d->shaders.value(k)); ds << shader.shader(); ds << shader.entryPoint(); } + ds << d->bindings.count(); + for (auto it = d->bindings.cbegin(), itEnd = d->bindings.cend(); it != itEnd; ++it) { + const QShaderKey &k(it.key()); + writeShaderKey(&ds, k); + const NativeResourceBindingMap &map(it.value()); + ds << map.count(); + for (auto mapIt = map.cbegin(), mapItEnd = map.cend(); mapIt != mapItEnd; ++mapIt) { + ds << mapIt.key(); + ds << mapIt.value().first; + ds << mapIt.value().second; + } + } return qCompress(buf.buffer()); } +static void readShaderKey(QDataStream *ds, QShaderKey *k) +{ + int intVal; + *ds >> intVal; + k->setSource(QShader::Source(intVal)); + QShaderVersion ver; + *ds >> intVal; + ver.setVersion(intVal); + *ds >> intVal; + ver.setFlags(QShaderVersion::Flags(intVal)); + k->setSourceVersion(ver); + *ds >> intVal; + k->setSourceVariant(QShader::Variant(intVal)); +} + /*! Creates a new QShader instance from the given \a data. @@ -396,8 +429,11 @@ QShader QShader::fromSerialized(const QByteArray &data) Q_ASSERT(d->ref.loadRelaxed() == 1); // must be detached int intVal; ds >> intVal; - if (intVal != QSB_VERSION) + const int qsbVersion = intVal; + if (qsbVersion != QSB_VERSION && qsbVersion != QSB_VERSION_WITHOUT_BINDINGS) { + qWarning("Attempted to deserialize QShader with unknown version %d.", qsbVersion); return QShader(); + } ds >> intVal; d->stage = Stage(intVal); @@ -408,16 +444,7 @@ QShader QShader::fromSerialized(const QByteArray &data) ds >> count; for (int i = 0; i < count; ++i) { QShaderKey k; - ds >> intVal; - k.setSource(Source(intVal)); - QShaderVersion ver; - ds >> intVal; - ver.setVersion(intVal); - ds >> intVal; - ver.setFlags(QShaderVersion::Flags(intVal)); - k.setSourceVersion(ver); - ds >> intVal; - k.setSourceVariant(Variant(intVal)); + readShaderKey(&ds, &k); QShaderCode shader; QByteArray s; ds >> s; @@ -427,6 +454,27 @@ QShader QShader::fromSerialized(const QByteArray &data) d->shaders[k] = shader; } + if (qsbVersion != QSB_VERSION_WITHOUT_BINDINGS) { + ds >> count; + for (int i = 0; i < count; ++i) { + QShaderKey k; + readShaderKey(&ds, &k); + NativeResourceBindingMap map; + int mapSize; + ds >> mapSize; + for (int b = 0; b < mapSize; ++b) { + int binding; + ds >> binding; + int firstNativeBinding; + ds >> firstNativeBinding; + int secondNativeBinding; + ds >> secondNativeBinding; + map.insert(binding, { firstNativeBinding, secondNativeBinding }); + } + d->bindings.insert(k, map); + } + } + return bs; } @@ -460,7 +508,7 @@ bool operator==(const QShader &lhs, const QShader &rhs) Q_DECL_NOTHROW { return lhs.d->stage == rhs.d->stage && lhs.d->shaders == rhs.d->shaders; - // do not bother with desc, if the shader code is the same, the description must match too + // do not bother with desc and bindings, if the shader code is the same, the description must match too } /*! @@ -586,4 +634,66 @@ QDebug operator<<(QDebug dbg, const QShaderVersion &v) } #endif // QT_NO_DEBUG_STREAM +/*! + \typedef QShader::NativeResourceBindingMap + + Synonym for QHash>. + + The resource binding model QRhi assumes is based on SPIR-V. This means that + uniform buffers, storage buffers, combined image samplers, and storage + images share a common binding point space. The binding numbers in + QShaderDescription and QRhiShaderResourceBinding are expected to match the + \c binding layout qualifier in the Vulkan-compatible GLSL shader. + + Graphics APIs other than Vulkan may use a resource binding model that is + not fully compatible with this. In addition, the generator of the shader + code translated from SPIR-V may choose not to take the SPIR-V binding + qualifiers into account, for various reasons. (this is the case with the + Metal backend of SPIRV-Cross, for example). + + Therefore, a QShader may expose an additional map that describes what the + native binding point for a given SPIR-V binding is. The QRhi backends are + expected to use this map automatically, as appropriate. The value is a + pair, because combined image samplers may map to two native resources (a + texture and a sampler) in some shading languages. In that case the second + value refers to the sampler. +*/ + +/*! + \return the native binding map for \a key or null if no extra mapping is + available, or is not applicable. + */ +const QShader::NativeResourceBindingMap *QShader::nativeResourceBindingMap(const QShaderKey &key) const +{ + auto it = d->bindings.constFind(key); + if (it == d->bindings.cend()) + return nullptr; + + return &it.value(); +} + +/*! + Stores the given native resource binding \a map associated with \a key. + + \sa nativeResourceBindingMap() + */ +void QShader::setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map) +{ + detach(); + d->bindings[key] = map; +} + +/*! + Removes the native resource binding map for \a key. + */ +void QShader::removeResourceBindingMap(const QShaderKey &key) +{ + auto it = d->bindings.find(key); + if (it == d->bindings.end()) + return; + + detach(); + d->bindings.erase(it); +} + QT_END_NAMESPACE diff --git a/src/gui/rhi/qshader_p.h b/src/gui/rhi/qshader_p.h index 243842a95a..4b561b6fa9 100644 --- a/src/gui/rhi/qshader_p.h +++ b/src/gui/rhi/qshader_p.h @@ -149,6 +149,11 @@ public: QByteArray serialized() const; static QShader fromSerialized(const QByteArray &data); + using NativeResourceBindingMap = QHash >; // binding -> native_binding[, native_binding] + const NativeResourceBindingMap *nativeResourceBindingMap(const QShaderKey &key) const; + void setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map); + void removeResourceBindingMap(const QShaderKey &key); + private: QShaderPrivate *d; friend struct QShaderPrivate; diff --git a/src/gui/rhi/qshader_p_p.h b/src/gui/rhi/qshader_p_p.h index 6473590e95..4535e01491 100644 --- a/src/gui/rhi/qshader_p_p.h +++ b/src/gui/rhi/qshader_p_p.h @@ -66,7 +66,8 @@ struct Q_GUI_EXPORT QShaderPrivate : ref(1), stage(other->stage), desc(other->desc), - shaders(other->shaders) + shaders(other->shaders), + bindings(other->bindings) { } @@ -77,6 +78,7 @@ struct Q_GUI_EXPORT QShaderPrivate QShader::Stage stage = QShader::VertexStage; QShaderDescription desc; QHash shaders; + QHash bindings; }; QT_END_NAMESPACE diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp index 7be114adf9..ed23a4d8d9 100644 --- a/src/gui/text/qtextdocumentlayout.cpp +++ b/src/gui/text/qtextdocumentlayout.cpp @@ -2088,8 +2088,12 @@ void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *pain tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect); - if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen) - || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) { + // if the block is empty and it precedes a table, do not draw the cursor. + // the cursor is drawn later after the table has been drawn so no need + // to draw it here. + if (!isEmptyBlockBeforeTable(frameIteratorForTextPosition(blpos)) + && ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen) + || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty()))) { int cpos = context.cursorPosition; if (cpos < -1) cpos = tl->preeditAreaPosition() - (cpos + 2); diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index b37353bf2c..209433dac5 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -2125,22 +2125,7 @@ void QTextEngine::itemize() const } #if QT_CONFIG(harfbuzz) analysis = scriptAnalysis.data(); - if (qt_useHarfbuzzNG()) { - // ### pretend HB-old behavior for now - for (int i = 0; i < length; ++i) { - switch (analysis[i].script) { - case QChar::Script_Latin: - case QChar::Script_Hiragana: - case QChar::Script_Katakana: - case QChar::Script_Bopomofo: - case QChar::Script_Han: - analysis[i].script = QChar::Script_Common; - break; - default: - break; - } - } - } else { + if (!qt_useHarfbuzzNG()) { for (int i = 0; i < length; ++i) analysis[i].script = hbscript_to_script(script_to_hbscript(analysis[i].script)); } @@ -3619,7 +3604,12 @@ int QTextEngine::positionInLigature(const QScriptItem *si, int end, int clusterLength = 0; if (si->analysis.script != QChar::Script_Common && - si->analysis.script != QChar::Script_Greek) { + si->analysis.script != QChar::Script_Greek && + si->analysis.script != QChar::Script_Latin && + si->analysis.script != QChar::Script_Hiragana && + si->analysis.script != QChar::Script_Katakana && + si->analysis.script != QChar::Script_Bopomofo && + si->analysis.script != QChar::Script_Han) { if (glyph_pos == -1) return si->position + end; else { diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp index d7fb080b49..78d807106b 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qsslsocket_schannel.cpp @@ -431,6 +431,53 @@ QByteArray createAlpnString(const QByteArrayList &nextAllowedProtocols) return alpnString; } #endif // SUPPORTS_ALPN + +qint64 readToBuffer(QByteArray &buffer, QTcpSocket *plainSocket) +{ + Q_ASSERT(plainSocket); + static const qint64 shrinkCutoff = 1024 * 12; + static const qint64 defaultRead = 1024 * 16; + qint64 bytesRead = 0; + + const auto toRead = std::min(defaultRead, plainSocket->bytesAvailable()); + if (toRead > 0) { + const auto bufferSize = buffer.size(); + buffer.reserve(bufferSize + toRead); // avoid growth strategy kicking in + buffer.resize(bufferSize + toRead); + bytesRead = plainSocket->read(buffer.data() + bufferSize, toRead); + buffer.resize(bufferSize + bytesRead); + // In case of excessive memory usage we shrink: + if (buffer.size() < shrinkCutoff && buffer.capacity() > defaultRead) + buffer.shrink_to_fit(); + } + + return bytesRead; +} + +void retainExtraData(QByteArray &buffer, const SecBuffer &secBuffer) +{ + Q_ASSERT(secBuffer.BufferType == SECBUFFER_EXTRA); + if (int(secBuffer.cbBuffer) >= buffer.size()) + return; + +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl, "We got SECBUFFER_EXTRA, will retain %lu bytes", secBuffer.cbBuffer); +#endif + std::move(buffer.end() - secBuffer.cbBuffer, buffer.end(), buffer.begin()); + buffer.resize(secBuffer.cbBuffer); +} + +qint64 checkIncompleteData(const SecBuffer &secBuffer) +{ + if (secBuffer.BufferType == SECBUFFER_MISSING) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl, "Need %lu more bytes.", secBuffer.cbBuffer); +#endif + return secBuffer.cbBuffer; +} + return 0; +} + } // anonymous namespace bool QSslSocketPrivate::s_loadRootCertsOnDemand = true; @@ -619,8 +666,8 @@ bool QSslSocketBackendPrivate::acquireCredentialsHandle() nullptr); if (!chainContext) { const QString message = isClient - ? QSslSocket::tr("The certificate provided cannot be used for a client.") - : QSslSocket::tr("The certificate provided cannot be used for a server."); + ? QSslSocket::tr("The certificate provided cannot be used for a client.") + : QSslSocket::tr("The certificate provided cannot be used for a server."); setErrorAndEmit(QAbstractSocket::SocketError::SslInvalidUserDataError, message); return false; } @@ -774,7 +821,11 @@ bool QSslSocketBackendPrivate::acceptContext() Q_ASSERT(mode == QSslSocket::SslServerMode); ULONG contextReq = getContextRequirements(); - intermediateBuffer += plainSocket->read(16384); + if (missingData > plainSocket->bytesAvailable()) + return true; + + missingData = 0; + readToBuffer(intermediateBuffer, plainSocket); if (intermediateBuffer.isEmpty()) return true; // definitely need more data.. @@ -830,6 +881,7 @@ bool QSslSocketBackendPrivate::acceptContext() if (status == SEC_E_INCOMPLETE_MESSAGE) { // Need more data + missingData = checkIncompleteData(outBuffers[0]); return true; } @@ -837,9 +889,9 @@ bool QSslSocketBackendPrivate::acceptContext() // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel // inBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need to // be stored. - intermediateBuffer = intermediateBuffer.right(int(inBuffers[1].cbBuffer)); + retainExtraData(intermediateBuffer, inBuffers[1]); } else { /* No 'extra' data, message not incomplete */ - intermediateBuffer.clear(); + intermediateBuffer.resize(0); } if (status != SEC_I_CONTINUE_NEEDED) { @@ -865,11 +917,15 @@ bool QSslSocketBackendPrivate::performHandshake() Q_ASSERT(schannelState == SchannelState::PerformHandshake); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Bytes available from socket:" << plainSocket->bytesAvailable(); - qCDebug(lcSsl) << "intermediateBuffer size:" << intermediateBuffer.size(); + qCDebug(lcSsl, "Bytes available from socket: %lld", plainSocket->bytesAvailable()); + qCDebug(lcSsl, "intermediateBuffer size: %d", intermediateBuffer.size()); #endif - intermediateBuffer += plainSocket->read(16384); + if (missingData > plainSocket->bytesAvailable()) + return true; + + missingData = 0; + readToBuffer(intermediateBuffer, plainSocket); if (intermediateBuffer.isEmpty()) return true; // no data, will fail @@ -918,11 +974,10 @@ bool QSslSocketBackendPrivate::performHandshake() // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel // inputBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need to // be stored. - intermediateBuffer = intermediateBuffer.right(int(inputBuffers[1].cbBuffer)); - } else { + retainExtraData(intermediateBuffer, inputBuffers[1]); + } else if (status != SEC_E_INCOMPLETE_MESSAGE) { // Clear the buffer if we weren't asked for more data - if (status != SEC_E_INCOMPLETE_MESSAGE) - intermediateBuffer.clear(); + intermediateBuffer.resize(0); } switch (status) { case SEC_E_OK: @@ -955,6 +1010,7 @@ bool QSslSocketBackendPrivate::performHandshake() return true; case SEC_E_INCOMPLETE_MESSAGE: // Simply incomplete, wait for more data + missingData = checkIncompleteData(outBuffers[0]); return true; case SEC_E_ALGORITHM_MISMATCH: setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, @@ -1157,6 +1213,8 @@ void QSslSocketBackendPrivate::reset() connectionEncrypted = false; shutdown = false; renegotiating = false; + + missingData = 0; } void QSslSocketBackendPrivate::startClientEncryption() @@ -1228,8 +1286,7 @@ void QSslSocketBackendPrivate::transmit() fullMessage.resize(inputBuffers[0].cbBuffer + inputBuffers[1].cbBuffer + inputBuffers[2].cbBuffer); const qint64 bytesWritten = plainSocket->write(fullMessage); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Wrote" << bytesWritten << "of total" - << fullMessage.length() << "bytes"; + qCDebug(lcSsl, "Wrote %lld of total %d bytes", bytesWritten, fullMessage.length()); #endif if (bytesWritten >= 0) { totalBytesWritten += bytesWritten; @@ -1254,36 +1311,32 @@ void QSslSocketBackendPrivate::transmit() int totalRead = 0; bool hadIncompleteData = false; while (!readBufferMaxSize || buffer.size() < readBufferMaxSize) { - QByteArray ciphertext; - if (intermediateBuffer.length()) { + if (missingData > plainSocket->bytesAvailable()) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Restoring data from intermediateBuffer:" - << intermediateBuffer.length() << "bytes"; + qCDebug(lcSsl, "We're still missing %lld bytes, will check later.", missingData); #endif - ciphertext.swap(intermediateBuffer); + break; } - int initialLength = ciphertext.length(); - ciphertext += plainSocket->read(16384); + + missingData = 0; + const qint64 bytesRead = readToBuffer(intermediateBuffer, plainSocket); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Read" << ciphertext.length() - initialLength - << "encrypted bytes from the socket"; + qCDebug(lcSsl, "Read %lld encrypted bytes from the socket", bytesRead); #endif - if (ciphertext.length() == 0 || (hadIncompleteData && initialLength == ciphertext.length())) { + if (intermediateBuffer.length() == 0 || (hadIncompleteData && bytesRead == 0)) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << (hadIncompleteData ? "No new data received, leaving loop!" - : "Nothing to decrypt, leaving loop!"); + qCDebug(lcSsl, (hadIncompleteData ? "No new data received, leaving loop!" + : "Nothing to decrypt, leaving loop!")); #endif - if (ciphertext.length()) // We have data, it came from intermediateBuffer, swap back - intermediateBuffer.swap(ciphertext); break; } hadIncompleteData = false; #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Total amount of bytes to decrypt:" << ciphertext.length(); + qCDebug(lcSsl, "Total amount of bytes to decrypt: %d", intermediateBuffer.length()); #endif SecBuffer dataBuffer[4]{ - createSecBuffer(ciphertext, SECBUFFER_DATA), + createSecBuffer(intermediateBuffer, SECBUFFER_DATA), createSecBuffer(nullptr, 0, SECBUFFER_EMPTY), createSecBuffer(nullptr, 0, SECBUFFER_EMPTY), createSecBuffer(nullptr, 0, SECBUFFER_EMPTY) @@ -1299,34 +1352,30 @@ void QSslSocketBackendPrivate::transmit() if (dataBuffer[1].cbBuffer > 0) { // It is always decrypted in-place. // But [0] is the STREAM_HEADER, [1] is the DATA and [2] is the STREAM_TRAILER. - // The pointers in all of those still point into the 'ciphertext' byte array. + // The pointers in all of those still point into 'intermediateBuffer'. buffer.append(static_cast(dataBuffer[1].pvBuffer), dataBuffer[1].cbBuffer); totalRead += dataBuffer[1].cbBuffer; #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Decrypted" << dataBuffer[1].cbBuffer - << "bytes. New read buffer size:" << buffer.size(); + qCDebug(lcSsl, "Decrypted %lu bytes. New read buffer size: %d", + dataBuffer[1].cbBuffer, buffer.size()); #endif } if (dataBuffer[3].BufferType == SECBUFFER_EXTRA) { // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel // dataBuffer[3].cbBuffer indicates the amount of bytes _NOT_ processed, // the rest need to be stored. -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "We've got excess data, moving it to the intermediate buffer:" - << dataBuffer[3].cbBuffer << "bytes"; -#endif - intermediateBuffer = ciphertext.right(int(dataBuffer[3].cbBuffer)); + retainExtraData(intermediateBuffer, dataBuffer[3]); + } else { + intermediateBuffer.resize(0); } } if (status == SEC_E_INCOMPLETE_MESSAGE) { - // Need more data before we can decrypt.. to the buffer it goes! + missingData = checkIncompleteData(dataBuffer[0]); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl, "We didn't have enough data to decrypt anything, will try again!"); #endif - Q_ASSERT(intermediateBuffer.isEmpty()); - intermediateBuffer.swap(ciphertext); // We try again, but if we don't get any more data then we leave hadIncompleteData = true; } else if (status == SEC_E_INVALID_HANDLE) { diff --git a/src/network/ssl/qsslsocket_schannel_p.h b/src/network/ssl/qsslsocket_schannel_p.h index 6ab200e1f9..a184deef49 100644 --- a/src/network/ssl/qsslsocket_schannel_p.h +++ b/src/network/ssl/qsslsocket_schannel_p.h @@ -145,6 +145,7 @@ private: const CERT_CONTEXT *localCertContext = nullptr; ULONG contextAttributes = 0; + qint64 missingData = 0; bool renegotiating = false; }; diff --git a/src/openglextensions/qopenglextensions.cpp b/src/openglextensions/qopenglextensions.cpp index 6413ae4a78..1660181e97 100644 --- a/src/openglextensions/qopenglextensions.cpp +++ b/src/openglextensions/qopenglextensions.cpp @@ -60,6 +60,12 @@ #include "qopenglextensions.h" #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE QAbstractOpenGLExtension::~QAbstractOpenGLExtension() @@ -7720,3 +7726,6 @@ bool QOpenGLExtension_QCOM_tiled_rendering::initializeOpenGLFunctions() QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif diff --git a/src/openglextensions/qopenglextensions.h b/src/openglextensions/qopenglextensions.h index 439e0e6530..eb473f3699 100644 --- a/src/openglextensions/qopenglextensions.h +++ b/src/openglextensions/qopenglextensions.h @@ -66,6 +66,12 @@ #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class QOpenGLContext; @@ -19473,6 +19479,10 @@ inline void QOpenGLExtension_QCOM_tiled_rendering::glEndTilingQCOM(GLbitfield pr QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL #endif diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index 9cdc5de0e1..56885f2e23 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -195,20 +195,6 @@ namespace QtAndroidInput angleDelta); } - void releaseMouse(int x, int y) - { - m_ignoreMouseEvents = true; - QPoint globalPos(x,y); - QWindow *tlw = topLevelWindowAt(globalPos); - QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos; - - // Release left button - QWindowSystemInterface::handleMouseEvent(tlw, - localPos, - globalPos, - Qt::MouseButtons(Qt::NoButton)); - } - static void longPress(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y) { QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext(); diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h index 2e2470ae8f..cc3070c4aa 100644 --- a/src/plugins/platforms/android/androidjniinput.h +++ b/src/plugins/platforms/android/androidjniinput.h @@ -61,8 +61,6 @@ namespace QtAndroidInput void updateHandles(int handleCount, QPoint editMenuPos = QPoint(), uint32_t editButtons = 0, QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false); bool registerNatives(JNIEnv *env); - - void releaseMouse(int x, int y); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index 5614d3b04f..e78c317863 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -827,9 +827,6 @@ void QAndroidInputContext::longPress(int x, int y) focusObjectStopComposing(); - // Release left button, otherwise the following events will cancel the menu popup - QtAndroidInput::releaseMouse(x, y); - const double pixelDensity = QGuiApplication::focusWindow() ? QHighDpiScaling::factor(QGuiApplication::focusWindow()) diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 33a45985a8..9b0a6b1b86 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -88,10 +88,13 @@ #include #include +QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application"); +QT_END_NAMESPACE + QT_USE_NAMESPACE @implementation QCocoaApplicationDelegate { - bool startedQuit; NSObject *reflectionDelegate; bool inLaunch; } @@ -140,46 +143,30 @@ QT_USE_NAMESPACE return [[self.dockMenu retain] autorelease]; } -- (BOOL)canQuit -{ - QCloseEvent ev; - QGuiApplication::sendEvent(qGuiApp, &ev); - return ev.isAccepted(); -} - // This function will only be called when NSApp is actually running. - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { if ([reflectionDelegate respondsToSelector:_cmd]) return [reflectionDelegate applicationShouldTerminate:sender]; - if ([self canQuit]) { - if (!startedQuit) { - startedQuit = true; - // Close open windows. This is done in order to deliver de-expose - // events while the event loop is still running. - const QWindowList topLevels = QGuiApplication::topLevelWindows(); - for (int i = 0; i < topLevels.size(); ++i) { - QWindow *topLevelWindow = topLevels.at(i); - // Already closed windows will not have a platform window, skip those - if (topLevelWindow->handle()) - QWindowSystemInterface::handleCloseEvent(topLevelWindow); - } - QWindowSystemInterface::flushWindowSystemEvents(); - - QGuiApplication::exit(0); - startedQuit = false; - } - } - if (QGuiApplicationPrivate::instance()->threadData->eventLoops.isEmpty()) { - // INVARIANT: No event loop is executing. This probably - // means that Qt is used as a plugin, or as a part of a native - // Cocoa application. In any case it should be fine to - // terminate now: + // No event loop is executing. This probably means that Qt is used as a plugin, + // or as a part of a native Cocoa application. In any case it should be fine to + // terminate now. + qCDebug(lcQpaApplication) << "No running event loops, terminating now"; return NSTerminateNow; } + if (!QWindowSystemInterface::handleApplicationTermination()) { + qCDebug(lcQpaApplication) << "Application termination canceled"; + return NSTerminateCancel; + } + + // Even if the application termination was accepted by the application we can't + // return NSTerminateNow, as that would trigger AppKit to ultimately call exit(). + // We need to ensure that the runloop continues spinning so that we can return + // from our own event loop back to main(), and exit from there. + qCDebug(lcQpaApplication) << "Termination accepted, but returning to runloop for exit through main()"; return NSTerminateCancel; } diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h index bb309c0713..4210a4ed3f 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.h +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -50,6 +50,8 @@ QT_BEGIN_NAMESPACE +class QCocoaWindow; + class QCocoaGLContext : public QPlatformOpenGLContext { public: @@ -76,12 +78,12 @@ private: static NSOpenGLPixelFormat *pixelFormatForSurfaceFormat(const QSurfaceFormat &format); bool setDrawable(QPlatformSurface *surface); + void prepareDrawable(QCocoaWindow *platformWindow); void updateSurfaceFormat(); NSOpenGLContext *m_context = nil; NSOpenGLContext *m_shareContext = nil; QSurfaceFormat m_format; - bool m_didCheckForSoftwareContext = false; QVarLengthArray m_updateObservers; QAtomicInt m_needsUpdate = false; }; diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index ba7d12ce30..b312e033cd 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -223,12 +223,10 @@ NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurface attrs << NSOpenGLPFAAllowOfflineRenderers; } - // FIXME: Pull this information out of the NSView - QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER"); - if (!useLayer.isEmpty() && useLayer.toInt() > 0) { - // Disable the software rendering fallback. This makes compositing - // OpenGL and raster NSViews using Core Animation layers possible. - attrs << NSOpenGLPFANoRecovery; + if (qGuiApp->testAttribute(Qt::AA_UseSoftwareOpenGL)) { + // kCGLRendererGenericFloatID is the modern software renderer on macOS, + // as opposed to kCGLRendererGenericID, which is deprecated. + attrs << NSOpenGLPFARendererID << kCGLRendererGenericFloatID; } attrs << 0; // 0-terminate array @@ -368,23 +366,6 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) [m_context makeCurrentContext]; if (surface->surface()->surfaceClass() == QSurface::Window) { - // Disable high-resolution surfaces when using the software renderer, which has the - // problem that the system silently falls back to a to using a low-resolution buffer - // when a high-resolution buffer is requested. This is not detectable using the NSWindow - // convertSizeToBacking and backingScaleFactor APIs. A typical result of this is that Qt - // will display a quarter of the window content when running in a virtual machine. - if (!m_didCheckForSoftwareContext) { - // FIXME: This ensures we check only once per context, - // but the context may be used for multiple surfaces. - m_didCheckForSoftwareContext = true; - - const GLubyte* renderer = glGetString(GL_RENDERER); - if (qstrcmp((const char *)renderer, "Apple Software Renderer") == 0) { - NSView *view = static_cast(surface)->m_view; - [view setWantsBestResolutionOpenGLSurface:NO]; - } - } - if (m_needsUpdate.fetchAndStoreRelaxed(false)) update(); } @@ -413,11 +394,14 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) } Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window); - QNSView *view = qnsview_cast(static_cast(surface)->view()); + auto *cocoaWindow = static_cast(surface); + QNSView *view = qnsview_cast(cocoaWindow->view()); if (view == m_context.view) return true; + prepareDrawable(cocoaWindow); + // Setting the drawable may happen on a separate thread as a result of // a call to makeCurrent, so we need to set up the observers before we // associate the view with the context. That way we will guarantee that @@ -460,6 +444,30 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) return true; } +void QCocoaGLContext::prepareDrawable(QCocoaWindow *platformWindow) +{ + // We generally want high-DPI GL surfaces, unless the user has explicitly disabled them + bool prefersBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, + platformWindow->window(), "_q_mac_wantsBestResolutionOpenGLSurface", + "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); + + auto *view = platformWindow->view(); + + // The only case we have to opt out ourselves is when using the Apple software renderer + // in combination with surface-backed views, as these together do not support high-DPI. + if (prefersBestResolutionOpenGLSurface) { + int rendererID = 0; + [m_context getValues:&rendererID forParameter:NSOpenGLContextParameterCurrentRendererID]; + bool isSoftwareRenderer = (rendererID & kCGLRendererIDMatchingMask) == kCGLRendererGenericFloatID; + if (isSoftwareRenderer && !view.layer) { + qCInfo(lcQpaOpenGLContext) << "Disabling high resolution GL surface due to software renderer"; + prefersBestResolutionOpenGLSurface = false; + } + } + + view.wantsBestResolutionOpenGLSurface = prefersBestResolutionOpenGLSurface; +} + // NSOpenGLContext is not re-entrant. Even when using separate contexts per thread, // view, and window, calls into the API will still deadlock. For more information // see https://openradar.appspot.com/37064579 @@ -491,6 +499,21 @@ void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) return; } + if (m_context.view.layer) { + // Flushing an NSOpenGLContext will hit the screen immediately, ignoring + // any Core Animation transactions in place. This may result in major + // visual artifacts if the flush happens out of sync with the size + // of the layer, view, and window reflected by other parts of the UI, + // e.g. if the application flushes in the resize event or a timer during + // window resizing, instead of in the expose event. + auto *cocoaWindow = static_cast(surface); + if (cocoaWindow->geometry().size() != cocoaWindow->m_exposedRect.size()) { + qCInfo(lcQpaOpenGLContext) << "Window exposed size does not match geometry (yet)." + << "Skipping flush to avoid visual artifacts."; + return; + } + } + QMutexLocker locker(&s_reentrancyMutex); [m_context flushBuffer]; } diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm index eb9286519d..2fd63fad67 100644 --- a/src/plugins/platforms/cocoa/qnsview_drawing.mm +++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm @@ -44,17 +44,6 @@ - (void)initDrawing { [self updateLayerBacking]; - - // Enable high-DPI OpenGL for retina displays. Enabling has the side - // effect that Cocoa will start calling glViewport(0, 0, width, height), - // overriding any glViewport calls in application code. This is usually not a - // problem, except if the application wants to have a "custom" viewport. - // (like the hellogl example) - if (m_platformWindow->window()->supportsOpenGL()) { - self.wantsBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, m_platformWindow->window(), - "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); - // See also QCocoaGLContext::makeCurrent for software renderer workarounds. - } } - (BOOL)isOpaque diff --git a/src/plugins/platforms/cocoa/qprintengine_mac.mm b/src/plugins/platforms/cocoa/qprintengine_mac.mm index 58ad53a6e1..dcb9a85a3c 100644 --- a/src/plugins/platforms/cocoa/qprintengine_mac.mm +++ b/src/plugins/platforms/cocoa/qprintengine_mac.mm @@ -59,6 +59,8 @@ QMacPrintEngine::QMacPrintEngine(QPrinter::PrinterMode mode, const QString &devi QString id = deviceId; if (id.isEmpty()) id = QCocoaPrinterSupport().defaultPrintDeviceId(); + else + setProperty(QPrintEngine::PPK_PrinterName, deviceId); d->m_printDevice.reset(new QCocoaPrintDevice(id)); d->m_pageLayout.setPageSize(d->m_printDevice->defaultPageSize()); d->initialize(); diff --git a/src/plugins/platforms/haiku/qhaikuapplication.cpp b/src/plugins/platforms/haiku/qhaikuapplication.cpp index b75810c453..de4acdfd4a 100644 --- a/src/plugins/platforms/haiku/qhaikuapplication.cpp +++ b/src/plugins/platforms/haiku/qhaikuapplication.cpp @@ -42,6 +42,8 @@ #include #include +#include + #include #include @@ -52,8 +54,7 @@ QHaikuApplication::QHaikuApplication(const char *signature) bool QHaikuApplication::QuitRequested() { - QEvent quitEvent(QEvent::Quit); - QCoreApplication::sendEvent(QCoreApplication::instance(), &quitEvent); + QWindowSystemInterface::handleApplicationTermination(); return true; } diff --git a/src/plugins/platforms/xcb/qxcbsessionmanager.cpp b/src/plugins/platforms/xcb/qxcbsessionmanager.cpp index 2303ccf806..f880d4d722 100644 --- a/src/plugins/platforms/xcb/qxcbsessionmanager.cpp +++ b/src/plugins/platforms/xcb/qxcbsessionmanager.cpp @@ -42,6 +42,8 @@ #ifndef QT_NO_SESSIONMANAGER +#include + #include #include #include @@ -289,8 +291,7 @@ static void sm_dieCallback(SmcConn smcConn, SmPointer /* clientData */) if (smcConn != smcConnection) return; resetSmState(); - QEvent quitEvent(QEvent::Quit); - QGuiApplication::sendEvent(qApp, &quitEvent); + QWindowSystemInterface::handleApplicationTermination(); } static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData) diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index 7f98efccba..7709b13cd1 100644 --- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -221,18 +221,18 @@ public: int disconnectCount; bool hasSQLFetchScroll; - bool isStmtHandleValid(); + bool isStmtHandleValid() const; void updateStmtHandleState(); }; -bool QODBCResultPrivate::isStmtHandleValid() +bool QODBCResultPrivate::isStmtHandleValid() const { - return disconnectCount == drv_d_func()->disconnectCount; + return drv_d_func() && disconnectCount == drv_d_func()->disconnectCount; } void QODBCResultPrivate::updateStmtHandleState() { - disconnectCount = drv_d_func()->disconnectCount; + disconnectCount = drv_d_func() ? drv_d_func()->disconnectCount : 0; } static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = 0) @@ -975,7 +975,7 @@ QODBCResult::QODBCResult(const QODBCDriver *db) QODBCResult::~QODBCResult() { Q_D(QODBCResult); - if (d->hStmt && d->isStmtHandleValid() && driver()->isOpen()) { + if (d->hStmt && d->isStmtHandleValid() && driver() && driver()->isOpen()) { SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") diff --git a/src/sql/models/qsqltablemodel.cpp b/src/sql/models/qsqltablemodel.cpp index a5232590d0..d6810cc6af 100644 --- a/src/sql/models/qsqltablemodel.cpp +++ b/src/sql/models/qsqltablemodel.cpp @@ -1369,7 +1369,7 @@ QSqlRecord QSqlTableModel::record(int row) const // get generated flags from the cache const QSqlTableModelPrivate::ModifiedRow mrow = d->cache.value(row); if (mrow.op() != QSqlTableModelPrivate::None) { - const QSqlRecord crec = mrow.rec(); + const QSqlRecord &crec = mrow.rec(); for (int i = 0, cnt = rec.count(); i < cnt; ++i) rec.setGenerated(i, crec.isGenerated(i)); } diff --git a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp index 202f87af52..5f71828595 100644 --- a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp +++ b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -296,3 +296,31 @@ QTest::keyClick(myWindow, Qt::Key_Escape, Qt::ShiftModifier, 200); } +//! [30] +void TestQLocale::initTestCase_data() +{ + QTest::addColumn("locale"); + QTest::newRow("C") << QLocale::c(); + QTest::newRow("UKish") << QLocale("en_GB"); + QTest::newRow("USAish") << QLocale(QLocale::English); +} + +void TestQLocale::roundTripInt_data() +{ + QTest::addColumn("number"); + QTest::newRow("one") << 1; + QTest::newRow("two") << 2; + QTest::newRow("ten") << 10; +} +//! [30] + +//! [31] +void TestQLocale::roundTripInt() +{ + QFETCH_GLOBAL(QLocale, locale); + QFETCH(int, number); + bool ok; + QCOMPARE(locale.toInt(locale.toString(number), &ok), number); + QVERIFY(ok); +} +//! [31] diff --git a/src/testlib/doc/src/qttestlib-manual.qdoc b/src/testlib/doc/src/qttestlib-manual.qdoc index 65836d0706..bb379fe029 100644 --- a/src/testlib/doc/src/qttestlib-manual.qdoc +++ b/src/testlib/doc/src/qttestlib-manual.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Copyright (C) 2016 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** @@ -89,12 +89,14 @@ private slot is a test function in your test. QTest::qExec() can be used to execute all test functions in the test object. - In addition, there are four private slots that are \e not treated as test functions. - They will be executed by the testing framework and can be used to initialize and - clean up either the entire test or the current test function. + In addition, you can define the following private slots that are \e not + treated as test functions. When present, they will be executed by the + testing framework and can be used to initialize and clean up either the + entire test or the current test function. \list \li \c{initTestCase()} will be called before the first test function is executed. + \li \c{initTestCase_data()} will be called to create a global test data table. \li \c{cleanupTestCase()} will be called after the last test function was executed. \li \c{init()} will be called before each test function is executed. \li \c{cleanup()} will be called after every test function. @@ -358,18 +360,44 @@ counters can be obtained by running any benchmark executable with the option \c -perfcounterlist. - \list - \li \b Notes: + \note \list \li Using the performance counter may require enabling access to non-privileged applications. \li Devices that do not support high-resolution timers default to one-millisecond granularity. \endlist - \endlist See \l {Chapter 5: Writing a Benchmark}{Writing a Benchmark} in the Qt Test Tutorial for more benchmarking examples. + + \section1 Using Global Test Data + + You can define \c{initTestCase_data()} to set up a global test data table. + Each test is run once for each row in the global test data table. When the + test function itself \l{Chapter 2: Data-driven Testing}{is data-driven}, + it is run for each local data row, for each global data row. So, if there + are \c g rows in the global data table and \c d rows in the test's own + data-table, the number of runs of this test is \c g times \c d. + + Global data is fetched from the table using the \l QFETCH_GLOBAL() macro. + + The following are typical use cases for global test data: + + \list + \li Selecting among the available database backends in QSql tests to run + every test against every database. + \li Doing all networking tests with and without SSL (HTTP versus HTTPS) + and proxying. + \li Testing a timer with a high precision clock and with a coarse one. + \li Selecting whether a parser shall read from a QByteArray or from a + QIODevice. + \endlist + + For example, to test each number provided by \c {roundTripInt_data()} with + each locale provided by \c {initTestCase_data()}: + + \snippet code/src_qtestlib_qtestcase.cpp 31 */ /*! @@ -513,10 +541,9 @@ QTest::newRow() function. Each set of data will become a separate row in the test table. - \l QTest::newRow() takes one argument: a name that will be - associated with the data set. If the test fails, the name will be - used in the test log, referencing the failed data. Then we - stream the data set into the new table row. First an arbitrary + \l QTest::newRow() takes one argument: a name that will be associated + with the data set and used in the test log to identify the data set. + Then we stream the data set into the new table row. First an arbitrary string, and then the expected result of applying the QString::toUpper() function to that string. @@ -548,6 +575,10 @@ \li HELLO \endtable + When data is streamed into the row, each datum is asserted to match + the type of the column whose value it supplies. If any assertion fails, + the test is aborted. + \section1 Rewriting the Test Function Our test function can now be rewritten: diff --git a/src/testlib/qsignalspy.qdoc b/src/testlib/qsignalspy.qdoc index 5ea6bc5dc7..d532ad478d 100644 --- a/src/testlib/qsignalspy.qdoc +++ b/src/testlib/qsignalspy.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -48,7 +48,7 @@ \snippet code/doc_src_qsignalspy.cpp 1 - \b {Note:} Non-standard data types need to be registered, using + \note Non-standard data types need to be registered, using the qRegisterMetaType() function, before you can create a QSignalSpy. For example: @@ -57,6 +57,18 @@ To retrieve the instance, you can use qvariant_cast: \snippet code/doc_src_qsignalspy.cpp 3 + + \section1 Verifying Signal Emissions + + The QSignalSpy class provides an elegant mechanism for capturing the list + of signals emitted by an object. However, you should verify its validity + after construction. The constructor does a number of sanity checks, such as + verifying that the signal to be spied upon actually exists. To make the + diagnosis of test failures easier, the results of these checks should be + checked by calling \c QVERIFY(spy.isValid()) before proceeding further with + a test. + + \sa QVERIFY() */ /*! \fn QSignalSpy::QSignalSpy(const QObject *object, const char *signal) diff --git a/src/testlib/qtestcase.qdoc b/src/testlib/qtestcase.qdoc index 2af016304d..9006d7b401 100644 --- a/src/testlib/qtestcase.qdoc +++ b/src/testlib/qtestcase.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -220,8 +220,8 @@ \relates QTest The fetch macro creates a local variable named \a name with the type \a type - on the stack. \a name has to match the element name from the test's data. - If no such element exists, the test will assert. + on the stack. The \a name and \a type must match a column from the test's + data table. This is asserted and the test will abort if the assertion fails. Assuming a test has the following data: @@ -235,10 +235,37 @@ \c aString and \c expected are variables on the stack that are initialized with the current test data. - \b {Note:} This macro can only be used in a test function that is invoked + \note This macro can only be used in a test function that is invoked by the test framework. The test function must have a _data function. */ +/*! \macro QFETCH_GLOBAL(type, name) + + \relates QTest + + This macro fetches a variable named \a name with the type \a type from + a row in the global data table. The \a name and \a type must match a + column in the global data table. This is asserted and the test will abort + if the assertion fails. + + Assuming a test has the following data: + + \snippet code/src_qtestlib_qtestcase.cpp 30 + + The test's own data is a single number per row. In this case, + \c initTestCase_data() also supplies a locale per row. Therefore, + this test will be run with every combination of locale from the + latter and number from the former. + + \snippet code/src_qtestlib_qtestcase.cpp 31 + + The locale is read from the global data table using QFETCH_GLOBAL(), + and the number is read from the local data table using QFETCH(). + + \note This macro can only be used in test methods of a class with an + \c initTestCase_data() method. +*/ + /*! \macro QWARN(message) \relates QTest @@ -255,7 +282,7 @@ This macro can be used to force a test failure. The test stops executing and the failure \a message is appended to the test log. - \b {Note:} This macro can only be used in a test function that is invoked + \note This macro can only be used in a test function that is invoked by the test framework. Example: @@ -332,7 +359,7 @@ \a mode is a \l QTest::TestFailMode and sets whether the test should continue to execute or not. - \b {Note:} This macro can only be used in a test function that is invoked + \note This macro can only be used in a test function that is invoked by the test framework. Example 1: @@ -394,13 +421,13 @@ test has been installed, and regardless of whether the test's build tree is equal to the test's source tree. - \b {Note:} reliable detection of testdata from the source directory requires + \note reliable detection of testdata from the source directory requires either that qmake is used, or the \c{QT_TESTCASE_BUILDDIR} macro is defined to point to the working directory from which the compiler is invoked, or only absolute paths to the source files are passed to the compiler. Otherwise, the absolute path of the source directory cannot be determined. - \b {Note:} For tests that use the \l QTEST_APPLESS_MAIN() macro to generate a + \note For tests that use the \l QTEST_APPLESS_MAIN() macro to generate a \c{main()} function, \c{QFINDTESTDATA} will not attempt to find test data relative to QCoreApplication::applicationDirPath(). In practice, this means that tests using \c{QTEST_APPLESS_MAIN()} will fail to find their test data @@ -422,7 +449,7 @@ Similarly, if qmake is used and the configuration includes \c{QT += gui}, then \c QT_GUI_LIB will be defined automatically. - \b {Note:} On platforms that have keypad navigation enabled by default, + \note On platforms that have keypad navigation enabled by default, this macro will forcefully disable it if \c QT_WIDGETS_LIB is defined. This is done to simplify the usage of key events when writing autotests. If you wish to write a test case that uses keypad navigation, you should enable it either in the @@ -662,7 +689,7 @@ Simulates pressing a \a key with an optional \a modifier on a \a widget. If \a delay is larger than 0, the test will wait for \a delay milliseconds before pressing the key. - \b {Note:} At some point you should release the key using \l keyRelease(). + \note At some point you should release the key using \l keyRelease(). \sa QTest::keyRelease(), QTest::keyClick() */ @@ -674,7 +701,7 @@ If \a delay is larger than 0, the test will wait for \a delay milliseconds before pressing the key. - \b {Note:} At some point you should release the key using \l keyRelease(). + \note At some point you should release the key using \l keyRelease(). \sa QTest::keyRelease(), QTest::keyClick() */ @@ -686,7 +713,7 @@ Simulates pressing a \a key with an optional \a modifier on a \a window. If \a delay is larger than 0, the test will wait for \a delay milliseconds before pressing the key. - \b {Note:} At some point you should release the key using \l keyRelease(). + \note At some point you should release the key using \l keyRelease(). \sa QTest::keyRelease(), QTest::keyClick() */ @@ -699,7 +726,7 @@ If \a delay is larger than 0, the test will wait for \a delay milliseconds before pressing the key. - \b {Note:} At some point you should release the key using \l keyRelease(). + \note At some point you should release the key using \l keyRelease(). \sa QTest::keyRelease(), QTest::keyClick() */ @@ -920,12 +947,12 @@ You can add specializations or overloads of this function to your test to enable verbose output. - \b {Note:} Starting with Qt 5.5, you should prefer to provide a toString() function + \note Starting with Qt 5.5, you should prefer to provide a toString() function in the type's namespace instead of specializing this template. If your code needs to continue to work with the QTestLib from Qt 5.4 or earlier, you need to continue to use specialization. - \b {Note:} The caller of toString() must delete the returned data + \note The caller of toString() must delete the returned data using \c{delete[]}. Your implementation should return a string created with \c{new[]} or qstrdup(). The easiest way to do so is to create a QByteArray or QString and calling QTest::toString() on it diff --git a/src/tools/bootstrap/bootstrap.pro b/src/tools/bootstrap/bootstrap.pro index 9863ff5e69..6230cc081d 100644 --- a/src/tools/bootstrap/bootstrap.pro +++ b/src/tools/bootstrap/bootstrap.pro @@ -63,8 +63,9 @@ SOURCES += \ ../../corelib/kernel/qsharedmemory.cpp \ ../../corelib/kernel/qsystemsemaphore.cpp \ ../../corelib/plugin/quuid.cpp \ + ../../corelib/serialization/qcborvalue.cpp \ ../../corelib/serialization/qdatastream.cpp \ - ../../corelib/serialization/qjson.cpp \ + ../../corelib/serialization/qjsoncbor.cpp \ ../../corelib/serialization/qjsondocument.cpp \ ../../corelib/serialization/qjsonobject.cpp \ ../../corelib/serialization/qjsonarray.cpp \ diff --git a/src/tools/uic/cpp/cppwriteinitialization.cpp b/src/tools/uic/cpp/cppwriteinitialization.cpp index fd5f8c9017..717bff4a51 100644 --- a/src/tools/uic/cpp/cppwriteinitialization.cpp +++ b/src/tools/uic/cpp/cppwriteinitialization.cpp @@ -1680,7 +1680,7 @@ static void writeResourceIcon(QTextStream &output, "Selected", "Off"); } if (i->hasElementSelectedOn()) { - writeIconAddFile(output, indent, iconName, i->elementSelectedOff()->text(), + writeIconAddFile(output, indent, iconName, i->elementSelectedOn()->text(), "Selected", "On"); } } diff --git a/src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc b/src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc index 098eaf4717..b0d042566f 100644 --- a/src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc +++ b/src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc @@ -1107,10 +1107,10 @@ QMenu::indicator:exclusive:checked:selected { QMenuBar { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 lightgray, stop:1 darkgray); + spacing: 3px; /* spacing between menu bar items */ } QMenuBar::item { - spacing: 3px; /* spacing between menu bar items */ padding: 1px 4px; background: transparent; border-radius: 4px; diff --git a/src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc b/src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc index 00323eace6..84233e4b62 100644 --- a/src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc +++ b/src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc @@ -549,6 +549,10 @@ If the property references an enum declared with Q_ENUMS, you should reference its constants by name, i.e., not their numeric value. + \note Use the qproperty syntax with care, as it modifies the + widget that is being painted. Also, the qproperty syntax is evaluated only + once, which is when the widget is polished by the style. This means that any + attempt to use them in pseudo-states such as QPushButton:hover, will not work. */ /*! diff --git a/src/widgets/graphicsview/qgraphicsscene.cpp b/src/widgets/graphicsview/qgraphicsscene.cpp index 1208adfd17..a47d0d879d 100644 --- a/src/widgets/graphicsview/qgraphicsscene.cpp +++ b/src/widgets/graphicsview/qgraphicsscene.cpp @@ -386,7 +386,15 @@ void QGraphicsScenePrivate::_q_emitUpdated() // Notify the changes to anybody interested. QList oldUpdatedRects; - oldUpdatedRects = updateAll ? (QList() << q->sceneRect()) : updatedRects; + if (updateAll) { + oldUpdatedRects << q->sceneRect(); + } else { + // Switch to a ranged constructor in Qt 6... + oldUpdatedRects.reserve(int(updatedRects.size())); + std::copy(updatedRects.cbegin(), updatedRects.cend(), + std::back_inserter(oldUpdatedRects)); + } + updateAll = false; updatedRects.clear(); emit q->changed(oldUpdatedRects); @@ -3219,8 +3227,7 @@ void QGraphicsScene::update(const QRectF &rect) view->d_func()->updateRectF(rect); } } else { - if (!d->updatedRects.contains(rect)) - d->updatedRects << rect; + d->updatedRects.insert(rect); } } diff --git a/src/widgets/graphicsview/qgraphicsscene_p.h b/src/widgets/graphicsview/qgraphicsscene_p.h index 7934359cee..9ecfca8ebf 100644 --- a/src/widgets/graphicsview/qgraphicsscene_p.h +++ b/src/widgets/graphicsview/qgraphicsscene_p.h @@ -69,6 +69,9 @@ #include #include +#include +#include + QT_REQUIRE_CONFIG(graphicsview); QT_BEGIN_NAMESPACE @@ -122,7 +125,19 @@ public: QRectF growingItemsBoundingRect; void _q_emitUpdated(); - QList updatedRects; + + struct UpdatedRectsCmp + { + bool operator() (const QRectF &a, const QRectF &b) const noexcept + { + return std::make_tuple(a.y(), a.x(), a.height(), a.width()) + < std::make_tuple(b.y(), b.x(), b.height(), b.width()); + } + }; + + // std::set was used here instead of std::unordered_set due to requiring only a comparator and + // showing equivalent performance in empirical measurements within the ranges of interest... + std::set updatedRects; QPainterPath selectionArea; int selectionChanging; diff --git a/src/widgets/itemviews/qstyleditemdelegate.cpp b/src/widgets/itemviews/qstyleditemdelegate.cpp index 22067851cb..702e290da3 100644 --- a/src/widgets/itemviews/qstyleditemdelegate.cpp +++ b/src/widgets/itemviews/qstyleditemdelegate.cpp @@ -514,15 +514,6 @@ void QStyledItemDelegate::updateEditorGeometry(QWidget *editor, QStyle *style = widget ? widget->style() : QApplication::style(); QRect geom = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget); - const int delta = qSmartMinSize(editor).width() - geom.width(); - if (delta > 0) { - //we need to widen the geometry - if (editor->layoutDirection() == Qt::RightToLeft) - geom.adjust(-delta, 0, 0, 0); - else - geom.adjust(0, 0, delta, 0); - } - editor->setGeometry(geom); } diff --git a/src/widgets/itemviews/qtableview.cpp b/src/widgets/itemviews/qtableview.cpp index c50156bbce..11c5be10fd 100644 --- a/src/widgets/itemviews/qtableview.cpp +++ b/src/widgets/itemviews/qtableview.cpp @@ -3331,7 +3331,7 @@ void QTableViewPrivate::selectRow(int row, bool anchor) if (q->selectionMode() != QTableView::SingleSelection && command.testFlag(QItemSelectionModel::Toggle)) { if (anchor) - ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows().contains(index) + ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows(column).contains(index) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; command &= ~QItemSelectionModel::Toggle; command |= ctrlDragSelectionFlag; diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index 3ded8c37e6..2d4f114638 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -1868,22 +1868,19 @@ void QApplication::aboutQt() bool QApplication::event(QEvent *e) { Q_D(QApplication); - if(e->type() == QEvent::Close) { - QCloseEvent *ce = static_cast(e); - ce->accept(); + if (e->type() == QEvent::Quit) { closeAllWindows(); - - const QWidgetList list = topLevelWidgets(); - for (auto *w : list) { + for (auto *w : topLevelWidgets()) { if (w->isVisible() && !(w->windowType() == Qt::Desktop) && !(w->windowType() == Qt::Popup) && (!(w->windowType() == Qt::Dialog) || !w->parentWidget())) { - ce->ignore(); - break; + e->ignore(); + return true; } } - if (ce->isAccepted()) { - return true; - } + // Explicitly call QCoreApplication instead of QGuiApplication so that + // we don't let QGuiApplication close any windows we skipped earlier in + // closeAllWindows(). FIXME: Unify all this close magic through closeAllWindows. + return QCoreApplication::event(e); #ifndef Q_OS_WIN } else if (e->type() == QEvent::LocaleChange) { // on Windows the event propagation is taken care by the diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index b9a67edc6a..24f8c5ce43 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -893,7 +893,7 @@ void QWidgetWindow::handleDragEnterEvent(QDragEnterEvent *event, QWidget *widget void QWidgetWindow::handleDragMoveEvent(QDragMoveEvent *event) { - auto *widget = findDnDTarget(m_widget, event->pos()); + QPointer widget = findDnDTarget(m_widget, event->pos()); if (!widget) { event->ignore(); if (m_dragTarget) { // Send DragLeave to previous @@ -916,14 +916,18 @@ void QWidgetWindow::handleDragMoveEvent(QDragMoveEvent *event) QGuiApplication::forwardEvent(m_dragTarget, &leaveEvent, event); m_dragTarget = nullptr; } - // Send DragEnter to new widget. - handleDragEnterEvent(static_cast(event), widget); - // Handling 'DragEnter' should suffice for the application. - translated.setDropAction(event->dropAction()); - translated.setAccepted(event->isAccepted()); - // The drag enter event is always immediately followed by a drag move event, - // see QDragEnterEvent documentation. - QGuiApplication::forwardEvent(m_dragTarget, &translated, event); + // widget might have been deleted when handling the leaveEvent + if (widget) { + // Send DragEnter to new widget. + handleDragEnterEvent(static_cast(event), widget); + // Handling 'DragEnter' should suffice for the application. + translated.setDropAction(event->dropAction()); + translated.setAccepted(event->isAccepted()); + // The drag enter event is always immediately followed by a drag move event, + // see QDragEnterEvent documentation. + if (m_dragTarget) + QGuiApplication::forwardEvent(m_dragTarget, &translated, event); + } } event->setAccepted(translated.isAccepted()); event->setDropAction(translated.dropAction()); diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp index 4f010f37c2..b8ae95dd93 100644 --- a/tests/auto/corelib/io/qfile/tst_qfile.cpp +++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp @@ -550,6 +550,10 @@ void tst_QFile::exists() QFile unc(uncPath); QVERIFY2(unc.exists(), msgFileDoesNotExist(uncPath).constData()); #endif + + QTest::ignoreMessage(QtWarningMsg, "Broken filename passed to function"); + QVERIFY(!QFile::exists(QDir::currentPath() + QLatin1Char('/') + + QChar(QChar::Null) + QLatin1String("x/y"))); } void tst_QFile::open_data() diff --git a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp index 56d924dcc6..dae4e66106 100644 --- a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp +++ b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp @@ -465,16 +465,6 @@ void tst_qstandardpaths::testRuntimeDirectory() #ifdef Q_XDG_PLATFORM const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); QVERIFY(!runtimeDir.isEmpty()); - - // Check that it can automatically fix permissions - QFile file(runtimeDir); - const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser; - const QFile::Permissions additionalPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; - QCOMPARE(file.permissions(), wantedPerms | additionalPerms); - QVERIFY(file.setPermissions(wantedPerms | QFile::ExeGroup)); - const QString runtimeDirAgain = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - QCOMPARE(runtimeDirAgain, runtimeDir); - QCOMPARE(QFile(runtimeDirAgain).permissions(), wantedPerms | additionalPerms); #endif } @@ -516,11 +506,27 @@ void tst_qstandardpaths::testCustomRuntimeDirectory() const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); QVERIFY2(runtimeDir.isEmpty(), qPrintable(runtimeDir)); - // When $XDG_RUNTIME_DIR points to a non-existing directory, QStandardPaths should warn (QTBUG-48771) - qputenv("XDG_RUNTIME_DIR", "does_not_exist"); - QTest::ignoreMessage(QtWarningMsg, "QStandardPaths: XDG_RUNTIME_DIR points to non-existing path 'does_not_exist', please create it with 0700 permissions."); + // When $XDG_RUNTIME_DIR points to a directory with wrong permissions, QStandardPaths should warn + const QByteArray wrongPermissionFileName = "wrong_permissions"; + QDir::current().mkdir(wrongPermissionFileName); + QFile wrongPermissionFile(wrongPermissionFileName); + const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser; + QVERIFY(wrongPermissionFile.setPermissions(wantedPerms | QFile::ExeGroup)); + + qputenv("XDG_RUNTIME_DIR", wrongPermissionFileName); + QTest::ignoreMessage(QtWarningMsg, + qPrintable(QString::fromLatin1("QStandardPaths: wrong permissions on runtime directory " + wrongPermissionFileName + ", 7710 instead of 7700"))); + const QString wrongPermissionRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + QVERIFY(wrongPermissionRuntimeDir.isEmpty()); + QDir::current().rmdir(wrongPermissionFileName); + + // When $XDG_RUNTIME_DIR points to a non-existing directory, QStandardPaths should create it first + const QByteArray nonExistingDir = "does_not_exist"; + qputenv("XDG_RUNTIME_DIR", nonExistingDir); const QString nonExistingRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - QVERIFY2(nonExistingRuntimeDir.isEmpty(), qPrintable(nonExistingRuntimeDir)); + QVERIFY2(!nonExistingRuntimeDir.compare(nonExistingDir), qPrintable(nonExistingRuntimeDir)); + QVERIFY(QDir::current().exists(nonExistingRuntimeDir)); + QDir::current().rmdir(nonExistingRuntimeDir); // When $XDG_RUNTIME_DIR points to a file, QStandardPaths should warn const QString file = QFINDTESTDATA("tst_qstandardpaths.cpp"); diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp index 624187349b..7f9a996136 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp @@ -26,25 +26,29 @@ ** ****************************************************************************/ -#include #include "tst_qsortfilterproxymodel.h" #include "dynamictreemodel.h" -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include Q_LOGGING_CATEGORY(lcItemModels, "qt.corelib.tests.itemmodels") +using IntPair = QPair; +using IntList = QVector; +using IntPairList = QVector; + // Testing get/set functions void tst_QSortFilterProxyModel::getSetCheck() { QSortFilterProxyModel obj1; - QCOMPARE(obj1.sourceModel(), (QAbstractItemModel *)0); + QCOMPARE(obj1.sourceModel(), nullptr); // int QSortFilterProxyModel::filterKeyColumn() // void QSortFilterProxyModel::setFilterKeyColumn(int) obj1.setFilterKeyColumn(0); @@ -55,15 +59,12 @@ void tst_QSortFilterProxyModel::getSetCheck() QCOMPARE(INT_MAX, obj1.filterKeyColumn()); } -tst_QSortFilterProxyModel::tst_QSortFilterProxyModel() - : m_model(0), m_proxy(0) -{ - qRegisterMetaType(); -} - +Q_DECLARE_METATYPE(Qt::MatchFlag) void tst_QSortFilterProxyModel::initTestCase() { + qRegisterMetaType(); qRegisterMetaType >(); + qRegisterMetaType(); m_model = new QStandardItemModel(0, 1); m_proxy = new QSortFilterProxyModel(); m_proxy->setSourceModel(m_model); @@ -97,13 +98,13 @@ void tst_QSortFilterProxyModel::cleanup() void tst_QSortFilterProxyModel::sort_data() { - QTest::addColumn("sortOrder"); - QTest::addColumn("sortCaseSensitivity"); + QTest::addColumn("sortOrder"); + QTest::addColumn("sortCaseSensitivity"); QTest::addColumn("initial"); QTest::addColumn("expected"); - QTest::newRow("flat descending") << static_cast(Qt::DescendingOrder) - << int(Qt::CaseSensitive) + QTest::newRow("flat descending") << Qt::DescendingOrder + << Qt::CaseSensitive << (QStringList() << "delta" << "yankee" @@ -158,8 +159,8 @@ void tst_QSortFilterProxyModel::sort_data() << "charlie" << "bravo" << "alpha"); - QTest::newRow("flat ascending") << static_cast(Qt::AscendingOrder) - << int(Qt::CaseSensitive) + QTest::newRow("flat ascending") << Qt::AscendingOrder + << Qt::CaseSensitive << (QStringList() << "delta" << "yankee" @@ -214,14 +215,14 @@ void tst_QSortFilterProxyModel::sort_data() << "xray" << "yankee" << "zulu"); - QTest::newRow("case insensitive") << static_cast(Qt::AscendingOrder) - << int(Qt::CaseInsensitive) + QTest::newRow("case insensitive") << Qt::AscendingOrder + << Qt::CaseInsensitive << (QStringList() << "alpha" << "BETA" << "Gamma" << "delta") << (QStringList() << "alpha" << "BETA" << "delta" << "Gamma"); - QTest::newRow("case sensitive") << static_cast(Qt::AscendingOrder) - << int(Qt::CaseSensitive) + QTest::newRow("case sensitive") << Qt::AscendingOrder + << Qt::CaseSensitive << (QStringList() << "alpha" << "BETA" << "Gamma" << "delta") << (QStringList() @@ -230,13 +231,13 @@ void tst_QSortFilterProxyModel::sort_data() QStringList list; for (int i = 10000; i < 20000; ++i) list.append(QStringLiteral("Number: ") + QString::number(i)); - QTest::newRow("large set ascending") << static_cast(Qt::AscendingOrder) << int(Qt::CaseSensitive) << list << list; + QTest::newRow("large set ascending") << Qt::AscendingOrder << Qt::CaseSensitive << list << list; } void tst_QSortFilterProxyModel::sort() { - QFETCH(int, sortOrder); - QFETCH(int, sortCaseSensitivity); + QFETCH(Qt::SortOrder, sortOrder); + QFETCH(Qt::CaseSensitivity, sortCaseSensitivity); QFETCH(QStringList, initial); QFETCH(QStringList, expected); @@ -259,8 +260,8 @@ void tst_QSortFilterProxyModel::sort() } // sort - m_proxy->sort(0, static_cast(sortOrder)); - m_proxy->setSortCaseSensitivity(static_cast(sortCaseSensitivity)); + m_proxy->sort(0, sortOrder); + m_proxy->setSortCaseSensitivity(sortCaseSensitivity); // make sure the model is unchanged for (int row = 0; row < m_model->rowCount(QModelIndex()); ++row) { @@ -285,24 +286,24 @@ void tst_QSortFilterProxyModel::sort() void tst_QSortFilterProxyModel::sortHierarchy_data() { - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("initial"); QTest::addColumn("expected"); QTest::newRow("flat ascending") - << static_cast(Qt::AscendingOrder) + << Qt::AscendingOrder << (QStringList() << "c" << "f" << "d" << "e" << "a" << "b") << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f"); QTest::newRow("simple hierarchy") - << static_cast(Qt::AscendingOrder) + << Qt::AscendingOrder << (QStringList() << "a" << "<" << "b" << "<" << "c" << ">" << ">") << (QStringList() << "a" << "<" << "b" << "<" << "c" << ">" << ">"); QTest::newRow("hierarchical ascending") - << static_cast(Qt::AscendingOrder) + << Qt::AscendingOrder << (QStringList() << "c" << "<" @@ -365,14 +366,14 @@ void tst_QSortFilterProxyModel::sortHierarchy_data() void tst_QSortFilterProxyModel::sortHierarchy() { - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QStringList, initial); QFETCH(QStringList, expected); buildHierarchy(initial, m_model); checkHierarchy(initial, m_model); checkHierarchy(initial, m_proxy); - m_proxy->sort(0, static_cast(sortOrder)); + m_proxy->sort(0, sortOrder); checkHierarchy(initial, m_model); checkHierarchy(expected, m_proxy); } @@ -872,13 +873,13 @@ class MyFilteredColumnProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - MyFilteredColumnProxyModel(FilterType filterType, QObject *parent = 0) : + MyFilteredColumnProxyModel(FilterType filterType, QObject *parent = nullptr) : QSortFilterProxyModel(parent), m_filterType(filterType) { } protected: - bool filterAcceptsColumn(int sourceColumn, const QModelIndex &) const + bool filterAcceptsColumn(int sourceColumn, const QModelIndex &) const override { QString key = sourceModel()->headerData(sourceColumn, Qt::Horizontal).toString(); bool result = false; @@ -1369,10 +1370,11 @@ void tst_QSortFilterProxyModel::setupFilter(QSortFilterProxyModel *model, const class TestModel: public QAbstractTableModel { + Q_OBJECT public: - int rowCount(const QModelIndex &) const { return 10000; } - int columnCount(const QModelIndex &) const { return 1; } - QVariant data(const QModelIndex &index, int role) const + int rowCount(const QModelIndex &) const override { return 10000; } + int columnCount(const QModelIndex &) const override { return 1; } + QVariant data(const QModelIndex &index, int role) const override { if (role != Qt::DisplayRole) return QVariant(); @@ -1402,7 +1404,7 @@ void tst_QSortFilterProxyModel::insertAfterSelect() view.setModel(&filter); view.show(); QModelIndex firstIndex = filter.mapFromSource(model.index(0, 0, QModelIndex())); - QCOMPARE(firstIndex.model(), (const QAbstractItemModel *)view.model()); + QCOMPARE(firstIndex.model(), view.model()); QVERIFY(firstIndex.isValid()); int itemOffset = view.visualRect(firstIndex).width() / 2; QPoint p(itemOffset, 1); @@ -1423,7 +1425,7 @@ void tst_QSortFilterProxyModel::removeAfterSelect() view.setModel(&filter); view.show(); QModelIndex firstIndex = filter.mapFromSource(model.index(0, 0, QModelIndex())); - QCOMPARE(firstIndex.model(), (const QAbstractItemModel *)view.model()); + QCOMPARE(firstIndex.model(), view.model()); QVERIFY(firstIndex.isValid()); int itemOffset = view.visualRect(firstIndex).width() / 2; QPoint p(itemOffset, 1); @@ -1488,8 +1490,8 @@ void tst_QSortFilterProxyModel::changeSourceLayout() QSortFilterProxyModel proxy; proxy.setSourceModel(&model); - QList persistentSourceIndexes; - QList persistentProxyIndexes; + QVector persistentSourceIndexes; + QVector persistentProxyIndexes; for (int row = 0; row < model.rowCount(); ++row) { persistentSourceIndexes.append(model.index(row, 0)); persistentProxyIndexes.append(proxy.index(row, 0)); @@ -1634,7 +1636,7 @@ void tst_QSortFilterProxyModel::removeSourceRows() QCOMPARE(aboutToRemoveSpy.count(), expectedRemovedProxyIntervals.count()); for (int i = 0; i < aboutToRemoveSpy.count(); ++i) { - QList args = aboutToRemoveSpy.at(i); + const auto &args = aboutToRemoveSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first); @@ -1642,7 +1644,7 @@ void tst_QSortFilterProxyModel::removeSourceRows() } QCOMPARE(removeSpy.count(), expectedRemovedProxyIntervals.count()); for (int i = 0; i < removeSpy.count(); ++i) { - QList args = removeSpy.at(i); + const auto &args = removeSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first); @@ -1664,14 +1666,14 @@ void tst_QSortFilterProxyModel::insertSourceRows_data() QTest::addColumn("sourceItems"); QTest::addColumn("start"); QTest::addColumn("newItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("proxyItems"); QTest::newRow("insert (1)") << (QStringList() << "c" << "b") // sourceItems << 1 // start << (QStringList() << "a") // newItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << (QStringList() << "a" << "b" << "c") // proxyItems ; @@ -1679,7 +1681,7 @@ void tst_QSortFilterProxyModel::insertSourceRows_data() << (QStringList() << "d" << "b" << "c") // sourceItems << 3 // start << (QStringList() << "a") // newItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << (QStringList() << "d" << "c" << "b" << "a") // proxyItems ; } @@ -1691,7 +1693,7 @@ void tst_QSortFilterProxyModel::insertSourceRows() QFETCH(QStringList, sourceItems); QFETCH(int, start); QFETCH(QStringList, newItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QStringList, proxyItems); QStandardItemModel model; @@ -1707,7 +1709,7 @@ void tst_QSortFilterProxyModel::insertSourceRows() model.setData(index, sourceItems.at(i), Qt::DisplayRole); } - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); (void)proxy.rowCount(QModelIndex()); // force mapping model.insertRows(start, newItems.size(), QModelIndex()); @@ -1727,7 +1729,7 @@ void tst_QSortFilterProxyModel::insertSourceRows() void tst_QSortFilterProxyModel::changeFilter_data() { QTest::addColumn("sourceItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("initialFilter"); QTest::addColumn("initialRemoveIntervals"); QTest::addColumn("initialProxyItems"); @@ -1738,7 +1740,7 @@ void tst_QSortFilterProxyModel::changeFilter_data() QTest::newRow("filter (1)") << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|b|c" // initialFilter << (IntPairList() << IntPair(3, 5)) // initialRemoveIntervals << (QStringList() << "a" << "b" << "c") // initialProxyItems @@ -1750,7 +1752,7 @@ void tst_QSortFilterProxyModel::changeFilter_data() QTest::newRow("filter (2)") << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|c|e" // initialFilter << (IntPairList() << IntPair(5, 5) << IntPair(3, 3) << IntPair(1, 1)) // initialRemoveIntervals << (QStringList() << "a" << "c" << "e") // initialProxyItems @@ -1762,7 +1764,7 @@ void tst_QSortFilterProxyModel::changeFilter_data() QTest::newRow("filter (3)") << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a" // initialFilter << (IntPairList() << IntPair(1, 2)) // initialRemoveIntervals << (QStringList() << "a") // initialProxyItems @@ -1777,7 +1779,7 @@ void tst_QSortFilterProxyModel::changeFilter_data() void tst_QSortFilterProxyModel::changeFilter() { QFETCH(QStringList, sourceItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QString, initialFilter); QFETCH(IntPairList, initialRemoveIntervals); QFETCH(QStringList, initialProxyItems); @@ -1798,7 +1800,7 @@ void tst_QSortFilterProxyModel::changeFilter() model.setData(index, sourceItems.at(i), Qt::DisplayRole); } - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); (void)proxy.rowCount(QModelIndex()); // force mapping QSignalSpy initialRemoveSpy(&proxy, &QSortFilterProxyModel::rowsRemoved); @@ -1812,7 +1814,7 @@ void tst_QSortFilterProxyModel::changeFilter() QCOMPARE(initialRemoveSpy.count(), initialRemoveIntervals.count()); QCOMPARE(initialInsertSpy.count(), 0); for (int i = 0; i < initialRemoveSpy.count(); ++i) { - QList args = initialRemoveSpy.at(i); + const auto &args = initialRemoveSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), initialRemoveIntervals.at(i).first); @@ -1835,7 +1837,7 @@ void tst_QSortFilterProxyModel::changeFilter() QCOMPARE(finalRemoveSpy.count(), finalRemoveIntervals.count()); for (int i = 0; i < finalRemoveSpy.count(); ++i) { - QList args = finalRemoveSpy.at(i); + const auto &args = finalRemoveSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), finalRemoveIntervals.at(i).first); @@ -1844,7 +1846,7 @@ void tst_QSortFilterProxyModel::changeFilter() QCOMPARE(finalInsertSpy.count(), insertIntervals.count()); for (int i = 0; i < finalInsertSpy.count(); ++i) { - QList args = finalInsertSpy.at(i); + const auto &args = finalInsertSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), insertIntervals.at(i).first); @@ -1861,7 +1863,7 @@ void tst_QSortFilterProxyModel::changeFilter() void tst_QSortFilterProxyModel::changeSourceData_data() { QTest::addColumn("sourceItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("filter"); QTest::addColumn("expectedInitialProxyItems"); QTest::addColumn("dynamic"); @@ -1875,7 +1877,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("move_to_end_ascending") << (QStringList() << "c" << "b" << "a") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << (QStringList() << "a" << "b" << "c") // expectedInitialProxyItems << true // dynamic @@ -1890,7 +1892,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("move_to_end_descending") << (QStringList() << "b" << "c" << "z") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "" // filter << (QStringList() << "z" << "c" << "b") // expectedInitialProxyItems << true // dynamic @@ -1905,7 +1907,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("no_op_change") << (QStringList() << "a" << "b") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "" // filter << (QStringList() << "b" << "a") // expectedInitialProxyItems << true // dynamic @@ -1920,7 +1922,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("no_effect_on_filtering") << (QStringList() << "a" << "b") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << (QStringList() << "a" << "b") // expectedInitialProxyItems << true // dynamic @@ -1935,7 +1937,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("filtered_out_value_stays_out") << (QStringList() << "a" << "b" << "c" << "d") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|c" // filter << (QStringList() << "a" << "c") // expectedInitialProxyItems << true // dynamic @@ -1950,7 +1952,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("filtered_out_now_matches") << (QStringList() << "a" << "b" << "c" << "d") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|c|x" // filter << (QStringList() << "a" << "c") // expectedInitialProxyItems << true // dynamic @@ -1965,7 +1967,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("value_is_now_filtered_out") << (QStringList() << "a" << "b" << "c" << "d") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|c" // filter << (QStringList() << "a" << "c") // expectedInitialProxyItems << true // dynamic @@ -1980,7 +1982,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("non_dynamic_filter_does_not_update_sort") << (QStringList() << "c" << "b" << "a") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << (QStringList() << "a" << "b" << "c") // expectedInitialProxyItems << false // dynamic @@ -1997,7 +1999,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() void tst_QSortFilterProxyModel::changeSourceData() { QFETCH(QStringList, sourceItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QString, filter); QFETCH(QStringList, expectedInitialProxyItems); QFETCH(bool, dynamic); @@ -2022,7 +2024,7 @@ void tst_QSortFilterProxyModel::changeSourceData() model.setData(index, sourceItems.at(i), Qt::DisplayRole); } - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); (void)proxy.rowCount(QModelIndex()); // force mapping setupFilter(&proxy, filter); @@ -2050,7 +2052,7 @@ void tst_QSortFilterProxyModel::changeSourceData() QCOMPARE(removeSpy.count(), removeIntervals.count()); for (int i = 0; i < removeSpy.count(); ++i) { - QList args = removeSpy.at(i); + const auto &args = removeSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), removeIntervals.at(i).first); @@ -2059,7 +2061,7 @@ void tst_QSortFilterProxyModel::changeSourceData() QCOMPARE(insertSpy.count(), insertIntervals.count()); for (int i = 0; i < insertSpy.count(); ++i) { - QList args = insertSpy.at(i); + const auto &args = insertSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), insertIntervals.at(i).first); @@ -2107,8 +2109,7 @@ void tst_QSortFilterProxyModel::changeSourceDataKeepsStableSorting_qtbug1548() // doesn't alter the sorting. In this case, we sort on the DisplayRole, // and play with other roles. - static const QStringList rows - = QStringList() << "a" << "b" << "b" << "b" << "c" << "c" << "x"; + const QStringList rows({"a", "b", "b", "b", "c", "c", "x"}); // Build a table of pairs (string, #row) in each row QStandardItemModel model(0, 2); @@ -2119,11 +2120,7 @@ void tst_QSortFilterProxyModel::changeSourceDataKeepsStableSorting_qtbug1548() column0->setCheckState(Qt::Unchecked); QStandardItem *column1 = new QStandardItem(QString::number(rowNumber)); - - const QList row - = QList() << column0 << column1; - - model.appendRow(row); + model.appendRow({column0, column1}); } checkSortedTableModel(&model, rows); @@ -2201,15 +2198,12 @@ void tst_QSortFilterProxyModel::sortFilterRole() proxy.setSourceModel(&model); model.insertColumns(0, 1); - QList > sourceItems; - sourceItems = QList >() - << QPair("b", 3) - << QPair("c", 2) - << QPair("a", 1); + const QVector> + sourceItems({QPair("b", 3), + QPair("c", 2), + QPair("a", 1)}); - QList orderedItems; - orderedItems = QList() - << 2 << 1; + const QVector orderedItems({2, 1}); model.insertRows(0, sourceItems.count()); for (int i = 0; i < sourceItems.count(); ++i) { @@ -2273,70 +2267,70 @@ void tst_QSortFilterProxyModel::selectionFilteredOut() void tst_QSortFilterProxyModel::match_data() { QTest::addColumn("sourceItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("filter"); QTest::addColumn("proxyStartRow"); QTest::addColumn("what"); - QTest::addColumn("matchFlags"); + QTest::addColumn("matchFlags"); QTest::addColumn("expectedProxyItems"); QTest::newRow("1") << (QStringList() << "a") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << 0 // proxyStartRow << "a" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << (IntList() << 0); // expectedProxyItems QTest::newRow("2") << (QStringList() << "a" << "b") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << 0 // proxyStartRow << "b" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << (IntList() << 1); // expectedProxyItems QTest::newRow("3") << (QStringList() << "a" << "b") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "" // filter << 0 // proxyStartRow << "a" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << (IntList() << 1); // expectedProxyItems QTest::newRow("4") << (QStringList() << "b" << "d" << "a" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << 1 // proxyStartRow << "a" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << IntList(); // expectedProxyItems QTest::newRow("5") << (QStringList() << "b" << "d" << "a" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|b" // filter << 0 // proxyStartRow << "c" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << IntList(); // expectedProxyItems QTest::newRow("6") << (QStringList() << "b" << "d" << "a" << "c") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "a|b" // filter << 0 // proxyStartRow << "b" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << (IntList() << 0); // expectedProxyItems } void tst_QSortFilterProxyModel::match() { QFETCH(QStringList, sourceItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QString, filter); QFETCH(int, proxyStartRow); QFETCH(QString, what); - QFETCH(int, matchFlags); + QFETCH(Qt::MatchFlag, matchFlags); QFETCH(IntList, expectedProxyItems); QStandardItemModel model; @@ -2351,13 +2345,13 @@ void tst_QSortFilterProxyModel::match() model.setData(index, sourceItems.at(i), Qt::DisplayRole); } - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); setupFilter(&proxy, filter); QModelIndex startIndex = proxy.index(proxyStartRow, 0); QModelIndexList indexes = proxy.match(startIndex, Qt::DisplayRole, what, expectedProxyItems.count(), - Qt::MatchFlags(matchFlags)); + matchFlags); QCOMPARE(indexes.count(), expectedProxyItems.count()); for (int i = 0; i < indexes.count(); ++i) QCOMPARE(indexes.at(i).row(), expectedProxyItems.at(i)); @@ -2480,8 +2474,9 @@ void tst_QSortFilterProxyModel::invalidateMappedChildren() class EvenOddFilterModel : public QSortFilterProxyModel { + Q_OBJECT public: - virtual bool filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const + bool filterAcceptsRow(int srcRow, const QModelIndex &srcParent) const override { if (srcParent.isValid()) return (srcParent.row() % 2) ^ !(srcRow % 2); @@ -2572,9 +2567,9 @@ void tst_QSortFilterProxyModel::sourceModelDeletion() { QStandardItemModel model; proxyModel.setSourceModel(&model); - QCOMPARE(proxyModel.sourceModel(), static_cast(&model)); + QCOMPARE(proxyModel.sourceModel(), &model); } - QCOMPARE(proxyModel.sourceModel(), static_cast(0)); + QCOMPARE(proxyModel.sourceModel(), nullptr); } void tst_QSortFilterProxyModel::sortColumnTracking1() @@ -2830,8 +2825,9 @@ void tst_QSortFilterProxyModel::dynamicSorting() class QtTestModel: public QAbstractItemModel { + Q_OBJECT public: - QtTestModel(int _rows, int _cols, QObject *parent = 0) + QtTestModel(int _rows, int _cols, QObject *parent = nullptr) : QAbstractItemModel(parent) , rows(_rows) , cols(_cols) @@ -2839,12 +2835,12 @@ public: { } - bool canFetchMore(const QModelIndex &idx) const + bool canFetchMore(const QModelIndex &idx) const override { return !fetched.contains(idx); } - void fetchMore(const QModelIndex &idx) + void fetchMore(const QModelIndex &idx) override { if (fetched.contains(idx)) return; @@ -2853,23 +2849,24 @@ public: endInsertRows(); } - bool hasChildren(const QModelIndex & = QModelIndex()) const + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override { + Q_UNUSED(parent) return true; } - int rowCount(const QModelIndex& parent = QModelIndex()) const + int rowCount(const QModelIndex& parent = QModelIndex()) const override { return fetched.contains(parent) ? rows : 0; } - int columnCount(const QModelIndex& parent = QModelIndex()) const + int columnCount(const QModelIndex& parent = QModelIndex()) const override { - Q_UNUSED(parent); + Q_UNUSED(parent) return cols; } - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if (row < 0 || column < 0 || column >= cols || row >= rows) { return QModelIndex(); @@ -2879,14 +2876,14 @@ public: return i; } - QModelIndex parent(const QModelIndex &index) const + QModelIndex parent(const QModelIndex &index) const override { if (!parentHash.contains(index)) return QModelIndex(); return parentHash[index]; } - QVariant data(const QModelIndex &idx, int role) const + QVariant data(const QModelIndex &idx, int role) const override { if (!idx.isValid()) return QVariant(); @@ -3099,10 +3096,8 @@ void tst_QSortFilterProxyModel::appearsAndSort() { class PModel : public QSortFilterProxyModel { - public: - PModel() : mVisible(false) {}; protected: - bool filterAcceptsRow(int, const QModelIndex &) const + bool filterAcceptsRow(int, const QModelIndex &) const override { return mVisible; } @@ -3114,13 +3109,11 @@ void tst_QSortFilterProxyModel::appearsAndSort() invalidate(); } private: - bool mVisible; + bool mVisible = false; } proxyModel; QStringListModel sourceModel; - QStringList list; - list << "b" << "a" << "c"; - sourceModel.setStringList(list); + sourceModel.setStringList({"b", "a", "c"}); proxyModel.setSourceModel(&sourceModel); proxyModel.setDynamicSortFilter(true); @@ -3195,43 +3188,40 @@ void tst_QSortFilterProxyModel::unnecessaryDynamicSorting() } } -class SelectionProxyModel : QAbstractProxyModel +class SelectionProxyModel : public QAbstractProxyModel { Q_OBJECT public: - SelectionProxyModel() - : QAbstractProxyModel(), selectionModel(0) - { - } - - QModelIndex mapFromSource(QModelIndex const&) const + QModelIndex mapFromSource(QModelIndex const&) const override { return QModelIndex(); } - QModelIndex mapToSource(QModelIndex const&) const + QModelIndex mapToSource(QModelIndex const&) const override { return QModelIndex(); } - QModelIndex index(int, int, const QModelIndex&) const + QModelIndex index(int, int, const QModelIndex&) const override { return QModelIndex(); } - QModelIndex parent(const QModelIndex&) const + QModelIndex parent(const QModelIndex&) const override { return QModelIndex(); } - int rowCount(const QModelIndex&) const + int rowCount(const QModelIndex&) const override { return 0; } - int columnCount(const QModelIndex&) const + int columnCount(const QModelIndex&) const override { return 0; } - void setSourceModel( QAbstractItemModel *sourceModel ) + void setSourceModel(QAbstractItemModel *sourceModel) override { beginResetModel(); - disconnect( sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(sourceModelAboutToBeReset()) ); + disconnect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, + this, &SelectionProxyModel::sourceModelAboutToBeReset); QAbstractProxyModel::setSourceModel( sourceModel ); - connect( sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(sourceModelAboutToBeReset()) ); + connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, + this, &SelectionProxyModel::sourceModelAboutToBeReset); endResetModel(); } - void setSelectionModel( QItemSelectionModel *_selectionModel ) + void setSelectionModel(QItemSelectionModel *_selectionModel) { selectionModel = _selectionModel; } @@ -3249,7 +3239,7 @@ private slots: } private: - QItemSelectionModel *selectionModel; + QItemSelectionModel *selectionModel = nullptr; }; void tst_QSortFilterProxyModel::testMultipleProxiesWithSelection() @@ -3275,7 +3265,7 @@ void tst_QSortFilterProxyModel::testMultipleProxiesWithSelection() selectionModel.select( proxy2.index( 0, 0 ), QItemSelectionModel::Select ); // trick the proxy into emitting begin/end reset signals. - proxy.setSourceModel(0); + proxy.setSourceModel(nullptr); } static bool isValid(const QItemSelection &selection) @@ -3321,7 +3311,7 @@ class Model10287 : public QStandardItemModel Q_OBJECT public: - Model10287(QObject *parent = 0) + Model10287(QObject *parent = nullptr) : QStandardItemModel(0, 1, parent) { parentItem = new QStandardItem("parent"); @@ -3352,7 +3342,7 @@ class Proxy10287 : public QSortFilterProxyModel Q_OBJECT public: - Proxy10287(QAbstractItemModel *model, QObject *parent = 0) + Proxy10287(QAbstractItemModel *model, QObject *parent = nullptr) : QSortFilterProxyModel(parent) { setSourceModel(model); @@ -3360,7 +3350,7 @@ public: } protected: - virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { // Filter based on UserRole in model QModelIndex i = sourceModel()->index(source_row, 0, source_parent); @@ -3380,13 +3370,9 @@ class FilteredColumnProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - FilteredColumnProxyModel(QObject *parent = 0) - : QSortFilterProxyModel(parent) - { - } - + using QSortFilterProxyModel::QSortFilterProxyModel; protected: - bool filterAcceptsColumn(int column, const QModelIndex & /* source_parent */) const + bool filterAcceptsColumn(int column, const QModelIndex &) const override { return column % 2 != 0; } @@ -3413,12 +3399,7 @@ class ChangableHeaderData : public QStringListModel { Q_OBJECT public: - explicit ChangableHeaderData(QObject *parent = 0) - : QStringListModel(parent) - { - - } - + using QStringListModel::QStringListModel; void emitHeaderDataChanged() { headerDataChanged(Qt::Vertical, 0, rowCount() - 1); @@ -3461,7 +3442,7 @@ void tst_QSortFilterProxyModel::resetInvalidate() struct Proxy : QSortFilterProxyModel { QString pattern; - virtual bool filterAcceptsRow(int source_row, const QModelIndex&) const + bool filterAcceptsRow(int source_row, const QModelIndex&) const override { return sourceModel()->data(sourceModel()->index(source_row, 0)).toString().contains(pattern); } @@ -3479,17 +3460,16 @@ void tst_QSortFilterProxyModel::resetInvalidate() } }; - QStringListModel sourceModel(QStringList() << "Poisson" << "Vache" << "Brebis" - << "Elephant" << "Cochon" << "Serpent" - << "Mouton" << "Ecureuil" << "Mouche"); + QStringListModel sourceModel({"Poisson", "Vache", "Brebis", + "Elephant", "Cochon", "Serpent", + "Mouton", "Ecureuil", "Mouche"}); Proxy proxy; proxy.pattern = QString::fromLatin1("n"); proxy.setSourceModel(&sourceModel); QCOMPARE(proxy.rowCount(), 5); - for (int i = 0; i < proxy.rowCount(); i++) { + for (int i = 0; i < proxy.rowCount(); i++) QVERIFY(proxy.data(proxy.index(i,0)).toString().contains('n')); - } proxy.pattern = QString::fromLatin1("o"); proxy.notifyChange(test); @@ -3511,13 +3491,13 @@ class CustomDataProxy : public QSortFilterProxyModel Q_OBJECT public: - CustomDataProxy(QObject *parent = 0) + CustomDataProxy(QObject *parent = nullptr) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); } - void setSourceModel(QAbstractItemModel *sourceModel) + void setSourceModel(QAbstractItemModel *sourceModel) override { // It would be possible to use only the modelReset signal of the source model to clear // the data in *this, however, this requires that the slot is connected @@ -3534,16 +3514,13 @@ public: for (int i = 0; i < sourceModel->rowCount(); ++i) { if (sourceModel->index(i, 0).data().toString().endsWith(QLatin1Char('y'))) - { m_backgroundColours.insert(i, Qt::blue); - } else if (sourceModel->index(i, 0).data().toString().endsWith(QLatin1Char('r'))) - { + else if (sourceModel->index(i, 0).data().toString().endsWith(QLatin1Char('r'))) m_backgroundColours.insert(i, Qt::red); - } } } - QVariant data(const QModelIndex &index, int role) const + QVariant data(const QModelIndex &index, int role) const override { if (role != Qt::BackgroundRole) return QSortFilterProxyModel::data(index, role); @@ -3564,12 +3541,14 @@ class ModelObserver : public QObject { Q_OBJECT public: - ModelObserver(QAbstractItemModel *model, QObject *parent = 0) - : QObject(parent), m_model(model) - { - connect(m_model, SIGNAL(modelAboutToBeReset()), SLOT(modelAboutToBeReset())); - connect(m_model, SIGNAL(modelReset()), SLOT(modelReset())); - } + ModelObserver(QAbstractItemModel *model, QObject *parent = nullptr) + : QObject(parent), m_model(model) + { + connect(m_model, &QAbstractItemModel::modelAboutToBeReset, + this, &ModelObserver::modelAboutToBeReset); + connect(m_model, &QAbstractItemModel::modelReset, + this, &ModelObserver::modelReset); + } public slots: void modelAboutToBeReset() @@ -3580,7 +3559,7 @@ public slots: QColor color = m_model->index(i, 0).data(Qt::BackgroundRole).value(); if (color == Qt::blue) ++blues; - if (color == Qt::red) + else if (color == Qt::red) ++reds; } QCOMPARE(blues, 11); @@ -3595,7 +3574,7 @@ public slots: QColor color = m_model->index(i, 0).data(Qt::BackgroundRole).value(); if (color == Qt::blue) ++blues; - if (color == Qt::red) + else if (color == Qt::red) ++reds; } QCOMPARE(reds, 0); @@ -3609,26 +3588,25 @@ private: void tst_QSortFilterProxyModel::testResetInternalData() { - - QStringListModel model(QStringList() << "Monday" - << "Tuesday" - << "Wednesday" - << "Thursday" - << "Friday" - << "January" - << "February" - << "March" - << "April" - << "May" - << "Saturday" - << "June" - << "Sunday" - << "July" - << "August" - << "September" - << "October" - << "November" - << "December"); + QStringListModel model({"Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "January", + "February", + "March", + "April", + "May", + "Saturday", + "June", + "Sunday", + "July", + "August", + "September", + "October", + "November", + "December"}); CustomDataProxy proxy; proxy.setSourceModel(&model); @@ -3636,7 +3614,7 @@ void tst_QSortFilterProxyModel::testResetInternalData() ModelObserver observer(&proxy); // Cause the source model to reset. - model.setStringList(QStringList() << "Spam" << "Eggs"); + model.setStringList({"Spam", "Eggs"}); } @@ -3752,13 +3730,17 @@ class SignalArgumentChecker : public QObject { Q_OBJECT public: - SignalArgumentChecker(QAbstractItemModel *model, QAbstractProxyModel *proxy, QObject *parent = 0) - : QObject(parent), m_proxy(proxy) + SignalArgumentChecker(QAbstractItemModel *model, QAbstractProxyModel *proxy, QObject *parent = nullptr) + : QObject(parent), m_proxy(proxy) { - connect(model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); - connect(model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(rowsMoved(QModelIndex,int,int,QModelIndex,int))); - connect(proxy, SIGNAL(layoutAboutToBeChanged(QList)), SLOT(layoutAboutToBeChanged(QList))); - connect(proxy, SIGNAL(layoutChanged(QList)), SLOT(layoutChanged(QList))); + connect(model, &QAbstractItemModel::rowsAboutToBeMoved, + this, &SignalArgumentChecker::rowsAboutToBeMoved); + connect(model, &QAbstractItemModel::rowsMoved, + this, &SignalArgumentChecker::rowsMoved); + connect(proxy, &QAbstractProxyModel::layoutAboutToBeChanged, + this, &SignalArgumentChecker::layoutAboutToBeChanged); + connect(proxy, &QAbstractProxyModel::layoutChanged, + this, &SignalArgumentChecker::layoutChanged); } private slots: @@ -3881,7 +3863,7 @@ void tst_QSortFilterProxyModel::moveSourceRows() QVERIFY(filterBothAfterParentLayoutSpy.isValid()); { - ModelMoveCommand moveCommand(&model, 0); + ModelMoveCommand moveCommand(&model, nullptr); moveCommand.setAncestorRowNumbers(QList() << 2); moveCommand.setDestAncestors(QList() << 5); moveCommand.setStartRow(3); @@ -3923,13 +3905,7 @@ class FilterProxy : public QSortFilterProxyModel { Q_OBJECT public: - FilterProxy(QObject *parent = 0) - : QSortFilterProxyModel(parent), - mode(false) - { - - } - + using QSortFilterProxyModel::QSortFilterProxyModel; public slots: void setMode(bool on) { @@ -3938,25 +3914,23 @@ public slots: } protected: - virtual bool filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { if (mode) { - if (!source_parent.isValid()) { + if (!source_parent.isValid()) return true; - } else { + else return (source_row % 2) != 0; - } } else { - if (!source_parent.isValid()) { + if (!source_parent.isValid()) return source_row >= 2 && source_row < 10; - } else { + else return true; - } } } private: - bool mode; + bool mode = false; }; void tst_QSortFilterProxyModel::hierarchyFilterInvalidation() @@ -3989,13 +3963,7 @@ class FilterProxy2 : public QSortFilterProxyModel { Q_OBJECT public: - FilterProxy2(QObject *parent = 0) - : QSortFilterProxyModel(parent), - mode(false) - { - - } - + using QSortFilterProxyModel::QSortFilterProxyModel; public slots: void setMode(bool on) { @@ -4004,21 +3972,17 @@ public slots: } protected: - virtual bool filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { - if (source_parent.isValid()) { + if (source_parent.isValid()) return true; - } else { - if (0 == source_row) { - return true; - } else { - return !mode; - } - } + if (0 == source_row) + return true; + return !mode; } private: - bool mode; + bool mode = false; }; void tst_QSortFilterProxyModel::simpleFilterInvalidation() @@ -4047,22 +4011,21 @@ class CustomRoleNameModel : public QAbstractListModel { Q_OBJECT public: - CustomRoleNameModel(QObject *parent = 0) : QAbstractListModel(parent) {} - - QVariant data(const QModelIndex &index, int role) const + using QAbstractListModel::QAbstractListModel; + QVariant data(const QModelIndex &index, int role) const override { - Q_UNUSED(index); - Q_UNUSED(role); + Q_UNUSED(index) + Q_UNUSED(role) return QVariant(); } - int rowCount(const QModelIndex &parent = QModelIndex()) const + int rowCount(const QModelIndex &parent = QModelIndex()) const override { - Q_UNUSED(parent); + Q_UNUSED(parent) return 0; } - QHash roleNames() const + QHash roleNames() const override { QHash rn = QAbstractListModel::roleNames(); rn[Qt::UserRole + 1] = "custom"; @@ -4089,8 +4052,7 @@ class DropOnOddRows : public QAbstractListModel { Q_OBJECT public: - DropOnOddRows(QObject *parent = 0) : QAbstractListModel(parent) {} - + using QAbstractListModel::QAbstractListModel; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (role == Qt::DisplayRole) @@ -4100,15 +4062,15 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override { - Q_UNUSED(parent); + Q_UNUSED(parent) return 10; } bool canDropMimeData(const QMimeData *, Qt::DropAction, int row, int column, const QModelIndex &parent) const override { - Q_UNUSED(row); - Q_UNUSED(column); + Q_UNUSED(row) + Q_UNUSED(column) return parent.row() % 2 == 0; } }; @@ -4117,12 +4079,7 @@ class SourceAssertion : public QSortFilterProxyModel { Q_OBJECT public: - explicit SourceAssertion(QObject *parent = 0) - : QSortFilterProxyModel(parent) - { - - } - + using QSortFilterProxyModel::QSortFilterProxyModel; QModelIndex mapToSource(const QModelIndex &proxyIndex) const override { Q_ASSERT(sourceModel()); @@ -4150,9 +4107,11 @@ void tst_QSortFilterProxyModel::noMapAfterSourceDelete() // QTBUG-39549, test whether canDropMimeData(), dropMimeData() are proxied as well // by invoking them on a QSortFilterProxyModel proxying a QStandardItemModel that allows drops // on row #1, filtering for that row. -class DropTestModel : public QStandardItemModel { +class DropTestModel : public QStandardItemModel +{ + Q_OBJECT public: - explicit DropTestModel(QObject *parent = 0) : QStandardItemModel(0, 1, parent) + explicit DropTestModel(QObject *parent = nullptr) : QStandardItemModel(0, 1, parent) { appendRow(new QStandardItem(QStringLiteral("Row0"))); appendRow(new QStandardItem(QStringLiteral("Row1"))); @@ -4173,11 +4132,12 @@ void tst_QSortFilterProxyModel::forwardDropApi() model.setSourceModel(new DropTestModel(&model)); model.setFilterFixedString(QStringLiteral("Row1")); QCOMPARE(model.rowCount(), 1); - QVERIFY(model.canDropMimeData(0, Qt::CopyAction, 0, 0, QModelIndex())); - QVERIFY(model.dropMimeData(0, Qt::CopyAction, 0, 0, QModelIndex())); + QVERIFY(model.canDropMimeData(nullptr, Qt::CopyAction, 0, 0, QModelIndex())); + QVERIFY(model.dropMimeData(nullptr, Qt::CopyAction, 0, 0, QModelIndex())); } -static QString rowTexts(QAbstractItemModel *model) { +static QString rowTexts(QAbstractItemModel *model) +{ QString str; for (int row = 0 ; row < model->rowCount(); ++row) str += model->index(row, 0).data().toString(); @@ -4199,7 +4159,7 @@ void tst_QSortFilterProxyModel::canDropMimeData() // the proxy should correctly map canDropMimeData to the source model, // i.e. accept drops on the first 5 rows and refuse drops on the next 5. for (int row = 0; row < proxy.rowCount(); ++row) - QCOMPARE(proxy.canDropMimeData(0, Qt::CopyAction, -1, -1, proxy.index(row, 0)), row < 5); + QCOMPARE(proxy.canDropMimeData(nullptr, Qt::CopyAction, -1, -1, proxy.index(row, 0)), row < 5); } void tst_QSortFilterProxyModel::resortingDoesNotBreakTreeModels() @@ -4239,12 +4199,7 @@ void tst_QSortFilterProxyModel::resortingDoesNotBreakTreeModels() void tst_QSortFilterProxyModel::filterHint() { // test that a filtering model does not emit layoutChanged with a hint - QStringListModel model(QStringList() << "one" - << "two" - << "three" - << "four" - << "five" - << "six"); + QStringListModel model({"one", "two", "three", "four", "five", "six"}); QSortFilterProxyModel proxy1; proxy1.setSourceModel(&model); proxy1.setSortRole(Qt::DisplayRole); @@ -4296,8 +4251,7 @@ class StepTreeModel : public QAbstractItemModel { Q_OBJECT public: - StepTreeModel(QObject * parent = 0) - : QAbstractItemModel(parent), m_depth(0) {} + using QAbstractItemModel::QAbstractItemModel; int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; } @@ -4338,7 +4292,7 @@ public: void setDepth(quintptr depth) { - int parentIdWithLayoutChange = (m_depth < depth) ? m_depth : depth; + quintptr parentIdWithLayoutChange = (m_depth < depth) ? m_depth : depth; QList parentsOfLayoutChange; parentsOfLayoutChange.push_back(createIndex(0, 0, parentIdWithLayoutChange)); @@ -4370,7 +4324,7 @@ private: } private: - quintptr m_depth; + quintptr m_depth = 0; }; void tst_QSortFilterProxyModel::sourceLayoutChangeLeavesValidPersistentIndexes() @@ -4439,7 +4393,7 @@ void tst_QSortFilterProxyModel::rowMoveLeavesValidPersistentIndexes() QPersistentModelIndex persistentIndex = proxy1.match(proxy1.index(0, 0), Qt::DisplayRole, "2", 1, Qt::MatchRecursive).first(); - ModelMoveCommand moveCommand(&model, 0); + ModelMoveCommand moveCommand(&model, nullptr); moveCommand.setAncestorRowNumbers(QList{0, 0, 0, 0}); moveCommand.setStartRow(0); moveCommand.setEndRow(0); @@ -4578,7 +4532,7 @@ void tst_QSortFilterProxyModel::emitLayoutChangedOnlyIfSortingChanged() void tst_QSortFilterProxyModel::removeIntervals_data() { QTest::addColumn("sourceItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("filter"); QTest::addColumn("replacementSourceItems"); QTest::addColumn("expectedRemovedProxyIntervals"); @@ -4588,7 +4542,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "[^x]" // filter << (QStringList() << "x" << "x" @@ -4601,7 +4555,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "[^x]" // filter << (QStringList() << "x" << "x" @@ -4614,7 +4568,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "[^x]" // filter << (QStringList() << "x" << "b" @@ -4627,7 +4581,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "[^x]" // filter << (QStringList() << "x" << "b" @@ -4640,7 +4594,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() void tst_QSortFilterProxyModel::removeIntervals() { QFETCH(QStringList, sourceItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QString, filter); QFETCH(QStringList, replacementSourceItems); QFETCH(IntPairList, expectedRemovedProxyIntervals); @@ -4659,9 +4613,7 @@ void tst_QSortFilterProxyModel::removeIntervals() } proxy.setDynamicSortFilter(true); - - if (sortOrder != -1) - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); if (!filter.isEmpty()) setupFilter(&proxy, filter); @@ -4681,7 +4633,7 @@ void tst_QSortFilterProxyModel::removeIntervals() QCOMPARE(aboutToRemoveSpy.count(), expectedRemovedProxyIntervals.count()); for (int i = 0; i < aboutToRemoveSpy.count(); ++i) { - QList args = aboutToRemoveSpy.at(i); + const auto &args = aboutToRemoveSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first); @@ -4689,7 +4641,7 @@ void tst_QSortFilterProxyModel::removeIntervals() } QCOMPARE(removeSpy.count(), expectedRemovedProxyIntervals.count()); for (int i = 0; i < removeSpy.count(); ++i) { - QList args = removeSpy.at(i); + const auto &args = removeSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first); @@ -4838,8 +4790,8 @@ void tst_QSortFilterProxyModel::filterAndInsertColumn_data() void tst_QSortFilterProxyModel::filterAndInsertColumn() { - class ColumnFilterProxy : public QSortFilterProxyModel { - Q_DISABLE_COPY(ColumnFilterProxy) + class ColumnFilterProxy : public QSortFilterProxyModel + { ColumnFilterMode filerMode; public: ColumnFilterProxy(ColumnFilterMode mode) diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h index 8ae97165b8..71662bda07 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h @@ -29,19 +29,10 @@ #ifndef TST_QSORTFILTERPROXYMODEL_H #define TST_QSORTFILTERPROXYMODEL_H -#include #include "dynamictreemodel.h" - -#include -#include -#include -#include - -#include - -typedef QList IntList; -typedef QPair IntPair; -typedef QList IntPairList; +#include +#include +#include enum class FilterType { RegExp, @@ -53,10 +44,6 @@ Q_DECLARE_METATYPE(QList) class tst_QSortFilterProxyModel : public QObject { Q_OBJECT - -public: - tst_QSortFilterProxyModel(); - public slots: void initTestCase(); void cleanupTestCase(); @@ -181,8 +168,8 @@ protected: FilterType m_filterType; private: - QStandardItemModel *m_model; - QSortFilterProxyModel *m_proxy; + QStandardItemModel *m_model = nullptr; + QSortFilterProxyModel *m_proxy = nullptr; }; Q_DECLARE_METATYPE(QAbstractItemModel::LayoutChangeHint) diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp index 7cae554963..dbafedbb5d 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp @@ -28,9 +28,8 @@ #include #include - -#include -#include +#include +#include Q_DECLARE_METATYPE(QModelIndex) diff --git a/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp b/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp index 72a6f0360d..4b13ac45cc 100644 --- a/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp +++ b/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp @@ -338,7 +338,7 @@ void tst_QStateMachine::transitionToRootState() machine.postEvent(new QEvent(QEvent::User)); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " - "Child mode of state machine 'machine' is not 'ExclusiveStates'!"); + "Child mode of state machine 'machine' is not 'ExclusiveStates'."); QCoreApplication::processEvents(); QVERIFY(machine.configuration().isEmpty()); QVERIFY(!machine.isRunning()); @@ -1064,7 +1064,7 @@ void tst_QStateMachine::transitionToStateNotInGraph() machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " - "Child mode of state machine '' is not 'ExclusiveStates'!"); + "Child mode of state machine '' is not 'ExclusiveStates'."); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), false); @@ -2103,7 +2103,7 @@ void tst_QStateMachine::parallelRootState() QVERIFY(finishedSpy.isValid()); machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " - "Child mode of state machine '' is not 'ExclusiveStates'!"); + "Child mode of state machine '' is not 'ExclusiveStates'."); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 4); QVERIFY(machine.configuration().contains(s1)); @@ -3316,7 +3316,7 @@ void tst_QStateMachine::targetStateWithNoParent() machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " - "Child mode of state machine '' is not 'ExclusiveStates'!"); + "Child mode of state machine '' is not 'ExclusiveStates'."); TEST_ACTIVE_CHANGED(s1, 2); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(machine.isRunning(), false); diff --git a/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp b/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp index f3c647515a..a9fd282ac9 100644 --- a/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp +++ b/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp @@ -40,6 +40,7 @@ #include "nontracked.h" #include "wrapper.h" +#include #include #include #include @@ -106,12 +107,15 @@ private slots: void sharedFromThis(); void constructorThrow(); + void overloads(); void threadStressTest_data(); void threadStressTest(); void validConstructs(); void invalidConstructs_data(); void invalidConstructs(); + + // let invalidConstructs be the last test, because it's the slowest; // add new tests above this block public slots: @@ -2383,6 +2387,11 @@ void tst_QSharedPointer::invalidConstructs_data() QTest::newRow("incompatible-custom-lambda-deleter") << &QTest::QExternalTest::tryCompileFail << "QSharedPointer ptr(new Data, [](int *) {});\n"; + + QTest::newRow("incompatible-overload") + << &QTest::QExternalTest::tryCompileFail + << "void foo(QSharedPointer) {}\n" + "void bar() { foo(QSharedPointer()); }\n"; } void tst_QSharedPointer::invalidConstructs() @@ -2883,5 +2892,50 @@ void tst_QSharedPointer::reentrancyWhileDestructing() ReentrancyWhileDestructing::A obj; } +namespace { +struct Base1 {}; +struct Base2 {}; + +struct Child1 : Base1 {}; +struct Child2 : Base2 {}; + +template class SmartPtr> +struct Overloaded +{ + std::array call(const SmartPtr &) + { + return {}; + } + std::array call(const SmartPtr &) + { + return {}; + } + static const Q_CONSTEXPR uint base1Called = sizeof(std::array); + static const Q_CONSTEXPR uint base2Called = sizeof(std::array); + + void test() + { +#define QVERIFY_CALLS(expr, base) Q_STATIC_ASSERT(sizeof(call(expr)) == base##Called) + QVERIFY_CALLS(SmartPtr{}, base1); + QVERIFY_CALLS(SmartPtr{}, base2); + QVERIFY_CALLS(SmartPtr{}, base1); + QVERIFY_CALLS(SmartPtr{}, base2); + QVERIFY_CALLS(SmartPtr{}, base1); + QVERIFY_CALLS(SmartPtr{}, base2); + QVERIFY_CALLS(SmartPtr{}, base1); + QVERIFY_CALLS(SmartPtr{}, base2); +#undef QVERIFY_CALLS + } +}; +} + +void tst_QSharedPointer::overloads() +{ + Overloaded sharedOverloaded; + sharedOverloaded.test(); + Overloaded weakOverloaded; + weakOverloaded.test(); +} + QTEST_MAIN(tst_QSharedPointer) #include "tst_qsharedpointer.moc" diff --git a/tests/auto/gui/image/qimage/tst_qimage.cpp b/tests/auto/gui/image/qimage/tst_qimage.cpp index b84aa52465..2a9b92ed35 100644 --- a/tests/auto/gui/image/qimage/tst_qimage.cpp +++ b/tests/auto/gui/image/qimage/tst_qimage.cpp @@ -2132,6 +2132,12 @@ void tst_QImage::paintEngine() QCOMPARE(engine, img.paintEngine()); QCOMPARE(img, expected); + + { + QImage img1(16, 16, QImage::Format_ARGB32); + QImage img2 = img1; + QVERIFY(img2.paintEngine()); + } } void tst_QImage::setAlphaChannelWhilePainting() diff --git a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp index a474acd790..2dcca0209e 100644 --- a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp +++ b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp @@ -2068,7 +2068,12 @@ void tst_QTextLayout::cursorInLigatureWithMultipleLines() void tst_QTextLayout::xToCursorForLigatures() { +#if defined(Q_OS_WIN32) + QTextLayout layout("fi", QFont("Calibri", 20)); +#else QTextLayout layout("fi", QFont("Times", 20)); +#endif + layout.setCacheEnabled(true); layout.beginLayout(); QTextLine line = layout.createLine(); diff --git a/tests/auto/network/ssl/qdtls/tst_qdtls.cpp b/tests/auto/network/ssl/qdtls/tst_qdtls.cpp index 6a94eee389..4dfdf14e5b 100644 --- a/tests/auto/network/ssl/qdtls/tst_qdtls.cpp +++ b/tests/auto/network/ssl/qdtls/tst_qdtls.cpp @@ -1131,7 +1131,7 @@ void tst_QDtls::handshakeReadyRead() QUdpSocket *socket = qobject_cast(sender()); Q_ASSERT(socket); - if (!socket->pendingDatagramSize()) + if (socket->pendingDatagramSize() <= 0) return; const bool isServer = socket == &serverSocket; diff --git a/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp b/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp index c90e9cb2c8..a273ceaa17 100644 --- a/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp +++ b/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp @@ -352,7 +352,7 @@ void tst_QDtlsCookie::receiveMessage(QUdpSocket *socket, QByteArray *message, { Q_ASSERT(socket && message); - if (!socket->pendingDatagramSize()) + if (socket->pendingDatagramSize() <= 0) testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); @@ -377,7 +377,7 @@ void tst_QDtlsCookie::serverReadyRead() { Q_ASSERT(clientsToWait); - if (!serverSocket.pendingDatagramSize()) + if (serverSocket.pendingDatagramSize() <= 0) return; QByteArray hello; @@ -410,7 +410,7 @@ void tst_QDtlsCookie::clientReadyRead() QUdpSocket *clientSocket = qobject_cast(sender()); Q_ASSERT(clientSocket); - if (!clientSocket->pendingDatagramSize()) + if (clientSocket->pendingDatagramSize() <= 0) return; QDtls *handshake = nullptr; diff --git a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp index cc58356103..1a5aa63489 100644 --- a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp +++ b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp @@ -190,8 +190,7 @@ private slots: void task_250026_data() { generic_data("QODBC"); } void task_250026(); - void task_205701_data() { generic_data("QMYSQL"); } - void task_205701(); + void crashQueryOnCloseDatabase(); void task_233829_data() { generic_data("QPSQL"); } void task_233829(); @@ -305,6 +304,8 @@ void tst_QSqlQuery::init() void tst_QSqlQuery::cleanup() { + if (QTest::currentTestFunction() == QLatin1String("crashQueryOnCloseDatabase")) + return; QFETCH( QString, dbName ); QSqlDatabase db = QSqlDatabase::database( dbName ); CHECK_DATABASE( db ); @@ -3427,19 +3428,17 @@ void tst_QSqlQuery::task_250026() QCOMPARE( q.value( 0 ).toString().length(), data1026.length() ); } -void tst_QSqlQuery::task_205701() +void tst_QSqlQuery::crashQueryOnCloseDatabase() { - QSqlDatabase qsdb = QSqlDatabase::addDatabase("QMYSQL", "atest"); - qsdb.setHostName("test"); - qsdb.setDatabaseName("test"); - qsdb.setUserName("test"); - qsdb.setPassword("test"); - qsdb.open(); - -// { - QSqlQuery query(qsdb); -// } - QSqlDatabase::removeDatabase("atest"); + for (const auto &dbName : qAsConst(dbs.dbNames)) { + QSqlDatabase clonedDb = QSqlDatabase::cloneDatabase( + QSqlDatabase::database(dbName), "crashTest"); + qDebug() << "Testing crash in sqlquery dtor for driver" << clonedDb.driverName(); + QVERIFY(clonedDb.open()); + QSqlQuery q(clonedDb); + clonedDb.close(); + QSqlDatabase::removeDatabase("crashTest"); + } } void tst_QSqlQuery::task_233829() diff --git a/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp b/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp index b49d2cd8b0..cfbe1f96ee 100644 --- a/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp @@ -3747,8 +3747,6 @@ void tst_QGraphicsScene::changedSignal() QCoreApplication::processEvents(); QCOMPARE(cl.changes.size(), 2); QCOMPARE(cl.changes.at(1).size(), 2); - QCOMPARE(cl.changes.at(1).first(), QRectF(0, 0, 10, 10)); - QCOMPARE(cl.changes.at(1).last(), QRectF(20, 0, 10, 10)); QCOMPARE(scene.sceneRect(), QRectF(0, 0, 30, 10)); } diff --git a/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp b/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp index cb3c28c909..1d0153213b 100644 --- a/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp +++ b/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp @@ -420,6 +420,7 @@ private slots: void taskQTBUG_10169_sizeHintForRow(); void taskQTBUG_30653_doItemsLayout(); void taskQTBUG_50171_selectRowAfterSwapColumns(); + void deselectRow(); #if QT_CONFIG(wheelevent) void mouseWheel_data(); @@ -4525,6 +4526,31 @@ void tst_QTableView::taskQTBUG_50171_selectRowAfterSwapColumns() } } +class DeselectTableWidget : public QTableWidget +{ +public: + using QTableWidget::QTableWidget; + QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &, + const QEvent * = nullptr) const override + { + return QItemSelectionModel::Toggle; + } +}; + +void tst_QTableView::deselectRow() +{ + DeselectTableWidget tw(20, 20); + tw.show(); + QVERIFY(QTest::qWaitForWindowExposed(&tw)); + tw.hideColumn(0); + QVERIFY(tw.isColumnHidden(0)); + tw.selectRow(1); + QVERIFY(tw.selectionModel()->isRowSelected(1, QModelIndex())); + tw.selectRow(1); + // QTBUG-79092 - deselection was not possible when column 0 was hidden + QVERIFY(!tw.selectionModel()->isRowSelected(1, QModelIndex())); +} + // This has nothing to do with QTableView, but it's convenient to reuse the QtTestTableModel #if QT_CONFIG(textmarkdownwriter)