diff options
Diffstat (limited to 'src/qml/common')
-rw-r--r-- | src/qml/common/common.pri | 35 | ||||
-rw-r--r-- | src/qml/common/qjsnumbercoercion.cpp | 62 | ||||
-rw-r--r-- | src/qml/common/qjsnumbercoercion.h | 120 | ||||
-rw-r--r-- | src/qml/common/qqmlapiversion_p.h | 56 | ||||
-rw-r--r-- | src/qml/common/qqmljsdiagnosticmessage_p.h | 48 | ||||
-rw-r--r-- | src/qml/common/qqmljsfixedpoolarray_p.h | 46 | ||||
-rw-r--r-- | src/qml/common/qqmljsmemorypool_p.h | 66 | ||||
-rw-r--r-- | src/qml/common/qqmljssourcelocation_p.h | 107 | ||||
-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.cpp | 125 | ||||
-rw-r--r-- | src/qml/common/qqmltranslation_p.h | 74 | ||||
-rw-r--r-- | src/qml/common/qv4alloca_p.h | 78 | ||||
-rw-r--r-- | src/qml/common/qv4calldata_p.h | 42 | ||||
-rw-r--r-- | src/qml/common/qv4compileddata.cpp | 431 | ||||
-rw-r--r-- | src/qml/common/qv4compileddata_p.h | 1110 | ||||
-rw-r--r-- | src/qml/common/qv4staticvalue_p.h | 612 | ||||
-rw-r--r-- | src/qml/common/qv4stringtoarrayindex_p.h | 46 |
18 files changed, 2466 insertions, 908 deletions
diff --git a/src/qml/common/common.pri b/src/qml/common/common.pri deleted file mode 100644 index bcc3ea0fa0..0000000000 --- a/src/qml/common/common.pri +++ /dev/null @@ -1,35 +0,0 @@ -!build_pass { - # Create a header containing a hash that describes this library. For a - # released version of Qt, we'll use the .tag file that is updated by git - # archive with the commit hash. For unreleased versions, we'll ask git - # describe. Note that it won't update unless qmake is run again, even if - # the commit change also changed something in this library. - tagFile = $$PWD/../../.tag - tag = - exists($$tagFile) { - tag = $$cat($$tagFile, singleline) - QMAKE_INTERNAL_INCLUDED_FILES += $$tagFile - } - !equals(tag, "$${LITERAL_DOLLAR}Format:%H$${LITERAL_DOLLAR}") { - QML_COMPILE_HASH = $$tag - } else:exists($$PWD/../../.git) { - commit = $$system(git rev-parse HEAD) - QML_COMPILE_HASH = $$commit - } - compile_hash_contents = \ - "// Generated file, DO NOT EDIT" \ - "$${LITERAL_HASH}define QML_COMPILE_HASH \"$$QML_COMPILE_HASH\"" \ - "$${LITERAL_HASH}define QML_COMPILE_HASH_LENGTH $$str_size($$QML_COMPILE_HASH)" - write_file("$$OUT_PWD/qml_compile_hash_p.h", compile_hash_contents)|error() -} - -HEADERS += \ - $$PWD/qqmlapiversion_p.h \ - $$PWD/qqmljsdiagnosticmessage_p.h \ - $$PWD/qqmljsfixedpoolarray_p.h \ - $$PWD/qqmljsmemorypool_p.h \ - $$PWD/qv4alloca_p.h \ - $$PWD/qv4calldata_p.h \ - $$PWD/qv4compileddata_p.h \ - $$PWD/qv4staticvalue_p.h \ - $$PWD/qv4stringtoarrayindex_p.h diff --git a/src/qml/common/qjsnumbercoercion.cpp b/src/qml/common/qjsnumbercoercion.cpp new file mode 100644 index 0000000000..8cd96a4e25 --- /dev/null +++ b/src/qml/common/qjsnumbercoercion.cpp @@ -0,0 +1,62 @@ +// Copyright (C) 2020 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 "qjsnumbercoercion.h" + +QT_BEGIN_NAMESPACE + +/*! + \since 6.1 + \class QJSNumberCoercion + \internal + + \brief Implements the JavaScript double-to-int coercion. + */ + +/*! + \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 + + Coerces the given \a d to a 32bit integer by JavaScript rules and returns + the result. + */ + +/*! + \fn bool equals(double lhs, double rhs) + \internal + + Compares \a lhs and \a rhs bit by bit without causing a compile warning. + Returns the \c true if they are equal, or \c false if not. + */ + +QT_END_NAMESPACE diff --git a/src/qml/common/qjsnumbercoercion.h b/src/qml/common/qjsnumbercoercion.h new file mode 100644 index 0000000000..0023bff6e8 --- /dev/null +++ b/src/qml/common/qjsnumbercoercion.h @@ -0,0 +1,120 @@ +// Copyright (C) 2020 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 QJSNUMBERCOERCION_H +#define QJSNUMBERCOERCION_H + +#include <QtCore/qglobal.h> +#include <cstring> + +QT_BEGIN_NAMESPACE + +class QJSNumberCoercion +{ +public: + + 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; + + 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(); + } + + static constexpr bool equals(double lhs, double rhs) + { + QT_WARNING_PUSH + QT_WARNING_DISABLE_FLOAT_COMPARE + return lhs == rhs; + QT_WARNING_POP + } + +private: + constexpr QJSNumberCoercion(double dbl) + { + // the dbl == 0 path is guaranteed constexpr. The other one may or may not be, depending + // on whether and how the compiler inlines the memcpy. + // In order to declare the ctor constexpr we need one guaranteed constexpr path. + if (!equals(dbl, 0)) + memcpy(&d, &dbl, sizeof(double)); + } + + constexpr int sign() const + { + return (d >> 63) ? -1 : 1; + } + + constexpr bool isDenormal() const + { + return static_cast<int>((d << 1) >> 53) == 0; + } + + constexpr int exponent() const + { + return static_cast<int>((d << 1) >> 53) - 1023; + } + + constexpr quint64 significant() const + { + quint64 m = (d << 12) >> 12; + if (!isDenormal()) + m |= (static_cast<quint64>(1) << 52); + return m; + } + + constexpr int toInteger() + { + int e = exponent() - 52; + if (e < 0) { + if (e <= -53) + return 0; + return sign() * static_cast<int>(significant() >> -e); + } else { + if (e > 31) + return 0; + return sign() * (static_cast<int>(significant()) << e); + } + } + + quint64 d = 0; +}; + +QT_END_NAMESPACE + +#endif // QJSNUMBERCOERCION_H diff --git a/src/qml/common/qqmlapiversion_p.h b/src/qml/common/qqmlapiversion_p.h deleted file mode 100644 index 576619c518..0000000000 --- a/src/qml/common/qqmlapiversion_p.h +++ /dev/null @@ -1,56 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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 QQMLAPIVERSION_P_H -#define QQMLAPIVERSION_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. -// - -#define Q_QML_PRIVATE_API_VERSION 6 - -#endif // QQMLAPIVERSION_P_H diff --git a/src/qml/common/qqmljsdiagnosticmessage_p.h b/src/qml/common/qqmljsdiagnosticmessage_p.h index 763332ba76..0e7adbc3be 100644 --- a/src/qml/common/qqmljsdiagnosticmessage_p.h +++ b/src/qml/common/qqmljsdiagnosticmessage_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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$ -** -****************************************************************************/ +// Copyright (C) 2019 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 QQMLJSDIAGNOSTICMESSAGE_P_H #define QQMLJSDIAGNOSTICMESSAGE_P_H @@ -56,7 +20,8 @@ // Include the API version here, to avoid complications when querying it for the // QQmlSourceLocation -> line/column change. -#include <private/qqmlapiversion_p.h> + +#include "qqmljssourcelocation_p.h" QT_BEGIN_NAMESPACE @@ -65,8 +30,7 @@ struct DiagnosticMessage { QString message; QtMsgType type = QtCriticalMsg; - quint32 line = 0; - quint32 column = 0; + SourceLocation loc; bool isError() const { @@ -85,7 +49,7 @@ struct DiagnosticMessage }; } // namespace QQmlJS -Q_DECLARE_TYPEINFO(QQmlJS::DiagnosticMessage, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QQmlJS::DiagnosticMessage, Q_RELOCATABLE_TYPE); QT_END_NAMESPACE diff --git a/src/qml/common/qqmljsfixedpoolarray_p.h b/src/qml/common/qqmljsfixedpoolarray_p.h index b65b994d6c..e946b1ecfd 100644 --- a/src/qml/common/qqmljsfixedpoolarray_p.h +++ b/src/qml/common/qqmljsfixedpoolarray_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QQMLJSFIXEDPOOLARRAY_P_H #define QQMLJSFIXEDPOOLARRAY_P_H @@ -80,13 +44,13 @@ public: void allocate(MemoryPool *pool, const QVector<T> &vector) { - count = vector.count(); + count = vector.size(); data = reinterpret_cast<T*>(pool->allocate(count * sizeof(T))); if (QTypeInfo<T>::isComplex) { for (int i = 0; i < count; ++i) new (data + i) T(vector.at(i)); - } else { + } else if (count) { memcpy(data, static_cast<const void*>(vector.constData()), count * sizeof(T)); } } @@ -94,7 +58,7 @@ public: template <typename Container> void allocate(MemoryPool *pool, const Container &container) { - count = container.count(); + count = container.size(); data = reinterpret_cast<T*>(pool->allocate(count * sizeof(T))); typename Container::ConstIterator it = container.constBegin(); for (int i = 0; i < count; ++i) diff --git a/src/qml/common/qqmljsmemorypool_p.h b/src/qml/common/qqmljsmemorypool_p.h index 0cf7ea84e6..6f0f8ed491 100644 --- a/src/qml/common/qqmljsmemorypool_p.h +++ b/src/qml/common/qqmljsmemorypool_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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$ -** -****************************************************************************/ +// Copyright (C) 2021 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 QQMLJSMEMORYPOOL_P_H #define QQMLJSMEMORYPOOL_P_H @@ -51,11 +15,11 @@ // We mean it. // -#include <QtCore/qglobal.h> -#include <QtCore/qshareddata.h> -#include <QtCore/qdebug.h> +#include <QtCore/private/qglobal_p.h> +#include <QtCore/qstring.h> +#include <QtCore/qvector.h> -#include <cstring> +#include <cstdlib> QT_BEGIN_NAMESPACE @@ -63,14 +27,12 @@ namespace QQmlJS { class Managed; -class MemoryPool : public QSharedData +class MemoryPool { - MemoryPool(const MemoryPool &other); - void operator =(const MemoryPool &other); + Q_DISABLE_COPY_MOVE(MemoryPool); public: - MemoryPool() {} - + MemoryPool() = default; ~MemoryPool() { if (_blocks) { @@ -81,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; @@ -105,9 +66,8 @@ public: template <typename Tp, typename... Ta> Tp *New(Ta... args) { return new (this->allocate(sizeof(Tp))) Tp(args...); } - QStringRef newString(const QString &string) { - strings.append(new QString(string)); - return QStringRef(strings.last()); + QStringView newString(QString string) { + return strings.emplace_back(std::move(string)); } private: @@ -151,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/qqmljssourcelocation_p.h b/src/qml/common/qqmljssourcelocation_p.h new file mode 100644 index 0000000000..9a007e4ac5 --- /dev/null +++ b/src/qml/common/qqmljssourcelocation_p.h @@ -0,0 +1,107 @@ +// Copyright (C) 2021 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 QQMLJSSOURCELOCATION_P_H +#define QQMLJSSOURCELOCATION_P_H + +#include <QtCore/private/qglobal_p.h> +#include <QtCore/qhashfunctions.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. +// + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { + +class SourceLocation +{ +public: + explicit SourceLocation(quint32 offset = 0, quint32 length = 0, quint32 line = 0, quint32 column = 0) + : offset(offset), length(length), + startLine(line), startColumn(column) + { } + + bool isValid() const { return *this != SourceLocation(); } + + quint32 begin() const { return offset; } + quint32 end() const { return offset + length; } + + // Returns a zero length location at the start of the current one. + SourceLocation startZeroLengthLocation() const + { + return SourceLocation(offset, 0, startLine, startColumn); + } + // Returns a zero length location at the end of the current one. + SourceLocation endZeroLengthLocation(QStringView text) const + { + quint32 i = offset; + quint32 endLine = startLine; + quint32 endColumn = startColumn; + while (i < end()) { + QChar c = text.at(i); + switch (c.unicode()) { + case '\n': + if (i + 1 < end() && text.at(i + 1) == QLatin1Char('\r')) + ++i; + Q_FALLTHROUGH(); + case '\r': + ++endLine; + endColumn = 1; + break; + default: + ++endColumn; + } + ++i; + } + return SourceLocation(offset + length, 0, endLine, endColumn); + } + +// attributes + // ### encode + quint32 offset; + quint32 length; + quint32 startLine; + quint32 startColumn; + + friend size_t qHash(const SourceLocation &location, size_t seed = 0) + { + return qHashMulti(seed, location.offset, location.length, + location.startLine, location.startColumn); + } + + friend bool operator==(const SourceLocation &a, const SourceLocation &b) + { + return a.offset == b.offset && a.length == b.length + && a.startLine == b.startLine && a.startColumn == b.startColumn; + } + + friend bool operator!=(const SourceLocation &a, const SourceLocation &b) { return !(a == b); } + + // Returns a source location starting at the beginning of l1, l2 and ending at the end of them. + // Ignores invalid source locations. + friend SourceLocation combine(const SourceLocation &l1, const SourceLocation &l2) { + quint32 e = qMax(l1.end(), l2.end()); + SourceLocation res; + if (l1.offset <= l2.offset) + res = (l1.isValid() ? l1 : l2); + else + res = (l2.isValid() ? l2 : l1); + res.length = e - res.offset; + return res; + } +}; + +} // namespace QQmlJS + +QT_END_NAMESPACE + +#endif 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.cpp b/src/qml/common/qqmltranslation.cpp new file mode 100644 index 0000000000..7120071b1a --- /dev/null +++ b/src/qml/common/qqmltranslation.cpp @@ -0,0 +1,125 @@ +// Copyright (C) 2022 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 "private/qqmltranslation_p.h" + +QQmlTranslation::QQmlTranslation(const Data &d) : data(d) { } +QQmlTranslation::QQmlTranslation() : data(nullptr) { } + +QString QQmlTranslation::translate() const +{ + return std::visit( + [](auto &&arg) -> QString { + using T = std::decay_t<decltype(arg)>; + if constexpr (!std::is_same_v<T, std::nullptr_t>) + return arg.translate(); + else { + Q_ASSERT_X(false, "QQmlTranslation", "Uninitialized Translation"); + return {}; + } + }, + data); +} + +QString QQmlTranslation::serializeForQmltc() const +{ + return std::visit( + [](auto &&arg) -> QString { + using T = std::decay_t<decltype(arg)>; + if constexpr (!std::is_same_v<T, std::nullptr_t>) + return arg.serializeForQmltc(); + else { + Q_ASSERT_X(false, "QQmlTranslation", "Uninitialized Translation"); + return {}; + } + }, + data); +} + +QString QQmlTranslation::idForQmlDebug() const +{ + return std::visit( + [](auto &&arg) -> QString { + using T = std::decay_t<decltype(arg)>; + if constexpr (!std::is_same_v<T, std::nullptr_t>) + return arg.idForQmlDebug(); + else { + Q_ASSERT_X(false, "QQmlTranslation", "Uninitialized Translation"); + return {}; + } + }, + data); +} + +QQmlTranslation::QsTrData::QsTrData(const QString &context, const QString &text, + const QString &comment, int number) + : context(context.toUtf8()), text(text.toUtf8()), comment(comment.toUtf8()), number(number) +{ +} + +QString QQmlTranslation::contextFromQmlFilename(const QString &qmlFilename) +{ + int lastSlash = qmlFilename.lastIndexOf(QLatin1Char('/')); + QStringView contextView = (lastSlash > -1) + ? QStringView{ qmlFilename }.mid(lastSlash + 1, qmlFilename.size() - lastSlash - 5) + : QStringView(); + return contextView.toString(); +} + +QString QQmlTranslation::QsTrData::translate() const +{ +#if !QT_CONFIG(translation) + return QString(); +#else + return QCoreApplication::translate(context, text, comment, number); +#endif +} + +QString QQmlTranslation::QsTrData::idForQmlDebug() const +{ + return QString::fromUtf8(text); +} + +QString QQmlTranslation::QsTrData::serializeForQmltc() const +{ + QString result = QStringLiteral(R"(QQmlTranslation(QQmlTranslation::QsTrData( + QStringLiteral("%1"), + QStringLiteral("%2"), + QStringLiteral("%3"), + %4)))") + .arg(QString::fromUtf8(context), QString::fromUtf8(text), + QString::fromUtf8(comment)) + .arg(number); + + return result; +} + +QQmlTranslation::QsTrIdData::QsTrIdData(const QString &id, int number) + : id(id.toUtf8()), number(number) +{ +} + +QString QQmlTranslation::QsTrIdData::translate() const +{ +#if !QT_CONFIG(translation) + return QString(); +#else + return qtTrId(id, number); +#endif +} + +QString QQmlTranslation::QsTrIdData::serializeForQmltc() const +{ + QString result = QStringLiteral(R"(QQmlTranslation(QQmlTranslation::QsTrIdData( + QStringLiteral("%1"), + %4)))") + .arg(QString::fromUtf8(id)) + .arg(number); + + return result; +} + +QString QQmlTranslation::QsTrIdData::idForQmlDebug() const +{ + return QString::fromUtf8(id); +} diff --git a/src/qml/common/qqmltranslation_p.h b/src/qml/common/qqmltranslation_p.h new file mode 100644 index 0000000000..9849203abe --- /dev/null +++ b/src/qml/common/qqmltranslation_p.h @@ -0,0 +1,74 @@ +// Copyright (C) 2022 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 QQMLTRANSLATION_P_H +#define QQMLTRANSLATION_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 <QtCore/qstring.h> + +#include <private/qv4qmlcontext_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QML_EXPORT QQmlTranslation +{ +public: + class Q_QML_EXPORT QsTrData + { + QByteArray context; + QByteArray text; + QByteArray comment; + int number; + + public: + QsTrData(const QString &fileNameForContext, const QString &text, const QString &comment, + int number); + QString translate() const; + QString serializeForQmltc() const; + QString idForQmlDebug() const; + }; + + class Q_QML_EXPORT QsTrIdData + { + QByteArray id; + int number; + + public: + QsTrIdData(const QString &id, int number); + QString translate() const; + QString serializeForQmltc() const; + QString idForQmlDebug() const; + }; + + // The static analyzer hates std::monostate in std::variant because + // that results in various uninitialized memory "problems". Just use + // std::nullptr_t to indicate "empty". + using Data = std::variant<std::nullptr_t, QsTrData, QsTrIdData>; + +private: + Data data; + +public: + QQmlTranslation(const Data &d); + QQmlTranslation(); + QString translate() const; + QString serializeForQmltc() const; + QString idForQmlDebug() const; + + static QString contextFromQmlFilename(const QString &qmlFilename); +}; + +QT_END_NAMESPACE + +#endif // QQMLTRANSLATION_P_H diff --git a/src/qml/common/qv4alloca_p.h b/src/qml/common/qv4alloca_p.h index 65c3e4d65a..51c99192d3 100644 --- a/src/qml/common/qv4alloca_p.h +++ b/src/qml/common/qv4alloca_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QV4_ALLOCA_H #define QV4_ALLOCA_H @@ -53,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 @@ -73,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 @@ -82,26 +47,17 @@ 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) \ - _qt_alloca_##name.allocate(size); \ - name = static_cast<type*>(_qt_alloca_##name.data()) + do { \ + _qt_alloca_##name.reset(new char[size]); \ + name = reinterpret_cast<type*>(_qt_alloca_##name.get()); \ + } while (false) #endif diff --git a/src/qml/common/qv4calldata_p.h b/src/qml/common/qv4calldata_p.h index 5a5280cb86..8d193bae4d 100644 --- a/src/qml/common/qv4calldata_p.h +++ b/src/qml/common/qv4calldata_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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$ -** -****************************************************************************/ +// Copyright (C) 2019 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 QV4CALLDATA_P_H #define QV4CALLDATA_P_H @@ -95,7 +59,7 @@ struct CallData StaticValue args[1]; - static Q_DECL_CONSTEXPR int HeaderSize() + static constexpr int HeaderSize() { return offsetof(CallData, args) / sizeof(QV4::StaticValue); } 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 11de506a53..c21fc19fa9 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QV4COMPILEDDATA_P_H #define QV4COMPILEDDATA_P_H @@ -52,19 +16,31 @@ #include <functional> -#include <QtCore/qstring.h> +#include <QtCore/qcryptographichash.h> +#include <QtCore/qhash.h> +#include <QtCore/qhashfunctions.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> #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> QT_BEGIN_NAMESPACE @@ -75,12 +51,18 @@ 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 0x26// support required properties +#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 AOTCompiledFunction; +} namespace QmlIR { struct Document; @@ -95,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; @@ -112,6 +104,7 @@ struct TableIterator int index; const ItemType *operator->() { return (container->*IndexedGetter)(index); } + ItemType operator*() {return *operator->();} void operator++() { ++index; } bool operator==(const TableIterator &rhs) const { return index == rhs.index; } bool operator!=(const TableIterator &rhs) const { return index != rhs.index; } @@ -119,18 +112,45 @@ struct TableIterator struct Location { - union { - quint32 _dummy; - quint32_le_bitfield<0, 20> line; - quint32_le_bitfield<20, 12> column; - }; - - Location() : _dummy(0) { } + Location() : m_data(QSpecialIntegerBitfieldZero) {} + Location(quint32 l, quint32 c) : Location() + { + m_data.set<LineField>(l); + m_data.set<ColumnField>(c); + Q_ASSERT(m_data.get<LineField>() == l); + Q_ASSERT(m_data.get<ColumnField>() == c); + } inline bool operator<(const Location &other) const { - return line < other.line || - (line == other.line && column < other.column); + return m_data.get<LineField>() < other.m_data.get<LineField>() + || (m_data.get<LineField>() == other.m_data.get<LineField>() + && m_data.get<ColumnField>() < other.m_data.get<ColumnField>()); + } + + friend size_t qHash(const Location &location, size_t seed = 0) + { + return QT_PREPEND_NAMESPACE(qHash)(location.m_data.data(), seed); } + + friend bool operator==(const Location &a, const Location &b) + { + return a.m_data.data()== b.m_data.data(); + } + + void set(quint32 line, quint32 column) + { + m_data.set<LineField>(line); + m_data.set<ColumnField>(column); + } + + quint32 line() const { return m_data.get<LineField>(); } + quint32 column() const { return m_data.get<ColumnField>(); } + +private: + using LineField = quint32_le_bitfield_member<0, 20>; + using ColumnField = quint32_le_bitfield_member<20, 12>; + + quint32_le_bitfield_union<LineField, ColumnField> m_data; }; static_assert(sizeof(Location) == 4, "Location structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); @@ -141,16 +161,24 @@ struct RegExp RegExp_Global = 0x01, RegExp_IgnoreCase = 0x02, RegExp_Multiline = 0x04, - RegExp_Unicode = 0x08, - RegExp_Sticky = 0x10 - }; - union { - quint32 _dummy; - quint32_le_bitfield<0, 5> flags; - quint32_le_bitfield<5, 27> stringIndex; + RegExp_Sticky = 0x08, + RegExp_Unicode = 0x10, }; - RegExp() : _dummy(0) { } + RegExp() : m_data(QSpecialIntegerBitfieldZero) {} + RegExp(quint32 flags, quint32 stringIndex) : RegExp() + { + m_data.set<FlagsField>(flags); + m_data.set<StringIndexField>(stringIndex); + } + + quint32 flags() const { return m_data.get<FlagsField>(); } + quint32 stringIndex() const { return m_data.get<StringIndexField>(); } + +private: + using FlagsField = quint32_le_bitfield_member<0, 5>; + using StringIndexField = quint32_le_bitfield_member<5, 27>; + quint32_le_bitfield_union<FlagsField, StringIndexField> m_data; }; static_assert(sizeof(RegExp) == 4, "RegExp structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); @@ -163,25 +191,49 @@ struct Lookup Type_QmlContextPropertyGetter = 3 }; - union { - quint32 _dummy; - quint32_le_bitfield<0, 4> type_and_flags; - quint32_le_bitfield<4, 28> nameIndex; + enum Mode : unsigned int { + Mode_ForStorage = 0, + Mode_ForCall = 1 }; - Lookup() : _dummy(0) { } + quint32 type() const { return m_data.get<TypeField>(); } + quint32 nameIndex() const { return m_data.get<NameIndexField>(); } + quint32 mode() const { return m_data.get<ModeField>(); } + + Lookup() : m_data(QSpecialIntegerBitfieldZero) {} + Lookup(Type type, Mode mode, quint32 nameIndex) : Lookup() + { + m_data.set<TypeField>(type); + m_data.set<ModeField>(mode); + m_data.set<NameIndexField>(nameIndex); + } + +private: + using TypeField = quint32_le_bitfield_member<0, 2>; + using ModeField = quint32_le_bitfield_member<2, 1>; + // 1 bit left + using NameIndexField = quint32_le_bitfield_member<4, 28>; + quint32_le_bitfield_union<TypeField, ModeField, NameIndexField> m_data; }; static_assert(sizeof(Lookup) == 4, "Lookup structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct JSClassMember { - union { - quint32 _dummy; - quint32_le_bitfield<0, 31> nameOffset; - quint32_le_bitfield<31, 1> isAccessor; - }; + JSClassMember() : m_data(QSpecialIntegerBitfieldZero) {} - JSClassMember() : _dummy(0) { } + void set(quint32 nameOffset, bool isAccessor) + { + m_data.set<NameOffsetField>(nameOffset); + m_data.set<IsAccessorField>(isAccessor ? 1 : 0); + } + + quint32 nameOffset() const { return m_data.get<NameOffsetField>(); } + bool isAccessor() const { return m_data.get<IsAccessorField>() != 0; } + +private: + using NameOffsetField = quint32_le_bitfield_member<0, 31>; + using IsAccessorField = quint32_le_bitfield_member<31, 1>; + quint32_le_bitfield_union<NameOffsetField, IsAccessorField> m_data; }; static_assert(sizeof(JSClassMember) == 4, "JSClassMember structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); @@ -194,45 +246,25 @@ struct JSClass }; static_assert(sizeof(JSClass) == 4, "JSClass structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); -// This data structure is intended to be binary compatible with QStringData/QStaticStringData on -// 64-bit and 32-bit little-endian architectures, in all directions. So the same structure mapped -// from a file must be castable to a QStringData regardless of the pointer size. With the first -// few fields that's easy, they're always 32-bit. However the offset field of QArrayData is a -// ptrdiff_t and thus variable in size. -// On 64-bit systems compilers enforce an 8-byte alignment and thus place it at offset 16, while -// on 32-bit systems offset 12 is sufficient. Therefore the two values don't overlap and contain -// the same value. struct String { - qint32_le refcount; // -1 qint32_le size; - quint32_le allocAndCapacityReservedFlag; // 0 - quint32_le offsetOn32Bit; - quint64_le offsetOn64Bit; - // uint16 strdata[] static int calculateSize(const QString &str) { - return (sizeof(String) + (str.length() + 1) * sizeof(quint16) + 7) & ~0x7; + // we cannot enconuter strings larger than INT_MAX anyway, as such a string + // would already break in other parts of the compilation process + return (sizeof(String) + (int(str.size()) + 1) * sizeof(quint16) + 7) & ~0x7; } }; -static_assert(sizeof(String) == 24, "String structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); - -// Ensure compatibility with QString -static_assert(offsetof(QArrayData, ref) == offsetof(String, refcount), "refcount must be at the same location"); -static_assert(offsetof(QArrayData, size) == offsetof(String, size), "size must be at the same location"); -static_assert(offsetof(String, offsetOn64Bit) == 16, "offset must be at 8-byte aligned location"); -static_assert(offsetof(String, offsetOn32Bit) == 12, "offset must be at 4-byte aligned location"); -#if QT_POINTER_SIZE == 8 -static_assert(offsetof(QArrayData, offset) == offsetof(String, offsetOn64Bit), "offset must be at the same location"); -#else -static_assert(offsetof(QArrayData, offset) == offsetof(String, offsetOn32Bit), "offset must be at the same location"); -#endif -struct CodeOffsetToLine { +static_assert (sizeof (String) == 4, "String structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct CodeOffsetToLineAndStatement { quint32_le codeOffset; - quint32_le line; + qint32_le line; // signed because debug instructions get negative line numbers + quint32_le statement; }; -static_assert(sizeof(CodeOffsetToLine) == 8, "CodeOffsetToLine structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +static_assert(sizeof(CodeOffsetToLineAndStatement) == 12, "CodeOffsetToLineAndStatement structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Block { @@ -256,19 +288,65 @@ 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, Variant, Int, Bool, Real, String, Url, Color, - Font, Time, Date, DateTime, Rect, Point, Size, - Vector2D, Vector3D, Vector4D, Matrix4x4, Quaternion, 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 { - union { - quint32 _dummy; - quint32_le_bitfield<0, 1> indexIsBuiltinType; - quint32_le_bitfield<1, 31> typeNameIndexOrBuiltinType; + enum Flag { + NoFlag = 0x0, + Common = 0x1, + List = 0x2, }; + Q_DECLARE_FLAGS(Flags, Flag); + + void set(Flags flags, quint32 typeNameIndexOrCommonType) + { + m_data.set<IsListField>(flags.testFlag(List) ? 1 : 0); + m_data.set<IndexIsCommonTypeField>(flags.testFlag(Common) ? 1 : 0); + m_data.set<TypeNameIndexOrCommonTypeField>(typeNameIndexOrCommonType); + } + + bool indexIsCommonType() const + { + return m_data.get<IndexIsCommonTypeField>() != 0; + } + + bool isList() const + { + return m_data.get<IsListField>() != 0; + } + + quint32 typeNameIndexOrCommonType() const + { + return m_data.get<TypeNameIndexOrCommonTypeField>(); + } + +private: + using IndexIsCommonTypeField = quint32_le_bitfield_member<0, 1>; + using IsListField = quint32_le_bitfield_member<1, 1>; + 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"); @@ -286,7 +364,8 @@ struct Function enum Flags : unsigned int { IsStrict = 0x1, IsArrowFunction = 0x2, - IsGenerator = 0x4 + IsGenerator = 0x4, + IsClosureWrapper = 0x8, }; // Absolute offset into file where the code for this function is located. @@ -300,17 +379,22 @@ struct Function ParameterType returnType; quint32_le localsOffset; quint16_le nLocals; - quint16_le nLineNumbers; - size_t lineNumberOffset() const { return localsOffset + nLocals * sizeof(quint32); } + quint16_le nLineAndStatementNumbers; + size_t lineAndStatementNumberOffset() const { return localsOffset + nLocals * sizeof(quint32); } quint32_le nestedFunctionIndex; // for functions that only return a single closure, used in signal handlers + + quint32_le nRegisters; + Location location; + quint32_le nLabelInfos; + quint16_le sizeOfLocalTemporalDeadZone; quint16_le firstTemporalDeadZoneRegister; quint16_le sizeOfRegisterTemporalDeadZone; - quint16_le nRegisters; - Location location; - quint32_le nLabelInfos; - size_t labelInfosOffset() const { return lineNumberOffset() + nLineNumbers * sizeof(CodeOffsetToLine); } + size_t labelInfosOffset() const + { + return lineAndStatementNumberOffset() + nLineAndStatementNumbers * sizeof(CodeOffsetToLineAndStatement); + } // Keep all unaligned data at the end quint8 flags; @@ -319,9 +403,21 @@ struct Function // quint32 formalsIndex[nFormals] // quint32 localsIndex[nLocals] - const Parameter *formalsTable() const { return reinterpret_cast<const Parameter *>(reinterpret_cast<const char *>(this) + formalsOffset); } - const quint32_le *localsTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + localsOffset); } - const CodeOffsetToLine *lineNumberTable() const { return reinterpret_cast<const CodeOffsetToLine *>(reinterpret_cast<const char *>(this) + lineNumberOffset()); } + const Parameter *formalsTable() const + { + return reinterpret_cast<const Parameter *>( + reinterpret_cast<const char *>(this) + formalsOffset); + } + const quint32_le *localsTable() const + { + return reinterpret_cast<const quint32_le *>( + reinterpret_cast<const char *>(this) + localsOffset); + } + const CodeOffsetToLineAndStatement *lineAndStatementNumberTable() const + { + return reinterpret_cast<const CodeOffsetToLineAndStatement *>( + reinterpret_cast<const char *>(this) + lineAndStatementNumberOffset()); + } // --- QQmlPropertyCacheCreator interface const Parameter *formalsBegin() const { return formalsTable(); } @@ -332,9 +428,13 @@ struct Function const char *code() const { return reinterpret_cast<const char *>(this) + codeOffset; } - static int calculateSize(int nFormals, int nLocals, int nLines, int nInnerfunctions, int labelInfoSize, int codeSize) { - int trailingData = nFormals * sizeof(Parameter) + (nLocals + nInnerfunctions + labelInfoSize)*sizeof (quint32) - + nLines*sizeof(CodeOffsetToLine); + static int calculateSize( + int nFormals, int nLocals, int nLinesAndStatements, int nInnerfunctions, + int labelInfoSize, int codeSize) + { + int trailingData = nFormals * sizeof(Parameter) + + (nLocals + nInnerfunctions + labelInfoSize) * sizeof (quint32) + + nLinesAndStatements * sizeof(CodeOffsetToLineAndStatement); size_t size = align(align(sizeof(Function)) + size_t(trailingData)) + align(codeSize); Q_ASSERT(size < INT_MAX); return int(size); @@ -434,10 +534,11 @@ 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; - quint32_le padding; + quint32_le contextIndex; }; static_assert(sizeof(TranslationData) == 16, "TranslationData structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); @@ -445,7 +546,7 @@ struct Binding { quint32_le propertyNameIndex; - enum ValueType : unsigned int { + enum Type : unsigned int { Type_Invalid, Type_Boolean, Type_Number, @@ -459,7 +560,7 @@ struct Binding Type_GroupProperty }; - enum Flags : unsigned int { + enum Flag : unsigned int { IsSignalHandlerExpression = 0x1, IsSignalHandlerObject = 0x2, IsOnAssignment = 0x4, @@ -469,13 +570,23 @@ struct Binding IsBindingToAlias = 0x40, IsDeferredBinding = 0x80, IsCustomParserBinding = 0x100, - IsFunctionExpression = 0x200 + IsFunctionExpression = 0x200, + IsPropertyObserver = 0x400 }; + Q_DECLARE_FLAGS(Flags, Flag); + + using FlagsField = quint32_le_bitfield_member<0, 16>; + using TypeField = quint32_le_bitfield_member<16, 16>; + quint32_le_bitfield_union<FlagsField, TypeField> flagsAndType; + + void clearFlags() { flagsAndType.set<FlagsField>(0); } + void setFlag(Flag flag) { flagsAndType.set<FlagsField>(flagsAndType.get<FlagsField>() | flag); } + bool hasFlag(Flag flag) const { return Flags(flagsAndType.get<FlagsField>()) & flag; } + Flags flags() const { return Flags(flagsAndType.get<FlagsField>()); } + + void setType(Type type) { flagsAndType.set<TypeField>(type); } + Type type() const { return Type(flagsAndType.get<TypeField>()); } - union { - quint32_le_bitfield<0, 16> flags; - quint32_le_bitfield<16, 16> type; - }; union { bool b; quint32_le constantValueIndex; @@ -489,23 +600,31 @@ struct Binding Location location; Location valueLocation; + bool hasSignalHandlerBindingFlag() const + { + const Flags bindingFlags = flags(); + return bindingFlags & IsSignalHandlerExpression + || bindingFlags & IsSignalHandlerObject + || bindingFlags & IsPropertyObserver; + } + bool isValueBinding() const { - if (type == Type_AttachedProperty - || type == Type_GroupProperty) - return false; - if (flags & IsSignalHandlerExpression - || flags & IsSignalHandlerObject) + switch (type()) { + case Type_AttachedProperty: + case Type_GroupProperty: return false; - return true; + default: + return !hasSignalHandlerBindingFlag(); + } } - bool isValueBindingNoAlias() const { return isValueBinding() && !(flags & IsBindingToAlias); } - bool isValueBindingToAlias() const { return isValueBinding() && (flags & IsBindingToAlias); } + bool isValueBindingNoAlias() const { return isValueBinding() && !hasFlag(IsBindingToAlias); } + bool isValueBindingToAlias() const { return isValueBinding() && hasFlag(IsBindingToAlias); } bool isSignalHandler() const { - if (flags & IsSignalHandlerExpression || flags & IsSignalHandlerObject) { + if (hasSignalHandlerBindingFlag()) { Q_ASSERT(!isValueBinding()); Q_ASSERT(!isAttachedProperty()); Q_ASSERT(!isGroupProperty()); @@ -516,7 +635,7 @@ struct Binding bool isAttachedProperty() const { - if (type == Type_AttachedProperty) { + if (type() == Type_AttachedProperty) { Q_ASSERT(!isValueBinding()); Q_ASSERT(!isSignalHandler()); Q_ASSERT(!isGroupProperty()); @@ -527,7 +646,7 @@ struct Binding bool isGroupProperty() const { - if (type == Type_GroupProperty) { + if (type() == Type_GroupProperty) { Q_ASSERT(!isValueBinding()); Q_ASSERT(!isSignalHandler()); Q_ASSERT(!isAttachedProperty()); @@ -536,13 +655,13 @@ struct Binding return false; } - bool isFunctionExpression() const { return (flags & IsFunctionExpression); } + bool isFunctionExpression() const { return hasFlag(IsFunctionExpression); } //reverse of Lexer::singleEscape() static QString escapedString(const QString &string) { QString tmp = QLatin1String("\""); - for (int i = 0; i < string.length(); ++i) { + for (int i = 0; i < string.size(); ++i) { const QChar &c = string.at(i); switch (c.unicode()) { case 0x08: @@ -581,20 +700,34 @@ struct Binding return tmp; } - bool isTranslationBinding() const { return type == Type_Translation || type == Type_TranslationById; } - bool evaluatesToString() const { return type == Type_String || isTranslationBinding(); } + bool isTranslationBinding() const + { + const Binding::Type bindingType = type(); + return bindingType == Type_Translation || bindingType == Type_TranslationById; + } + bool evaluatesToString() const { return type() == Type_String || isTranslationBinding(); } + + bool isNumberBinding() const { return type() == Type_Number; } bool valueAsBoolean() const { - if (type == Type_Boolean) + if (type() == Type_Boolean) return value.b; return false; } - }; static_assert(sizeof(Binding) == 24, "Binding structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +struct InlineComponent +{ + quint32_le objectIndex; + quint32_le nameIndex; + Location location; +}; + +static_assert(sizeof(InlineComponent) == 12, "InlineComponent structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + struct EnumValue { quint32_le nameIndex; @@ -654,50 +787,89 @@ static_assert(sizeof(Signal) == 12, "Signal structure needs to have the expected struct Property { - quint32_le nameIndex; - union { - quint32_le_bitfield<0, 28> builtinTypeOrTypeNameIndex; - quint32_le_bitfield<28, 1> isRequired; - quint32_le_bitfield<29, 1> isBuiltinType; - quint32_le_bitfield<30, 1> isList; - quint32_le_bitfield<31, 1> isReadOnly; - }; +private: + using CommonTypeOrTypeNameIndexField = quint32_le_bitfield_member<0, 28>; + using IsRequiredField = quint32_le_bitfield_member<28, 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< + CommonTypeOrTypeNameIndexField, + IsRequiredField, + IsCommonTypeField, + IsListField, + IsReadOnlyField> data; Location location; - void setBuiltinType(BuiltinType t) + void setCommonType(CommonType t) { - builtinTypeOrTypeNameIndex = static_cast<quint32>(t); - isBuiltinType = true; + data.set<CommonTypeOrTypeNameIndexField>(static_cast<quint32>(t)); + data.set<IsCommonTypeField>(true); } - BuiltinType builtinType() const { - if (isBuiltinType) - return static_cast<BuiltinType>(quint32(builtinTypeOrTypeNameIndex)); - 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<CommonTypeOrTypeNameIndexField>(nameIndex); + data.set<IsCommonTypeField>(false); + } + + int typeNameIndex() const { - builtinTypeOrTypeNameIndex = nameIndex; - isBuiltinType = false; + return data.get<IsCommonTypeField>() ? -1 : data.get<CommonTypeOrTypeNameIndexField>(); } + + 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); } + + bool isRequired() const { return data.get<IsRequiredField>(); } + void setIsRequired(bool isRequired) { data.set<IsRequiredField>(isRequired); } + + bool isReadOnly() const { return data.get<IsReadOnlyField>(); } + void setIsReadOnly(bool isReadOnly) { data.set<IsReadOnlyField>(isReadOnly); } }; static_assert(sizeof(Property) == 12, "Property structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +struct RequiredPropertyExtraData { + quint32_le nameIndex; +}; + +static_assert (sizeof(RequiredPropertyExtraData) == 4, "RequiredPropertyExtraData structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + struct Alias { - enum Flags : unsigned int { +private: + using NameIndexField = quint32_le_bitfield_member<0, 29>; + using FlagsField = quint32_le_bitfield_member<29, 3>; + + // object id index (in QQmlContextData::idValues) + using TargetObjectIdField = quint32_le_bitfield_member<0, 31>; + using AliasToLocalAliasField = quint32_le_bitfield_member<31, 1>; + using IdIndexField = quint32_le_bitfield_member<0, 32>; + +public: + + enum Flag : unsigned int { IsReadOnly = 0x1, Resolved = 0x2, AliasPointsToPointerObject = 0x4 }; - union { - quint32_le_bitfield<0, 29> nameIndex; - quint32_le_bitfield<29, 3> flags; - }; - union { - quint32_le idIndex; // string index - quint32_le_bitfield<0, 31> targetObjectId; // object id index (in QQmlContextData::idValues) - quint32_le_bitfield<31, 1> aliasToLocalAlias; - }; + Q_DECLARE_FLAGS(Flags, Flag) + + quint32_le_bitfield_union<NameIndexField, FlagsField> nameIndexAndFlags; + quint32_le_bitfield_union<IdIndexField, TargetObjectIdField, AliasToLocalAliasField> + idIndexAndTargetObjectIdAndAliasToLocalAlias; + union { quint32_le propertyNameIndex; // string index qint32_le encodedMetaPropertyIndex; @@ -706,32 +878,94 @@ struct Alias { Location location; Location referenceLocation; - bool isObjectAlias() const { - Q_ASSERT(flags & Resolved); + bool hasFlag(Flag flag) const + { + return nameIndexAndFlags.get<FlagsField>() & flag; + } + + void setFlag(Flag flag) + { + nameIndexAndFlags.set<FlagsField>(nameIndexAndFlags.get<FlagsField>() | flag); + } + + void clearFlags() + { + nameIndexAndFlags.set<FlagsField>(0); + } + + quint32 nameIndex() const + { + return nameIndexAndFlags.get<NameIndexField>(); + } + + void setNameIndex(quint32 nameIndex) + { + nameIndexAndFlags.set<NameIndexField>(nameIndex); + } + + bool isObjectAlias() const + { + Q_ASSERT(hasFlag(Resolved)); return encodedMetaPropertyIndex == -1; } + + quint32 idIndex() const + { + return idIndexAndTargetObjectIdAndAliasToLocalAlias.get<IdIndexField>(); + } + + void setIdIndex(quint32 idIndex) + { + idIndexAndTargetObjectIdAndAliasToLocalAlias.set<IdIndexField>(idIndex); + } + + + bool isAliasToLocalAlias() const + { + return idIndexAndTargetObjectIdAndAliasToLocalAlias.get<AliasToLocalAliasField>(); + } + + void setIsAliasToLocalAlias(bool isAliasToLocalAlias) + { + idIndexAndTargetObjectIdAndAliasToLocalAlias.set<AliasToLocalAliasField>(isAliasToLocalAlias); + } + + quint32 targetObjectId() const + { + return idIndexAndTargetObjectIdAndAliasToLocalAlias.get<TargetObjectIdField>(); + } + + void setTargetObjectId(quint32 targetObjectId) + { + idIndexAndTargetObjectIdAndAliasToLocalAlias.set<TargetObjectIdField>(targetObjectId); + } }; static_assert(sizeof(Alias) == 20, "Alias structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Object { - enum Flags : unsigned int { +private: + using FlagsField = quint32_le_bitfield_member<0, 15>; + using DefaultPropertyIsAliasField = quint32_le_bitfield_member<15, 1>; + using IdField = quint32_le_bitfield_member<16, 16, qint32>; +public: + enum Flag : unsigned int { NoFlag = 0x0, IsComponent = 0x1, // object was identified to be an explicit or implicit component boundary HasDeferredBindings = 0x2, // any of the bindings are deferred - HasCustomParserBindings = 0x4 + HasCustomParserBindings = 0x4, + IsInlineComponentRoot = 0x8, + IsPartOfInlineComponent = 0x10 }; + Q_DECLARE_FLAGS(Flags, Flag); // Depending on the use, this may be the type name to instantiate before instantiating this // object. For grouped properties the type name will be empty and for attached properties // it will be the name of the attached type. quint32_le inheritedTypeNameIndex; quint32_le idNameIndex; - union { - quint32_le_bitfield<0, 15> flags; - quint32_le_bitfield<15, 1> defaultPropertyIsAlias; - qint32_le_bitfield<16, 16> id; - }; + quint32_le_bitfield_union<FlagsField, DefaultPropertyIsAliasField, IdField> + flagsAndDefaultPropertyIsAliasAndId; qint32_le indexOfDefaultPropertyOrAlias; // -1 means no default property declared in this object quint16_le nFunctions; quint16_le nProperties; @@ -749,12 +983,60 @@ struct Object quint32_le offsetToNamedObjectsInComponent; Location location; Location locationOfIdProperty; + quint32_le offsetToInlineComponents; + quint16_le nInlineComponents; + quint32_le offsetToRequiredPropertyExtraData; + quint16_le nRequiredPropertyExtraData; // Function[] // Property[] // Signal[] // Binding[] +// InlineComponent[] +// RequiredPropertyExtraData[] + + Flags flags() const + { + return Flags(flagsAndDefaultPropertyIsAliasAndId.get<FlagsField>()); + } + + bool hasFlag(Flag flag) const + { + return flagsAndDefaultPropertyIsAliasAndId.get<FlagsField>() & flag; + } + + void setFlag(Flag flag) + { + flagsAndDefaultPropertyIsAliasAndId.set<FlagsField>( + flagsAndDefaultPropertyIsAliasAndId.get<FlagsField>() | flag); + } + + void setFlags(Flags flags) + { + flagsAndDefaultPropertyIsAliasAndId.set<FlagsField>(flags); + } + + bool hasAliasAsDefaultProperty() const + { + return flagsAndDefaultPropertyIsAliasAndId.get<DefaultPropertyIsAliasField>(); + } + + void setHasAliasAsDefaultProperty(bool defaultAlias) + { + flagsAndDefaultPropertyIsAliasAndId.set<DefaultPropertyIsAliasField>(defaultAlias); + } - static int calculateSizeExcludingSignalsAndEnums(int nFunctions, int nProperties, int nAliases, int nEnums, int nSignals, int nBindings, int nNamedObjectsInComponent) + qint32 objectId() const + { + return flagsAndDefaultPropertyIsAliasAndId.get<IdField>(); + } + + void setObjectId(qint32 id) + { + flagsAndDefaultPropertyIsAliasAndId.set<IdField>(id); + } + + + static int calculateSizeExcludingSignalsAndEnums(int nFunctions, int nProperties, int nAliases, int nEnums, int nSignals, int nBindings, int nNamedObjectsInComponent, int nInlineComponents, int nRequiredPropertyExtraData) { return ( sizeof(Object) + nFunctions * sizeof(quint32) @@ -764,6 +1046,8 @@ struct Object + nSignals * sizeof(quint32) + nBindings * sizeof(Binding) + nNamedObjectsInComponent * sizeof(int) + + nInlineComponents * sizeof(InlineComponent) + + nRequiredPropertyExtraData * sizeof(RequiredPropertyExtraData) + 0x7 ) & ~0x7; } @@ -802,11 +1086,31 @@ struct Object return reinterpret_cast<const Signal*>(reinterpret_cast<const char*>(this) + offset); } + const InlineComponent *inlineComponentAt(int idx) const + { + return inlineComponentTable() + idx; + } + const quint32_le *namedObjectsInComponentTable() const { return reinterpret_cast<const quint32_le*>(reinterpret_cast<const char *>(this) + offsetToNamedObjectsInComponent); } + const InlineComponent *inlineComponentTable() const + { + return reinterpret_cast<const InlineComponent*>(reinterpret_cast<const char *>(this) + offsetToInlineComponents); + } + + const RequiredPropertyExtraData *requiredPropertyExtraDataAt(int idx) const + { + return requiredPropertyExtraDataTable() + idx; + } + + const RequiredPropertyExtraData *requiredPropertyExtraDataTable() const + { + return reinterpret_cast<const RequiredPropertyExtraData*>(reinterpret_cast<const char *>(this) + offsetToRequiredPropertyExtraData); + } + // --- QQmlPropertyCacheCreator interface int propertyCount() const { return nProperties; } int aliasCount() const { return nAliases; } @@ -816,6 +1120,7 @@ struct Object 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; } @@ -831,31 +1136,42 @@ struct Object SignalIterator signalsBegin() const { return SignalIterator(this, 0); } SignalIterator signalsEnd() const { return SignalIterator(this, nSignals); } + typedef TableIterator<InlineComponent, Object, &Object::inlineComponentAt> InlineComponentIterator; + InlineComponentIterator inlineComponentsBegin() const {return InlineComponentIterator(this, 0);} + InlineComponentIterator inlineComponentsEnd() const {return InlineComponentIterator(this, nInlineComponents);} + + typedef TableIterator<RequiredPropertyExtraData, Object, &Object::requiredPropertyExtraDataAt> RequiredPropertyExtraDataIterator; + RequiredPropertyExtraDataIterator requiredPropertyExtraDataBegin() const {return RequiredPropertyExtraDataIterator(this, 0); } + RequiredPropertyExtraDataIterator requiredPropertyExtraDataEnd() const {return RequiredPropertyExtraDataIterator(this, nRequiredPropertyExtraData); } + int namedObjectsInComponentCount() const { return nNamedObjectsInComponent; } // --- }; -static_assert(sizeof(Object) == 68, "Object structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +static_assert(sizeof(Object) == 84, "Object structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Import { enum ImportType : unsigned int { ImportLibrary = 0x1, ImportFile = 0x2, - ImportScript = 0x3 + ImportScript = 0x3, + ImportInlineComponent = 0x4 }; quint32_le type; quint32_le uriIndex; quint32_le qualifierIndex; - qint32_le majorVersion; - qint32_le minorVersion; - Location location; + QTypeRevision version; + quint16_le reserved; - Import() { type = 0; uriIndex = 0; qualifierIndex = 0; majorVersion = 0; minorVersion = 0; } + Import() + { + type = 0; uriIndex = 0; qualifierIndex = 0; version = QTypeRevision::zero(); reserved = 0; + } }; -static_assert(sizeof(Import) == 24, "Import structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +static_assert(sizeof(Import) == 20, "Import structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct QmlUnit { @@ -900,7 +1216,18 @@ struct Unit IsSingleton = 0x4, IsSharedLibrary = 0x8, // .pragma shared? IsESModule = 0x10, - PendingTypeCompilation = 0x20 // the QML data structures present are incomplete and require type compilation + PendingTypeCompilation = 0x20, // the QML data structures present are incomplete and require type compilation + IsStrict = 0x40, + ListPropertyAssignReplaceIfDefault = 0x80, + ListPropertyAssignReplaceIfNotDefault = 0x100, + ListPropertyAssignReplace + = ListPropertyAssignReplaceIfDefault | ListPropertyAssignReplaceIfNotDefault, + ComponentsBound = 0x200, + FunctionSignaturesIgnored = 0x400, + NativeMethodsAcceptThisObject = 0x800, + ValueTypesCopied = 0x1000, + ValueTypesAddressable = 0x2000, + ValueTypesAssertable = 0x4000, }; quint32_le flags; quint32_le stringTableSize; @@ -954,19 +1281,18 @@ 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); + Q_ASSERT(str->size >= 0); if (str->size == 0) return QString(); #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - if (flags & StaticData) { - const QStringDataPtr holder = { const_cast<QStringData *>(reinterpret_cast<const QStringData*>(str)) }; - return QString(holder); - } const QChar *characters = reinterpret_cast<const QChar *>(str + 1); + if (flags & StaticData) + return QString::fromRawData(characters, str->size); return QString(characters, str->size); #else const quint16_le *characters = reinterpret_cast<const quint16_le *>(str + 1); @@ -1028,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"); @@ -1070,20 +1412,26 @@ struct TypeReferenceMap : QHash<int, TypeReference> } auto prop = obj->propertiesBegin(); - auto propEnd = obj->propertiesEnd(); + 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; } } auto binding = obj->bindingsBegin(); - auto bindingEnd = obj->bindingsEnd(); + auto const bindingEnd = obj->bindingsEnd(); for ( ; binding != bindingEnd; ++binding) { - if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) + if (binding->type() == QV4::CompiledData::Binding::Type_AttachedProperty) this->add(binding->propertyNameIndex, binding->location); } + + auto ic = obj->inlineComponentsBegin(); + auto const icEnd = obj->inlineComponentsEnd(); + for (; ic != icEnd; ++ic) { + this->add(ic->nameIndex, ic->location); + } } template <typename Iterator> @@ -1096,110 +1444,93 @@ 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; -public: - using CompiledObject = CompiledData::Object; + const QQmlPrivate::AOTCompiledFunction *aotCompiledFunctions = nullptr; - CompilationUnit(const Unit *unitData = nullptr, const QString &fileName = QString(), - const QString &finalUrlString = QString()) - { - setUnitData(unitData, nullptr, fileName, finalUrlString); - } + // pointers either to data->constants() or little-endian memory copy. + const StaticValue *constants = nullptr; - ~CompilationUnit() - { - if (data) { - if (data->qmlUnit() != qmlData) - free(const_cast<QmlUnit *>(qmlData)); - qmlData = nullptr; + std::unique_ptr<CompilationUnitMapper> backingFile; - 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 + 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 - delete [] imports; - imports = nullptr; - } + 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; - CompilationUnit(CompilationUnit &&other) noexcept + QQmlType qmlType; + + QVector<QQmlRefPointer<QQmlScriptData>> dependentScripts; + +public: + // --- interface for QQmlPropertyCacheCreator + using CompiledObject = const CompiledData::Object; + using CompiledFunction = const CompiledData::Function; + using CompiledBinding = const CompiledData::Binding; + + // Empty dummy. We don't need to do this when loading from cache. + class IdToObjectMap { - *this = std::move(other); - } + public: + void insert(int, int) {} + void clear() {} - CompilationUnit &operator=(CompilationUnit &&other) noexcept + // 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) { - if (this != &other) { - data = other.data; - other.data = nullptr; - qmlData = other.qmlData; - other.qmlData = nullptr; - dynamicStrings = std::move(other.dynamicStrings); - 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; + this->aotCompiledFunctions = aotCompiledFunctions; } + Q_QML_EXPORT CompilationUnit( + const Unit *unitData = nullptr, const QString &fileName = QString(), + const QString &finalUrlString = QString()); + + Q_QML_EXPORT ~CompilationUnit(); + const Unit *unitData() const { return data; } void setUnitData(const Unit *unitData, const QmlUnit *qmlUnit = nullptr, @@ -1232,24 +1563,207 @@ 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; + switch (binding->type()) { + case Binding::Type_Script: + case Binding::Type_String: + return stringAt(binding->stringIndex); + case Binding::Type_Null: + return QStringLiteral("null"); + case Binding::Type_Boolean: + return binding->value.b ? QStringLiteral("true") : QStringLiteral("false"); + case Binding::Type_Number: + return QString::number(bindingValueAsNumber(binding), 'g', QLocale::FloatingPointShortest); + case Binding::Type_Invalid: + return QString(); + case Binding::Type_TranslationById: + case Binding::Type_Translation: + return stringAt(data->translations()[binding->value.translationDataIndex].stringIndex); + default: + break; + } + return QString(); + } + + QString bindingValueAsScriptString(const CompiledData::Binding *binding) const + { + return (binding->type() == CompiledData::Binding::Type_String) + ? CompiledData::Binding::escapedString(stringAt(binding->stringIndex)) + : bindingValueAsString(binding); + } + + double bindingValueAsNumber(const CompiledData::Binding *binding) const + { + if (binding->type() != CompiledData::Binding::Type_Number) + return 0.0; + 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 @@ -1267,7 +1781,8 @@ public: template<typename Char> bool saveToDisk(const std::function<bool(const Char *, quint32)> &writer) const { - auto cleanup = qScopeGuard([this]() { mutableFlags() ^= temporaryFlags; }); + const quint32_le oldFlags = mutableFlags(); + auto cleanup = qScopeGuard([this, oldFlags]() { mutableFlags() = oldFlags; }); mutableFlags() |= temporaryFlags; return writer(data<Char>(), size()); } @@ -1287,7 +1802,7 @@ public: errorString->clear(); return true; #else - Q_UNUSED(outputFileName) + Q_UNUSED(outputFileName); *errorString = QStringLiteral("features.temporaryfile is disabled."); return false; #endif @@ -1321,6 +1836,7 @@ private: } // CompiledData namespace } // QV4 namespace +Q_DECLARE_OPERATORS_FOR_FLAGS(QV4::CompiledData::ParameterType::Flags); Q_DECLARE_TYPEINFO(QV4::CompiledData::JSClassMember, Q_PRIMITIVE_TYPE); QT_END_NAMESPACE diff --git a/src/qml/common/qv4staticvalue_p.h b/src/qml/common/qv4staticvalue_p.h index 0716f7ea20..9b4f582c00 100644 --- a/src/qml/common/qv4staticvalue_p.h +++ b/src/qml/common/qv4staticvalue_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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$ -** -****************************************************************************/ +// Copyright (C) 2019 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 QV4STATICVALUE_P_H #define QV4STATICVALUE_P_H @@ -50,7 +14,11 @@ // We mean it. // +#include <qjsnumbercoercion.h> + #include <QtCore/private/qnumeric_p.h> +#include <private/qtqmlglobal_p.h> + #include <cstring> #ifdef QT_NO_DEBUG @@ -69,55 +37,14 @@ namespace QV4 { // It will be returned in rax on x64, [eax,edx] on x86 and [r0,r1] on arm typedef quint64 ReturnedValue; -struct Double { - quint64 d; - - Double(double dbl) { - memcpy(&d, &dbl, sizeof(double)); - } - - int sign() const { - return (d >> 63) ? -1 : 1; - } - - bool isDenormal() const { - return static_cast<int>((d << 1) >> 53) == 0; - } - - int exponent() const { - return static_cast<int>((d << 1) >> 53) - 1023; - } - - quint64 significant() const { - quint64 m = (d << 12) >> 12; - if (!isDenormal()) - m |= (static_cast<quint64>(1) << 52); - return m; - } - - static int toInt32(double d) { - int i = static_cast<int>(d); - if (i == d) - return i; - return Double(d).toInt32(); - } - - int toInt32() { - int e = exponent() - 52; - if (e < 0) { - if (e <= -53) - return 0; - return sign() * static_cast<int>(significant() >> -e); - } else { - if (e > 31) - return 0; - return sign() * (static_cast<int>(significant()) << e); - } - } -}; +namespace Heap { +struct Base; +} struct StaticValue { + using HeapBasePtr = Heap::Base *; + StaticValue() = default; constexpr StaticValue(quint64 val) : _val(val) {} @@ -137,21 +64,31 @@ struct StaticValue Value &asValue(); /* - We use 8 bytes for a value and a different variant of NaN boxing. A Double - NaN (actually -qNaN) is indicated by a number that has the top 13 bits set, and for a - signalling NaN it is the top 14 bits. The other values are usually set to 0 by the - processor, and are thus free for us to store other data. We keep pointers in there for - managed objects, and encode the other types using the free space given to use by the unused - bits for NaN values. This also works for pointers on 64 bit systems, as they all currently - only have 48 bits of addressable memory. (Note: we do leave the lower 49 bits available for - pointers.) - - We xor Doubles with (0xffff8000 << 32). That has the effect that no doubles will - get encoded with bits 63-49 all set to 0. We then use bit 48 to distinguish between - managed/undefined (0), or Null/Int/Bool/Empty (1). So, storing a 49 bit pointer will leave - the top 15 bits 0, which is exactly the 'natural' representation of pointers. If bit 49 is - set, bit 48 indicates Empty (0) or integer-convertible (1). Then the 3 bit below that are - used to encode Null/Int/Bool. + We use 8 bytes for a value. In order to store all possible values we employ a variant of NaN + boxing. A "special" Double is indicated by a number that has the 11 exponent bits set to 1. + Those can be NaN, positive or negative infinity. We only store one variant of NaN: The sign + bit has to be off and the bit after the exponent ("quiet bit") has to be on. However, since + the exponent bits are enough to identify special doubles, we can use a different bit as + discriminator to tell us how the rest of the bits (including quiet and sign) are to be + interpreted. This bit is bit 48. If set, we have an unmanaged value, which includes the + special doubles and various other values. If unset, we have a managed value, and all of the + other bits can be used to assemble a pointer. + + On 32bit systems the pointer can just live in the lower 4 bytes. On 64 bit systems the lower + 48 bits can be used for verbatim pointer bits. However, since all our heap objects are + aligned to 32 bytes, we can use the 5 least significant bits of the pointer to store, e.g. + pointer tags on android. The same holds for the 3 bits between the double exponent and + bit 48. + + With that out of the way, we can use the other bits to store different values. + + We xor Doubles with (0x7ff48000 << 32). That has the effect that any double with all the + exponent bits set to 0 is one of our special doubles. Those special doubles then get the + other two bits in the mask (Special and Number) set to 1, as they cannot have 1s in those + positions to begin with. + + We dedicate further bits to integer-convertible and bool-or-int. With those bits we can + describe all values we need to store. Undefined is encoded as a managed pointer with value 0. This is the same as a nullptr. @@ -159,41 +96,38 @@ struct StaticValue 0 = always 0 1 = always 1 x = stored value - a,b,c,d = specific bit values, see notes + y = stored value, shifted to different position + a = xor-ed bits, where at least one bit is set + b = xor-ed bits 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210 | 66665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000 | JS Value ------------------------------------------------------------------------+-------------- 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 | Undefined - 00000000 0000000x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Managed (heap pointer) - a0000000 0000bc00 00000000 00000000 00000000 00000000 00000000 00000000 | NaN/Inf - dddddddd ddddddxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | double - 00000000 00000010 00000000 00000000 00000000 00000000 00000000 00000000 | empty (non-sparse array hole) - 00000000 00000010 10000000 00000000 00000000 00000000 00000000 00000000 | Null - 00000000 00000011 00000000 00000000 00000000 00000000 00000000 0000000x | Bool - 00000000 00000011 10000000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int - - Notes: - - a: xor-ed signbit, always 1 for NaN - - bc, xor-ed values: 11 = inf, 10 = sNaN, 01 = qNaN, 00 = boxed value - - d: xor-ed bits, where at least one bit is set, so: (val >> (64-14)) > 0 - - Undefined maps to C++ nullptr, so the "default" initialization is the same for both C++ - and JS - - Managed has the left 15 bits set to 0, so: (val >> (64-15)) == 0 - - empty, Null, Bool, and Int have the left 14 bits set to 0, and bit 49 set to 1, - so: (val >> (64-15)) == 1 - - Null, Bool, and Int have bit 48 set, indicating integer-convertible - - xoring _val with NaNEncodeMask will convert to a double in "natural" representation, where - any non double results in a NaN - - on 32bit we can use the fact that addresses are 32bits wide, so the tag part (bits 32 to - 63) are zero. No need to shift. + y0000000 0000yyy0 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxyyyyy | Managed (heap pointer) + 00000000 00001101 10000000 00000000 00000000 00000000 00000000 00000000 | NaN + 00000000 00000101 10000000 00000000 00000000 00000000 00000000 00000000 | +Inf + 10000000 00000101 10000000 00000000 00000000 00000000 00000000 00000000 | -Inf + xaaaaaaa aaaaxbxb bxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | double + 00000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000 | empty (non-sparse array hole) + 00000000 00000011 00000000 00000000 00000000 00000000 00000000 00000000 | Null + 00000000 00000011 10000000 00000000 00000000 00000000 00000000 0000000x | Bool + 00000000 00000011 11000000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int + ^ ^^^ ^^ + | ||| || + | ||| |+-> Number + | ||| +--> Int or Bool + | ||+----> Unmanaged + | |+-----> Integer compatible + | +------> Special double + +--------------------> Double sign, also used for special doubles */ quint64 _val; - QV4_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR quint64 &rawValueRef() { return _val; } - QV4_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR quint64 rawValue() const { return _val; } - QV4_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setRawValue(quint64 raw) { _val = raw; } + QV4_NEARLY_ALWAYS_INLINE constexpr quint64 &rawValueRef() { return _val; } + QV4_NEARLY_ALWAYS_INLINE constexpr quint64 rawValue() const { return _val; } + QV4_NEARLY_ALWAYS_INLINE constexpr void setRawValue(quint64 raw) { _val = raw; } #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN static inline int valueOffset() { return 0; } @@ -202,108 +136,145 @@ struct StaticValue static inline int valueOffset() { return 4; } static inline int tagOffset() { return 0; } #endif - static inline constexpr quint64 tagValue(quint32 tag, quint32 value) { return quint64(tag) << 32 | value; } - QV4_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setTagValue(quint32 tag, quint32 value) { _val = quint64(tag) << 32 | value; } + static inline constexpr quint64 tagValue(quint32 tag, quint32 value) { return quint64(tag) << Tag_Shift | value; } + QV4_NEARLY_ALWAYS_INLINE constexpr void setTagValue(quint32 tag, quint32 value) { _val = quint64(tag) << Tag_Shift | value; } QV4_NEARLY_ALWAYS_INLINE constexpr quint32 value() const { return _val & quint64(~quint32(0)); } - QV4_NEARLY_ALWAYS_INLINE constexpr quint32 tag() const { return _val >> 32; } - QV4_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setTag(quint32 tag) { setTagValue(tag, value()); } + QV4_NEARLY_ALWAYS_INLINE constexpr quint32 tag() const { return _val >> Tag_Shift; } + QV4_NEARLY_ALWAYS_INLINE constexpr void setTag(quint32 tag) { setTagValue(tag, value()); } QV4_NEARLY_ALWAYS_INLINE constexpr int int_32() const { return int(value()); } - QV4_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setInt_32(int i) + QV4_NEARLY_ALWAYS_INLINE constexpr void setInt_32(int i) { - setTagValue(quint32(ValueTypeInternal::Integer), quint32(i)); + setTagValue(quint32(QuickType::Integer), quint32(i)); } QV4_NEARLY_ALWAYS_INLINE uint uint_32() const { return value(); } - QV4_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setEmpty() + QV4_NEARLY_ALWAYS_INLINE constexpr void setEmpty() { - setTagValue(quint32(ValueTypeInternal::Empty), 0); - } - - // ### Fix for 32 bit (easiest solution is to set highest bit to 1 for mananged/undefined/integercompatible - // and use negative numbers here - enum QuickType { - QT_ManagedOrUndefined = 0, - QT_ManagedOrUndefined1 = 1, - QT_ManagedOrUndefined2 = 2, - QT_ManagedOrUndefined3 = 3, - QT_Empty = 4, - QT_Null = 5, - QT_Bool = 6, - QT_Int = 7 - // all other values are doubles - }; - - enum Type { - Undefined_Type = 0, - Managed_Type = 1, - Empty_Type = 4, - Null_Type = 5, - Boolean_Type = 6, - Integer_Type = 7, - Double_Type = 8 + setTagValue(quint32(QuickType::Empty), 0); + } + + enum class TagBit { + // s: sign bit + // e: double exponent bit + // u: upper 3 bits if managed + // m: bit 48, denotes "unmanaged" if 1 + // p: significant pointer bits (some re-used for non-managed) + // seeeeeeeeeeeuuumpppp + SpecialNegative = 0b10000000000000000000 << 12, + SpecialQNaN = 0b00000000000010000000 << 12, + Special = 0b00000000000001000000 << 12, + IntCompat = 0b00000000000000100000 << 12, + Unmanaged = 0b00000000000000010000 << 12, + IntOrBool = 0b00000000000000001000 << 12, + Number = 0b00000000000000000100 << 12, }; - inline Type type() const { - int t = quickType(); - if (t < QT_Empty) - return _val ? Managed_Type : Undefined_Type; - if (t > QT_Int) - return Double_Type; - return static_cast<Type>(t); - } + static inline constexpr quint64 tagBitMask(TagBit bit) { return quint64(bit) << Tag_Shift; } - // Shared between 32-bit and 64-bit encoding - enum { - Tag_Shift = 32 + enum Type { + // Managed, Double and undefined are not directly encoded + Managed_Type = 0, + Double_Type = 1, + Undefined_Type = 2, + + Empty_Type = quint32(TagBit::Unmanaged), + Null_Type = Empty_Type | quint32(TagBit::IntCompat), + Boolean_Type = Null_Type | quint32(TagBit::IntOrBool), + Integer_Type = Boolean_Type | quint32(TagBit::Number) }; - // Used only by 64-bit encoding - static const quint64 NaNEncodeMask = 0xfffc000000000000ull; enum { - IsDouble_Shift = 64-14, - IsManagedOrUndefined_Shift = 64-15, - IsIntegerConvertible_Shift = 64-15, - IsIntegerOrBool_Shift = 64-16, - QuickType_Shift = 64 - 17, - IsPositiveIntShift = 31 - }; - - static const quint64 Immediate_Mask_64 = 0x00020000u; // bit 49 + Tag_Shift = 32, - enum class ValueTypeInternal_64 { - Empty = Immediate_Mask_64 | 0, - Null = Immediate_Mask_64 | 0x08000u, - Boolean = Immediate_Mask_64 | 0x10000u, - Integer = Immediate_Mask_64 | 0x18000u - }; + IsIntegerConvertible_Shift = 48, + IsIntegerConvertible_Value = 3, // Unmanaged | IntCompat after shifting - // Used only by 32-bit encoding - enum Masks { - SilentNaNBit = 0x00040000, - NotDouble_Mask = 0x7ffa0000, + IsIntegerOrBool_Shift = 47, + IsIntegerOrBool_Value = 7, // Unmanaged | IntCompat | IntOrBool after shifting }; - static const quint64 Immediate_Mask_32 = NotDouble_Mask | 0x00020000u | SilentNaNBit; - enum class ValueTypeInternal_32 { - Empty = Immediate_Mask_32 | 0, - Null = Immediate_Mask_32 | 0x08000u, - Boolean = Immediate_Mask_32 | 0x10000u, - Integer = Immediate_Mask_32 | 0x18000u + static_assert(IsIntegerConvertible_Value == + (quint32(TagBit::IntCompat) | quint32(TagBit::Unmanaged)) + >> (IsIntegerConvertible_Shift - Tag_Shift)); + + static_assert(IsIntegerOrBool_Value == + (quint32(TagBit::IntOrBool) | quint32(TagBit::IntCompat) | quint32(TagBit::Unmanaged)) + >> (IsIntegerOrBool_Shift - Tag_Shift)); + + static constexpr quint64 ExponentMask = 0b0111111111110000ull << 48; + + static constexpr quint64 Top1Mask = 0b1000000000000000ull << 48; + static constexpr quint64 Upper3Mask = 0b0000000000001110ull << 48; + static constexpr quint64 Lower5Mask = 0b0000000000011111ull; + + static constexpr quint64 ManagedMask = ExponentMask | quint64(TagBit::Unmanaged) << Tag_Shift; + static constexpr quint64 DoubleMask = ManagedMask | quint64(TagBit::Special) << Tag_Shift; + static constexpr quint64 NumberMask = ManagedMask | quint64(TagBit::Number) << Tag_Shift; + static constexpr quint64 IntOrBoolMask = ManagedMask | quint64(TagBit::IntOrBool) << Tag_Shift; + static constexpr quint64 IntCompatMask = ManagedMask | quint64(TagBit::IntCompat) << Tag_Shift; + + static constexpr quint64 EncodeMask = DoubleMask | NumberMask; + + static constexpr quint64 DoubleDiscriminator + = ((quint64(TagBit::Unmanaged) | quint64(TagBit::Special)) << Tag_Shift); + static constexpr quint64 NumberDiscriminator + = ((quint64(TagBit::Unmanaged) | quint64(TagBit::Number)) << Tag_Shift); + + // Things we can immediately determine by just looking at the upper 4 bytes. + enum class QuickType : quint32 { + // Managed takes precedence over all others. That is, other bits may be set if it's managed. + // However, since all others include the Unmanaged bit, we can still check them with simple + // equality operations. + Managed = Managed_Type, + + Empty = Empty_Type, + Null = Null_Type, + Boolean = Boolean_Type, + Integer = Integer_Type, + + PlusInf = quint32(TagBit::Number) | quint32(TagBit::Special) | quint32(TagBit::Unmanaged), + MinusInf = PlusInf | quint32(TagBit::SpecialNegative), + NaN = PlusInf | quint32(TagBit::SpecialQNaN), + MinusNaN = NaN | quint32(TagBit::SpecialNegative), // Can happen with UMinus on NaN + // All other values are doubles }; + // Aliases for easier porting. Remove those when possible + using ValueTypeInternal = QuickType; enum { - Managed_Type_Internal = 0 + QT_Empty = Empty_Type, + QT_Null = Null_Type, + QT_Bool = Boolean_Type, + QT_Int = Integer_Type, + QuickType_Shift = Tag_Shift, }; - using ValueTypeInternal = ValueTypeInternal_64; + inline Type type() const + { + const quint64 masked = _val & DoubleMask; + if (masked >= DoubleDiscriminator) + return Double_Type; - enum { - NaN_Mask = 0x7ff80000, - }; + // Any bit set in the exponent would have been caught above, as well as both bits being set. + // None of them being set as well as only Special being set means "managed". + // Only Unmanaged being set means "unmanaged". That's all remaining options. + if (masked != tagBitMask(TagBit::Unmanaged)) { + Q_ASSERT((_val & tagBitMask(TagBit::Unmanaged)) == 0); + return isUndefined() ? Undefined_Type : Managed_Type; + } + + const Type ret = Type(tag()); + Q_ASSERT( + ret == Empty_Type || + ret == Null_Type || + ret == Boolean_Type || + ret == Integer_Type); + return ret; + } inline quint64 quickType() const { return (_val >> QuickType_Shift); } @@ -313,34 +284,52 @@ struct StaticValue inline bool isBoolean() const { return tag() == quint32(ValueTypeInternal::Boolean); } inline bool isInteger() const { return tag() == quint32(ValueTypeInternal::Integer); } inline bool isNullOrUndefined() const { return isNull() || isUndefined(); } - inline bool isNumber() const { return quickType() >= QT_Int; } - inline bool isUndefined() const { return _val == 0; } - inline bool isDouble() const { return (_val >> IsDouble_Shift); } - inline bool isManaged() const + + inline bool isDouble() const { -#if QT_POINTER_SIZE == 4 - return value() && tag() == Managed_Type_Internal; -#else - return _val && ((_val >> IsManagedOrUndefined_Shift) == 0); -#endif + // If any of the flipped exponent bits are 1, it's a regular double, and the masked tag is + // larger than Unmanaged | Special. + // + // If all (flipped) exponent bits are 0: + // 1. If Unmanaged bit is 0, it's managed + // 2. If the Unmanaged bit it is 1, and the Special bit is 0, it's not a special double + // 3. If both are 1, it is a special double and the masked tag equals Unmanaged | Special. + + return (_val & DoubleMask) >= DoubleDiscriminator; } - inline bool isManagedOrUndefined() const + + inline bool isNumber() const { -#if QT_POINTER_SIZE == 4 - return tag() == Managed_Type_Internal; -#else - return ((_val >> IsManagedOrUndefined_Shift) == 0); -#endif + // If any of the flipped exponent bits are 1, it's a regular double, and the masked tag is + // larger than Unmanaged | Number. + // + // If all (flipped) exponent bits are 0: + // 1. If Unmanaged bit is 0, it's managed + // 2. If the Unmanaged bit it is 1, and the Number bit is 0, it's not number + // 3. If both are 1, it is a number and masked tag equals Unmanaged | Number. + + return (_val & NumberMask) >= NumberDiscriminator; } - inline bool isIntOrBool() const { - return (_val >> IsIntegerOrBool_Shift) == 3; + inline bool isManagedOrUndefined() const { return (_val & ManagedMask) == 0; } + + // If any other bit is set in addition to the managed mask, it's not undefined. + inline bool isManaged() const + { + return isManagedOrUndefined() && !isUndefined(); + } + + inline bool isIntOrBool() const + { + // It's an int or bool if all the exponent bits are 0, + // and the "int or bool" bit as well as the "umanaged" bit are set, + return (_val >> IsIntegerOrBool_Shift) == IsIntegerOrBool_Value; } inline bool integerCompatible() const { Q_ASSERT(!isEmpty()); - return (_val >> IsIntegerConvertible_Shift) == 1; + return (_val >> IsIntegerConvertible_Shift) == IsIntegerConvertible_Value; } static inline bool integerCompatible(StaticValue a, StaticValue b) { @@ -353,36 +342,45 @@ struct StaticValue inline bool isNaN() const { - return (tag() & 0x7ffc0000 ) == 0x00040000; + switch (QuickType(tag())) { + case QuickType::NaN: + case QuickType::MinusNaN: + return true; + default: + return false; + } } inline bool isPositiveInt() const { -#if QT_POINTER_SIZE == 4 return isInteger() && int_32() >= 0; -#else - return (_val >> IsPositiveIntShift) == (quint64(ValueTypeInternal::Integer) << 1); -#endif } QV4_NEARLY_ALWAYS_INLINE double doubleValue() const { Q_ASSERT(isDouble()); double d; - StaticValue v = *this; - v._val ^= NaNEncodeMask; - memcpy(&d, &v._val, 8); + const quint64 unmasked = _val ^ EncodeMask; + memcpy(&d, &unmasked, 8); return d; } QV4_NEARLY_ALWAYS_INLINE void setDouble(double d) { - if (qt_is_nan(d)) - d = qt_qnan(); - memcpy(&_val, &d, 8); - _val ^= NaNEncodeMask; + 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 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(std::signbit(d) ? QuickType::MinusNaN : QuickType::NaN) << Tag_Shift); + Q_ASSERT(isNaN()); + } else { + memcpy(&_val, &d, 8); + _val ^= EncodeMask; + } + Q_ASSERT(isDouble()); } inline bool isInt32() { - if (tag() == quint32(ValueTypeInternal::Integer)) + if (tag() == quint32(QuickType::Integer)) return true; if (isDouble()) { double d = doubleValue(); @@ -395,12 +393,12 @@ 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))); } double asDouble() const { - if (tag() == quint32(ValueTypeInternal::Integer)) + if (tag() == quint32(QuickType::Integer)) return int_32(); return doubleValue(); } @@ -416,7 +414,7 @@ struct StaticValue inline bool tryIntegerConversion() { bool b = integerCompatible(); if (b) - setTagValue(quint32(ValueTypeInternal::Integer), value()); + setTagValue(quint32(QuickType::Integer), value()); return b; } @@ -440,24 +438,25 @@ struct StaticValue case Integer_Type: return int_32(); case Double_Type: - return Double::toInt32(doubleValue()); + return QJSNumberCoercion::toInteger(doubleValue()); case Empty_Type: case Undefined_Type: case Managed_Type: - break; + return 0; // Coercion of NaN to int, results in 0; } - return Double::toInt32(std::numeric_limits<double>::quiet_NaN()); + + Q_UNREACHABLE_RETURN(0); } ReturnedValue *data_ptr() { return &_val; } constexpr ReturnedValue asReturnedValue() const { return _val; } constexpr static StaticValue fromReturnedValue(ReturnedValue val) { return {val}; } - inline static constexpr StaticValue emptyValue() { return { tagValue(quint32(ValueTypeInternal::Empty), 0) }; } - static inline constexpr StaticValue fromBoolean(bool b) { return { tagValue(quint32(ValueTypeInternal::Boolean), b) }; } - static inline constexpr StaticValue fromInt32(int i) { return { tagValue(quint32(ValueTypeInternal::Integer), quint32(i)) }; } + inline static constexpr StaticValue emptyValue() { return { tagValue(quint32(QuickType::Empty), 0) }; } + static inline constexpr StaticValue fromBoolean(bool b) { return { tagValue(quint32(QuickType::Boolean), b) }; } + static inline constexpr StaticValue fromInt32(int i) { return { tagValue(quint32(QuickType::Integer), quint32(i)) }; } inline static constexpr StaticValue undefinedValue() { return { 0 }; } - static inline constexpr StaticValue nullValue() { return { tagValue(quint32(ValueTypeInternal::Null), 0) }; } + static inline constexpr StaticValue nullValue() { return { tagValue(quint32(QuickType::Null), 0) }; } static inline StaticValue fromDouble(double d) { @@ -470,7 +469,7 @@ struct StaticValue { StaticValue v; if (i < uint(std::numeric_limits<int>::max())) { - v.setTagValue(quint32(ValueTypeInternal::Integer), i); + v.setTagValue(quint32(QuickType::Integer), i); } else { v.setDouble(i); } @@ -488,15 +487,140 @@ struct StaticValue static int toInt32(double d) { - return Double::toInt32(d); + return QJSNumberCoercion::toInteger(d); } static unsigned int toUInt32(double d) { return static_cast<uint>(toInt32(d)); } + + // While a value containing a Heap::Base* is not actually static, we still implement + // the setting and retrieving of heap pointers here in order to have the encoding + // scheme completely in one place. + +#if QT_POINTER_SIZE == 8 + + // All pointer shifts are from more significant to less significant bits. + // When encoding, we shift right by that amount. When decoding, we shift left. + // Negative numbers mean shifting the other direction. 0 means no shifting. + // + // The IA64 and Sparc64 cases are mostly there to demonstrate the idea. Sparc64 + // and IA64 are not officially supported, but we can expect more platforms with + // similar "problems" in the future. + enum PointerShift { +#if 0 && defined(Q_OS_ANDROID) && defined(Q_PROCESSOR_ARM_64) + // We used to assume that Android on arm64 uses the top byte to store pointer tags. + // However, at least currently, the pointer tags are only applied on new/malloc and + // delete/free, not on mmap() and munmap(). We manage the JS heap directly using + // mmap, so we don't have to preserve any tags. + // + // If this ever changes, here is how to preserve the top byte: + // Move it to Upper3 and Lower5. + Top1Shift = 0, + Upper3Shift = 12, + Lower5Shift = 56, +#elif defined(Q_PROCESSOR_IA64) + // On ia64, bits 63-61 in a 64-bit pointer are used to store the virtual region + // number. We can move those to Upper3. + Top1Shift = 0, + Upper3Shift = 12, + Lower5Shift = 0, +#elif defined(Q_PROCESSOR_SPARC_64) + // Sparc64 wants to use 52 bits for pointers. + // Upper3 can stay where it is, bit48 moves to the top bit. + Top1Shift = -15, + Upper3Shift = 0, + Lower5Shift = 0, +#elif 0 // TODO: Once we need 5-level page tables, add the appropriate check here. + // With 5-level page tables (as possible on linux) we need 57 address bits. + // Upper3 can stay where it is, bit48 moves to the top bit, the rest moves to Lower5. + Top1Shift = -15, + Upper3Shift = 0, + Lower5Shift = 52, +#else + Top1Shift = 0, + Upper3Shift = 0, + Lower5Shift = 0 +#endif + }; + + template<int Offset, quint64 Mask> + static constexpr quint64 movePointerBits(quint64 val) + { + if constexpr (Offset > 0) + return (val & ~Mask) | ((val & Mask) >> Offset); + if constexpr (Offset < 0) + return (val & ~Mask) | ((val & Mask) << -Offset); + return val; + } + + template<int Offset, quint64 Mask> + static constexpr quint64 storePointerBits(quint64 val) + { + constexpr quint64 OriginMask = movePointerBits<-Offset, Mask>(Mask); + return movePointerBits<Offset, OriginMask>(val); + } + + template<int Offset, quint64 Mask> + static constexpr quint64 retrievePointerBits(quint64 val) + { + return movePointerBits<-Offset, Mask>(val); + } + + QML_NEARLY_ALWAYS_INLINE HeapBasePtr m() const + { + Q_ASSERT(!(_val & ManagedMask)); + + // Re-assemble the pointer from its fragments. + const quint64 tmp = retrievePointerBits<Top1Shift, Top1Mask>( + retrievePointerBits<Upper3Shift, Upper3Mask>( + retrievePointerBits<Lower5Shift, Lower5Mask>(_val))); + + HeapBasePtr b; + memcpy(&b, &tmp, 8); + return b; + } + QML_NEARLY_ALWAYS_INLINE void setM(HeapBasePtr b) + { + quint64 tmp; + memcpy(&tmp, &b, 8); + + // 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 + { + Q_STATIC_ASSERT(sizeof(HeapBasePtr) == sizeof(quint32)); + HeapBasePtr b; + quint32 v = value(); + memcpy(&b, &v, 4); + return b; + } + QML_NEARLY_ALWAYS_INLINE void setM(HeapBasePtr b) + { + quint32 v; + memcpy(&v, &b, 4); + setTagValue(quint32(QuickType::Managed), v); + } +#else +# error "unsupported pointer size" +#endif }; -Q_STATIC_ASSERT(std::is_trivial<StaticValue>::value); +Q_STATIC_ASSERT(std::is_trivial_v<StaticValue>); struct Encode { static constexpr ReturnedValue undefined() { diff --git a/src/qml/common/qv4stringtoarrayindex_p.h b/src/qml/common/qv4stringtoarrayindex_p.h index 61bd988d1e..7bdabd4f3f 100644 --- a/src/qml/common/qv4stringtoarrayindex_p.h +++ b/src/qml/common/qv4stringtoarrayindex_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml 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$ -** -****************************************************************************/ +// Copyright (C) 2019 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 QV4STRINGTOARRAYINDEX_P_H #define QV4STRINGTOARRAYINDEX_P_H @@ -65,6 +29,8 @@ inline uint charToUInt(const char *ch) { return static_cast<unsigned char>(*ch); template <typename T> uint stringToArrayIndex(const T *ch, const T *end) { + if (ch == end) + return std::numeric_limits<uint>::max(); uint i = charToUInt(ch) - '0'; if (i > 9) return std::numeric_limits<uint>::max(); @@ -77,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; } @@ -86,7 +52,7 @@ uint stringToArrayIndex(const T *ch, const T *end) inline uint stringToArrayIndex(const QString &str) { - return stringToArrayIndex(str.constData(), str.constData() + str.length()); + return stringToArrayIndex(str.constData(), str.constData() + str.size()); } } // namespace QV4 |