diff options
| -rw-r--r-- | src/qml/jsruntime/qv4arrayobject_p.h | 25 | ||||
| -rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper.cpp | 2 | ||||
| -rw-r--r-- | src/qml/jsruntime/qv4sequenceobject.cpp | 192 | ||||
| -rw-r--r-- | src/qml/jsruntime/qv4sequenceobject_p.h | 19 | ||||
| -rw-r--r-- | src/qml/qml/qqmllistwrapper.cpp | 26 | ||||
| -rw-r--r-- | src/qml/qml/qqmlvmemetaobject.cpp | 2 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlecmascript/data/sequenceConversion.indexes.qml | 12 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 14 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/data/v4SequenceMethods.qml | 198 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/data/v4SequenceMethodsWithParams.qml | 47 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 163 |
11 files changed, 566 insertions, 134 deletions
diff --git a/src/qml/jsruntime/qv4arrayobject_p.h b/src/qml/jsruntime/qv4arrayobject_p.h index f6b95e2413..b07e27b24f 100644 --- a/src/qml/jsruntime/qv4arrayobject_p.h +++ b/src/qml/jsruntime/qv4arrayobject_p.h @@ -20,6 +20,31 @@ QT_BEGIN_NAMESPACE +inline bool qIsAtMostUintLimit(qsizetype length, uint limit = std::numeric_limits<uint>::max()) +{ + // Use the type with the larger positive range to do the comparison. + + Q_ASSERT(length >= 0); + if constexpr (sizeof(qsizetype) > sizeof(uint)) { + return length <= qsizetype(limit); + } else { + return uint(length) <= limit; + } +} + +inline bool qIsAtMostSizetypeLimit(uint length, qsizetype limit = std::numeric_limits<qsizetype>::max()) +{ + // Use the type with the larger positive range to do the comparison. + + Q_ASSERT(limit >= 0); + if constexpr (sizeof(qsizetype) > sizeof(uint)) { + return qsizetype(length) <= limit; + } else { + return length <= uint(limit); + } +} + + namespace QV4 { namespace Heap { diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 3fa107ebda..021872b7ad 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -597,7 +597,7 @@ void QObjectWrapper::setProperty( PROPERTY_STORE(QQmlScriptString, ss); } else { QVariant v; - if (property->isQList()) + if (property->isQList() && propType.flags().testFlag(QMetaType::IsQmlList)) v = scope.engine->toVariant(value, QMetaType::fromType<QList<QObject *> >()); else v = scope.engine->toVariant(value, propType); diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp index 7f4b6057ce..4dc15009c9 100644 --- a/src/qml/jsruntime/qv4sequenceobject.cpp +++ b/src/qml/jsruntime/qv4sequenceobject.cpp @@ -77,17 +77,24 @@ struct SequenceOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator s->loadReference(); } - if (arrayIndex < quint32(s->size())) { - uint index = arrayIndex; + const qsizetype size = s->size(); + if (size > 0 && qIsAtMostSizetypeLimit(arrayIndex, size - 1)) { + const uint index = arrayIndex; ++arrayIndex; if (attrs) *attrs = QV4::Attr_Data; if (pd) - pd->value = s->engine()->fromVariant(s->at(index)); + pd->value = s->engine()->fromVariant(s->at(qsizetype(index))); return PropertyKey::fromArrayIndex(index); } - return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); + if (memberIndex == 0) { + ++memberIndex; + return o->engine()->id_length()->propertyKey(); + } + + // You cannot add any own properties via the regular JavaScript interfaces. + return PropertyKey::invalid(); } }; @@ -182,7 +189,7 @@ qsizetype Sequence::size() const return metaSequence(p)->size(p->container); } -QVariant Sequence::at(int index) const +QVariant Sequence::at(qsizetype index) const { const auto *p = d(); const QMetaType v = valueMetaType(p); @@ -196,41 +203,50 @@ QVariant Sequence::at(int index) const return result; } -void Sequence::append(const QVariant &item) + +template<typename Action> +void convertAndDo(const QVariant &item, const QMetaType v, Action action) { - const auto *p = d(); - const auto *m = metaSequence(p); - const QMetaType v = valueMetaType(p); if (item.metaType() == v) { - m->addValueAtEnd(p->container, item.constData()); + action(item.constData()); } else if (v == QMetaType::fromType<QVariant>()) { - m->addValueAtEnd(p->container, &item); + action(&item); } else { QVariant converted = item; if (!converted.convert(v)) converted = QVariant(v); - m->addValueAtEnd(p->container, converted.constData()); + action(converted.constData()); } } -void Sequence::replace(int index, const QVariant &item) +void Sequence::append(const QVariant &item) { - const auto *p = d(); - const auto *m = metaSequence(p); - const QMetaType v = valueMetaType(p); - if (item.metaType() == v) { - m->setValueAtIndex(p->container, index, item.constData()); - } else if (v == QMetaType::fromType<QVariant>()) { - m->setValueAtIndex(p->container, index, &item); - } else { - QVariant converted = item; - if (!converted.convert(v)) - converted = QVariant(v); - m->setValueAtIndex(p->container, index, converted.constData()); - } + const Heap::Sequence *p = d(); + convertAndDo(item, valueMetaType(p), [p](const void *data) { + metaSequence(p)->addValueAtEnd(p->container, data); + }); +} + +void Sequence::append(qsizetype num, const QVariant &item) +{ + const Heap::Sequence *p = d(); + convertAndDo(item, valueMetaType(p), [p, num](const void *data) { + const QMetaSequence *m = metaSequence(p); + void *container = p->container; + for (qsizetype i = 0; i < num; ++i) + m->addValueAtEnd(container, data); + }); } -void Sequence::removeLast(int num) +void Sequence::replace(qsizetype index, const QVariant &item) +{ + const Heap::Sequence *p = d(); + convertAndDo(item, valueMetaType(p), [p, index](const void *data) { + metaSequence(p)->setValueAtIndex(p->container, index, data); + }); +} + +void Sequence::removeLast(qsizetype num) { const auto *p = d(); const auto *m = metaSequence(p); @@ -254,15 +270,8 @@ QVariant Sequence::toVariant() const return QVariant(p->typePrivate->listId, p->container); } -ReturnedValue Sequence::containerGetIndexed(uint index, bool *hasProperty) const +ReturnedValue Sequence::containerGetIndexed(qsizetype index, bool *hasProperty) const { - /* Qt containers have int (rather than uint) allowable indexes. */ - if (index > INT_MAX) { - generateWarning(engine(), QLatin1String("Index out of range during indexed get")); - if (hasProperty) - *hasProperty = false; - return Encode::undefined(); - } if (d()->isReference) { if (!d()->object) { if (hasProperty) @@ -271,7 +280,7 @@ ReturnedValue Sequence::containerGetIndexed(uint index, bool *hasProperty) const } loadReference(); } - if (index < quint32(size())) { + if (index >= 0 && index < size()) { if (hasProperty) *hasProperty = true; return engine()->fromVariant(at(index)); @@ -281,17 +290,11 @@ ReturnedValue Sequence::containerGetIndexed(uint index, bool *hasProperty) const return Encode::undefined(); } -bool Sequence::containerPutIndexed(uint index, const Value &value) +bool Sequence::containerPutIndexed(qsizetype index, const Value &value) { if (internalClass()->engine->hasException) return false; - /* Qt containers have int (rather than uint) allowable indexes. */ - if (index > INT_MAX) { - generateWarning(engine(), QLatin1String("Index out of range during indexed set")); - return false; - } - if (d()->isReadOnly) { engine()->throwTypeError(QLatin1String("Cannot insert into a readonly container")); return false; @@ -303,10 +306,13 @@ bool Sequence::containerPutIndexed(uint index, const Value &value) loadReference(); } - quint32 count = quint32(size()); + const qsizetype count = size(); const QMetaType valueType = valueMetaType(d()); const QVariant element = engine()->toVariant(value, valueType, false); + if (index < 0) + return false; + if (index == count) { append(element); } else if (index < count) { @@ -314,11 +320,8 @@ bool Sequence::containerPutIndexed(uint index, const Value &value) } else { /* according to ECMA262r3 we need to insert */ /* the value at the given index, increasing length to index+1. */ - while (index > count++) { - append(valueType == QMetaType::fromType<QVariant>() - ? QVariant() - : QVariant(valueType)); - } + append(index - count, + valueType == QMetaType::fromType<QVariant>() ? QVariant() : QVariant(valueType)); append(element); } @@ -327,32 +330,14 @@ bool Sequence::containerPutIndexed(uint index, const Value &value) return true; } -PropertyAttributes Sequence::containerQueryIndexed(uint index) const -{ - /* Qt containers have int (rather than uint) allowable indexes. */ - if (index > INT_MAX) { - generateWarning(engine(), QLatin1String("Index out of range during indexed query")); - return QV4::Attr_Invalid; - } - if (d()->isReference) { - if (!d()->object) - return QV4::Attr_Invalid; - loadReference(); - } - return (index < quint32(size())) ? QV4::Attr_Data : QV4::Attr_Invalid; -} - SequenceOwnPropertyKeyIterator *containerOwnPropertyKeys(const Object *m, Value *target) { *target = *m; return new SequenceOwnPropertyKeyIterator; } -bool Sequence::containerDeleteIndexedProperty(uint index) +bool Sequence::containerDeleteIndexedProperty(qsizetype index) { - /* Qt containers have int (rather than uint) allowable indexes. */ - if (index > INT_MAX) - return false; if (d()->isReadOnly) return false; if (d()->isReference) { @@ -361,7 +346,7 @@ bool Sequence::containerDeleteIndexedProperty(uint index) loadReference(); } - if (index >= quint32(size())) + if (index < 0 || index >= size()) return false; /* according to ECMA262r3 it should be Undefined, */ @@ -433,26 +418,40 @@ void Sequence::storeReference() ReturnedValue Sequence::virtualGet(const Managed *that, PropertyKey id, const Value *receiver, bool *hasProperty) { - if (!id.isArrayIndex()) - return Object::virtualGet(that, id, receiver, hasProperty); - return static_cast<const Sequence *>(that)->containerGetIndexed(id.asArrayIndex(), hasProperty); + if (id.isArrayIndex()) { + const uint index = id.asArrayIndex(); + if (qIsAtMostSizetypeLimit(index)) + return static_cast<const Sequence *>(that)->containerGetIndexed(qsizetype(index), hasProperty); + + generateWarning(that->engine(), QLatin1String("Index out of range during indexed get")); + return false; + } + + return Object::virtualGet(that, id, receiver, hasProperty); } bool Sequence::virtualPut(Managed *that, PropertyKey id, const Value &value, Value *receiver) { - if (id.isArrayIndex()) - return static_cast<Sequence *>(that)->containerPutIndexed(id.asArrayIndex(), value); + if (id.isArrayIndex()) { + const uint index = id.asArrayIndex(); + if (qIsAtMostSizetypeLimit(index)) + return static_cast<Sequence *>(that)->containerPutIndexed(qsizetype(index), value); + + generateWarning(that->engine(), QLatin1String("Index out of range during indexed set")); + return false; + } return Object::virtualPut(that, id, value, receiver); } -PropertyAttributes Sequence::queryIndexed(const Managed *that, uint index) -{ return static_cast<const Sequence *>(that)->containerQueryIndexed(index); } - bool Sequence::virtualDeleteProperty(Managed *that, PropertyKey id) { if (id.isArrayIndex()) { - uint index = id.asArrayIndex(); - return static_cast<Sequence *>(that)->containerDeleteIndexedProperty(index); + const uint index = id.asArrayIndex(); + if (qIsAtMostSizetypeLimit(index)) + return static_cast<Sequence *>(that)->containerDeleteIndexedProperty(qsizetype(index)); + + generateWarning(that->engine(), QLatin1String("Index out of range during indexed delete")); + return false; } return Object::virtualDeleteProperty(that, id); } @@ -479,7 +478,12 @@ static QV4::ReturnedValue method_get_length(const FunctionObject *b, const Value RETURN_RESULT(Encode(0)); This->loadReference(); } - RETURN_RESULT(Encode(qint32(This->size()))); + + const qsizetype size = This->size(); + if (qIsAtMostUintLimit(size)) + RETURN_RESULT(Encode(uint(size))); + + return scope.engine->throwRangeError(QLatin1String("Sequence length out of range")); } static QV4::ReturnedValue method_set_length(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) @@ -489,9 +493,9 @@ static QV4::ReturnedValue method_set_length(const FunctionObject *f, const Value if (!This) THROW_TYPE_ERROR(); - quint32 newLength = argc ? argv[0].toUInt32() : 0; - /* Qt containers have int (rather than uint) allowable indexes. */ - if (newLength > INT_MAX) { + bool ok = false; + const quint32 argv0 = argc ? argv[0].asArrayLength(&ok) : 0; + if (!ok || !qIsAtMostSizetypeLimit(argv0)) { generateWarning(scope.engine, QLatin1String("Index out of range during length set")); RETURN_UNDEFINED(); } @@ -499,15 +503,17 @@ static QV4::ReturnedValue method_set_length(const FunctionObject *f, const Value if (This->d()->isReadOnly) THROW_TYPE_ERROR(); + const qsizetype newCount = qsizetype(argv0); + /* Read the sequence from the QObject property if we're a reference */ if (This->d()->isReference) { if (!This->d()->object) RETURN_UNDEFINED(); This->loadReference(); } + /* Determine whether we need to modify the sequence */ - quint32 newCount = static_cast<quint32>(newLength); - quint32 count = static_cast<quint32>(This->size()); + const qsizetype count = This->size(); if (newCount == count) { RETURN_UNDEFINED(); } else if (newCount > count) { @@ -515,19 +521,20 @@ static QV4::ReturnedValue method_set_length(const FunctionObject *f, const Value /* according to ECMA262r3 we need to insert */ /* undefined values increasing length to newLength. */ /* We cannot, so we insert default-values instead. */ - while (newCount > count++) - This->append(QVariant(valueMetaType)); + This->append(newCount - count, QVariant(valueMetaType)); } else { /* according to ECMA262r3 we need to remove */ /* elements until the sequence is the required length. */ - if (newCount < count) - This->removeLast(count - newCount); + Q_ASSERT(newCount < count); + This->removeLast(count - newCount); } + /* write back if required. */ if (This->d()->isReference) { /* write back. already checked that object is non-null, so skip that check here. */ This->storeReference(); } + RETURN_UNDEFINED(); } @@ -631,9 +638,12 @@ QVariant SequencePrototype::toVariant(const QV4::Value &array, QMetaType typeHin const QMetaSequence *meta = priv->extraData.ld; const QMetaType containerMetaType(priv->listId); QVariant result(containerMetaType); - quint32 length = a->getLength(); + qint64 length = a->getLength(); + Q_ASSERT(length >= 0); + Q_ASSERT(length <= qint64(std::numeric_limits<quint32>::max())); + QV4::ScopedValue v(scope); - for (quint32 i = 0; i < length; ++i) { + for (quint32 i = 0; i < quint32(length); ++i) { const QMetaType valueMetaType = priv->typeId; QVariant variant = scope.engine->toVariant(a->get(i), valueMetaType, false); if (valueMetaType == QMetaType::fromType<QVariant>()) { diff --git a/src/qml/jsruntime/qv4sequenceobject_p.h b/src/qml/jsruntime/qv4sequenceobject_p.h index 51cdc2fc2c..256d324355 100644 --- a/src/qml/jsruntime/qv4sequenceobject_p.h +++ b/src/qml/jsruntime/qv4sequenceobject_p.h @@ -75,26 +75,21 @@ public: static QV4::ReturnedValue virtualGet( const QV4::Managed *that, PropertyKey id, const Value *receiver, bool *hasProperty); static bool virtualPut(Managed *that, PropertyKey id, const QV4::Value &value, Value *receiver); - static QV4::PropertyAttributes queryIndexed(const QV4::Managed *that, uint index); static bool virtualDeleteProperty(QV4::Managed *that, PropertyKey id); static bool virtualIsEqualTo(Managed *that, Managed *other); static QV4::OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); qsizetype size() const; - QVariant at(int index) const; + QVariant at(qsizetype index) const; void append(const QVariant &item); - void replace(int index, const QVariant &item); - void removeLast(int num); + void append(qsizetype num, const QVariant &item); + void replace(qsizetype index, const QVariant &item); + void removeLast(qsizetype num); QVariant toVariant() const; - // ### Qt 7 use qsizetype instead. - QV4::ReturnedValue containerGetIndexed(uint index, bool *hasProperty) const; - - // ### Qt 7 use qsizetype instead. - bool containerPutIndexed(uint index, const QV4::Value &value); - - QV4::PropertyAttributes containerQueryIndexed(uint index) const; - bool containerDeleteIndexedProperty(uint index); + QV4::ReturnedValue containerGetIndexed(qsizetype index, bool *hasProperty) const; + bool containerPutIndexed(qsizetype index, const QV4::Value &value); + bool containerDeleteIndexedProperty(qsizetype index); bool containerIsEqualTo(Managed *other); bool sort(const FunctionObject *f, const Value *, const Value *argv, int argc); void *getRawContainerPtr() const; diff --git a/src/qml/qml/qqmllistwrapper.cpp b/src/qml/qml/qqmllistwrapper.cpp index 29da1002c4..ba6058ff3d 100644 --- a/src/qml/qml/qqmllistwrapper.cpp +++ b/src/qml/qml/qqmllistwrapper.cpp @@ -20,18 +20,6 @@ using namespace Qt::StringLiterals; DEFINE_OBJECT_VTABLE(QmlListWrapper); -static bool isAtMostUintLimit(qsizetype length, uint limit = std::numeric_limits<uint>::max()) -{ - // Use the type with the larger positive range to do the comparison. - - Q_ASSERT(length >= 0); - if constexpr (sizeof(qsizetype) > sizeof(uint)) { - return length <= qsizetype(limit); - } else { - return uint(length) <= limit; - } -} - void Heap::QmlListWrapper::init() { Object::init(); @@ -249,7 +237,7 @@ ReturnedValue PropertyListPrototype::method_push(const FunctionObject *b, const } const qsizetype length = property->count(property); - if (!isAtMostUintLimit(length, std::numeric_limits<uint>::max() - argc)) + if (!qIsAtMostUintLimit(length, std::numeric_limits<uint>::max() - argc)) return scope.engine->throwRangeError(QString::fromLatin1("List length out of range.")); for (int i = 0; i < argc; ++i) { @@ -334,7 +322,7 @@ ReturnedValue PropertyListPrototype::method_splice(const FunctionObject *b, cons return scope.engine->throwTypeError(); } - if (!isAtMostUintLimit(deleteCount, std::numeric_limits<uint>::max() - 1)) + if (!qIsAtMostUintLimit(deleteCount, std::numeric_limits<uint>::max() - 1)) return scope.engine->throwRangeError(QString::fromLatin1("List length out of range.")); if (!property->at) @@ -404,7 +392,7 @@ ReturnedValue PropertyListPrototype::method_unshift(const FunctionObject *b, con return scope.engine->throwTypeError(u"List doesn't define a Count function"_s); const qsizetype len = property->count(property); - if (std::numeric_limits<qsizetype>::max() - len < argc || !isAtMostUintLimit(len + argc)) + if (std::numeric_limits<qsizetype>::max() - len < argc || !qIsAtMostUintLimit(len + argc)) return scope.engine->throwRangeError(QString::fromLatin1("List length out of range.")); if (!property->append) @@ -490,7 +478,7 @@ ReturnedValue PropertyListPrototype::method_indexOf(const FunctionObject *b, con for (qsizetype i = fromIndex; i < len; ++i) { if (property->at(property, i) == searchValue) { - if (isAtMostUintLimit(i)) + if (qIsAtMostUintLimit(i)) return Encode(uint(i)); return engine->throwRangeError(QString::fromLatin1("List length out of range.")); } @@ -523,7 +511,7 @@ ReturnedValue PropertyListPrototype::method_lastIndexOf(const FunctionObject *b, for (qsizetype i = fromIndex; i >= 0; --i) { if (property->at(property, i) == searchValue) { - if (isAtMostUintLimit(i)) + if (qIsAtMostUintLimit(i)) return Encode(uint(i)); return engine->throwRangeError(QString::fromLatin1("List length out of range.")); } @@ -585,7 +573,7 @@ ReturnedValue PropertyListPrototype::method_get_length(const FunctionObject *b, return scope.engine->throwTypeError(u"List doesn't define a Count function"_s); qsizetype count = property->count(property); - if (isAtMostUintLimit(count)) + if (qIsAtMostUintLimit(count)) return Encode(uint(count)); return scope.engine->throwRangeError(QString::fromLatin1("List length out of range.")); @@ -618,7 +606,7 @@ ReturnedValue PropertyListPrototype::method_set_length(const FunctionObject *b, return scope.engine->throwTypeError(u"List doesn't define a Count function"_s); qsizetype count = property->count(property); - if (!isAtMostUintLimit(count)) + if (!qIsAtMostUintLimit(count)) return scope.engine->throwRangeError(QString::fromLatin1("List length out of range.")); if (newLength < uint(count)) { diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp index dec7eb63b7..8a0bc1a08e 100644 --- a/src/qml/qml/qqmlvmemetaobject.cpp +++ b/src/qml/qml/qqmlvmemetaobject.cpp @@ -829,7 +829,7 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void * engine, propType, a[0], &success)); if (!success) { qmlWarning(object) - << "Could not create a QML sequence object for" + << "Could not create a QML sequence object for " << propType.name(); } needActivate = true; diff --git a/tests/auto/qml/qqmlecmascript/data/sequenceConversion.indexes.qml b/tests/auto/qml/qqmlecmascript/data/sequenceConversion.indexes.qml index a3f306f717..b1d422b6e9 100644 --- a/tests/auto/qml/qqmlecmascript/data/sequenceConversion.indexes.qml +++ b/tests/auto/qml/qqmlecmascript/data/sequenceConversion.indexes.qml @@ -30,15 +30,19 @@ Item { var tooBigIndex = msco.tooBigIndex; var negativeIndex = msco.negativeIndex; - // shouldn't be able to set the length > maxIndex. - msco.intListProperty.length = tooBigIndex; + // We cannot test this anymore since INT_MAX + <a bit> is actually supported on 64bit. + // Trying to do this just wastes a lot of memory and takes forever. + // msco.intListProperty.length = tooBigIndex; + if (msco.intListProperty.length != expectedLength) success = false; if (!verifyExpected(msco.intListProperty, 4)) success = false; - // shouldn't be able to set any index > maxIndex. - msco.intListProperty[tooBigIndex] = 12; + // We cannot test this anymore since INT_MAX + <a bit> is actually supported on 64bit. + // Trying to do this just wastes a lot of memory and takes forever. + // msco.intListProperty[tooBigIndex] = 12; + if (msco.intListProperty.length != expectedLength) success = false; if (!verifyExpected(msco.intListProperty, 4)) diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 9aacb05043..033de56695 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -5948,17 +5948,19 @@ void tst_qqmlecmascript::sequenceConversionIndexes() { // ensure that we gracefully fail if unsupported index values are specified. // Qt container classes only support non-negative, signed integer index values. + + // Since Qt6, on 64bit the maximum length is beyond what we can encode in a 32bit integer. + // Therefore we cannot test the overflow anymore. + QUrl qmlFile = testFileUrl("sequenceConversion.indexes.qml"); QQmlEngine engine; QQmlComponent component(&engine, qmlFile); QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); - QString w1 = qmlFile.toString() + QLatin1String(":34: Index out of range during length set"); - QString w2 = qmlFile.toString() + QLatin1String(":41: Index out of range during indexed set"); - QString w3 = qmlFile.toString() + QLatin1String(":48: Index out of range during indexed get"); - QTest::ignoreMessage(QtWarningMsg, qPrintable(w1)); - QTest::ignoreMessage(QtWarningMsg, qPrintable(w2)); - QTest::ignoreMessage(QtWarningMsg, qPrintable(w3)); + + const QString w = qmlFile.toString() + QLatin1String(":59: Index out of range during length set"); + QTest::ignoreMessage(QtWarningMsg, qPrintable(w)); + QMetaObject::invokeMethod(object.data(), "indexedAccess"); QVERIFY(object->property("success").toBool()); QMetaObject::invokeMethod(object.data(), "indexOf"); diff --git a/tests/auto/qml/qqmllanguage/data/v4SequenceMethods.qml b/tests/auto/qml/qqmllanguage/data/v4SequenceMethods.qml new file mode 100644 index 0000000000..9553ed6087 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/v4SequenceMethods.qml @@ -0,0 +1,198 @@ +import QtQml + +QtObject { + property rect self: Qt.rect(9, 9, 9, 9) + + property rect r1: Qt.rect(1, 2, 3, 4) + property rect r2: Qt.rect(5, 6, 7, 8) + property rect r3: Qt.rect(9, 10, 11, 12) + + function jsArray() { return [r1, r2, r3] } + property list<rect> v4Sequence: [r1, r2, r3] + + property string jsArrayToString: jsArray().toString() + property string v4SequenceToString: v4Sequence.toString() + + property string jsArrayToLocaleString: jsArray().toLocaleString() + property string v4SequenceToLocaleString: v4Sequence.toLocaleString() + + property list<rect> v4SequenceConcat: v4Sequence.concat(v4Sequence) + property list<rect> jsArrayConcat: jsArray().concat(jsArray()) + + property rect v4SequenceFind: v4Sequence.find(element => element.x === 1) + property rect jsArrayFind: jsArray().find(element => element.x === 1) + + property int v4SequenceFindIndex: v4Sequence.findIndex(element => element === r2) + property int jsArrayFindIndex: jsArray().findIndex(element => element === r2) + + property bool v4SequenceIncludes: v4Sequence.includes(r3) + property bool jsArrayIncludes: jsArray().includes(r3) + + property string v4SequenceJoin: v4Sequence.join() + property string jsArrayJoin: jsArray().join() + + property bool entriesMatch: { + var iterator = v4Sequence.entries(); + for (var [index, element] of jsArray().entries()) { + var v = iterator.next().value; + if (index !== v[0] || element !== v[1]) { + console.log(index, v[0], element, v[1]); + return false; + } + } + + var iterator = jsArray().entries(); + for (var [index, element] of v4Sequence.entries()) { + var v = iterator.next().value; + if (index !== v[0] || element !== v[1]) { + console.log(index, v[0], element, v[1]); + return false; + } + } + + return true; + } + + property bool keysMatch: { + var iterator = v4Sequence.keys(); + for (var index of jsArray().keys()) { + var v = iterator.next().value; + if (index !== v) { + console.log(index, v); + return false; + } + } + + var iterator = jsArray().keys(); + for (var index of v4Sequence.keys()) { + var v = iterator.next().value; + if (index !== v) { + console.log(index, v); + return false; + } + } + + return true; + } + + property bool valuesMatch: { + var iterator = v4Sequence.values(); + for (var obj of jsArray().values()) { + var v = iterator.next().value; + if (obj !== v) { + console.log(obj, v); + return false; + } + } + + var iterator = jsArray().values(); + for (var obj of v4Sequence.values()) { + var v = iterator.next().value; + if (obj !== v) { + console.log(obj, v); + return false; + } + } + + return true; + } + + property list<rect> v4SequencePop: v4Sequence + property rect v4SequencePopped + + property list<rect> jsArrayPop + property rect jsArrayPopped + + property list<rect> v4SequencePush: v4Sequence + property int v4SequencePushed + + property list<rect> jsArrayPush + property int jsArrayPushed + + property list<rect> v4SequenceReverse: v4Sequence + property list<rect> jsArrayReverse: jsArray().reverse() + + property list<rect> v4SequenceShift: v4Sequence + property rect v4SequenceShifted + + property list<rect> jsArrayShift + property rect jsArrayShifted + + property list<rect> v4SequenceSplice: v4Sequence + property list<rect> v4SequenceSpliced + + property list<rect> jsArraySplice + property list<rect> jsArraySpliced + + property list<rect> v4SequenceUnshift: v4Sequence + property int v4SequenceUnshifted + + property list<rect> jsArrayUnshift + property int jsArrayUnshifted + + property int v4SequenceIndexOf: v4Sequence.indexOf(r2) + property int jsArrayIndexOf: jsArray().indexOf(r2) + + property int v4SequenceLastIndexOf: v4Sequence.lastIndexOf(r3) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(r3) + + property bool v4SequenceEvery: v4Sequence.every((element) => element != null) + property bool jsArrayEvery: jsArray().every((element) => element != null) + + property bool v4SequenceSome: v4Sequence.some((element) => element.x === 1) + property bool jsArraySome: jsArray().some((element) => element.x === 1) + + property string v4SequenceForEach + property string jsArrayForEach + + property list<int> v4SequenceMap: v4Sequence.map(((element) => element.x)) + property list<int> jsArrayMap: jsArray().map(((element) => element.x)) + + property list<rect> v4SequenceFilter: v4Sequence.filter((element) => element.x != 1) + property list<rect> jsArrayFilter: jsArray().filter((element) => element.x != 1) + + property string v4SequenceReduce: v4Sequence.reduce((element, v) => v + '-' + element.x + 'v', "") + property string jsArrayReduce: jsArray().reduce((element, v) => v + '-' + element.x + 'v', "") + + property string v4SequenceReduceRight: v4Sequence.reduceRight((element, v) => v + '-' + element.x + 'v', "") + property string jsArrayReduceRight: jsArray().reduceRight((element, v) => v + '-' + element.x + 'v', "") + + property list<string> jsArrayOwnPropertyNames: Object.getOwnPropertyNames(jsArray()) + property list<string> v4SequenceOwnPropertyNames: Object.getOwnPropertyNames(v4Sequence) + + property list<rect> v4SequenceSort1: v4Sequence + property list<rect> jsArraySort1: jsArray().sort() + + property list<rect> v4SequenceSort2: v4Sequence + property list<rect> jsArraySort2: jsArray().sort((a, b) => (a.x - b.x)) + + Component.onCompleted: { + v4SequenceReverse.reverse(); + + v4SequencePopped = v4SequencePop.pop(); + var a = jsArray(); + jsArrayPopped = a.pop(); + jsArrayPop = a; + + v4SequencePushed = v4SequencePush.push(self); + a = jsArray(); + jsArrayPushed = a.push(self); + jsArrayPush = a; + + v4SequenceShifted = v4SequenceShift.shift(); + a = jsArray(); + jsArrayShifted = a.shift(); + jsArrayShift = a; + + v4SequenceUnshifted = v4SequenceUnshift.unshift(self); + a = jsArray(); + jsArrayUnshifted = a.unshift(self); + jsArrayUnshift = a; + + v4Sequence.forEach((element) => { v4SequenceForEach += "-" + element.x + "-" }); + jsArray().forEach((element) => { jsArrayForEach += "-" + element.x + "-" }); + + v4SequenceSort1.sort(); + v4SequenceSort2.sort((a, b) => (a.x - b.x)) + } +} diff --git a/tests/auto/qml/qqmllanguage/data/v4SequenceMethodsWithParams.qml b/tests/auto/qml/qqmllanguage/data/v4SequenceMethodsWithParams.qml new file mode 100644 index 0000000000..001f092c0a --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/v4SequenceMethodsWithParams.qml @@ -0,0 +1,47 @@ +import QtQml + +QtObject { + property rect self: Qt.rect(9, 9, 9, 9) + + required property real i + required property real j + required property real k + + property rect r1: Qt.rect(1, 2, 3, 4) + property rect r2: Qt.rect(5, 6, 7, 8) + property rect r3: Qt.rect(9, 10, 11, 12) + + function jsArray() { return [r1, r2, r3] } + property list<rect> v4Sequence: [r1, r2, r3] + + property list<rect> v4SequenceCopyWithin: v4Sequence + property list<rect> jsArrayCopyWithin: jsArray().copyWithin(i, j, k) + + property list<rect> v4SequenceFill: v4Sequence + property list<rect> jsArrayFill: jsArray().fill(self, i, Math.min(j, 1024)) + + property list<rect> v4SequenceSlice: v4Sequence.slice(i, j) + property list<rect> jsArraySlice: jsArray().slice(i, j) + + property list<rect> v4SequenceSplice: v4Sequence + property list<rect> v4SequenceSpliced + + property list<rect> jsArraySplice + property list<rect> jsArraySpliced + + property int v4SequenceIndexOf: v4Sequence.indexOf(r2, i) + property int jsArrayIndexOf: jsArray().indexOf(r2, i) + + property int v4SequenceLastIndexOf: v4Sequence.lastIndexOf(r3, i) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(r3, i) + + Component.onCompleted: { + v4SequenceCopyWithin.copyWithin(i, j, k); + v4SequenceFill.fill(self, i, Math.min(j, 1024)); + + v4SequenceSpliced = v4SequenceSplice.splice(i, j, self, self, self); + var a = jsArray(); + jsArraySpliced = a.splice(i, j, self, self, self); + jsArraySplice = a; + } +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index ebc677c74f..8bed8a9880 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -11,6 +11,7 @@ #include <QtCore/qfileinfo.h> #include <QtCore/qdir.h> #include <QtCore/qscopeguard.h> +#include <QtCore/qrandom.h> #include <QtGui/qevent.h> #include <QSignalSpy> #include <QFont> @@ -388,6 +389,9 @@ private slots: void leakingAttributesQmlForeign(); void attachedOwnProperties(); void bindableOnly(); + void v4SequenceMethods(); + void v4SequenceMethodsWithParams_data(); + void v4SequenceMethodsWithParams(); private: QQmlEngine engine; @@ -7410,6 +7414,165 @@ void tst_qqmllanguage::bindableOnly() QCOMPARE(o->property("a").toInt(), 5); } +static void listsEqual(QObject *object, const char *method) +{ + const QByteArray v4SequencePropertyName = QByteArray("v4Sequence") + method; + const QByteArray jsArrayPropertyName = QByteArray("jsArray") + method; + + const QList<QRectF> v4SequenceProperty + = object->property(v4SequencePropertyName.constData()).value<QList<QRectF>>(); + const QList<QRectF> jsArrayProperty + = object->property(jsArrayPropertyName.constData()).value<QList<QRectF>>(); + + const qsizetype v4SequenceCount = v4SequenceProperty.count(); + QCOMPARE(v4SequenceCount, jsArrayProperty.count()); + + for (qsizetype i = 0; i < v4SequenceCount; ++i) + QCOMPARE(v4SequenceProperty.at(i), jsArrayProperty.at(i)); +} + +void tst_qqmllanguage::v4SequenceMethods() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("v4SequenceMethods.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QCOMPARE(object->property("v4SequenceToString"), object->property("jsArrayToString")); + QCOMPARE(object->property("v4SequenceToLocaleString"), object->property("jsArrayToLocaleString")); + + QVERIFY(object->property("entriesMatch").toBool()); + QVERIFY(object->property("keysMatch").toBool()); + QVERIFY(object->property("valuesMatch").toBool()); + + listsEqual(object.data(), "Concat"); + listsEqual(object.data(), "Pop"); + listsEqual(object.data(), "Push"); + listsEqual(object.data(), "Reverse"); + listsEqual(object.data(), "Shift"); + listsEqual(object.data(), "Unshift"); + listsEqual(object.data(), "Filter"); + listsEqual(object.data(), "Sort1"); + listsEqual(object.data(), "Sort2"); + + QCOMPARE(object->property("v4SequenceFind"), object->property("jsArrayFind")); + QCOMPARE(object->property("v4SequenceFind").value<QRectF>().x(), 1.0); + + QCOMPARE(object->property("v4SequenceFindIndex"), object->property("jsArrayFindIndex")); + QCOMPARE(object->property("v4SequenceFindIndex").toInt(), 1); + + QCOMPARE(object->property("v4SequenceIncludes"), object->property("jsArrayIncludes")); + QVERIFY(object->property("v4SequenceIncludes").toBool()); + + QCOMPARE(object->property("v4SequenceJoin"), object->property("jsArrayJoin")); + QVERIFY(object->property("v4SequenceJoin").toString().contains(QStringLiteral("1"))); + + QCOMPARE(object->property("v4SequencePopped"), object->property("jsArrayPopped")); + QVERIFY(object->property("v4SequencePopped").value<QRectF>().x() != 1.0); + + QCOMPARE(object->property("v4SequencePushed"), object->property("jsArrayPushed")); + QCOMPARE(object->property("v4SequencePushed").toInt(), 4); + + QCOMPARE(object->property("v4SequenceShifted"), object->property("jsArrayShifted")); + QCOMPARE(object->property("v4SequenceShifted").value<QRectF>().x(), 1.0); + + QCOMPARE(object->property("v4SequenceUnshifted"), object->property("jsArrayUnshifted")); + QCOMPARE(object->property("v4SequenceUnshifted").toInt(), 4); + + QCOMPARE(object->property("v4SequenceIndexOf"), object->property("jsArrayIndexOf")); + QCOMPARE(object->property("v4SequenceIndexOf").toInt(), 1); + + QCOMPARE(object->property("v4SequenceLastIndexOf"), object->property("jsArrayLastIndexOf")); + QCOMPARE(object->property("v4SequenceLastIndexOf").toInt(), 2); + + QCOMPARE(object->property("v4SequenceEvery"), object->property("jsArrayEvery")); + QVERIFY(object->property("v4SequenceEvery").toBool()); + + QCOMPARE(object->property("v4SequenceSome"), object->property("jsArrayEvery")); + QVERIFY(object->property("v4SequenceSome").toBool()); + + QCOMPARE(object->property("v4SequenceForEach"), object->property("jsArrayForEach")); + QCOMPARE(object->property("v4SequenceForEach").toString(), QStringLiteral("-1--5--9-")); + + QCOMPARE(object->property("v4SequenceMap").toStringList(), object->property("jsArrayMap").toStringList()); + QCOMPARE(object->property("v4SequenceReduce").toString(), object->property("jsArrayReduce").toString()); + + QCOMPARE(object->property("v4SequenceOwnPropertyNames").toStringList(), + object->property("jsArrayOwnPropertyNames").toStringList()); +} + +void tst_qqmllanguage::v4SequenceMethodsWithParams_data() +{ + QTest::addColumn<double>("i"); + QTest::addColumn<double>("j"); + QTest::addColumn<double>("k"); + + const double indices[] = { + double(std::numeric_limits<qsizetype>::min()), + double(std::numeric_limits<qsizetype>::min()) + 1, + double(std::numeric_limits<uint>::min()) - 1, + double(std::numeric_limits<uint>::min()), + double(std::numeric_limits<uint>::min()) + 1, + double(std::numeric_limits<int>::min()), + -10, -3, -2, -1, 0, 1, 2, 3, 10, + double(std::numeric_limits<int>::max()), + double(std::numeric_limits<uint>::max()) - 1, + double(std::numeric_limits<uint>::max()), + double(std::numeric_limits<uint>::max()) + 1, + double(std::numeric_limits<qsizetype>::max() - 1), + double(std::numeric_limits<qsizetype>::max()), + }; + + // We cannot test the full cross product. So, take a random sample instead. + const qsizetype numIndices = sizeof(indices) / sizeof(double); + qsizetype seed = QRandomGenerator::global()->generate(); + const int numSamples = 4; + for (int i = 0; i < numSamples; ++i) { + seed = qHash(i, seed); + const double vi = indices[qAbs(seed) % numIndices]; + for (int j = 0; j < numSamples; ++j) { + seed = qHash(j, seed); + const double vj = indices[qAbs(seed) % numIndices]; + for (int k = 0; k < numSamples; ++k) { + seed = qHash(k, seed); + const double vk = indices[qAbs(seed) % numIndices]; + const QString tag = QLatin1String("%1/%2/%3") + .arg(QString::number(vi), QString::number(vj), QString::number(vk)); + QTest::newRow(qPrintable(tag)) << vi << vj << vk; + + // output all the tags so that we can find out what combination caused a test to hang. + qDebug().noquote() << "scheduling" << tag; + } + } + } +} + +void tst_qqmllanguage::v4SequenceMethodsWithParams() +{ + QFETCH(double, i); + QFETCH(double, j); + QFETCH(double, k); + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("v4SequenceMethodsWithParams.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.createWithInitialProperties({ + {QStringLiteral("i"), i}, + {QStringLiteral("j"), j}, + {QStringLiteral("k"), k} + })); + QVERIFY(!object.isNull()); + + listsEqual(object.data(), "CopyWithin"); + listsEqual(object.data(), "Fill"); + listsEqual(object.data(), "Slice"); + listsEqual(object.data(), "Splice"); + listsEqual(object.data(), "Spliced"); + + QCOMPARE(object->property("v4SequenceIndexOf"), object->property("jsArrayIndexOf")); + QCOMPARE(object->property("v4SequenceLastIndexOf"), object->property("jsArrayLastIndexOf")); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |
