diff options
Diffstat (limited to 'src/qml/common')
-rw-r--r-- | src/qml/common/qjsnumbercoercion.cpp | 30 | ||||
-rw-r--r-- | src/qml/common/qjsnumbercoercion.h | 40 | ||||
-rw-r--r-- | src/qml/common/qqmljsmemorypool_p.h | 10 | ||||
-rw-r--r-- | src/qml/common/qqmlsignalnames.cpp | 257 | ||||
-rw-r--r-- | src/qml/common/qqmlsignalnames_p.h | 59 | ||||
-rw-r--r-- | src/qml/common/qqmltranslation_p.h | 6 | ||||
-rw-r--r-- | src/qml/common/qv4alloca_p.h | 36 | ||||
-rw-r--r-- | src/qml/common/qv4compileddata.cpp | 431 | ||||
-rw-r--r-- | src/qml/common/qv4compileddata_p.h | 472 | ||||
-rw-r--r-- | src/qml/common/qv4staticvalue_p.h | 13 | ||||
-rw-r--r-- | src/qml/common/qv4stringtoarrayindex_p.h | 2 |
11 files changed, 1168 insertions, 188 deletions
diff --git a/src/qml/common/qjsnumbercoercion.cpp b/src/qml/common/qjsnumbercoercion.cpp index 986a3e97f2..8cd96a4e25 100644 --- a/src/qml/common/qjsnumbercoercion.cpp +++ b/src/qml/common/qjsnumbercoercion.cpp @@ -14,6 +14,36 @@ QT_BEGIN_NAMESPACE */ /*! + \fn bool QJSNumberCoercion::isInteger(double d) + \internal + \deprecated 6.7 + */ + +/*! + \fn bool QJSNumberCoercion::isArrayIndex(double d) + \internal + + Checks whether \a d contains a value that can serve as an index into an array. + For that, \a d must be a non-negative value representable as an unsigned 32bit int. + */ + +/*! + \fn bool QJSNumberCoercion::isArrayIndex(qint64 i) + \internal + + Checks whether \a i contains a value that can serve as an index into an array. + For that, \a d must be a non-negative value representable as an unsigned 32bit int. +*/ + +/*! + \fn bool QJSNumberCoercion::isArrayIndex(quint64 i) + \internal + + Checks whether \a i contains a value that can serve as an index into an array. + For that, \a d must be a value representable as an unsigned 32bit int. +*/ + +/*! \fn int QJSNumberCoercion::toInteger(double d) \internal diff --git a/src/qml/common/qjsnumbercoercion.h b/src/qml/common/qjsnumbercoercion.h index 03827f82f1..0023bff6e8 100644 --- a/src/qml/common/qjsnumbercoercion.h +++ b/src/qml/common/qjsnumbercoercion.h @@ -12,17 +12,47 @@ QT_BEGIN_NAMESPACE class QJSNumberCoercion { public: - static constexpr bool isInteger(double d) { - return equals(d, d) && equals(static_cast<int>(d), d); + + static constexpr bool isInteger(double d) + { + // Comparing d with itself checks for NaN and comparing d with the min and max values + // for int also covers infinities. + if (!equals(d, d) || d < (std::numeric_limits<int>::min)() + || d > (std::numeric_limits<int>::max)()) { + return false; + } + + return equals(static_cast<int>(d), d); + } + + static constexpr bool isArrayIndex(double d) + { + return d >= 0 + && equals(d, d) + && d <= (std::numeric_limits<uint>::max)() + && equals(static_cast<uint>(d), d); + } + + static constexpr bool isArrayIndex(qint64 i) + { + return i >= 0 && i <= (std::numeric_limits<uint>::max)(); + } + + static constexpr bool isArrayIndex(quint64 i) + { + return i <= (std::numeric_limits<uint>::max)(); } static constexpr int toInteger(double d) { + // Check for NaN if (!equals(d, d)) return 0; - const int i = static_cast<int>(d); - if (equals(i, d)) - return i; + if (d >= (std::numeric_limits<int>::min)() && d <= (std::numeric_limits<int>::max)()) { + const int i = static_cast<int>(d); + if (equals(i, d)) + return i; + } return QJSNumberCoercion(d).toInteger(); } diff --git a/src/qml/common/qqmljsmemorypool_p.h b/src/qml/common/qqmljsmemorypool_p.h index a2d18f9e7e..6f0f8ed491 100644 --- a/src/qml/common/qqmljsmemorypool_p.h +++ b/src/qml/common/qqmljsmemorypool_p.h @@ -43,13 +43,12 @@ public: free(_blocks); } - qDeleteAll(strings); } inline void *allocate(size_t size) { size = (size + 7) & ~size_t(7); - if (Q_LIKELY(_ptr && (_ptr + size < _end))) { + if (Q_LIKELY(_ptr && size < size_t(_end - _ptr))) { void *addr = _ptr; _ptr += size; return addr; @@ -67,9 +66,8 @@ public: template <typename Tp, typename... Ta> Tp *New(Ta... args) { return new (this->allocate(sizeof(Tp))) Tp(args...); } - QStringView newString(const QString &string) { - strings.append(new QString(string)); - return QStringView(*strings.last()); + QStringView newString(QString string) { + return strings.emplace_back(std::move(string)); } private: @@ -113,7 +111,7 @@ private: int _blockCount = -1; char *_ptr = nullptr; char *_end = nullptr; - QVector<QString*> strings; + QStringList strings; enum { diff --git a/src/qml/common/qqmlsignalnames.cpp b/src/qml/common/qqmlsignalnames.cpp new file mode 100644 index 0000000000..d2a23205a6 --- /dev/null +++ b/src/qml/common/qqmlsignalnames.cpp @@ -0,0 +1,257 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmlsignalnames_p.h" +#include <iterator> +#include <algorithm> +#include <optional> +#include <string> + +QT_BEGIN_NAMESPACE + +using namespace Qt::Literals; + +static constexpr const QLatin1String On("on"); +static constexpr const QLatin1String Changed("Changed"); + +static constexpr const qsizetype StrlenOn = On.length(); +static constexpr const qsizetype StrlenChanged = Changed.length(); + +static std::optional<qsizetype> firstLetterIdx(QStringView name, qsizetype removePrefix = 0, + qsizetype removeSuffix = 0) +{ + auto end = std::prev(name.cend(), removeSuffix); + auto result = std::find_if(std::next(name.cbegin(), removePrefix), end, + [](const QChar &c) { return c.isLetter(); }); + if (result != end) + return std::distance(name.begin(), result); + + return {}; +} + +static std::optional<QChar> firstLetter(QStringView name, qsizetype removePrefix = 0, + qsizetype removeSuffix = 0) +{ + if (auto idx = firstLetterIdx(name, removePrefix, removeSuffix)) + return name[*idx]; + return {}; +} + +enum ChangeCase { ToUpper, ToLower }; +static void changeCaseOfFirstLetter(QString &name, ChangeCase option, qsizetype removePrefix = 0, + qsizetype removeSuffix = 0) +{ + auto idx = firstLetterIdx(name, removePrefix, removeSuffix); + if (!idx) + return; + + QChar &changeMe = name[*idx]; + changeMe = option == ToUpper ? changeMe.toUpper() : changeMe.toLower(); +}; + +static std::optional<QString> toQStringData(std::optional<QStringView> view) +{ + if (view) + return view->toString(); + return std::nullopt; +} + +static QByteArray toUtf8Data(QUtf8StringView view) +{ + return QByteArray(view.data(), view.size()); +} + +static std::optional<QByteArray> toUtf8Data(std::optional<QUtf8StringView> view) +{ + if (view) + return toUtf8Data(*view); + return std::nullopt; +} + +/*! +\internal +\class QQmlSignalNames + +QQmlSignalNames contains a list of helper methods to manipulate signal names. +Always try to use the most specific one, as combining them might lead to incorrect +results like wrong upper/lower case, for example. +*/ + +/*! +\internal +Concatenate a prefix to a property name and uppercases the first letter of the property name. +*/ +QString QQmlSignalNames::addPrefixToPropertyName(QStringView prefix, QStringView propertyName) +{ + QString result = prefix.toString().append(propertyName); + changeCaseOfFirstLetter(result, ToUpper, prefix.size()); + return result; +} + +QString QQmlSignalNames::propertyNameToChangedSignalName(QStringView property) +{ + return property.toString().append(Changed); +} + +QByteArray QQmlSignalNames::propertyNameToChangedSignalName(QUtf8StringView property) +{ + return toUtf8Data(property).append(QByteArrayView(Changed)); +} + +QString QQmlSignalNames::propertyNameToChangedHandlerName(QStringView property) +{ + return propertyNameToChangedSignalName(signalNameToHandlerName(property)); +} + +template<typename View> +std::optional<View> changedSignalNameToPropertyNameTemplate(View changeSignal) +{ + const qsizetype changeSignalSize = changeSignal.size(); + if (changeSignalSize < StrlenChanged || changeSignal.last(StrlenChanged).compare(Changed) != 0) + return std::nullopt; + + const View result = changeSignal.sliced(0, changeSignalSize - StrlenChanged); + if (!result.isEmpty()) + return result; + + return {}; +} + +/*! +\internal +Obtain a propertyName from its changed signal handler. +Do not call this on a value obtained from handlerNameToSignalName! Instead use +changedHandlerNameToPropertyName() directly. Otherwise you might end up with a wrong +capitalization of _Changed for "on_Changed", for example. +*/ + +std::optional<QString> QQmlSignalNames::changedSignalNameToPropertyName(QStringView signalName) +{ + return toQStringData(changedSignalNameToPropertyNameTemplate(signalName)); +} +std::optional<QByteArray> +QQmlSignalNames::changedSignalNameToPropertyName(QUtf8StringView signalName) +{ + return toUtf8Data(changedSignalNameToPropertyNameTemplate(signalName)); +} + +/*! +\internal +Returns a property name from \a changedHandler. +This fails for property names starting with an upper-case letter, as it will lower-case it in the +process. +*/ +std::optional<QString> QQmlSignalNames::changedHandlerNameToPropertyName(QStringView handler) +{ + if (!isChangedHandlerName(handler)) + return {}; + + if (auto withoutChangedSuffix = changedSignalNameToPropertyName(handler)) { + return handlerNameToSignalName(*withoutChangedSuffix); + } + return {}; +} + +QString QQmlSignalNames::signalNameToHandlerName(QAnyStringView signal) +{ + QString handlerName; + handlerName.reserve(StrlenOn + signal.length()); + handlerName.append(On); + + signal.visit([&handlerName](auto &&s) { handlerName.append(s); }); + + changeCaseOfFirstLetter(handlerName, ToUpper, StrlenOn); + return handlerName; +} + +enum HandlerType { ChangedHandler, Handler }; + +template<HandlerType type> +static std::optional<QString> handlerNameToSignalNameHelper(QStringView handler) +{ + if (!QQmlSignalNames::isHandlerName(handler)) + return {}; + + QString signalName = handler.sliced(StrlenOn).toString(); + Q_ASSERT(!signalName.isEmpty()); + + changeCaseOfFirstLetter(signalName, ToLower, 0, type == ChangedHandler ? StrlenChanged : 0); + return signalName; +} + +/*! +\internal +Returns a signal name from \a handlerName string. Do not use it on changed handlers, see +changedHandlerNameToSignalName for that! +*/ +std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler) +{ + return handlerNameToSignalNameHelper<Handler>(handler); +} + +/*! +\internal +Returns a signal name from \a handlerName string. Do not use it on changed handlers, see +changedHandlerNameToSignalName for that! Accepts improperly capitalized handler names and +incorrectly resolves signal names that start with '_' or '$'. +*/ +std::optional<QString> QQmlSignalNames::badHandlerNameToSignalName(QStringView handler) +{ + if (handler.size() <= StrlenOn || !handler.startsWith(On)) + return {}; + + QString signalName = handler.sliced(StrlenOn).toString(); + + // This is quite wrong. But we need it for backwards compatibility. + signalName.front() = signalName.front().toLower(); + + return signalName; +} + +/*! +\internal +Returns a signal name from \a changedHandlerName string. Makes sure not to lowercase the 'C' from +Changed. +*/ +std::optional<QString> QQmlSignalNames::changedHandlerNameToSignalName(QStringView handler) +{ + return handlerNameToSignalNameHelper<ChangedHandler>(handler); +} + +bool QQmlSignalNames::isChangedSignalName(QStringView signalName) +{ + if (signalName.size() <= StrlenChanged || !signalName.endsWith(Changed)) + return false; + + if (auto letter = firstLetter(signalName, 0, StrlenChanged)) + return letter->isLower(); + + return true; +} + +bool QQmlSignalNames::isChangedHandlerName(QStringView signalName) +{ + if (signalName.size() <= (StrlenOn + StrlenChanged) + || !signalName.startsWith(On) + || !signalName.endsWith(Changed)) { + return false; + } + + if (auto letter = firstLetter(signalName, StrlenOn, StrlenChanged)) + return letter->isUpper(); + + return true; +} + +bool QQmlSignalNames::isHandlerName(QStringView signalName) +{ + if (signalName.size() <= StrlenOn || !signalName.startsWith(On)) + return false; + + if (auto letter = firstLetter(signalName, StrlenOn)) + return letter->isUpper(); + + return true; +} + +QT_END_NAMESPACE diff --git a/src/qml/common/qqmlsignalnames_p.h b/src/qml/common/qqmlsignalnames_p.h new file mode 100644 index 0000000000..9c3d192666 --- /dev/null +++ b/src/qml/common/qqmlsignalnames_p.h @@ -0,0 +1,59 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLSIGNALANDPROPERTYNAMES_P_H +#define QQMLSIGNALANDPROPERTYNAMES_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 <cstddef> +#include <optional> + +#include <QtQml/private/qtqmlglobal_p.h> +#include <QtCore/qstringview.h> +#include <QtCore/qstring.h> +#include <type_traits> + +QT_BEGIN_NAMESPACE + +class Q_QML_EXPORT QQmlSignalNames +{ +public: + static QString propertyNameToChangedSignalName(QStringView property); + static QByteArray propertyNameToChangedSignalName(QUtf8StringView property); + + static QString propertyNameToChangedHandlerName(QStringView property); + + static QString signalNameToHandlerName(QAnyStringView signal); + + static std::optional<QString> changedSignalNameToPropertyName(QStringView changeSignal); + static std::optional<QByteArray> changedSignalNameToPropertyName(QUtf8StringView changeSignal); + + static std::optional<QString> changedHandlerNameToPropertyName(QStringView handler); + static std::optional<QByteArray> changedHandlerNameToPropertyName(QUtf8StringView handler); + + static std::optional<QString> handlerNameToSignalName(QStringView handler); + static std::optional<QString> changedHandlerNameToSignalName(QStringView changedHandler); + + static bool isChangedHandlerName(QStringView signalName); + static bool isChangedSignalName(QStringView signalName); + static bool isHandlerName(QStringView signalName); + + static QString addPrefixToPropertyName(QStringView prefix, QStringView propertyName); + + // ### Qt7: remove this + static std::optional<QString> badHandlerNameToSignalName(QStringView handler); +}; + +QT_END_NAMESPACE + +#endif // QQMLSIGNALANDPROPERTYNAMES_P_H diff --git a/src/qml/common/qqmltranslation_p.h b/src/qml/common/qqmltranslation_p.h index 4a565ff5f7..9849203abe 100644 --- a/src/qml/common/qqmltranslation_p.h +++ b/src/qml/common/qqmltranslation_p.h @@ -21,10 +21,10 @@ QT_BEGIN_NAMESPACE -class Q_QML_PRIVATE_EXPORT QQmlTranslation +class Q_QML_EXPORT QQmlTranslation { public: - class Q_QML_PRIVATE_EXPORT QsTrData + class Q_QML_EXPORT QsTrData { QByteArray context; QByteArray text; @@ -39,7 +39,7 @@ public: QString idForQmlDebug() const; }; - class Q_QML_PRIVATE_EXPORT QsTrIdData + class Q_QML_EXPORT QsTrIdData { QByteArray id; int number; diff --git a/src/qml/common/qv4alloca_p.h b/src/qml/common/qv4alloca_p.h index c1d1e6e87d..51c99192d3 100644 --- a/src/qml/common/qv4alloca_p.h +++ b/src/qml/common/qv4alloca_p.h @@ -17,16 +17,17 @@ #include <QtCore/private/qglobal_p.h> -#if QT_CONFIG(alloca_h) +#include <stdlib.h> +#if __has_include(<alloca.h>) # include <alloca.h> -#elif QT_CONFIG(alloca_malloc_h) +#endif +#if __has_include(<malloc.h>) # include <malloc.h> +#endif + +#ifdef Q_CC_MSVC // This does not matter unless compiling in strict standard mode. -# ifdef Q_CC_MSVC -# define alloca _alloca -# endif -#else -# include <stdlib.h> +# define alloca _alloca #endif // Define Q_ALLOCA_VAR macro to be used instead of #ifdeffing @@ -37,7 +38,7 @@ Q_ALLOCA_DECLARE(type, name); \ Q_ALLOCA_ASSIGN(type, name, size) -#if QT_CONFIG(alloca) +#ifdef alloca #define Q_ALLOCA_DECLARE(type, name) \ type *name = 0 @@ -46,27 +47,16 @@ name = static_cast<type*>(alloca(size)) #else -QT_BEGIN_NAMESPACE -class Qt_AllocaWrapper -{ -public: - Qt_AllocaWrapper() { m_data = 0; } - ~Qt_AllocaWrapper() { free(m_data); } - void *data() { return m_data; } - void allocate(int size) { m_data = malloc(size); memset(m_data, 0, size); } -private: - void *m_data; -}; -QT_END_NAMESPACE +# include <memory> #define Q_ALLOCA_DECLARE(type, name) \ - Qt_AllocaWrapper _qt_alloca_##name; \ + std::unique_ptr<char[]> _qt_alloca_##name; \ type *name = nullptr #define Q_ALLOCA_ASSIGN(type, name, size) \ do { \ - _qt_alloca_##name.allocate(size); \ - name = static_cast<type*>(_qt_alloca_##name.data()); \ + _qt_alloca_##name.reset(new char[size]); \ + name = reinterpret_cast<type*>(_qt_alloca_##name.get()); \ } while (false) #endif diff --git a/src/qml/common/qv4compileddata.cpp b/src/qml/common/qv4compileddata.cpp new file mode 100644 index 0000000000..9dee91f713 --- /dev/null +++ b/src/qml/common/qv4compileddata.cpp @@ -0,0 +1,431 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qv4compileddata_p.h" + +#include <private/inlinecomponentutils_p.h> +#include <private/qml_compile_hash_p.h> +#include <private/qqmlscriptdata_p.h> +#include <private/qqmltypenamecache_p.h> +#include <private/qv4resolvedtypereference_p.h> + +#include <QtQml/qqmlfile.h> + +#include <QtCore/qdir.h> +#include <QtCore/qscopeguard.h> +#include <QtCore/qstandardpaths.h> + +static_assert(QV4::CompiledData::QmlCompileHashSpace > QML_COMPILE_HASH_LENGTH); + +#if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0 +# ifdef Q_OS_LINUX +// Place on a separate section on Linux so it's easier to check from outside +// what the hash version is. +__attribute__((section(".qml_compile_hash"))) +# endif +const char qml_compile_hash[QV4::CompiledData::QmlCompileHashSpace] = QML_COMPILE_HASH; +static_assert(sizeof(QV4::CompiledData::Unit::libraryVersionHash) > QML_COMPILE_HASH_LENGTH, + "Compile hash length exceeds reserved size in data structure. Please adjust and bump the format version"); +#else +# error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files" +#endif + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace CompiledData { + + +bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const +{ + if (strncmp(magic, CompiledData::magic_str, sizeof(magic))) { + *errorString = QStringLiteral("Magic bytes in the header do not match"); + return false; + } + + if (version != quint32(QV4_DATA_STRUCTURE_VERSION)) { + *errorString = QString::fromUtf8("V4 data structure version mismatch. Found %1 expected %2") + .arg(version, 0, 16).arg(QV4_DATA_STRUCTURE_VERSION, 0, 16); + return false; + } + + if (qtVersion != quint32(QT_VERSION)) { + *errorString = QString::fromUtf8("Qt version mismatch. Found %1 expected %2") + .arg(qtVersion, 0, 16).arg(QT_VERSION, 0, 16); + return false; + } + + if (sourceTimeStamp) { + // Files from the resource system do not have any time stamps, so fall back to the application + // executable. + if (!expectedSourceTimeStamp.isValid()) + expectedSourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified(); + + if (expectedSourceTimeStamp.isValid() + && expectedSourceTimeStamp.toMSecsSinceEpoch() != sourceTimeStamp) { + *errorString = QStringLiteral("QML source file has a different time stamp than cached file."); + return false; + } + } + +#if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0 + if (qstrncmp(qml_compile_hash, libraryVersionHash, QML_COMPILE_HASH_LENGTH) != 0) { + *errorString = QStringLiteral("QML compile hashes don't match. Found %1 expected %2") + .arg(QString::fromLatin1( + QByteArray(libraryVersionHash, QML_COMPILE_HASH_LENGTH) + .toPercentEncoding()), + QString::fromLatin1( + QByteArray(qml_compile_hash, QML_COMPILE_HASH_LENGTH) + .toPercentEncoding())); + return false; + } +#else +#error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files" +#endif + return true; +} + +/*! + \internal + This function creates a temporary key vector and sorts it to guarantuee a stable + hash. This is used to calculate a check-sum on dependent meta-objects. + */ +bool ResolvedTypeReferenceMap::addToHash( + QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) const +{ + std::vector<int> keys (size()); + int i = 0; + for (auto it = constBegin(), end = constEnd(); it != end; ++it) { + keys[i] = it.key(); + ++i; + } + std::sort(keys.begin(), keys.end()); + for (int key: keys) { + if (!this->operator[](key)->addToHash(hash, checksums)) + return false; + } + + return true; +} + +CompilationUnit::CompilationUnit( + const Unit *unitData, const QString &fileName, const QString &finalUrlString) +{ + setUnitData(unitData, nullptr, fileName, finalUrlString); +} + +CompilationUnit::~CompilationUnit() +{ + qDeleteAll(resolvedTypes); + + if (data) { + if (data->qmlUnit() != qmlData) + free(const_cast<QmlUnit *>(qmlData)); + qmlData = nullptr; + + if (!(data->flags & QV4::CompiledData::Unit::StaticData)) + free(const_cast<Unit *>(data)); + } + data = nullptr; +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + delete [] constants; + constants = nullptr; +#endif +} + +QString CompilationUnit::localCacheFilePath(const QUrl &url) +{ + static const QByteArray envCachePath = qgetenv("QML_DISK_CACHE_PATH"); + + const QString localSourcePath = QQmlFile::urlToLocalFileOrQrc(url); + const QString cacheFileSuffix + = QFileInfo(localSourcePath + QLatin1Char('c')).completeSuffix(); + QCryptographicHash fileNameHash(QCryptographicHash::Sha1); + fileNameHash.addData(localSourcePath.toUtf8()); + QString directory = envCachePath.isEmpty() + ? QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + + QLatin1String("/qmlcache/") + : QString::fromLocal8Bit(envCachePath) + QLatin1String("/"); + QDir::root().mkpath(directory); + return directory + QString::fromUtf8(fileNameHash.result().toHex()) + + QLatin1Char('.') + cacheFileSuffix; +} + +bool CompilationUnit::loadFromDisk( + const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString) +{ + if (!QQmlFile::isLocalFile(url)) { + *errorString = QStringLiteral("File has to be a local file."); + return false; + } + + const QString sourcePath = QQmlFile::urlToLocalFileOrQrc(url); + auto cacheFile = std::make_unique<CompilationUnitMapper>(); + + const QStringList cachePaths = { sourcePath + QLatin1Char('c'), localCacheFilePath(url) }; + for (const QString &cachePath : cachePaths) { + Unit *mappedUnit = cacheFile->get(cachePath, sourceTimeStamp, errorString); + if (!mappedUnit) + continue; + + const Unit *oldData = unitData(); + const Unit * const oldDataPtr + = (oldData && !(oldData->flags & Unit::StaticData)) + ? oldData + : nullptr; + + auto dataPtrRevert = qScopeGuard([this, oldData](){ + setUnitData(oldData); + }); + setUnitData(mappedUnit); + + if (mappedUnit->sourceFileIndex != 0) { + if (mappedUnit->sourceFileIndex >= + mappedUnit->stringTableSize + dynamicStrings.size()) { + *errorString = QStringLiteral("QML source file index is invalid."); + continue; + } + if (sourcePath != + QQmlFile::urlToLocalFileOrQrc(stringAt(mappedUnit->sourceFileIndex))) { + *errorString = QStringLiteral("QML source file has moved to a different location."); + continue; + } + } + + dataPtrRevert.dismiss(); + free(const_cast<Unit*>(oldDataPtr)); + backingFile = std::move(cacheFile); + return true; + } + + return false; +} + +bool CompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) +{ + if (unitData()->sourceTimeStamp == 0) { + *errorString = QStringLiteral("Missing time stamp for source file"); + return false; + } + + if (!QQmlFile::isLocalFile(unitUrl)) { + *errorString = QStringLiteral("File has to be a local file."); + return false; + } + + return SaveableUnitPointer(unitData()).saveToDisk<char>( + [&unitUrl, errorString](const char *data, quint32 size) { + const QString cachePath = localCacheFilePath(unitUrl); + if (SaveableUnitPointer::writeDataToFile( + cachePath, data, size, errorString)) { + CompilationUnitMapper::invalidate(cachePath); + return true; + } + + return false; + }); +} + +QStringList CompilationUnit::moduleRequests() const +{ + QStringList requests; + requests.reserve(data->moduleRequestTableSize); + for (uint i = 0; i < data->moduleRequestTableSize; ++i) + requests << stringAt(data->moduleRequestTable()[i]); + return requests; +} + +ResolvedTypeReference *CompilationUnit::resolvedType(QMetaType type) const +{ + for (ResolvedTypeReference *ref : std::as_const(resolvedTypes)) { + if (ref->type().typeId() == type) + return ref; + } + return nullptr; + +} + +int CompilationUnit::totalBindingsCount() const +{ + if (!icRootName) + return m_totalBindingsCount; + return inlineComponentData[*icRootName].totalBindingCount; +} + +int CompilationUnit::totalObjectCount() const +{ + if (!icRootName) + return m_totalObjectCount; + return inlineComponentData[*icRootName].totalObjectCount; +} + +template<typename F> +void processInlinComponentType( + const QQmlType &type, + const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, + F &&populateIcData) +{ + if (type.isInlineComponentType()) { + QString icRootName; + if (compilationUnit->icRootName) { + icRootName = type.elementName(); + std::swap(*compilationUnit->icRootName, icRootName); + } else { + compilationUnit->icRootName = std::make_unique<QString>(type.elementName()); + } + + populateIcData(); + + if (icRootName.isEmpty()) + compilationUnit->icRootName.reset(); + else + std::swap(*compilationUnit->icRootName, icRootName); + } else { + populateIcData(); + } +} + +void CompiledData::CompilationUnit::finalizeCompositeType(const QQmlType &type) +{ + // Add to type registry of composites + if (propertyCaches.needsVMEMetaObject(/*root object*/0)) { + // qmlType is only valid for types that have references to themselves. + if (type.isValid()) { + qmlType = type; + } else { + qmlType = QQmlMetaType::findCompositeType( + finalUrl(), this, (unitData()->flags & CompiledData::Unit::IsSingleton) + ? QQmlMetaType::Singleton + : QQmlMetaType::NonSingleton); + } + + QQmlMetaType::registerInternalCompositeType(this); + } else { + const QV4::CompiledData::Object *obj = objectAt(/*root object*/0); + auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + if (const auto compilationUnit = typeRef->compilationUnit()) + qmlType = compilationUnit->qmlType; + else + qmlType = typeRef->type(); + } + + // Collect some data for instantiation later. + using namespace icutils; + std::vector<QV4::CompiledData::InlineComponent> allICs {}; + for (int i=0; i != objectCount(); ++i) { + const CompiledObject *obj = objectAt(i); + for (auto it = obj->inlineComponentsBegin(); it != obj->inlineComponentsEnd(); ++it) { + allICs.push_back(*it); + } + } + NodeList nodes; + nodes.resize(allICs.size()); + std::iota(nodes.begin(), nodes.end(), 0); + AdjacencyList adjacencyList; + adjacencyList.resize(nodes.size()); + fillAdjacencyListForInlineComponents(this, adjacencyList, nodes, allICs); + bool hasCycle = false; + auto nodesSorted = topoSort(nodes, adjacencyList, hasCycle); + Q_ASSERT(!hasCycle); // would have already been discovered by qqmlpropertycachcecreator + + // We need to first iterate over all inline components, + // as the containing component might create instances of them + // and in that case we need to add its object count + for (auto nodeIt = nodesSorted.rbegin(); nodeIt != nodesSorted.rend(); ++nodeIt) { + const auto &ic = allICs.at(nodeIt->index()); + const int lastICRoot = ic.objectIndex; + for (int i = ic.objectIndex; i<objectCount(); ++i) { + const QV4::CompiledData::Object *obj = objectAt(i); + bool leftCurrentInlineComponent + = (i != lastICRoot + && obj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot)) + || !obj->hasFlag(QV4::CompiledData::Object::IsPartOfInlineComponent); + if (leftCurrentInlineComponent) + break; + const QString lastICRootName = stringAt(ic.nameIndex); + inlineComponentData[lastICRootName].totalBindingCount + += obj->nBindings; + + if (auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) { + const auto type = typeRef->type(); + if (type.isValid() && type.parserStatusCast() != -1) + ++inlineComponentData[lastICRootName].totalParserStatusCount; + + ++inlineComponentData[lastICRootName].totalObjectCount; + if (const auto compilationUnit = typeRef->compilationUnit()) { + // if the type is an inline component type, we have to extract the information + // from it. + // This requires that inline components are visited in the correct order. + processInlinComponentType(type, compilationUnit, [&]() { + auto &icData = inlineComponentData[lastICRootName]; + icData.totalBindingCount += compilationUnit->totalBindingsCount(); + icData.totalParserStatusCount += compilationUnit->totalParserStatusCount(); + icData.totalObjectCount += compilationUnit->totalObjectCount(); + }); + } + } + } + } + int bindingCount = 0; + int parserStatusCount = 0; + int objectCount = 0; + for (quint32 i = 0, count = this->objectCount(); i < count; ++i) { + const QV4::CompiledData::Object *obj = objectAt(i); + if (obj->hasFlag(QV4::CompiledData::Object::IsPartOfInlineComponent)) + continue; + + bindingCount += obj->nBindings; + if (auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) { + const auto type = typeRef->type(); + if (type.isValid() && type.parserStatusCast() != -1) + ++parserStatusCount; + ++objectCount; + if (const auto compilationUnit = typeRef->compilationUnit()) { + processInlinComponentType(type, compilationUnit, [&](){ + bindingCount += compilationUnit->totalBindingsCount(); + parserStatusCount += compilationUnit->totalParserStatusCount(); + objectCount += compilationUnit->totalObjectCount(); + }); + } + } + } + + m_totalBindingsCount = bindingCount; + m_totalParserStatusCount = parserStatusCount; + m_totalObjectCount = objectCount; +} + +int CompilationUnit::totalParserStatusCount() const +{ + if (!icRootName) + return m_totalParserStatusCount; + return inlineComponentData[*icRootName].totalParserStatusCount; +} + +bool CompilationUnit::verifyChecksum(const DependentTypesHasher &dependencyHasher) const +{ + if (!dependencyHasher) { + for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) { + if (data->dependencyMD5Checksum[i] != 0) + return false; + } + return true; + } + const QByteArray checksum = dependencyHasher(); + return checksum.size() == sizeof(data->dependencyMD5Checksum) + && memcmp(data->dependencyMD5Checksum, checksum.constData(), + sizeof(data->dependencyMD5Checksum)) == 0; +} + +QQmlType CompilationUnit::qmlTypeForComponent(const QString &inlineComponentName) const +{ + if (inlineComponentName.isEmpty()) + return qmlType; + return inlineComponentData[inlineComponentName].qmlType; +} + +} // namespace CompiledData +} // namespace QV4 + +QT_END_NAMESPACE diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index cd96067769..c21fc19fa9 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -16,21 +16,29 @@ #include <functional> +#include <QtCore/qcryptographichash.h> +#include <QtCore/qhash.h> #include <QtCore/qhashfunctions.h> -#include <QtCore/qstring.h> +#include <QtCore/qlocale.h> #include <QtCore/qscopeguard.h> -#include <QtCore/qvector.h> +#include <QtCore/qstring.h> #include <QtCore/qstringlist.h> -#include <QtCore/qhash.h> +#include <QtCore/qurl.h> +#include <QtCore/qvector.h> #include <QtCore/qversionnumber.h> -#include <QtCore/qlocale.h> #if QT_CONFIG(temporaryfile) #include <QtCore/qsavefile.h> #endif #include <private/qendian_p.h> +#include <private/qqmlnullablevalue_p.h> +#include <private/qqmlpropertycachevector_p.h> +#include <private/qqmlrefcount_p.h> +#include <private/qqmltype_p.h> +#include <private/qv4compilationunitmapper_p.h> #include <private/qv4staticvalue_p.h> + #include <functional> #include <limits.h> @@ -43,15 +51,17 @@ QT_BEGIN_NAMESPACE // Also change the comment behind the number to describe the latest change. This has the added // benefit that if another patch changes the version too, it will result in a merge conflict, and // not get removed silently. -#define QV4_DATA_STRUCTURE_VERSION 0x3B // Add isList flag to method parameters and return types +#define QV4_DATA_STRUCTURE_VERSION 0x42 // Change metatype computation of AOT-compiled functions class QIODevice; class QQmlTypeNameCache; class QQmlType; class QQmlEngine; +class QQmlPropertyData; +class QQmlScriptData; namespace QQmlPrivate { -struct TypedFunction; +struct AOTCompiledFunction; } namespace QmlIR { @@ -67,9 +77,19 @@ struct InternalClass; struct Function; class EvalISelFactory; +class ResolvedTypeReference; namespace CompiledData { +// index is per-object binding index +using BindingPropertyData = QVector<const QQmlPropertyData *>; + +// map from name index +struct ResolvedTypeReferenceMap: public QHash<int, ResolvedTypeReference*> +{ + bool addToHash(QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) const; +}; + struct String; struct Function; struct Lookup; @@ -141,8 +161,8 @@ struct RegExp RegExp_Global = 0x01, RegExp_IgnoreCase = 0x02, RegExp_Multiline = 0x04, - RegExp_Unicode = 0x08, - RegExp_Sticky = 0x10 + RegExp_Sticky = 0x08, + RegExp_Unicode = 0x10, }; RegExp() : m_data(QSpecialIntegerBitfieldZero) {} @@ -268,31 +288,48 @@ struct Block }; static_assert(sizeof(Block) == 12, "Block structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); -enum class BuiltinType : unsigned int { - Var = 0, Int, Bool, Real, String, Url, - Time, Date, DateTime, Rect, Point, Size, - InvalidBuiltin +enum class NamedBuiltin: unsigned int { + Void, Var, Int, Bool, Real, String, Url, DateTime, RegExp +}; + +enum class CommonType : unsigned int { + // Actual named builtins + Void = uint(NamedBuiltin::Void), + Var = uint(NamedBuiltin::Var), + Int = uint(NamedBuiltin::Int), + Bool = uint(NamedBuiltin::Bool), + Real = uint(NamedBuiltin::Real), + String = uint(NamedBuiltin::String), + Url = uint(NamedBuiltin::Url), + DateTime = uint(NamedBuiltin::DateTime), + RegExp = uint(NamedBuiltin::RegExp), + + // Optimization for very common other types + Time, Date, Rect, Point, Size, + + // No type specified or not recognized + Invalid }; struct ParameterType { enum Flag { NoFlag = 0x0, - Builtin = 0x1, + Common = 0x1, List = 0x2, }; Q_DECLARE_FLAGS(Flags, Flag); - void set(Flags flags, quint32 typeNameIndexOrBuiltinType) + void set(Flags flags, quint32 typeNameIndexOrCommonType) { m_data.set<IsListField>(flags.testFlag(List) ? 1 : 0); - m_data.set<IndexIsBuiltinTypeField>(flags.testFlag(Builtin) ? 1 : 0); - m_data.set<TypeNameIndexOrBuiltinTypeField>(typeNameIndexOrBuiltinType); + m_data.set<IndexIsCommonTypeField>(flags.testFlag(Common) ? 1 : 0); + m_data.set<TypeNameIndexOrCommonTypeField>(typeNameIndexOrCommonType); } - bool indexIsBuiltinType() const + bool indexIsCommonType() const { - return m_data.get<IndexIsBuiltinTypeField>() != 0; + return m_data.get<IndexIsCommonTypeField>() != 0; } bool isList() const @@ -300,16 +337,16 @@ struct ParameterType return m_data.get<IsListField>() != 0; } - quint32 typeNameIndexOrBuiltinType() const + quint32 typeNameIndexOrCommonType() const { - return m_data.get<TypeNameIndexOrBuiltinTypeField>(); + return m_data.get<TypeNameIndexOrCommonTypeField>(); } private: - using IndexIsBuiltinTypeField = quint32_le_bitfield_member<0, 1>; + using IndexIsCommonTypeField = quint32_le_bitfield_member<0, 1>; using IsListField = quint32_le_bitfield_member<1, 1>; - using TypeNameIndexOrBuiltinTypeField = quint32_le_bitfield_member<2, 30>; - quint32_le_bitfield_union<IndexIsBuiltinTypeField, IsListField, TypeNameIndexOrBuiltinTypeField> m_data; + using TypeNameIndexOrCommonTypeField = quint32_le_bitfield_member<2, 30>; + quint32_le_bitfield_union<IndexIsCommonTypeField, IsListField, TypeNameIndexOrCommonTypeField> m_data; }; static_assert(sizeof(ParameterType) == 4, "ParameterType structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); @@ -497,6 +534,7 @@ static_assert(sizeof(ImportEntry) == 16, "ImportEntry structure needs to have th struct TranslationData { + enum { NoContextIndex = std::numeric_limits<quint32>::max() }; quint32_le stringIndex; quint32_le commentIndex; qint32_le number; @@ -669,6 +707,8 @@ struct Binding } bool evaluatesToString() const { return type() == Type_String || isTranslationBinding(); } + bool isNumberBinding() const { return type() == Type_Number; } + bool valueAsBoolean() const { if (type() == Type_Boolean) @@ -748,47 +788,47 @@ static_assert(sizeof(Signal) == 12, "Signal structure needs to have the expected struct Property { private: - using BuiltinTypeOrTypeNameIndexField = quint32_le_bitfield_member<0, 28>; + using CommonTypeOrTypeNameIndexField = quint32_le_bitfield_member<0, 28>; using IsRequiredField = quint32_le_bitfield_member<28, 1>; - using IsBuiltinTypeField = quint32_le_bitfield_member<29, 1>; + using IsCommonTypeField = quint32_le_bitfield_member<29, 1>; using IsListField = quint32_le_bitfield_member<30, 1>; using IsReadOnlyField = quint32_le_bitfield_member<31, 1>; public: quint32_le nameIndex; quint32_le_bitfield_union< - BuiltinTypeOrTypeNameIndexField, + CommonTypeOrTypeNameIndexField, IsRequiredField, - IsBuiltinTypeField, + IsCommonTypeField, IsListField, IsReadOnlyField> data; Location location; - void setBuiltinType(BuiltinType t) + void setCommonType(CommonType t) { - data.set<BuiltinTypeOrTypeNameIndexField>(static_cast<quint32>(t)); - data.set<IsBuiltinTypeField>(true); + data.set<CommonTypeOrTypeNameIndexField>(static_cast<quint32>(t)); + data.set<IsCommonTypeField>(true); } - BuiltinType builtinType() const { - if (data.get<IsBuiltinTypeField>() != 0) - return BuiltinType(data.get<BuiltinTypeOrTypeNameIndexField>()); - return BuiltinType::InvalidBuiltin; + CommonType commonType() const { + if (data.get<IsCommonTypeField>() != 0) + return CommonType(data.get<CommonTypeOrTypeNameIndexField>()); + return CommonType::Invalid; } - void setCustomType(int nameIndex) + void setTypeNameIndex(int nameIndex) { - data.set<BuiltinTypeOrTypeNameIndexField>(nameIndex); - data.set<IsBuiltinTypeField>(false); + data.set<CommonTypeOrTypeNameIndexField>(nameIndex); + data.set<IsCommonTypeField>(false); } - int customType() const + int typeNameIndex() const { - return data.get<IsBuiltinTypeField>() ? -1 : data.get<BuiltinTypeOrTypeNameIndexField>(); + return data.get<IsCommonTypeField>() ? -1 : data.get<CommonTypeOrTypeNameIndexField>(); } - bool isBuiltinType() const { return data.get<IsBuiltinTypeField>(); } - uint builtinTypeOrTypeNameIndex() const { return data.get<BuiltinTypeOrTypeNameIndexField>(); } + bool isCommonType() const { return data.get<IsCommonTypeField>(); } + uint commonTypeOrTypeNameIndex() const { return data.get<CommonTypeOrTypeNameIndexField>(); } bool isList() const { return data.get<IsListField>(); } void setIsList(bool isList) { data.set<IsListField>(isList); } @@ -1080,6 +1120,7 @@ public: const Binding *bindingsBegin() const { return bindingTable(); } const Binding *bindingsEnd() const { return bindingTable() + nBindings; } + int bindingCount() const { return nBindings; } const Property *propertiesBegin() const { return propertyTable(); } const Property *propertiesEnd() const { return propertyTable() + nProperties; } @@ -1182,9 +1223,11 @@ struct Unit ListPropertyAssignReplace = ListPropertyAssignReplaceIfDefault | ListPropertyAssignReplaceIfNotDefault, ComponentsBound = 0x200, - FunctionSignaturesEnforced = 0x400, + FunctionSignaturesIgnored = 0x400, NativeMethodsAcceptThisObject = 0x800, ValueTypesCopied = 0x1000, + ValueTypesAddressable = 0x2000, + ValueTypesAssertable = 0x4000, }; quint32_le flags; quint32_le stringTableSize; @@ -1238,8 +1281,8 @@ struct Unit } /* end QML specific fields*/ - QString stringAtInternal(int idx) const { - Q_ASSERT(idx < int(stringTableSize)); + QString stringAtInternal(uint idx) const { + Q_ASSERT(idx < stringTableSize); const quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToStringTable); const quint32_le offset = offsetTable[idx]; const String *str = reinterpret_cast<const String*>(reinterpret_cast<const char *>(this) + offset); @@ -1311,12 +1354,28 @@ struct Unit return reinterpret_cast<const TranslationData *>(reinterpret_cast<const char *>(this) + offsetToTranslationTable); } + const quint32_le *translationContextIndex() const{ + if ( translationTableSize == 0) + return nullptr; + return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + + offsetToTranslationTable + + translationTableSize * sizeof(CompiledData::TranslationData)); } + + quint32_le *translationContextIndex() { + if ( translationTableSize == 0) + return nullptr; + return reinterpret_cast<quint32_le*>((reinterpret_cast<char *>(this)) + + offsetToTranslationTable + + translationTableSize * sizeof(CompiledData::TranslationData)); } + const ImportEntry *importEntryTable() const { return reinterpret_cast<const ImportEntry *>(reinterpret_cast<const char *>(this) + offsetToImportEntryTable); } const ExportEntry *localExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToLocalExportEntryTable); } const ExportEntry *indirectExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToIndirectExportEntryTable); } const ExportEntry *starExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToStarExportEntryTable); } const quint32_le *moduleRequestTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToModuleRequestTable); } + + bool verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const; }; static_assert(sizeof(Unit) == 248, "Unit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); @@ -1355,8 +1414,8 @@ struct TypeReferenceMap : QHash<int, TypeReference> auto prop = obj->propertiesBegin(); auto const propEnd = obj->propertiesEnd(); for ( ; prop != propEnd; ++prop) { - if (!prop->isBuiltinType()) { - TypeReference &r = this->add(prop->builtinTypeOrTypeNameIndex(), prop->location); + if (!prop->isCommonType()) { + TypeReference &r = this->add(prop->commonTypeOrTypeNameIndex(), prop->location); r.errorWhenNotFound = true; } } @@ -1385,118 +1444,92 @@ struct TypeReferenceMap : QHash<int, TypeReference> using DependentTypesHasher = std::function<QByteArray()>; -// This is how this hooks into the existing structures: - -struct CompilationUnitBase -{ - Q_DISABLE_COPY(CompilationUnitBase) - - CompilationUnitBase() = default; - ~CompilationUnitBase() = default; - - CompilationUnitBase(CompilationUnitBase &&other) noexcept { *this = std::move(other); } - - CompilationUnitBase &operator=(CompilationUnitBase &&other) noexcept - { - if (this != &other) { - runtimeStrings = other.runtimeStrings; - other.runtimeStrings = nullptr; - constants = other.constants; - other.constants = nullptr; - runtimeRegularExpressions = other.runtimeRegularExpressions; - other.runtimeRegularExpressions = nullptr; - runtimeClasses = other.runtimeClasses; - other.runtimeClasses = nullptr; - imports = other.imports; - other.imports = nullptr; - } - return *this; - } +struct InlineComponentData { + + InlineComponentData() = default; + InlineComponentData( + const QQmlType &qmlType, int objectIndex, int nameIndex, int totalObjectCount, + int totalBindingCount, int totalParserStatusCount) + : qmlType(qmlType) + , objectIndex(objectIndex) + , nameIndex(nameIndex) + , totalObjectCount(totalObjectCount) + , totalBindingCount(totalBindingCount) + , totalParserStatusCount(totalParserStatusCount) + {} - // pointers either to data->constants() or little-endian memory copy. - Heap::String **runtimeStrings = nullptr; // Array - const StaticValue* constants = nullptr; - QV4::StaticValue *runtimeRegularExpressions = nullptr; - Heap::InternalClass **runtimeClasses = nullptr; - const StaticValue** imports = nullptr; + QQmlType qmlType; + int objectIndex = -1; + int nameIndex = -1; + int totalObjectCount = 0; + int totalBindingCount = 0; + int totalParserStatusCount = 0; }; -Q_STATIC_ASSERT(std::is_standard_layout<CompilationUnitBase>::value); -Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeStrings) == 0); -Q_STATIC_ASSERT(offsetof(CompilationUnitBase, constants) == sizeof(QV4::Heap::String **)); -Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeRegularExpressions) == offsetof(CompilationUnitBase, constants) + sizeof(const StaticValue *)); -Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeClasses) == offsetof(CompilationUnitBase, runtimeRegularExpressions) + sizeof(const StaticValue *)); -Q_STATIC_ASSERT(offsetof(CompilationUnitBase, imports) == offsetof(CompilationUnitBase, runtimeClasses) + sizeof(const StaticValue *)); - -struct CompilationUnit : public CompilationUnitBase +struct CompilationUnit final : public QQmlRefCounted<CompilationUnit> { - Q_DISABLE_COPY(CompilationUnit) + Q_DISABLE_COPY_MOVE(CompilationUnit) const Unit *data = nullptr; const QmlUnit *qmlData = nullptr; QStringList dynamicStrings; - const QQmlPrivate::TypedFunction *aotCompiledFunctions = nullptr; + const QQmlPrivate::AOTCompiledFunction *aotCompiledFunctions = nullptr; + + // pointers either to data->constants() or little-endian memory copy. + const StaticValue *constants = nullptr; + + std::unique_ptr<CompilationUnitMapper> backingFile; + + int m_totalBindingsCount = 0; // Number of bindings used in this type + int m_totalParserStatusCount = 0; // Number of instantiated types that are QQmlParserStatus subclasses + int m_totalObjectCount = 0; // Number of objects explicitly instantiated + + std::unique_ptr<QString> icRootName; + QHash<QString, InlineComponentData> inlineComponentData; + + // index is object index. This allows fast access to the + // property data when initializing bindings, avoiding expensive + // lookups by string (property name). + QVector<BindingPropertyData> bindingPropertyDataPerObject; + + ResolvedTypeReferenceMap resolvedTypes; + QQmlRefPointer<QQmlTypeNameCache> typeNameCache; + + QQmlPropertyCacheVector propertyCaches; + + QQmlType qmlType; + + QVector<QQmlRefPointer<QQmlScriptData>> dependentScripts; + public: - using CompiledObject = CompiledData::Object; + // --- interface for QQmlPropertyCacheCreator + using CompiledObject = const CompiledData::Object; + using CompiledFunction = const CompiledData::Function; + using CompiledBinding = const CompiledData::Binding; - CompilationUnit(const Unit *unitData = nullptr, const QString &fileName = QString(), - const QString &finalUrlString = QString()) + // Empty dummy. We don't need to do this when loading from cache. + class IdToObjectMap { - setUnitData(unitData, nullptr, fileName, finalUrlString); - } + public: + void insert(int, int) {} + void clear() {} - explicit CompilationUnit(const Unit *unitData, const QQmlPrivate::TypedFunction *aotCompiledFunctions, + // We have already checked uniqueness of IDs when creating the CU + bool contains(int) { return false; } + }; + + explicit CompilationUnit(const Unit *unitData, const QQmlPrivate::AOTCompiledFunction *aotCompiledFunctions, const QString &fileName = QString(), const QString &finalUrlString = QString()) : CompilationUnit(unitData, fileName, finalUrlString) { this->aotCompiledFunctions = aotCompiledFunctions; } - ~CompilationUnit() - { - if (data) { - if (data->qmlUnit() != qmlData) - free(const_cast<QmlUnit *>(qmlData)); - qmlData = nullptr; - - if (!(data->flags & QV4::CompiledData::Unit::StaticData)) - free(const_cast<Unit *>(data)); - } - data = nullptr; -#if Q_BYTE_ORDER == Q_BIG_ENDIAN - delete [] constants; - constants = nullptr; -#endif - - delete [] imports; - imports = nullptr; - } - - CompilationUnit(CompilationUnit &&other) noexcept - { - *this = std::move(other); - } + Q_QML_EXPORT CompilationUnit( + const Unit *unitData = nullptr, const QString &fileName = QString(), + const QString &finalUrlString = QString()); - CompilationUnit &operator=(CompilationUnit &&other) noexcept - { - if (this != &other) { - data = other.data; - other.data = nullptr; - qmlData = other.qmlData; - other.qmlData = nullptr; - dynamicStrings = std::move(other.dynamicStrings); - aotCompiledFunctions = other.aotCompiledFunctions; - other.dynamicStrings.clear(); - m_fileName = std::move(other.m_fileName); - other.m_fileName.clear(); - m_finalUrlString = std::move(other.m_finalUrlString); - other.m_finalUrlString.clear(); - m_module = other.m_module; - other.m_module = nullptr; - CompilationUnitBase::operator=(std::move(other)); - } - return *this; - } + Q_QML_EXPORT ~CompilationUnit(); const Unit *unitData() const { return data; } @@ -1530,19 +1563,19 @@ public: m_finalUrlString = !finalUrlString.isEmpty() ? finalUrlString : stringAt(data->finalUrlIndex); } - QString stringAt(int index) const + QString stringAt(uint index) const { - if (uint(index) >= data->stringTableSize) - return dynamicStrings.at(index - data->stringTableSize); - return data->stringAtInternal(index); + if (index < data->stringTableSize) + return data->stringAtInternal(index); + + const qsizetype dynamicIndex = index - data->stringTableSize; + Q_ASSERT(dynamicIndex < dynamicStrings.size()); + return dynamicStrings.at(dynamicIndex); } QString fileName() const { return m_fileName; } QString finalUrlString() const { return m_finalUrlString; } - Heap::Module *module() const { return m_module; } - void setModule(Heap::Module *module) { m_module = module; } - QString bindingValueAsString(const CompiledData::Binding *binding) const { using namespace CompiledData; @@ -1581,11 +1614,156 @@ public: return constants[binding->value.constantValueIndex].doubleValue(); } + Q_QML_EXPORT static QString localCacheFilePath(const QUrl &url); + Q_QML_EXPORT bool loadFromDisk( + const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString); + Q_QML_EXPORT bool saveToDisk(const QUrl &unitUrl, QString *errorString); + + int importCount() const { return qmlData->nImports; } + const CompiledData::Import *importAt(int index) const { return qmlData->importAt(index); } + + Q_QML_EXPORT QStringList moduleRequests() const; + + // url() and fileName() shall be used to load the actual QML/JS code or to show errors or + // warnings about that code. They include any potential URL interceptions and thus represent the + // "physical" location of the code. + // + // finalUrl() and finalUrlString() shall be used to resolve further URLs referred to in the code + // They are _not_ intercepted and thus represent the "logical" name for the code. + + QUrl url() const + { + if (!m_url.isValid()) + m_url = QUrl(fileName()); + return m_url; + } + + QUrl finalUrl() const + { + if (!m_finalUrl.isValid()) + m_finalUrl = QUrl(finalUrlString()); + return m_finalUrl; + } + + ResolvedTypeReference *resolvedType(int id) const { return resolvedTypes.value(id); } + ResolvedTypeReference *resolvedType(QMetaType type) const; + + QQmlPropertyCache::ConstPtr rootPropertyCache() const + { + return propertyCaches.at(/*root object*/0); + } + + int objectCount() const { return qmlData->nObjects; } + const CompiledObject *objectAt(int index) const { return qmlData->objectAt(index); } + + int totalBindingsCount() const; + int totalParserStatusCount() const; + int totalObjectCount() const; + + int inlineComponentId(const QString &inlineComponentName) const + { + for (uint i = 0; i < qmlData->nObjects; ++i) { + auto *object = qmlData->objectAt(i); + for (auto it = object->inlineComponentsBegin(), end = object->inlineComponentsEnd(); + it != end; ++it) { + if (stringAt(it->nameIndex) == inlineComponentName) + return it->objectIndex; + } + } + return -1; + } + + void finalizeCompositeType(const QQmlType &type); + + bool verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const; + + enum class ListPropertyAssignBehavior { Append, Replace, ReplaceIfNotDefault }; + ListPropertyAssignBehavior listPropertyAssignBehavior() const + { + if (unitData()->flags & CompiledData::Unit::ListPropertyAssignReplace) + return ListPropertyAssignBehavior::Replace; + if (unitData()->flags & CompiledData::Unit::ListPropertyAssignReplaceIfNotDefault) + return ListPropertyAssignBehavior::ReplaceIfNotDefault; + return ListPropertyAssignBehavior::Append; + } + + bool ignoresFunctionSignature() const + { + return unitData()->flags & CompiledData::Unit::FunctionSignaturesIgnored; + } + + bool nativeMethodsAcceptThisObjects() const + { + return unitData()->flags & CompiledData::Unit::NativeMethodsAcceptThisObject; + } + + bool valueTypesAreCopied() const + { + return unitData()->flags & CompiledData::Unit::ValueTypesCopied; + } + + bool valueTypesAreAddressable() const + { + return unitData()->flags & CompiledData::Unit::ValueTypesAddressable; + } + + bool valueTypesAreAssertable() const + { + return unitData()->flags & CompiledData::Unit::ValueTypesAssertable; + } + + bool componentsAreBound() const + { + return unitData()->flags & CompiledData::Unit::ComponentsBound; + } + + bool isESModule() const + { + return unitData()->flags & CompiledData::Unit::IsESModule; + } + + bool isSharedLibrary() const + { + return unitData()->flags & CompiledData::Unit::IsSharedLibrary; + } + + struct FunctionIterator + { + FunctionIterator(const CompiledData::Unit *unit, const CompiledObject *object, int index) + : unit(unit), object(object), index(index) {} + const CompiledData::Unit *unit; + const CompiledObject *object; + int index; + + const CompiledFunction *operator->() const + { + return unit->functionAt(object->functionOffsetTable()[index]); + } + + void operator++() { ++index; } + bool operator==(const FunctionIterator &rhs) const { return index == rhs.index; } + bool operator!=(const FunctionIterator &rhs) const { return index != rhs.index; } + }; + + FunctionIterator objectFunctionsBegin(const CompiledObject *object) const + { + return FunctionIterator(unitData(), object, 0); + } + + FunctionIterator objectFunctionsEnd(const CompiledObject *object) const + { + return FunctionIterator(unitData(), object, object->nFunctions); + } + + QQmlType qmlTypeForComponent(const QString &inlineComponentName = QString()) const; + QMetaType metaType() const { return qmlType.typeId(); } + private: QString m_fileName; // initialized from data->sourceFileIndex QString m_finalUrlString; // initialized from data->finalUrlIndex - Heap::Module *m_module = nullptr; + mutable QQmlNullableValue<QUrl> m_url; + mutable QQmlNullableValue<QUrl> m_finalUrl; }; class SaveableUnitPointer diff --git a/src/qml/common/qv4staticvalue_p.h b/src/qml/common/qv4staticvalue_p.h index e9c3554104..9b4f582c00 100644 --- a/src/qml/common/qv4staticvalue_p.h +++ b/src/qml/common/qv4staticvalue_p.h @@ -366,10 +366,10 @@ struct StaticValue QV4_NEARLY_ALWAYS_INLINE void setDouble(double d) { if (qt_is_nan(d)) { // We cannot store just any NaN. It has to be a NaN with only the quiet bit - // set in the upper bits of the mantissa and the sign bit off. + // set in the upper bits of the mantissa and the sign bit either on or off. // qt_qnan() happens to produce such a thing via std::numeric_limits, // but this is actually not guaranteed. Therefore, we make our own. - _val = (quint64(QuickType::NaN) << Tag_Shift); + _val = (quint64(std::signbit(d) ? QuickType::MinusNaN : QuickType::NaN) << Tag_Shift); Q_ASSERT(isNaN()); } else { memcpy(&_val, &d, 8); @@ -393,7 +393,7 @@ struct StaticValue } QV4_NEARLY_ALWAYS_INLINE static bool isInt32(double d) { - int i = int(d); + int i = QJSNumberCoercion::toInteger(d); return (i == d && !(d == 0 && std::signbit(d))); } @@ -589,10 +589,17 @@ struct StaticValue // Has to be aligned to 32 bytes Q_ASSERT(!(tmp & Lower5Mask)); + // MinGW produces a bogus warning about array bounds. + // There is no array access here. + QT_WARNING_PUSH + QT_WARNING_DISABLE_GCC("-Warray-bounds") + // Encode the pointer. _val = storePointerBits<Top1Shift, Top1Mask>( storePointerBits<Upper3Shift, Upper3Mask>( storePointerBits<Lower5Shift, Lower5Mask>(tmp))); + + QT_WARNING_POP } #elif QT_POINTER_SIZE == 4 QML_NEARLY_ALWAYS_INLINE HeapBasePtr m() const diff --git a/src/qml/common/qv4stringtoarrayindex_p.h b/src/qml/common/qv4stringtoarrayindex_p.h index fc71ca7072..7bdabd4f3f 100644 --- a/src/qml/common/qv4stringtoarrayindex_p.h +++ b/src/qml/common/qv4stringtoarrayindex_p.h @@ -43,7 +43,7 @@ uint stringToArrayIndex(const T *ch, const T *end) uint x = charToUInt(ch) - '0'; if (x > 9) return std::numeric_limits<uint>::max(); - if (mul_overflow(i, uint(10), &i) || add_overflow(i, x, &i)) // i = i * 10 + x + if (qMulOverflow(i, uint(10), &i) || qAddOverflow(i, x, &i)) // i = i * 10 + x return std::numeric_limits<uint>::max(); ++ch; } |