QmlCompiler: Guard against disappearing arrow functions
You can override a QObject method with a JavaScript function and take away the JavaScript function later by swapping out objects. This should not crash. Pick-to: 6.10 6.9 Fixes: QTBUG-140074 Change-Id: I85b17f4f619235024d0f1a27b4ff4128c7a57083 Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
This commit is contained in:
parent
de368f8f4d
commit
7105eb6d0d
|
@ -2139,8 +2139,13 @@ static bool callQObjectMethodAsVariant(
|
|||
QV4::Scope scope(engine);
|
||||
QV4::ScopedValue wrappedObject(scope, QV4::QObjectWrapper::wrap(scope.engine, thisObject));
|
||||
QV4::ScopedFunctionObject function(scope, lookup->getter(scope.engine, wrappedObject));
|
||||
Q_ASSERT(function);
|
||||
Q_ASSERT(lookup->asVariant); // The getter mustn't reset the isVariant flag
|
||||
|
||||
// The getter mustn't reset the isVariant flag
|
||||
Q_ASSERT(lookup->asVariant);
|
||||
|
||||
// Since we have an asVariant lookup, the function may have been overridden in the mean time.
|
||||
if (!function)
|
||||
return false;
|
||||
|
||||
Q_ALLOCA_VAR(QMetaType, types, (argc + 1) * sizeof(QMetaType));
|
||||
std::fill(types, types + argc + 1, QMetaType::fromType<QVariant>());
|
||||
|
@ -2232,31 +2237,6 @@ static bool callArrowFunction(
|
|||
Q_UNREACHABLE_RETURN(false);
|
||||
}
|
||||
|
||||
static bool callArrowFunctionAsVariant(
|
||||
QV4::ExecutionEngine *engine, QV4::ArrowFunction *function,
|
||||
QObject *thisObject, void **args, int argc)
|
||||
{
|
||||
QV4::Function *v4Function = function->function();
|
||||
Q_ASSERT(v4Function);
|
||||
|
||||
switch (v4Function->kind) {
|
||||
case QV4::Function::JsUntyped:
|
||||
// We cannot assert anything here because the method can be shadowed.
|
||||
// That's why we wrap everything in QVariant.
|
||||
case QV4::Function::AotCompiled:
|
||||
case QV4::Function::JsTyped: {
|
||||
Q_ALLOCA_VAR(QMetaType, types, (argc + 1) * sizeof(QMetaType));
|
||||
std::fill(types, types + argc + 1, QMetaType::fromType<QVariant>());
|
||||
function->call(thisObject, args, types, argc);
|
||||
return !engine->hasException;
|
||||
}
|
||||
case QV4::Function::Eval:
|
||||
break;
|
||||
}
|
||||
|
||||
Q_UNREACHABLE_RETURN(false);
|
||||
}
|
||||
|
||||
bool AOTCompiledContext::callQmlContextPropertyLookup(uint index, void **args, int argc) const
|
||||
{
|
||||
QV4::Lookup *lookup = compilationUnit->runtimeLookups + index;
|
||||
|
@ -2436,16 +2416,25 @@ bool AOTCompiledContext::callObjectPropertyLookup(
|
|||
: callQObjectMethod(engine->handle(), lookup, object, args, argc);
|
||||
case QV4::Lookup::Call::GetterQObjectProperty:
|
||||
case QV4::Lookup::Call::GetterQObjectPropertyFallback: {
|
||||
const bool asVariant = lookup->asVariant;
|
||||
// Here we always retrieve a fresh method via the getter. No need to re-init.
|
||||
if (lookup->asVariant) {
|
||||
// If the method can be shadowed, the overridden method can be taken away, too.
|
||||
// In that case we might end up with a QObjectMethod or random other values instead.
|
||||
// callQObjectMethodAsVariant is flexible enough to handle that.
|
||||
return callQObjectMethodAsVariant(engine->handle(), lookup, object, args, argc);
|
||||
}
|
||||
|
||||
// Here we always retrieve a fresh ArrowFunction via the getter.
|
||||
QV4::Scope scope(engine->handle());
|
||||
QV4::ScopedValue thisObject(scope, QV4::QObjectWrapper::wrap(scope.engine, object));
|
||||
QV4::Scoped<QV4::ArrowFunction> function(scope, lookup->getter(scope.engine, thisObject));
|
||||
|
||||
// The getter mustn't touch the asVariant bit
|
||||
Q_ASSERT(!lookup->asVariant);
|
||||
|
||||
// If the method can't be shadowed, it has to stay the same.
|
||||
Q_ASSERT(function);
|
||||
Q_ASSERT(lookup->asVariant == asVariant); // The getter mustn't touch the asVariant bit
|
||||
return asVariant
|
||||
? callArrowFunctionAsVariant(scope.engine, function, qmlScopeObject, args, argc)
|
||||
: callArrowFunction(scope.engine, function, qmlScopeObject, args, argc);
|
||||
|
||||
return callArrowFunction(scope.engine, function, qmlScopeObject, args, argc);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
|
@ -2464,16 +2453,16 @@ void AOTCompiledContext::initCallObjectPropertyLookupAsVariant(uint index, QObje
|
|||
QV4::Lookup *lookup = compilationUnit->runtimeLookups + index;
|
||||
QV4::Scope scope(engine->handle());
|
||||
|
||||
const auto throwInvalidObjectError = [&]() {
|
||||
const auto throwInvalidObjectError = [&](const QString &object) {
|
||||
scope.engine->throwTypeError(
|
||||
QStringLiteral("Property '%1' of object [object Object] is not a function")
|
||||
.arg(compilationUnit->runtimeStrings[lookup->nameIndex]->toQString()));
|
||||
QStringLiteral("Property '%1' of object %2 is not a function").arg(
|
||||
compilationUnit->runtimeStrings[lookup->nameIndex]->toQString(), object));
|
||||
};
|
||||
|
||||
const auto *ddata = QQmlData::get(object, false);
|
||||
if (ddata && ddata->hasVMEMetaObject && ddata->jsWrapper.isNullOrUndefined()) {
|
||||
// We cannot lookup functions on an object with VME metaobject but no QObjectWrapper
|
||||
throwInvalidObjectError();
|
||||
throwInvalidObjectError(QStringLiteral("[object Object]"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2491,7 +2480,7 @@ void AOTCompiledContext::initCallObjectPropertyLookupAsVariant(uint index, QObje
|
|||
return;
|
||||
}
|
||||
|
||||
throwInvalidObjectError();
|
||||
throwInvalidObjectError(thisObject->toQStringNoThrow());
|
||||
}
|
||||
|
||||
void AOTCompiledContext::initCallObjectPropertyLookup(
|
||||
|
|
|
@ -151,6 +151,7 @@ set(qml_files
|
|||
detachedreferences.qml
|
||||
dialog.qml
|
||||
dialogButtonBox.qml
|
||||
disappearingArrowFunction.qml
|
||||
dynamicscene.qml
|
||||
enforceSignature.qml
|
||||
enumConversion.qml
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
pragma Strict
|
||||
import QtQml
|
||||
|
||||
QtObject {
|
||||
property Person inner: Person {
|
||||
function getName() : int { return 5 }
|
||||
}
|
||||
|
||||
property Person none: Person {}
|
||||
|
||||
property Person evil: Person {
|
||||
property string getName: "not a function"
|
||||
}
|
||||
|
||||
onObjectNameChanged: console.log(inner.getName())
|
||||
|
||||
function swapNone() {
|
||||
let t = inner;
|
||||
inner = none;
|
||||
none = t;
|
||||
}
|
||||
|
||||
function swapEvil() {
|
||||
let t = inner;
|
||||
inner = evil;
|
||||
evil = t;
|
||||
}
|
||||
}
|
|
@ -103,6 +103,7 @@ private slots:
|
|||
void detachOnAssignment();
|
||||
void detachedReferences();
|
||||
void dialogButtonBox();
|
||||
void disappearingArrowFunction();
|
||||
void enumConversion();
|
||||
void enumFromBadSingleton();
|
||||
void enumLookup();
|
||||
|
@ -1866,6 +1867,56 @@ void tst_QmlCppCodegen::dialogButtonBox()
|
|||
QPlatformDialogHelper::Ok | QPlatformDialogHelper::Cancel);
|
||||
}
|
||||
|
||||
void tst_QmlCppCodegen::disappearingArrowFunction()
|
||||
{
|
||||
QQmlEngine engine;
|
||||
const QUrl url(u"qrc:/qt/qml/TestTypes/disappearingArrowFunction.qml"_s);
|
||||
QQmlComponent c(&engine, url);
|
||||
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
|
||||
QScopedPointer<QObject> o(c.create());
|
||||
QVERIFY(!o.isNull());
|
||||
|
||||
QTest::ignoreMessage(QtDebugMsg, "5");
|
||||
o->setObjectName("no");
|
||||
|
||||
QMetaObject::invokeMethod(o.data(), "swapNone");
|
||||
QTest::ignoreMessage(QtDebugMsg, "Bart");
|
||||
o->setObjectName("nono");
|
||||
|
||||
QMetaObject::invokeMethod(o.data(), "swapNone");
|
||||
QTest::ignoreMessage(QtDebugMsg, "5");
|
||||
o->setObjectName("nonono");
|
||||
|
||||
const QRegularExpression warning(
|
||||
QRegularExpression::escape(url.toString())
|
||||
+ u"\\:15\\: TypeError\\: Property 'getName' of object "
|
||||
"Person_QML_[0-9]+\\(0x[0-9a-f]+\\) is not a function"_s);
|
||||
|
||||
QMetaObject::invokeMethod(o.data(), "swapEvil");
|
||||
QTest::ignoreMessage(QtWarningMsg, warning);
|
||||
o->setObjectName("nononono");
|
||||
|
||||
QMetaObject::invokeMethod(o.data(), "swapEvil");
|
||||
QTest::ignoreMessage(QtDebugMsg, "5");
|
||||
o->setObjectName("nonononono");
|
||||
|
||||
QMetaObject::invokeMethod(o.data(), "swapNone");
|
||||
QTest::ignoreMessage(QtDebugMsg, "Bart");
|
||||
o->setObjectName("nononononono");
|
||||
|
||||
QMetaObject::invokeMethod(o.data(), "swapEvil");
|
||||
QTest::ignoreMessage(QtWarningMsg, warning);
|
||||
o->setObjectName("nonononononono");
|
||||
|
||||
QMetaObject::invokeMethod(o.data(), "swapEvil");
|
||||
QTest::ignoreMessage(QtDebugMsg, "Bart");
|
||||
o->setObjectName("nononononononono");
|
||||
|
||||
QMetaObject::invokeMethod(o.data(), "swapNone");
|
||||
QTest::ignoreMessage(QtDebugMsg, "5");
|
||||
o->setObjectName("nonononononononono");
|
||||
}
|
||||
|
||||
void tst_QmlCppCodegen::enumConversion()
|
||||
{
|
||||
QQmlEngine engine;
|
||||
|
|
Loading…
Reference in New Issue