aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/jsruntime/qv4qmlcontext.cpp35
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/lostContextObject.qml28
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp43
-rw-r--r--tests/auto/qml/qqmlecmascript/data/deletedContextObjectProperty.qml22
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp57
6 files changed, 170 insertions, 16 deletions
diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp
index ecf56e90c5..0fd7cdc1ff 100644
--- a/src/qml/jsruntime/qv4qmlcontext.cpp
+++ b/src/qml/jsruntime/qv4qmlcontext.cpp
@@ -632,8 +632,17 @@ static ReturnedValue revertObjectMethodLookup(Lookup *l, ExecutionEngine *engine
return QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(l, engine, base);
}
+// The object may be gone once it is being destroyed. Its name is not defined then, just like
+// when resolving the name anew.
+static ReturnedValue throwLostObjectReferenceError(Lookup *l, ExecutionEngine *engine)
+{
+ return engine->throwReferenceError(
+ engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]
+ ->toQString());
+}
+
template<typename Call>
-ReturnedValue callWithScopeObject(ExecutionEngine *engine, Value *base, Call c)
+ReturnedValue callWithScopeObject(Lookup *l, ExecutionEngine *engine, Value *base, Call c)
{
Scope scope(engine);
Scoped<QmlContext> qmlContext(scope, engine->qmlContext());
@@ -641,11 +650,8 @@ ReturnedValue callWithScopeObject(ExecutionEngine *engine, Value *base, Call c)
return QV4::Encode::undefined();
QObject *scopeObject = qmlContext->qmlScope();
- if (!scopeObject)
- return QV4::Encode::undefined();
-
- if (QQmlData::wasDeleted(scopeObject))
- return QV4::Encode::undefined();
+ if (!scopeObject || QQmlData::wasDeleted(scopeObject))
+ return throwLostObjectReferenceError(l, engine);
ScopedValue obj(scope, QV4::QObjectWrapper::wrap(engine, scopeObject));
@@ -657,7 +663,7 @@ ReturnedValue callWithScopeObject(ExecutionEngine *engine, Value *base, Call c)
ReturnedValue QQmlContextWrapper::lookupScopeObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base)
{
- return callWithScopeObject(engine, base, [l, engine, base](const Value &obj) {
+ return callWithScopeObject(l, engine, base, [l, engine, base](const Value &obj) {
const QObjectWrapper::Flags flags = l->forCall
? QObjectWrapper::NoFlag
: QObjectWrapper::AttachMethods;
@@ -669,7 +675,7 @@ ReturnedValue QQmlContextWrapper::lookupScopeObjectProperty(Lookup *l, Execution
ReturnedValue QQmlContextWrapper::lookupScopeObjectMethod(Lookup *l, ExecutionEngine *engine, Value *base)
{
- return callWithScopeObject(engine, base, [l, engine, base](const Value &obj) {
+ return callWithScopeObject(l, engine, base, [l, engine, base](const Value &obj) {
const QObjectWrapper::Flags flags = l->forCall
? QObjectWrapper::NoFlag
: QObjectWrapper::AttachMethods;
@@ -680,7 +686,7 @@ ReturnedValue QQmlContextWrapper::lookupScopeObjectMethod(Lookup *l, ExecutionEn
}
template<typename Call>
-ReturnedValue callWithContextObject(ExecutionEngine *engine, Value *base, Call c)
+ReturnedValue callWithContextObject(Lookup *l, ExecutionEngine *engine, Value *base, Call c)
{
Scope scope(engine);
Scoped<QmlContext> qmlContext(scope, engine->qmlContext());
@@ -692,11 +698,8 @@ ReturnedValue callWithContextObject(ExecutionEngine *engine, Value *base, Call c
return QV4::Encode::undefined();
QObject *contextObject = context->contextObject();
- if (!contextObject)
- return QV4::Encode::undefined();
-
- if (QQmlData::wasDeleted(contextObject))
- return QV4::Encode::undefined();
+ if (!contextObject || QQmlData::wasDeleted(contextObject))
+ return throwLostObjectReferenceError(l, engine);
ScopedValue obj(scope, QV4::QObjectWrapper::wrap(engine, contextObject));
@@ -709,7 +712,7 @@ ReturnedValue callWithContextObject(ExecutionEngine *engine, Value *base, Call c
ReturnedValue QQmlContextWrapper::lookupContextObjectProperty(
Lookup *l, ExecutionEngine *engine, Value *base)
{
- return callWithContextObject(engine, base, [l, engine, base](const Value &obj) {
+ return callWithContextObject(l, engine, base, [l, engine, base](const Value &obj) {
const QObjectWrapper::Flags flags = l->forCall
? QObjectWrapper::NoFlag
: QObjectWrapper::AttachMethods;
@@ -722,7 +725,7 @@ ReturnedValue QQmlContextWrapper::lookupContextObjectProperty(
ReturnedValue QQmlContextWrapper::lookupContextObjectMethod(
Lookup *l, ExecutionEngine *engine, Value *base)
{
- return callWithContextObject(engine, base, [l, engine, base](const Value &obj) {
+ return callWithContextObject(l, engine, base, [l, engine, base](const Value &obj) {
const QObjectWrapper::Flags flags = l->forCall
? QObjectWrapper::NoFlag
: QObjectWrapper::AttachMethods;
diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
index 009edbb34d..92831cb33d 100644
--- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
+++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
@@ -248,6 +248,7 @@ set(qml_files
listPropertyAsModel.qml
listToString.qml
listlength.qml
+ lostContextObject.qml
markJSValue.qml
markRecursive.qml
math.qml
diff --git a/tests/auto/qml/qmlcppcodegen/data/lostContextObject.qml b/tests/auto/qml/qmlcppcodegen/data/lostContextObject.qml
new file mode 100644
index 0000000000..772058190c
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/lostContextObject.qml
@@ -0,0 +1,28 @@
+pragma ComponentBehavior: Bound
+import QtQml
+
+QtObject {
+ id: root
+
+ property QtObject inner: component.createObject(root)
+
+ property Component component: Component {
+ QtObject {
+ property int value: 5
+
+ function read(): int {
+ const v = value
+ console.warn("continued")
+ return v
+ }
+
+ property QtObject child: QtObject {
+ function read(): int {
+ const v = value
+ console.warn("continued")
+ return v
+ }
+ }
+ }
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
index e8ced6cbea..4fa96c7f49 100644
--- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
+++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
@@ -212,6 +212,8 @@ private slots:
void listOfInlineComponent();
void listPropertyAsModel();
void listToString();
+ void lostContextObject_data();
+ void lostContextObject();
void lotsOfRegisters();
void math();
void mathMinMax();
@@ -4006,6 +4008,47 @@ void tst_QmlCppCodegen::listToString()
std::unique_ptr<QObject> o(c.create());
}
+void tst_QmlCppCodegen::lostContextObject_data()
+{
+ QTest::addColumn<bool>("onScopeObject");
+ QTest::newRow("scope object") << true;
+ QTest::newRow("context object") << false;
+}
+
+void tst_QmlCppCodegen::lostContextObject()
+{
+ QFETCH(bool, onScopeObject);
+
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/lostContextObject.qml"_s));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ std::unique_ptr<QObject> root(component.create());
+ QVERIFY(root);
+
+ QObject *inner = root->property("inner").value<QObject *>();
+ QVERIFY(inner);
+ QObject *child = inner->property("child").value<QObject *>();
+ QVERIFY(child);
+
+ // The child outlives its context object, like a control's background.
+ child->setParent(root.get());
+
+ QJSValue read = engine.newQObject(onScopeObject ? inner : child).property(u"read"_s);
+ QVERIFY(read.isCallable());
+
+ // Populate the lookups.
+ QTest::ignoreMessage(QtWarningMsg, "continued");
+ QCOMPARE(read.call().toInt(), 5);
+
+ delete inner;
+
+ // Reading the value of the lost object throws. The function must not continue.
+ QTest::failOnWarning("continued");
+ const QJSValue result = read.call();
+ QVERIFY(result.isError());
+ QCOMPARE(result.errorType(), QJSValue::ReferenceError);
+}
+
void tst_QmlCppCodegen::lotsOfRegisters()
{
QQmlEngine engine;
diff --git a/tests/auto/qml/qqmlecmascript/data/deletedContextObjectProperty.qml b/tests/auto/qml/qqmlecmascript/data/deletedContextObjectProperty.qml
new file mode 100644
index 0000000000..4315eea822
--- /dev/null
+++ b/tests/auto/qml/qqmlecmascript/data/deletedContextObjectProperty.qml
@@ -0,0 +1,22 @@
+import QtQml
+
+QtObject {
+ id: root
+
+ property QtObject inner: component.createObject(root)
+
+ property Component component: Component {
+ QtObject {
+ property int value: 5
+ function method() { return 7 }
+
+ function readValue() { return value }
+ function callMethod() { return method() }
+
+ property QtObject child: QtObject {
+ function readValue() { return value }
+ function callMethod() { return method() }
+ }
+ }
+ }
+}
diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
index f773e8477d..c6b57b8cdd 100644
--- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
+++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
@@ -451,6 +451,8 @@ private slots:
void namedFunctionExpressionOwnNameIsShadowable();
void cuObjectIndex();
void vmeMetaObjectAccessors();
+ void deletedContextObjectProperty_data();
+ void deletedContextObjectProperty();
private:
// static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
@@ -10983,6 +10985,61 @@ void tst_qqmlecmascript::vmeMetaObjectAccessors()
vme->parentVMEMetaObject();
}
+void tst_qqmlecmascript::deletedContextObjectProperty_data()
+{
+ QTest::addColumn<bool>("onScopeObject");
+ QTest::addColumn<QString>("function");
+ QTest::addColumn<QVariant>("expected");
+ QTest::addColumn<bool>("cached");
+
+ for (const bool cached : { false, true }) {
+ const char *suffix = cached ? " cached" : "";
+ QTest::addRow("scope object property%s", suffix)
+ << true << u"readValue"_s << QVariant(5) << cached;
+ QTest::addRow("scope object method%s", suffix)
+ << true << u"callMethod"_s << QVariant(7) << cached;
+ QTest::addRow("context object property%s", suffix)
+ << false << u"readValue"_s << QVariant(5) << cached;
+ QTest::addRow("context object method%s", suffix)
+ << false << u"callMethod"_s << QVariant(7) << cached;
+ }
+}
+
+void tst_qqmlecmascript::deletedContextObjectProperty()
+{
+ QFETCH(bool, onScopeObject);
+ QFETCH(QString, function);
+ QFETCH(QVariant, expected);
+ QFETCH(bool, cached);
+
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("deletedContextObjectProperty.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> root(component.create());
+ QVERIFY(root);
+
+ QObject *inner = root->property("inner").value<QObject *>();
+ QVERIFY(inner);
+ QObject *child = inner->property("child").value<QObject *>();
+ QVERIFY(child);
+
+ // Like a control's background, the child is not a QObject child of its context object. It is
+ // not marked as deleted along with it, and its functions can still run after the context has
+ // lost its context object.
+ child->setParent(root.data());
+
+ QJSValue fn = engine.newQObject(onScopeObject ? inner : child).property(function);
+ QVERIFY(fn.isCallable());
+ if (cached)
+ QCOMPARE(fn.call().toVariant(), expected);
+
+ // No matter whether the lookup was cached before, the name is not defined anymore.
+ QQmlData::markAsDeleted(inner);
+ const QJSValue result = fn.call();
+ QVERIFY(result.isError());
+ QCOMPARE(result.errorType(), QJSValue::ReferenceError);
+}
+
QTEST_MAIN(tst_qqmlecmascript)
#include "tst_qqmlecmascript.moc"