diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2022-07-12 16:08:17 +0200 |
|---|---|---|
| committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-07-19 10:52:31 +0200 |
| commit | ef057772c41e84e9813f0ed113fc55224013404c (patch) | |
| tree | 80d1a7247825bcfccb577ce2ab6c0f248d435439 | |
| parent | b335d8311f7c0be9261305d5b41bbcbf94cd3d27 (diff) | |
Fix array-like methods on V4 sequences
We need to properly convert value type lists on assignment and we need
to add the "length" property to the own properties. Furthermore, the V4
sequence methods were confused about integer type ranges. We teach
them about qsizetype, properly range check everything, and drop the
artificial limitation to INT_MAX.
Pick-to: 6.4
Task-number: QTBUG-82443
Change-Id: Ie5af1130c9e78e412c171e6fa26a28a6a7a5d498
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
| -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" |
