diff options
37 files changed, 700 insertions, 106 deletions
diff --git a/src/particles/qquickitemparticle.cpp b/src/particles/qquickitemparticle.cpp index 740d0f60f3..ce254c212d 100644 --- a/src/particles/qquickitemparticle.cpp +++ b/src/particles/qquickitemparticle.cpp @@ -136,6 +136,7 @@ QQuickItemParticle::QQuickItemParticle(QQuickItem *parent) : QQuickItemParticle::~QQuickItemParticle() { delete clock; + qDeleteAll(m_managed); } void QQuickItemParticle::freeze(QQuickItem* item) @@ -172,7 +173,8 @@ void QQuickItemParticle::give(QQuickItem *item) void QQuickItemParticle::initialize(int gIdx, int pIdx) { - m_loadables << m_system->groupData[gIdx]->data[pIdx];//defer to other thread + Q_UNUSED(gIdx); + Q_UNUSED(pIdx); } void QQuickItemParticle::commit(int, int) @@ -205,50 +207,43 @@ void QQuickItemParticle::tick(int time) { Q_UNUSED(time);//only needed because QTickAnimationProxy expects one processDeletables(); - - foreach (QQuickParticleData* d, m_loadables){ - Q_ASSERT(d); - if (m_stasis.contains(d->delegate)) - qWarning() << "Current model particles prefers overwrite:false"; - //remove old item from the particle that is dying to make room for this one - if (d->delegate) { - m_deletables << d->delegate; - d->delegate = nullptr; - } - QQuickItem* parentItem = nullptr; - if (!m_pendingItems.isEmpty()){ - QQuickItem *item = m_pendingItems.front(); - m_pendingItems.pop_front(); - parentItem = item->parentItem(); - d->delegate = item; - }else if (m_delegate){ - d->delegate = qobject_cast<QQuickItem*>(m_delegate->create(qmlContext(this))); - if (d->delegate) - m_managed << d->delegate; - } - if (d && d->delegate){//###Data can be zero if creating an item leads to a reset - this screws things up. - d->delegate->setX(d->curX(m_system) - d->delegate->width() / 2); //TODO: adjust for system? - d->delegate->setY(d->curY(m_system) - d->delegate->height() / 2); - QQuickItemParticleAttached* mpa = qobject_cast<QQuickItemParticleAttached*>(qmlAttachedPropertiesObject<QQuickItemParticle>(d->delegate)); - if (mpa){ - mpa->m_parentItem = parentItem; - mpa->m_mp = this; - mpa->attach(); + for (auto groupId : groupIds()) { + for (QQuickParticleData* d : qAsConst(m_system->groupData[groupId]->data)) { + if (!d->delegate && d->t != -1 && d->stillAlive(m_system)) { + QQuickItem* parentItem = nullptr; + if (!m_pendingItems.isEmpty()){ + QQuickItem *item = m_pendingItems.front(); + m_pendingItems.pop_front(); + parentItem = item->parentItem(); + d->delegate = item; + }else if (m_delegate){ + d->delegate = qobject_cast<QQuickItem*>(m_delegate->create(qmlContext(this))); + if (d->delegate) + m_managed << d->delegate; + } + if (d && d->delegate){//###Data can be zero if creating an item leads to a reset - this screws things up. + d->delegate->setX(d->curX(m_system) - d->delegate->width() / 2); //TODO: adjust for system? + d->delegate->setY(d->curY(m_system) - d->delegate->height() / 2); + QQuickItemParticleAttached* mpa = qobject_cast<QQuickItemParticleAttached*>(qmlAttachedPropertiesObject<QQuickItemParticle>(d->delegate)); + if (mpa){ + mpa->m_parentItem = parentItem; + mpa->m_mp = this; + mpa->attach(); + } + d->delegate->setParentItem(this); + if (m_fade) + d->delegate->setOpacity(0.); + d->delegate->setVisible(false);//Will be set to true when we prepare the next frame + m_activeCount++; + } } - d->delegate->setParentItem(this); - if (m_fade) - d->delegate->setOpacity(0.); - d->delegate->setVisible(false);//Will be set to true when we prepare the next frame - m_activeCount++; } } - m_loadables.clear(); } void QQuickItemParticle::reset() { QQuickParticlePainter::reset(); - m_loadables.clear(); // delete all managed items which had their logical particles cleared // but leave it alone if the logical particle is maintained @@ -258,7 +253,7 @@ void QQuickItemParticle::reset() lost.remove(d->delegate); } } - m_deletables.append(lost.values()); + m_deletables.unite(lost); //TODO: This doesn't yet handle calling detach on taken particles in the system reset case processDeletables(); } @@ -267,18 +262,9 @@ void QQuickItemParticle::reset() QSGNode* QQuickItemParticle::updatePaintNode(QSGNode* n, UpdatePaintNodeData* d) { //Dummy update just to get painting tick - if (m_pleaseReset){ + if (m_pleaseReset) m_pleaseReset = false; - //Refill loadables, delayed here so as to only happen once per frame max - //### Constant resetting might lead to m_loadables never being populated when tick() occurs - for (auto groupId : groupIds()) { - for (QQuickParticleData* d : qAsConst(m_system->groupData[groupId]->data)) { - if (!d->delegate && d->t != -1 && d->stillAlive(m_system)) { - m_loadables << d; - } - } - } - } + prepareNextFrame(); update();//Get called again diff --git a/src/particles/qquickitemparticle_p.h b/src/particles/qquickitemparticle_p.h index ea46c67db7..32c9881691 100644 --- a/src/particles/qquickitemparticle_p.h +++ b/src/particles/qquickitemparticle_p.h @@ -108,9 +108,8 @@ protected: private: void processDeletables(); void tick(int time = 0); - QList<QQuickItem* > m_deletables; + QSet<QQuickItem* > m_deletables; QList<QQuickItem* > m_managed; - QList< QQuickParticleData* > m_loadables; bool m_fade; QList<QQuickItem*> m_pendingItems; diff --git a/src/qml/jsruntime/qv4arraydata.cpp b/src/qml/jsruntime/qv4arraydata.cpp index 654d33b8d1..1c587d2367 100644 --- a/src/qml/jsruntime/qv4arraydata.cpp +++ b/src/qml/jsruntime/qv4arraydata.cpp @@ -586,7 +586,7 @@ uint ArrayData::append(Object *obj, ArrayObject *otherObj, uint n) obj->arrayPut(oldSize, os->values.data() + os->offset, chunk); toCopy -= chunk; if (toCopy) - obj->arrayPut(oldSize + chunk, os->values.data(), toCopy); + obj->setArrayLength(oldSize + chunk + toCopy); } return oldSize + n; @@ -659,6 +659,12 @@ bool ArrayElementLessThan::operator()(Value v1, Value v2) const } ScopedString p1s(scope, v1.toString(scope.engine)); ScopedString p2s(scope, v2.toString(scope.engine)); + + if (!p1s) + return false; + if (!p2s) + return true; + return p1s->toQString() < p2s->toQString(); } diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp index d8d0d4739f..6c51b4cbcb 100644 --- a/src/qml/jsruntime/qv4arrayobject.cpp +++ b/src/qml/jsruntime/qv4arrayobject.cpp @@ -1048,8 +1048,9 @@ ReturnedValue ArrayPrototype::method_includes(const FunctionObject *b, const Val } } + ScopedValue val(scope); while (k < len) { - ScopedValue val(scope, instance->get(k)); + val = instance->get(k); if (val->sameValueZero(argv[0])) { return Encode(true); } diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index cd2327a45e..14eaa5bce8 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -43,6 +43,7 @@ #include <private/qqmljsdiagnosticmessage_p.h> #include <QtCore/QTextStream> +#include <QtCore/private/qvariant_p.h> #include <QDateTime> #include <QDir> #include <QFileInfo> @@ -118,6 +119,7 @@ #endif #include <private/qv4sqlerrors_p.h> #include <qqmlfile.h> +#include <qmetatype.h> #if USE(PTHREADS) # include <pthread.h> @@ -185,6 +187,91 @@ static void restoreJSValue(QDataStream &stream, void *data) } } +struct JSArrayIterator { + QJSValue const* data; + quint32 index; +}; + +namespace { +void createNewIteratorIfNonExisting(void **iterator) { + if (*iterator == nullptr) + *iterator = new JSArrayIterator; +} +} + +static QtMetaTypePrivate::QSequentialIterableImpl jsvalueToSequence (const QJSValue& value) { + using namespace QtMetaTypePrivate; + + QSequentialIterableImpl iterator {}; + if (!value.isArray()) { + // set up some functions so that non-array QSequentialIterables do not crash + // but instead appear as an empty sequence + iterator._size = [](const void *) {return 0;}; + iterator._moveToBegin = [](const void *, void **) {}; + iterator._moveToEnd = [](const void *, void **) {}; + iterator._advance = [](void **, int) {}; + iterator._equalIter = [](void * const *, void * const *){return true; /*all iterators are nullptr*/}; + iterator._destroyIter = [](void **){}; + return iterator; + } + + iterator._iterable = &value; + iterator._iterator = nullptr; + iterator._metaType_id = qMetaTypeId<QVariant>(); + iterator._metaType_flags = QVariantConstructionFlags::ShouldDeleteVariantData; + iterator._iteratorCapabilities = RandomAccessCapability | BiDirectionalCapability | ForwardCapability; + iterator._size = [](const void *p) -> int { + return static_cast<QJSValue const *>(p)->property(QString::fromLatin1("length")).toInt(); + }; + /* Lifetime management notes: + * _at and _get return a pointer to a JSValue allocated via QMetaType::create + * Because we set QVariantConstructionFlags::ShouldDeleteVariantData, QSequentialIterable::at + * and QSequentialIterable::operator*() will free that memory + */ + + iterator._at = [](const void *iterable, int index) -> void const * { + auto const value = static_cast<QJSValue const *>(iterable)->property(quint32(index)).toVariant(); + return QMetaType::create(qMetaTypeId<QVariant>(), &value); + }; + iterator._moveToBegin = [](const void *iterable, void **iterator) { + createNewIteratorIfNonExisting(iterator); + auto jsArrayIterator = static_cast<JSArrayIterator *>(*iterator); + jsArrayIterator->index = 0; + jsArrayIterator->data = reinterpret_cast<QJSValue const*>(iterable); + }; + iterator._moveToEnd = [](const void *iterable, void **iterator) { + createNewIteratorIfNonExisting(iterator); + auto jsArrayIterator = static_cast<JSArrayIterator *>(*iterator); + auto length = static_cast<QJSValue const *>(iterable)->property(QString::fromLatin1("length")).toInt(); + jsArrayIterator->data = reinterpret_cast<QJSValue const*>(iterable); + jsArrayIterator->index = quint32(length); + }; + iterator._advance = [](void **iterator, int advanceBy) { + static_cast<JSArrayIterator *>(*iterator)->index += quint32(advanceBy); + }; + iterator._get = []( void * const *iterator, int metaTypeId, uint flags) -> VariantData { + auto const * const arrayIterator = static_cast<const JSArrayIterator *>(*iterator); + QJSValue const * const jsArray = arrayIterator->data; + auto const value = jsArray->property(arrayIterator->index).toVariant(); + Q_ASSERT(flags & QVariantConstructionFlags::ShouldDeleteVariantData); + return {metaTypeId, QMetaType::create(qMetaTypeId<QVariant>(), &value), flags}; + }; + iterator._destroyIter = [](void **iterator) { + delete static_cast<JSArrayIterator *>(*iterator); + }; + iterator._equalIter = [](void * const *p, void * const *other) { + auto this_ = static_cast<const JSArrayIterator *>(*p); + auto that_ = static_cast<const JSArrayIterator *>(*other); + return this_->index == that_->index && this_->data == that_->data; + }; + iterator._copyIter = [](void **iterator, void * const * otherIterator) { + auto *otherIter = (static_cast<JSArrayIterator const *>(*otherIterator)); + static_cast<JSArrayIterator *>(*iterator)->index = otherIter->index; + static_cast<JSArrayIterator *>(*iterator)->data = otherIter->data; + }; + return iterator; +} + ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) : executableAllocator(new QV4::ExecutableAllocator) , regExpAllocator(new QV4::ExecutableAllocator) @@ -712,6 +799,8 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) QMetaType::registerConverter<QJSValue, QVariantList>(convertJSValueToVariantType<QVariantList>); if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QStringList>()) QMetaType::registerConverter<QJSValue, QStringList>(convertJSValueToVariantType<QStringList>); + if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QtMetaTypePrivate::QSequentialIterableImpl>()) + QMetaType::registerConverter<QJSValue, QtMetaTypePrivate::QSequentialIterableImpl>(jsvalueToSequence); QMetaType::registerStreamOperators(qMetaTypeId<QJSValue>(), saveJSValue, restoreJSValue); QV4::QObjectWrapper::initializeBindings(this); @@ -1178,8 +1267,11 @@ QUrl ExecutionEngine::resolvedUrl(const QString &file) void ExecutionEngine::markObjects(MarkStack *markStack) { for (int i = 0; i < NClasses; ++i) - if (classes[i]) + if (classes[i]) { classes[i]->mark(markStack); + if (markStack->top >= markStack->limit) + markStack->drain(); + } markStack->drain(); identifierTable->markObjects(markStack); @@ -2238,4 +2330,9 @@ int ExecutionEngine::registerExtension() return registrationData()->extensionCount++; } +QNetworkAccessManager *QV4::detail::getNetworkAccessManager(ExecutionEngine *engine) +{ + return engine->qmlEngine()->networkAccessManager(); +} + QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index d233347060..e0025feb00 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -91,7 +91,14 @@ class PageAllocation; QT_BEGIN_NAMESPACE -namespace QV4 { struct QObjectMethod; } +class QNetworkAccessManager; + +namespace QV4 { +struct QObjectMethod; +namespace detail { +QNetworkAccessManager *getNetworkAccessManager(ExecutionEngine *engine); +} +} // Used to allow a QObject method take and return raw V4 handles without having to expose // 48 in the public API. @@ -348,6 +355,8 @@ public: FunctionObject *getStackFunction() const { return reinterpret_cast<FunctionObject *>(jsObjects + GetStack_Function); } FunctionObject *thrower() const { return reinterpret_cast<FunctionObject *>(jsObjects + ThrowerObject); } + QNetworkAccessManager* (*networkAccessManager)(ExecutionEngine*) = detail::getNetworkAccessManager; + enum JSStrings { String_Empty, String_undefined, diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index 6fb7946023..dfef52583e 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -364,7 +364,13 @@ ReturnedValue FunctionPrototype::method_apply(const QV4::FunctionObject *b, cons if (!arr) return v4->throwTypeError(); - uint len = arr->getLength(); + const qint64 len64 = arr->getLength(); + if (len64 < 0ll || len64 > qint64(std::numeric_limits<int>::max())) + return v4->throwRangeError(QStringLiteral("Invalid array length.")); + if (len64 > qint64(v4->jsStackLimit - v4->jsStackTop)) + return v4->throwRangeError(QStringLiteral("Array too large for apply().")); + + const uint len = uint(len64); Scope scope(v4); Value *arguments = scope.alloc<Scope::Uninitialized>(len); diff --git a/src/qml/jsruntime/qv4object_p.h b/src/qml/jsruntime/qv4object_p.h index f3375929a3..9e901d6a34 100644 --- a/src/qml/jsruntime/qv4object_p.h +++ b/src/qml/jsruntime/qv4object_p.h @@ -157,7 +157,7 @@ struct Q_QML_EXPORT Object: Managed { const Value *propertyData(uint index) const { return d()->propertyData(index); } Heap::ArrayData *arrayData() const { return d()->arrayData; } - void setArrayData(ArrayData *a) { d()->arrayData.set(engine(), a->d()); } + void setArrayData(ArrayData *a) { d()->arrayData.set(engine(), a ? a->d() : nullptr); } void getProperty(const InternalClassEntry &entry, Property *p) const; void setProperty(const InternalClassEntry &entry, const Property *p); diff --git a/src/qml/jsruntime/qv4objectiterator.cpp b/src/qml/jsruntime/qv4objectiterator.cpp index e529b8e86b..65f6fa8b12 100644 --- a/src/qml/jsruntime/qv4objectiterator.cpp +++ b/src/qml/jsruntime/qv4objectiterator.cpp @@ -182,7 +182,7 @@ PropertyKey ForInIteratorObject::nextProperty() const if (d()->current != d()->object) { o = d()->object; bool shadowed = false; - while (o->d() != c->heapObject()) { + while (o && o->d() != c->heapObject()) { if (o->getOwnProperty(key) != Attr_Invalid) { shadowed = true; break; diff --git a/src/qml/jsruntime/qv4objectproto.cpp b/src/qml/jsruntime/qv4objectproto.cpp index 3d3b3f413f..7d910a1cbc 100644 --- a/src/qml/jsruntime/qv4objectproto.cpp +++ b/src/qml/jsruntime/qv4objectproto.cpp @@ -658,6 +658,7 @@ ReturnedValue ObjectPrototype::method_toString(const FunctionObject *b, const Va ReturnedValue ObjectPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); + CHECK_STACK_LIMITS(scope.engine) ScopedObject o(scope, thisObject->toObject(scope.engine)); if (!o) RETURN_UNDEFINED(); diff --git a/src/qml/jsruntime/qv4proxy.cpp b/src/qml/jsruntime/qv4proxy.cpp index 9325e2e53b..51f96b9003 100644 --- a/src/qml/jsruntime/qv4proxy.cpp +++ b/src/qml/jsruntime/qv4proxy.cpp @@ -515,7 +515,7 @@ ProxyObjectOwnPropertyKeyIterator::ProxyObjectOwnPropertyKeyIterator(ArrayObject PropertyKey ProxyObjectOwnPropertyKeyIterator::next(const Object *m, Property *pd, PropertyAttributes *attrs) { - if (index >= len) + if (index >= len || m == nullptr) return PropertyKey::invalid(); Scope scope(m); diff --git a/src/qml/jsruntime/qv4regexp.cpp b/src/qml/jsruntime/qv4regexp.cpp index 4ed1dbd5aa..76daead842 100644 --- a/src/qml/jsruntime/qv4regexp.cpp +++ b/src/qml/jsruntime/qv4regexp.cpp @@ -73,14 +73,13 @@ DEFINE_MANAGED_VTABLE(RegExp); uint RegExp::match(const QString &string, int start, uint *matchOffsets) { - static const uint offsetJITFail = std::numeric_limits<unsigned>::max() - 1; - if (!isValid()) return JSC::Yarr::offsetNoMatch; WTF::String s(string); #if ENABLE(YARR_JIT) + static const uint offsetJITFail = std::numeric_limits<unsigned>::max() - 1; auto *priv = d(); if (priv->hasValidJITCode()) { uint ret = JSC::Yarr::offsetNoMatch; diff --git a/src/qml/jsruntime/qv4regexpobject.cpp b/src/qml/jsruntime/qv4regexpobject.cpp index c1a42c4afa..f1375e4ca4 100644 --- a/src/qml/jsruntime/qv4regexpobject.cpp +++ b/src/qml/jsruntime/qv4regexpobject.cpp @@ -961,8 +961,8 @@ ReturnedValue RegExpPrototype::method_compile(const FunctionObject *b, const Val return scope.engine->throwTypeError(); Scoped<RegExpObject> re(scope, scope.engine->regExpCtor()->callAsConstructor(argv, argc)); - - r->d()->value.set(scope.engine, re->value()); + if (re) // Otherwise the regexp constructor should have thrown an exception + r->d()->value.set(scope.engine, re->value()); return Encode::undefined(); } diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index 9c50ac2b95..2ed1971235 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -1409,7 +1409,8 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, if (scope.engine->hasException || l != len) return scope.engine->throwTypeError(); - if (offset + l > a->length()) + const uint aLength = a->length(); + if (offset > aLength || l > aLength - offset) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); uint idx = 0; @@ -1439,7 +1440,9 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, return scope.engine->throwTypeError(); uint l = srcTypedArray->length(); - if (offset + l > a->length()) + + const uint aLength = a->length(); + if (offset > aLength || l > aLength - offset) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); char *dest = buffer->d()->data->data() + a->d()->byteOffset + offset*elementSize; diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index 4e901721cb..ae12033eb4 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -555,6 +555,8 @@ struct ValueArray { } else { while (v < end) { v->mark(markStack); + if (markStack->top >= markStack->limit) + markStack->drain(); ++v; } } diff --git a/src/qml/memory/qv4mm.cpp b/src/qml/memory/qv4mm.cpp index 3465036c86..35b2ff2749 100644 --- a/src/qml/memory/qv4mm.cpp +++ b/src/qml/memory/qv4mm.cpp @@ -1220,6 +1220,8 @@ void MemoryManager::collectFromJSStack(MarkStack *markStack) const Q_ASSERT(m->inUse()); // Skip pointers to already freed objects, they are bogus as well m->mark(markStack); + if (markStack->top >= markStack->limit) + markStack->drain(); } ++v; } diff --git a/src/qml/qml/qqmlapplicationengine.cpp b/src/qml/qml/qqmlapplicationengine.cpp index adb036e2d0..7f80fe5e1c 100644 --- a/src/qml/qml/qqmlapplicationengine.cpp +++ b/src/qml/qml/qqmlapplicationengine.cpp @@ -127,7 +127,7 @@ void QQmlApplicationEnginePrivate::finishLoad(QQmlComponent *c) switch (c->status()) { case QQmlComponent::Error: qWarning() << "QQmlApplicationEngine failed to load component"; - qWarning() << qPrintable(c->errorString()); + warning(c->errors()); q->objectCreated(nullptr, c->url()); break; case QQmlComponent::Ready: { diff --git a/src/qml/qml/qqmlxmlhttprequest.cpp b/src/qml/qml/qqmlxmlhttprequest.cpp index df4639bb1d..c2e7be73e7 100644 --- a/src/qml/qml/qqmlxmlhttprequest.cpp +++ b/src/qml/qml/qqmlxmlhttprequest.cpp @@ -1651,7 +1651,7 @@ struct QQmlXMLHttpRequestCtor : public FunctionObject Scope scope(f->engine()); const QQmlXMLHttpRequestCtor *ctor = static_cast<const QQmlXMLHttpRequestCtor *>(f); - QQmlXMLHttpRequest *r = new QQmlXMLHttpRequest(scope.engine->qmlEngine()->networkAccessManager(), scope.engine); + QQmlXMLHttpRequest *r = new QQmlXMLHttpRequest(scope.engine->networkAccessManager(scope.engine), scope.engine); Scoped<QQmlXMLHttpRequestWrapper> w(scope, scope.engine->memoryManager->allocate<QQmlXMLHttpRequestWrapper>(r)); ScopedObject proto(scope, ctor->d()->proto); w->setPrototypeUnchecked(proto); diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp index d634a48443..f5e723419e 100644 --- a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp @@ -1553,11 +1553,12 @@ static QString jsStack(QV4::ExecutionEngine *engine) { return stack; } -static QString serializeArray(Object *array, ExecutionEngine *v4) { +static QString serializeArray(Object *array, ExecutionEngine *v4, QSet<QV4::Heap::Object *> &alreadySeen) { Scope scope(v4); ScopedValue val(scope); QString result; + alreadySeen.insert(array->d()); result += QLatin1Char('['); const uint length = array->getLength(); for (uint i = 0; i < length; ++i) { @@ -1565,12 +1566,15 @@ static QString serializeArray(Object *array, ExecutionEngine *v4) { result += QLatin1Char(','); val = array->get(i); if (val->isManaged() && val->managed()->isArrayLike()) - result += serializeArray(val->objectValue(), v4); + if (!alreadySeen.contains(val->objectValue()->d())) + result += serializeArray(val->objectValue(), v4, alreadySeen); + else + result += QLatin1String("[Circular]"); else result += val->toQStringNoThrow(); } result += QLatin1Char(']'); - + alreadySeen.remove(array->d()); return result; }; @@ -1600,8 +1604,9 @@ static ReturnedValue writeToConsole(const FunctionObject *b, const Value *, cons if (i != start) result.append(QLatin1Char(' ')); + QSet<QV4::Heap::Object *> alreadySeenElements; if (argv[i].isManaged() && argv[i].managed()->isArrayLike()) - result.append(serializeArray(argv[i].objectValue(), v4)); + result.append(serializeArray(argv[i].objectValue(), v4, alreadySeenElements)); else result.append(argv[i].toQStringNoThrow()); } diff --git a/src/qmlmodels/qqmladaptormodel.cpp b/src/qmlmodels/qqmladaptormodel.cpp index 012540244f..cf0d8fbb2f 100644 --- a/src/qmlmodels/qqmladaptormodel.cpp +++ b/src/qmlmodels/qqmladaptormodel.cpp @@ -199,12 +199,11 @@ public: RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"))); const QQmlAdaptorModel *const model = static_cast<QQmlDMCachedModelData *>(o->d()->item)->type->model; - if (o->d()->item->index >= 0 && *model) { - const QAbstractItemModel * const aim = model->aim(); - RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex)))); - } else { - RETURN_RESULT(QV4::Encode(false)); + if (o->d()->item->index >= 0) { + if (const QAbstractItemModel *const aim = model->aim()) + RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex)))); } + RETURN_RESULT(QV4::Encode(false)); } @@ -400,23 +399,24 @@ public: bool hasModelChildren() const { - if (index >= 0 && *type->model) { - const QAbstractItemModel * const model = type->model->aim(); - return model->hasChildren(model->index(row, column, type->model->rootIndex)); - } else { - return false; + if (index >= 0) { + if (const QAbstractItemModel *const model = type->model->aim()) + return model->hasChildren(model->index(row, column, type->model->rootIndex)); } + return false; } QVariant value(int role) const override { - return type->model->aim()->index(row, column, type->model->rootIndex).data(role); + if (const QAbstractItemModel *aim = type->model->aim()) + return aim->index(row, column, type->model->rootIndex).data(role); + return QVariant(); } void setValue(int role, const QVariant &value) override { - type->model->aim()->setData( - type->model->aim()->index(row, column, type->model->rootIndex), value, role); + if (QAbstractItemModel *aim = type->model->aim()) + aim->setData(aim->index(row, column, type->model->rootIndex), value, role); } QV4::ReturnedValue get() override @@ -444,12 +444,16 @@ public: int rowCount(const QQmlAdaptorModel &model) const override { - return model.aim()->rowCount(model.rootIndex); + if (const QAbstractItemModel *aim = model.aim()) + return aim->rowCount(model.rootIndex); + return 0; } int columnCount(const QQmlAdaptorModel &model) const override { - return model.aim()->columnCount(model.rootIndex); + if (const QAbstractItemModel *aim = model.aim()) + return aim->columnCount(model.rootIndex); + return 0; } void cleanup(QQmlAdaptorModel &) const override @@ -464,39 +468,46 @@ public: dataType->initializeMetaType(model); } - QHash<QByteArray, int>::const_iterator it = roleNames.find(role.toUtf8()); - if (it != roleNames.end()) { - return model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex).data(*it); - } else if (role == QLatin1String("hasModelChildren")) { - return QVariant(model.aim()->hasChildren(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex))); - } else { - return QVariant(); + if (const QAbstractItemModel *aim = model.aim()) { + QHash<QByteArray, int>::const_iterator it = roleNames.find(role.toUtf8()); + if (it != roleNames.end()) { + return aim->index(model.rowAt(index), model.columnAt(index), + model.rootIndex).data(*it); + } else if (role == QLatin1String("hasModelChildren")) { + return QVariant(aim->hasChildren(aim->index(model.rowAt(index), + model.columnAt(index), + model.rootIndex))); + } } + return QVariant(); } QVariant parentModelIndex(const QQmlAdaptorModel &model) const override { - return model - ? QVariant::fromValue(model.aim()->parent(model.rootIndex)) - : QVariant(); + if (const QAbstractItemModel *aim = model.aim()) + return QVariant::fromValue(aim->parent(model.rootIndex)); + return QVariant(); } QVariant modelIndex(const QQmlAdaptorModel &model, int index) const override { - return model - ? QVariant::fromValue(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex)) - : QVariant(); + if (const QAbstractItemModel *aim = model.aim()) + return QVariant::fromValue(aim->index(model.rowAt(index), model.columnAt(index), + model.rootIndex)); + return QVariant(); } bool canFetchMore(const QQmlAdaptorModel &model) const override { - return model && model.aim()->canFetchMore(model.rootIndex); + if (const QAbstractItemModel *aim = model.aim()) + return aim->canFetchMore(model.rootIndex); + return false; } void fetchMore(QQmlAdaptorModel &model) const override { - if (model) - model.aim()->fetchMore(model.rootIndex); + if (QAbstractItemModel *aim = model.aim()) + aim->fetchMore(model.rootIndex); } QQmlDelegateModelItem *createItem( @@ -516,7 +527,8 @@ public: setModelDataType<QQmlDMAbstractItemModelData>(&builder, this); const QByteArray propertyType = QByteArrayLiteral("QVariant"); - const QHash<int, QByteArray> names = model.aim()->roleNames(); + const QAbstractItemModel *aim = model.aim(); + const QHash<int, QByteArray> names = aim ? aim->roleNames() : QHash<int, QByteArray>(); for (QHash<int, QByteArray>::const_iterator it = names.begin(), cend = names.end(); it != cend; ++it) { const int propertyId = propertyRoles.count(); propertyRoles.append(it.key()); diff --git a/src/qmlworkerscript/qquickworkerscript.cpp b/src/qmlworkerscript/qquickworkerscript.cpp index 9e4b3e1b46..72ae6d5bd4 100644 --- a/src/qmlworkerscript/qquickworkerscript.cpp +++ b/src/qmlworkerscript/qquickworkerscript.cpp @@ -130,6 +130,7 @@ struct WorkerScript : public QV4::ExecutionEngine { QQuickWorkerScriptEnginePrivate *p = nullptr; QUrl source; QQuickWorkerScript *owner = nullptr; + QScopedPointer<QNetworkAccessManager> scriptLocalNAM; int id = -1; }; @@ -389,6 +390,16 @@ WorkerScript::WorkerScript(int id, QQuickWorkerScriptEnginePrivate *parent) QV4::ScopedValue sendMessage(scope, QV4::FunctionObject::createBuiltinFunction(this, name, QQuickWorkerScriptEnginePrivate::method_sendMessage, 1)); api->put(QV4::ScopedString(scope, scope.engine->newString(QStringLiteral("sendMessage"))), sendMessage); globalObject->put(QV4::ScopedString(scope, scope.engine->newString(QStringLiteral("WorkerScript"))), api); + networkAccessManager = [](QV4::ExecutionEngine *engine){ + auto *workerScript = static_cast<WorkerScript *>(engine); + if (workerScript->scriptLocalNAM) + return workerScript->scriptLocalNAM.get(); + if (auto *namFactory = workerScript->p->qmlengine->networkAccessManagerFactory()) + workerScript->scriptLocalNAM.reset(namFactory->create(workerScript->p)); + else + workerScript->scriptLocalNAM.reset(new QNetworkAccessManager(workerScript->p)); + return workerScript->scriptLocalNAM.get(); + }; } int QQuickWorkerScriptEngine::registerWorkerScript(QQuickWorkerScript *owner) diff --git a/src/quick/configure.json b/src/quick/configure.json index 0cb1e7470b..f8277332dc 100644 --- a/src/quick/configure.json +++ b/src/quick/configure.json @@ -3,9 +3,11 @@ "depends": [ "core-private", "qml-private", + "gui", "gui-private", "qmlmodels-private" ], + "condition": "module.gui", "testDir": "../../config.tests", "commandline": { diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index bc0d04c763..baef37a0ff 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -533,7 +533,7 @@ public: void refWindow(QQuickWindow *); void derefWindow(); - QQuickItem *subFocusItem; + QPointer<QQuickItem> subFocusItem; void updateSubFocusItem(QQuickItem *scope, bool focus); QTransform windowToItemTransform() const; diff --git a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp index d0108bc56e..89c9ea4d5b 100644 --- a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp +++ b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp @@ -145,7 +145,9 @@ void QSGRhiTextureGlyphCache::prepareGlyphImage(QImage *img) { const int maskWidth = img->width(); const int maskHeight = img->height(); +#if Q_BYTE_ORDER != Q_BIG_ENDIAN const bool supportsBgra = m_rhi->isTextureFormatSupported(QRhiTexture::BGRA8); +#endif m_bgra = false; if (img->format() == QImage::Format_Mono) { diff --git a/tests/auto/particles/qquickitemparticle/data/loader.qml b/tests/auto/particles/qquickitemparticle/data/loader.qml new file mode 100644 index 0000000000..beac7a0410 --- /dev/null +++ b/tests/auto/particles/qquickitemparticle/data/loader.qml @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Particles 2.0 + +Rectangle { + color: "black" + width: 320 + height: 320 + + Component { + id: component + + ParticleSystem { + id: sys + objectName: "system" + anchors.fill: parent + running: visible + + ItemParticle { + delegate: Image { source: "../../shared/star.png" } + } + + Emitter { + //0,0 position + size: 32 + emitRate: 10 + lifeSpan: 150000 + } + } + } + + Loader { + id: loader + objectName: "loader" + sourceComponent: component + anchors.fill: parent + } +} diff --git a/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp b/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp index 0087c74a9c..28ebbb3c05 100644 --- a/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp +++ b/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp @@ -46,6 +46,8 @@ private slots: void test_deletion(); void test_noDeletion(); void test_takeGive(); + void test_noCrashOnReset(); + void test_noLeakWhenDeleted(); }; void tst_qquickitemparticle::initTestCase() @@ -120,6 +122,41 @@ void tst_qquickitemparticle::test_takeGive() delete view; } +void tst_qquickitemparticle::test_noCrashOnReset() +{ + QQuickView* view = createView(testFileUrl("basic.qml"), 600); + QQuickParticleSystem* system = view->rootObject()->findChild<QQuickParticleSystem*>("system"); + + for (int i = 0; i < 10; ++i) { + ensureAnimTime(16, system->m_animation); + system->reset(); + } + + delete view; +} + +void tst_qquickitemparticle::test_noLeakWhenDeleted() +{ + QQuickView* view = createView(testFileUrl("loader.qml"), 500); + QQuickParticleSystem* system = view->rootObject()->findChild<QQuickParticleSystem*>("system"); + ensureAnimTime(100, system->m_animation); + + auto particles = qAsConst(system->groupData[0]->data); + QVERIFY(!particles.isEmpty()); + + QQuickParticleData* firstParticleData = particles.first(); + QPointer<QQuickItem> firstParticleDelegate = firstParticleData->delegate; + QVERIFY(!firstParticleDelegate.isNull()); + + QQuickItem* loader = view->rootObject()->findChild<QQuickItem*>("loader"); + loader->setProperty("active", false); //This should destroy the ParticleSystem, ItemParticle and Emitter + + QTest::qWait(1); //Process events to make sure the loader is properly unloaded + QVERIFY(firstParticleDelegate.isNull()); //Delegates should be deleted + + delete view; +} + QTEST_MAIN(tst_qquickitemparticle); #include "tst_qquickitemparticle.moc" diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index f1ff396d4f..66a526fda8 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -252,6 +252,17 @@ private slots: void interrupt(); void triggerBackwardJumpWithDestructuring(); + void arrayConcatOnSparseArray(); + void sortSparseArray(); + void compileBrokenRegexp(); + void sortNonStringArray(); + void iterateInvalidProxy(); + void applyOnHugeArray(); + + void tostringRecursionCheck(); + void arrayIncludesWithLargeArray(); + void printCircularArray(); + void typedArraySet(); public: Q_INVOKABLE QJSValue throwingCppMethod1(); @@ -4961,6 +4972,177 @@ void tst_QJSEngine::triggerBackwardJumpWithDestructuring() QVERIFY(!value.isError()); } +void tst_QJSEngine::arrayConcatOnSparseArray() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::GarbageCollectionExtension); + const auto value = engine.evaluate( + "(function() {\n" + " const v4 = [1,2,3];\n" + " const v7 = [4,5];\n" + " v7.length = 1337;\n" + " const v9 = v4.concat(v7);\n" + " gc();\n" + " return v9;\n" + "})();"); + QCOMPARE(value.property("length").toInt(), 1340); + for (int i = 0; i < 5; ++i) + QCOMPARE(value.property(i).toInt(), i + 1); + for (int i = 5; i < 1340; ++i) + QVERIFY(value.property(i).isUndefined()); +} + +void tst_QJSEngine::sortSparseArray() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + const auto value = engine.evaluate( + "(function() {\n" + " var sparse = [0];\n" + " sparse = Object.defineProperty(sparse, \"10\", " + " {get: ()=>{return 2}, set: ()=>{return 2}} );\n" + " return Array.prototype.sort.call(sparse, ()=>{});\n" + "})();"); + + QCOMPARE(value.property("length").toInt(), 11); + QVERIFY(value.property(0).isNumber()); + QCOMPARE(value.property(0).toInt(), 0); + QVERIFY(value.property(1).isNumber()); + QCOMPARE(value.property(1).toInt(), 2); + QVERIFY(value.property(10).isUndefined()); +} + +void tst_QJSEngine::compileBrokenRegexp() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "(function() {" + "var ret = new RegExp(Array(4097).join(" + " String.fromCharCode(58)) + Array(4097).join(String.fromCharCode(480)) " + " + Array(65537).join(String.fromCharCode(5307)));" + "return RegExp.prototype.compile.call(ret, 'a','b');" + "})();" + ); + + QVERIFY(value.isError()); + QCOMPARE(value.toString(), "SyntaxError: Invalid flags supplied to RegExp constructor"); +} + +void tst_QJSEngine::tostringRecursionCheck() +{ + QJSEngine engine; + auto value = engine.evaluate(R"js( + var a = {}; + var b = new Array(1337); + function main() { + var ret = a.toLocaleString; + b[1] = ret; + Array = {}; + Object.toString = b[1]; + var ret = String.prototype.lastIndexOf.call({}, b[1]); + var ret = String.prototype.charAt.call(Function, Object); + } + main(); + )js"); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), QLatin1String("RangeError: Maximum call stack size exceeded.")); +} + +void tst_QJSEngine::arrayIncludesWithLargeArray() +{ + QJSEngine engine; + auto value = engine.evaluate(R"js( + let arr = new Array(10000000) + arr.includes(42) + )js"); + QVERIFY(value.isBool()); + QCOMPARE(value.toBool(), false); +} + +void tst_QJSEngine::printCircularArray() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + QTest::ignoreMessage(QtMsgType::QtDebugMsg, "[[Circular]]"); + auto value = engine.evaluate(R"js( + let v1 = [] + v1.push(v1) + console.log(v1) + )js"); +} + +void tst_QJSEngine::sortNonStringArray() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "const v4 = [Symbol.iterator, 1];" + "const v5 = v4.sort();" + ); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), "TypeError: Cannot convert a symbol to a string."); +} + +void tst_QJSEngine::iterateInvalidProxy() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "const v1 = new Proxy(Reflect, Reflect);" + "for (const v2 in v1) {}" + "const v3 = { getOwnPropertyDescriptor: eval, getPrototypeOf: eval };" + "const v4 = new Proxy(v3, v3);" + "for (const v5 in v4) {}" + ); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), "TypeError: Type error"); +} + +void tst_QJSEngine::applyOnHugeArray() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "var a = new Array(10);" + "a[536870912] = Function;" + "Function.apply('aaaaaaaa', a);" + ); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), "RangeError: Array too large for apply()."); +} + +void tst_QJSEngine::typedArraySet() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "(function() {" + " var length = 0xffffffe;" + " var offset = 0xfffffff0;" + " var e1;" + " var e2;" + " try {" + " var source1 = new Int8Array(length);" + " var target1 = new Int8Array(length);" + " target1.set(source1, offset);" + " } catch (intError) {" + " e1 = intError;" + " }" + " try {" + " var source2 = new Array(length);" + " var target2 = new Int8Array(length);" + " target2.set(source2, offset);" + " } catch (arrayError) {" + " e2 = arrayError;" + " }" + " return [e1, e2];" + "})();" + ); + + QVERIFY(value.isArray()); + for (int i = 0; i < 2; ++i) { + const auto error = value.property(i); + QVERIFY(error.isError()); + QCOMPARE(error.toString(), "RangeError: TypedArray.set: out of range"); + } +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp index 37d0ea4dea..95f554776f 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp @@ -2708,6 +2708,70 @@ void tst_QJSValue::nestedObjectToVariant() QCOMPARE(o.toVariant(), expected); } +static int instanceCount = 0; + +struct MyType +{ + MyType(int n = 0, const char *t=nullptr): number(n), text(t) + { + ++instanceCount; + } + MyType(const MyType &other) + : number(other.number), text(other.text) + { + ++instanceCount; + } + ~MyType() + { + --instanceCount; + } + int number; + const char *text; +}; + +Q_DECLARE_METATYPE(MyType) +Q_DECLARE_METATYPE(MyType*) + +void tst_QJSValue::jsvalueArrayToSequenceType() +{ + QCOMPARE(instanceCount, 0); + { + QJSEngine eng {}; + auto testObject = eng.newObject(); + testObject.setProperty("test", 42); + testObject.setProperty("mytypeobject", eng.toScriptValue(QVariant::fromValue(MyType {42, "hello"}))); + auto array = eng.newArray(4); + array.setProperty(0, QLatin1String("Hello World")); + array.setProperty(1, 42); + array.setProperty(2, QJSValue(QJSValue::UndefinedValue)); + array.setProperty(3, testObject); + auto asVariant = QVariant::fromValue(array); + QVERIFY(asVariant.canConvert<QVariantList>()); + auto asIterable = asVariant.value<QSequentialIterable>(); + for (auto it = asIterable.begin(); it != asIterable.end(); ++it) { + Q_UNUSED(*it) + } + int i = 0; + for (QVariant myVariant: asIterable) { + QCOMPARE(myVariant.isValid(), i != 2); + ++i; + } + QVERIFY(asIterable.at(2).value<QVariant>().isNull()); + QCOMPARE(asIterable.at(3).value<QVariantMap>().find("mytypeobject")->value<MyType>().number, 42); + QCOMPARE(asIterable.at(0).value<QVariant>().toString(), QLatin1String("Hello World")); + auto it1 = asIterable.begin(); + auto it2 = asIterable.begin(); + QCOMPARE((*it1).value<QVariant>().toString(), (*it2).value<QVariant>().toString()); + QCOMPARE((*it1).value<QVariant>().toString(), QLatin1String("Hello World")); + ++it2; + QCOMPARE((*it1).value<QVariant>().toString(), QLatin1String("Hello World")); + QCOMPARE((*it2).value<QVariant>().toInt(), 42); + } + // tests need to be done after engine has been destroyed, else it will hold a reference until + // the gc decides to collect it + QCOMPARE(instanceCount, 0); +} + void tst_QJSValue::deleteFromDifferentThread() { #if !QT_CONFIG(thread) diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.h b/tests/auto/qml/qjsvalue/tst_qjsvalue.h index f704169d43..d85b9a0552 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.h +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.h @@ -142,6 +142,8 @@ private slots: void nestedObjectToVariant_data(); void nestedObjectToVariant(); + void jsvalueArrayToSequenceType(); + void deleteFromDifferentThread(); private: diff --git a/tests/auto/qml/qqmlapplicationengine/data/invalid.qml b/tests/auto/qml/qqmlapplicationengine/data/invalid.qml new file mode 100644 index 0000000000..5939a69a7a --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/data/invalid.qml @@ -0,0 +1,5 @@ +import QtQml 2.12 + +QtObject { + JUST_SOME_INVALID_PROPERTY: 0 +} diff --git a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp index 0f5eea8b95..5e855efe1a 100644 --- a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp +++ b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp @@ -30,6 +30,7 @@ #include <QQmlApplicationEngine> #include <QScopedPointer> #include <QSignalSpy> +#include <QRegularExpression> #if QT_CONFIG(process) #include <QProcess> #endif @@ -53,6 +54,7 @@ private slots: void loadTranslation_data(); void loadTranslation(); void setInitialProperties(); + void failureToLoadTriggersWarningSignal(); private: QString buildDir; @@ -293,6 +295,20 @@ void tst_qqmlapplicationengine::setInitialProperties() } } +Q_DECLARE_METATYPE(QList<QQmlError>) // for signalspy below + +void tst_qqmlapplicationengine::failureToLoadTriggersWarningSignal() +{ + auto url = testFileUrl("invalid.qml"); + qRegisterMetaType<QList<QQmlError>>(); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QQmlApplicationEngine failed to load component"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(url.toString() + QLatin1Char('*'))); + QQmlApplicationEngine test; + QSignalSpy warningObserver(&test, &QQmlApplicationEngine::warnings); + test.load(url); + QTRY_COMPARE(warningObserver.count(), 1); +} + QTEST_MAIN(tst_qqmlapplicationengine) #include "tst_qqmlapplicationengine.moc" diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 46297b3c16..ec0db16114 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -381,6 +381,8 @@ private slots: void semicolonAfterProperty(); void hugeStack(); + void gcCrashRegressionTest(); + private: // static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); static void verifyContextLifetime(QQmlContextData *ctxt); @@ -9213,6 +9215,53 @@ void tst_qqmlecmascript::hugeStack() QCOMPARE(qvariant_cast<QJSValue>(huge).property(QLatin1String("length")).toInt(), 33059); } +void tst_qqmlecmascript::gcCrashRegressionTest() +{ + const QString qmljs = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmljs"; + if (!QFile::exists(qmljs)) { + QSKIP("Tets requires qmljs"); + } + QProcess process; + + QTemporaryFile infile; + QVERIFY(infile.open()); + infile.write(R"js( + function i_want_to_break_free() { + var n = 400; + var m = 10; + var regex = new RegExp("(ab)".repeat(n), "g"); // g flag to trigger the vulnerable path + var part = "ab".repeat(n); // matches have to be at least size 2 to prevent interning + var s = (part + "|").repeat(m); + var cnt = 0; + var ary = []; + s.replace(regex, function() { + for (var i = 1; i < arguments.length-2; ++i) { + if (typeof arguments[i] !== 'string') { + i_am_free = arguments[i]; + throw "success"; + } + ary[cnt++] = arguments[i]; // root everything to force GC + } + return "x"; + }); + } + try { i_want_to_break_free(); } catch (e) {console.log("hi") } + console.log(typeof(i_am_free)); // will print "object" + )js"); + infile.close(); + + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + environment.insert("QV4_GC_MAX_STACK_SIZE", "32768"); + + process.setProcessEnvironment(environment); + process.start(qmljs, QStringList({infile.fileName()})); + QVERIFY(process.waitForStarted()); + const qint64 pid = process.processId(); + QVERIFY(pid != 0); + QVERIFY(process.waitForFinished()); + QCOMPARE(process.exitCode(), 0); +} + QTEST_MAIN(tst_qqmlecmascript) #include "tst_qqmlecmascript.moc" diff --git a/tests/auto/qml/qquickworkerscript/data/doRequest.mjs b/tests/auto/qml/qquickworkerscript/data/doRequest.mjs new file mode 100644 index 0000000000..d607c3400d --- /dev/null +++ b/tests/auto/qml/qquickworkerscript/data/doRequest.mjs @@ -0,0 +1,6 @@ +WorkerScript.onMessage = function(message) +{ + var req = new XMLHttpRequest(); + req.open("GET", message.url, true); + req.send(); +}; diff --git a/tests/auto/qml/qquickworkerscript/data/xmlHttpRequest.qml b/tests/auto/qml/qquickworkerscript/data/xmlHttpRequest.qml new file mode 100644 index 0000000000..42136d78f0 --- /dev/null +++ b/tests/auto/qml/qquickworkerscript/data/xmlHttpRequest.qml @@ -0,0 +1,16 @@ +import QtQuick 2.14 + +Rectangle +{ + width: 100 + height: 100 + + WorkerScript + { + source: "doRequest.mjs" + Component.onCompleted: + { + sendMessage({"url": "https://example.com"}); + } + } +} diff --git a/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp b/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp index bb4c9a7c1e..2f79f7157f 100644 --- a/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp +++ b/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp @@ -60,6 +60,7 @@ private slots: void script_function(); void script_var(); void stressDispose(); + void xmlHttpRequest(); private: void waitForEchoMessage(QQuickWorkerScript *worker) { @@ -359,6 +360,13 @@ void tst_QQuickWorkerScript::stressDispose() } } +void tst_QQuickWorkerScript::xmlHttpRequest() +{ + QQmlComponent component(&m_engine, testFileUrl("xmlHttpRequest.qml")); + QScopedPointer<QObject> root{component.create()}; // should not crash + QVERIFY(root); +} + QTEST_MAIN(tst_QQuickWorkerScript) #include "tst_qquickworkerscript.moc" diff --git a/tools/qml/qml.pro b/tools/qml/qml.pro index f086b7bff9..5dcbb3567a 100644 --- a/tools/qml/qml.pro +++ b/tools/qml/qml.pro @@ -8,7 +8,7 @@ RESOURCES += qml.qrc QMAKE_TARGET_DESCRIPTION = QML Runtime -ICON = resources/qml64.png +ICON = resources/qml-64.png win32 { RC_ICONS = resources/qml.ico } diff --git a/tools/qmlcachegen/qtquickcompiler.prf b/tools/qmlcachegen/qtquickcompiler.prf index a31a7f5714..8aac3b9e07 100644 --- a/tools/qmlcachegen/qtquickcompiler.prf +++ b/tools/qmlcachegen/qtquickcompiler.prf @@ -27,7 +27,8 @@ defineReplace(qmlCacheResourceFileOutputName) { } # Flatten RESOURCES that may contain individual files or objects -load(resources) +load(resources_functions) +qtFlattenResources() NEWRESOURCES = QMLCACHE_RESOURCE_FILES = |