aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/common/qv4compileddata_p.h1
-rw-r--r--src/qml/compiler/qqmlirbuilder.cpp25
-rw-r--r--src/qml/compiler/qqmlirbuilder_p.h8
-rw-r--r--src/qml/doc/src/cppintegration/exposecppattributes.qdoc48
-rw-r--r--src/qml/jsruntime/qv4executablecompilationunit_p.h5
-rw-r--r--src/qml/jsruntime/qv4qobjectwrapper.cpp172
-rw-r--r--src/qml/jsruntime/qv4qobjectwrapper_p.h28
-rw-r--r--src/qml/qml/qqmlirloader.cpp9
-rw-r--r--src/qml/qml/qqmlobjectorgadget_p.h1
-rw-r--r--tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsAcceptThisObject.qml33
-rw-r--r--tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsRejectThisObject.qml32
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.h1
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp86
13 files changed, 399 insertions, 50 deletions
diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h
index 8636fa4fbe..58a7009083 100644
--- a/src/qml/common/qv4compileddata_p.h
+++ b/src/qml/common/qv4compileddata_p.h
@@ -1183,6 +1183,7 @@ struct Unit
= ListPropertyAssignReplaceIfDefault | ListPropertyAssignReplaceIfNotDefault,
ComponentsBound = 0x200,
FunctionSignaturesEnforced = 0x400,
+ NativeMethodsAcceptThisObject = 0x800,
};
quint32_le flags;
quint32_le stringTableSize;
diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp
index 47671bddfb..ef27567eea 100644
--- a/src/qml/compiler/qqmlirbuilder.cpp
+++ b/src/qml/compiler/qqmlirbuilder.cpp
@@ -790,6 +790,8 @@ private:
return Pragma::ListPropertyAssignBehavior;
} else if constexpr (std::is_same_v<Argument, Pragma::FunctionSignatureBehaviorValue>) {
return Pragma::FunctionSignatureBehavior;
+ } else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
+ return Pragma::NativeMethodBehavior;
}
Q_UNREACHABLE_RETURN(Pragma::PragmaType(-1));
@@ -831,6 +833,15 @@ private:
pragma->functionSignatureBehavior = Pragma::Enforced;
return true;
}
+ } else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
+ if (value == "AcceptThisObject"_L1) {
+ pragma->nativeMethodBehavior = Pragma::AcceptThisObject;
+ return true;
+ }
+ if (value == "RejectThisObject"_L1) {
+ pragma->nativeMethodBehavior = Pragma::RejectThisObject;
+ return true;
+ }
}
return false;
@@ -854,6 +865,8 @@ private:
return "component behavior"_L1;
case Pragma::FunctionSignatureBehavior:
return "function signature behavior"_L1;
+ case Pragma::NativeMethodBehavior:
+ return "native method behavior"_L1;
default:
break;
}
@@ -879,6 +892,9 @@ bool IRBuilder::visit(QQmlJS::AST::UiPragma *node)
} else if (node->name == QStringLiteral("FunctionSignatureBehavior")) {
if (!PragmaParser<Pragma::FunctionSignatureBehaviorValue>::run(this, node, pragma))
return false;
+ } else if (node->name == QStringLiteral("NativeMethodBehavior")) {
+ if (!PragmaParser<Pragma::NativeMethodBehaviorValue>::run(this, node, pragma))
+ return false;
} else {
recordError(node->pragmaToken, QCoreApplication::translate(
"QQmlParser", "Unknown pragma '%1'").arg(node->name));
@@ -1650,6 +1666,15 @@ void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::Depen
break;
}
break;
+ case Pragma::NativeMethodBehavior:
+ switch (p->nativeMethodBehavior) {
+ case Pragma::AcceptThisObject:
+ createdUnit->flags |= Unit::NativeMethodsAcceptThisObject;
+ break;
+ case Pragma::RejectThisObject:
+ // this is the default;
+ break;
+ }
}
}
diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h
index 85578cdd6d..852c2d2244 100644
--- a/src/qml/compiler/qqmlirbuilder_p.h
+++ b/src/qml/compiler/qqmlirbuilder_p.h
@@ -406,6 +406,7 @@ struct Q_QML_COMPILER_PRIVATE_EXPORT Pragma
ListPropertyAssignBehavior,
ComponentBehavior,
FunctionSignatureBehavior,
+ NativeMethodBehavior,
};
enum ListPropertyAssignBehaviorValue
@@ -427,12 +428,19 @@ struct Q_QML_COMPILER_PRIVATE_EXPORT Pragma
Enforced
};
+ enum NativeMethodBehaviorValue
+ {
+ AcceptThisObject,
+ RejectThisObject
+ };
+
PragmaType type;
union {
ListPropertyAssignBehaviorValue listPropertyAssignBehavior;
ComponentBehaviorValue componentBehavior;
FunctionSignatureBehaviorValue functionSignatureBehavior;
+ NativeMethodBehaviorValue nativeMethodBehavior;
};
QV4::CompiledData::Location location;
diff --git a/src/qml/doc/src/cppintegration/exposecppattributes.qdoc b/src/qml/doc/src/cppintegration/exposecppattributes.qdoc
index 477f17584c..e3b3288a98 100644
--- a/src/qml/doc/src/cppintegration/exposecppattributes.qdoc
+++ b/src/qml/doc/src/cppintegration/exposecppattributes.qdoc
@@ -448,6 +448,54 @@ be called according to the number and the types of arguments that are provided.
Values returned from C++ methods are converted to JavaScript values when
accessed from JavaScript expressions in QML.
+\section2 C++ methods and the 'this' object
+
+You may want to retrieve a C++ method from one object and call it on a different
+object. Consider the following example, within a QML module called \c{Example}:
+
+\table
+\row
+\li C++
+\li
+\code
+class Invokable : public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+public:
+ Invokable(QObject *parent = nullptr) : QObject(parent) {}
+
+ Q_INVOKABLE void invoke() { qDebug() << "invoked on " << objectName(); }
+};
+\endcode
+\row
+\li QML
+\li
+\qml
+import QtQml
+import Example
+
+Invokable {
+ objectName: "parent"
+ property Invokable child: Invokable {}
+ Component.onCompleted: child.invoke.call(this)
+}
+\endqml
+\endtable
+
+If you load the QML code from a suitable main.cpp, it should print
+"invoked on parent". However, due to a long standing bug, it doesn't.
+Historically, the 'this' object of C++-based methods is inseparably bound to
+the method. Changing this behavior for existing code would cause subtle errors
+since the 'this' object is implicit in many places. Since Qt 6.5 you can
+explicitly opt into the correct behavior and allow C++ methods to accept a
+'this' object. To do so, add the following pragma to your QML documents:
+
+\qml
+pragma NativeMethodBehavior: AcceptThisObject
+\endqml
+
+With this line added, the example above will work as expected.
\section1 Exposing Signals
diff --git a/src/qml/jsruntime/qv4executablecompilationunit_p.h b/src/qml/jsruntime/qv4executablecompilationunit_p.h
index bb99040b9a..0b27e68a20 100644
--- a/src/qml/jsruntime/qv4executablecompilationunit_p.h
+++ b/src/qml/jsruntime/qv4executablecompilationunit_p.h
@@ -171,6 +171,11 @@ public:
return data->flags & CompiledData::Unit::FunctionSignaturesEnforced;
}
+ bool nativeMethodsAcceptThisObjects() const
+ {
+ return data->flags & CompiledData::Unit::NativeMethodsAcceptThisObject;
+ }
+
int objectCount() const { return qmlData->nObjects; }
const CompiledObject *objectAt(int index) const
{
diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp
index dd0abf818e..873262ea16 100644
--- a/src/qml/jsruntime/qv4qobjectwrapper.cpp
+++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp
@@ -54,6 +54,7 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcBindingRemoval, "qt.qml.binding.removal", QtWarningMsg)
Q_LOGGING_CATEGORY(lcObjectConnect, "qt.qml.object.connect", QtWarningMsg)
Q_LOGGING_CATEGORY(lcOverloadResolution, "qt.qml.overloadresolution", QtWarningMsg)
+Q_LOGGING_CATEGORY(lcMethodBehavior, "qt.qml.method.behavior")
// The code in this file does not violate strict aliasing, but GCC thinks it does
// so turn off the warnings for us to have a clean build
@@ -2326,7 +2327,7 @@ void Heap::QObjectMethod::init(QV4::ExecutionContext *scope)
Heap::FunctionObject::init(scope);
}
-const QMetaObject *Heap::QObjectMethod::metaObject()
+const QMetaObject *Heap::QObjectMethod::metaObject() const
{
if (valueTypeWrapper)
return valueTypeWrapper->metaObject();
@@ -2367,17 +2368,88 @@ bool Heap::QObjectMethod::isAttachedTo(QObject *o) const
return true;
}
-void Heap::QObjectMethod::ensureMethodsCache(QObject *o)
+Heap::QObjectMethod::ThisObjectMode Heap::QObjectMethod::checkThisObject(
+ const QMetaObject *thisMeta) const
+{
+ // Check that the metaobject matches.
+
+ if (!thisMeta) {
+ // You can only get a detached method via a lookup, and then you have a thisObject.
+ Q_ASSERT(valueTypeWrapper || qObj);
+ return Included;
+ }
+
+ const auto check = [&](const QMetaObject *included) {
+ const auto stackFrame = internalClass->engine->currentStackFrame;
+ if (stackFrame && !stackFrame->v4Function->executableCompilationUnit()
+ ->nativeMethodsAcceptThisObjects()) {
+ qCWarning(lcMethodBehavior,
+ "%s:%d: Calling C++ methods with 'this' objects different from the one "
+ "they were retrieved from is broken, due to historical reasons. The "
+ "original object is used as 'this' object. You can allow the given "
+ "'this' object to be used by setting "
+ "'pragma NativeMethodBehavior: AcceptThisObject'",
+ qPrintable(stackFrame->source()), stackFrame->lineNumber());
+ return Included;
+ }
+
+ // Find the base type the method belongs to.
+ int methodOffset = included->methodOffset();
+ while (true) {
+ if (included == thisMeta)
+ return Explicit;
+
+ if (methodOffset <= index)
+ return thisMeta->inherits(included) ? Explicit : Invalid;
+
+ methodOffset -= QMetaObjectPrivate::get(included)->methodCount;
+ included = included->superClass();
+ Q_ASSERT(included);
+ };
+
+ Q_UNREACHABLE_RETURN(Invalid);
+ };
+
+ if (QObject *o = object())
+ return check(o->metaObject());
+
+ if (valueTypeWrapper)
+ return check(valueTypeWrapper->metaObject());
+
+ // If the QObjectMethod is detached, we can only have gotten here via a lookup.
+ // The lookup checks that the QQmlPropertyCache matches.
+ return Explicit;
+}
+
+QString Heap::QObjectMethod::name() const
+{
+ if (index == QV4::QObjectMethod::DestroyMethod)
+ return QStringLiteral("destroy");
+ else if (index == QV4::QObjectMethod::ToStringMethod)
+ return QStringLiteral("toString");
+
+ const QMetaObject *mo = metaObject();
+ if (!mo)
+ return QString();
+
+ int methodOffset = mo->methodOffset();
+ while (methodOffset > index) {
+ mo = mo->superClass();
+ methodOffset -= QMetaObjectPrivate::get(mo)->methodCount;
+ }
+
+ return QLatin1String("%1::%2").arg(mo->className(), mo->method(index).name());
+}
+
+void Heap::QObjectMethod::ensureMethodsCache(const QMetaObject *thisMeta)
{
if (methods)
return;
const QMetaObject *mo = metaObject();
- if (!mo) {
- Q_ASSERT(o);
- mo = o->metaObject();
- }
+ if (!mo)
+ mo = thisMeta;
Q_ASSERT(mo);
@@ -2420,29 +2492,20 @@ static QObject *qObject(const Value *v)
return nullptr;
}
-ReturnedValue QObjectMethod::method_toString(
- ExecutionEngine *engine, const QV4::Value *thisObject) const
+ReturnedValue QObjectMethod::method_toString(ExecutionEngine *engine, QObject *o) const
{
- QObject *o = qObject(thisObject);
-
- d()->assertIntegrity(o);
-
- const QMetaObject *metaObject = d()->metaObject();
- if (!metaObject && o)
- metaObject = o->metaObject();
-
- return engine->newString(QObjectWrapper::objectToString(
- engine, metaObject, o ? o : d()->object()))->asReturnedValue();
+ return engine->newString(
+ QObjectWrapper::objectToString(
+ engine, o ? o->metaObject() : d()->metaObject(), o))->asReturnedValue();
}
-ReturnedValue QObjectMethod::method_destroy(ExecutionEngine *engine, const QV4::Value *thisObject, const Value *args, int argc) const
+ReturnedValue QObjectMethod::method_destroy(
+ ExecutionEngine *engine, QObject *o, const Value *args, int argc) const
{
- QObject *o = qObject(thisObject);
- QObject *object = o ? o : d()->object();
- if (!object)
+ if (!o)
return Encode::undefined();
- if (QQmlData::keepAliveDuringGarbageCollection(object))
+ if (QQmlData::keepAliveDuringGarbageCollection(o))
return engine->throwError(QStringLiteral("Invalid attempt to destroy() an indestructible object"));
int delay = 0;
@@ -2450,9 +2513,9 @@ ReturnedValue QObjectMethod::method_destroy(ExecutionEngine *engine, const QV4::
delay = args[0].toUInt32();
if (delay > 0)
- QTimer::singleShot(delay, object, SLOT(deleteLater()));
+ QTimer::singleShot(delay, o, SLOT(deleteLater()));
else
- object->deleteLater();
+ o->deleteLater();
return Encode::undefined();
}
@@ -2467,23 +2530,62 @@ ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value *
{
ExecutionEngine *v4 = engine();
- if (d()->index == DestroyMethod)
- return method_destroy(v4, thisObject, argv, argc);
- else if (d()->index == ToStringMethod)
- return method_toString(v4, thisObject);
+ const QMetaObject *thisMeta = nullptr;
QObject *o = qObject(thisObject);
+ Heap::QQmlValueTypeWrapper *wrapper = nullptr;
+ if (o) {
+ thisMeta = o->metaObject();
+ } else if (const QQmlValueTypeWrapper *value = thisObject->as<QQmlValueTypeWrapper>()) {
+ wrapper = value->d();
+ thisMeta = wrapper->metaObject();
+ }
+
+ Heap::QObjectMethod::ThisObjectMode mode = Heap::QObjectMethod::Invalid;
+ if (o && o == d()->object()) {
+ mode = Heap::QObjectMethod::Explicit;
+ // Nothing to do; objects are the same. This should be common
+ } else if (wrapper && wrapper == d()->valueTypeWrapper) {
+ mode = Heap::QObjectMethod::Explicit;
+ // Nothing to do; gadgets are the same. This should be somewhat common
+ } else {
+ mode = d()->checkThisObject(thisMeta);
+ if (mode == Heap::QObjectMethod::Invalid) {
+ v4->throwError(QLatin1String("Cannot call method %1 on %2").arg(
+ d()->name(), thisObject->toQStringNoThrow()));
+ return Encode::undefined();
+ }
+ }
+
+ QQmlObjectOrGadget object = [&](){
+ if (mode == Heap::QObjectMethod::Included) {
+ if (QObject *qObject = d()->qObj)
+ return QQmlObjectOrGadget(qObject);
- d()->assertIntegrity(o);
- d()->ensureMethodsCache(o);
+ wrapper = d()->valueTypeWrapper;
+ Q_ASSERT(wrapper);
+ return QQmlObjectOrGadget(wrapper->metaObject(), wrapper->gadgetPtr());
+ } else {
+ if (o)
+ return QQmlObjectOrGadget(o);
- QQmlObjectOrGadget object = d()->valueTypeWrapper
- ? QQmlObjectOrGadget(d()->metaObject(), d()->valueTypeWrapper->gadgetPtr())
- : QQmlObjectOrGadget(o ? o : d()->object());
+ Q_ASSERT(wrapper);
+ if (!wrapper->enforcesLocation())
+ QV4::ReferenceObject::readReference(wrapper);
+ return QQmlObjectOrGadget(thisMeta, wrapper->gadgetPtr());
+ }
+ }();
if (object.isNull())
return Encode::undefined();
+ if (d()->index == DestroyMethod)
+ return method_destroy(v4, object.qObject(), argv, argc);
+ else if (d()->index == ToStringMethod)
+ return method_toString(v4, object.qObject());
+
+ d()->ensureMethodsCache(thisMeta);
+
Scope scope(v4);
JSCallData cData(thisObject, argv, argc);
CallData *callData = cData.callData(scope);
@@ -2494,7 +2596,7 @@ ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value *
// The method might change the value.
const auto doCall = [&](const auto &call) {
if (!method->isConstant()) {
- if (d()->valueTypeWrapper && d()->valueTypeWrapper->isReference()) {
+ if (wrapper && wrapper->isReference()) {
ScopedValue rv(scope, call());
d()->valueTypeWrapper->writeBack();
return rv->asReturnedValue();
diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h
index d17b12ea4a..3f94cc7798 100644
--- a/src/qml/jsruntime/qv4qobjectwrapper_p.h
+++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h
@@ -80,26 +80,23 @@ DECLARE_HEAP_OBJECT(QObjectMethod, FunctionObject) {
FunctionObject::destroy();
}
- void ensureMethodsCache(QObject *o);
+ void ensureMethodsCache(const QMetaObject *thisMeta);
+ QString name() const;
- const QMetaObject *metaObject();
+ const QMetaObject *metaObject() const;
QObject *object() const { return qObj.data(); }
void setObject(QObject *o) { qObj = o; }
bool isDetached() const;
bool isAttachedTo(QObject *o) const;
- void assertIntegrity(QObject *thisObject) const
- {
- // This implies that the metaobject matches.
- // If the QObjectMethod is detached, we can only have gotten here via a lookup.
- // The lookup checks that the QQmlPropertyCache matches.
- // We can also call methods on standalone value type wrappers, without an object.
- if (thisObject)
- Q_ASSERT(isDetached() || isAttachedTo(thisObject));
- else
- Q_ASSERT(valueTypeWrapper || !isDetached());
- }
+ enum ThisObjectMode {
+ Invalid,
+ Included,
+ Explicit,
+ };
+
+ QV4::Heap::QObjectMethod::ThisObjectMode checkThisObject(const QMetaObject *thisMeta) const;
};
struct QMetaObjectWrapper : FunctionObject {
@@ -373,8 +370,9 @@ struct Q_QML_EXPORT QObjectMethod : public QV4::FunctionObject
int methodIndex() const { return d()->index; }
QObject *object() const { return d()->object(); }
- QV4::ReturnedValue method_toString(QV4::ExecutionEngine *engine, const QV4::Value *thisObject) const;
- QV4::ReturnedValue method_destroy(QV4::ExecutionEngine *ctx, const QV4::Value *thisObject, const Value *args, int argc) const;
+ QV4::ReturnedValue method_toString(QV4::ExecutionEngine *engine, QObject *o) const;
+ QV4::ReturnedValue method_destroy(
+ QV4::ExecutionEngine *ctx, QObject *o, const Value *args, int argc) const;
static ReturnedValue virtualCall(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
diff --git a/src/qml/qml/qqmlirloader.cpp b/src/qml/qml/qqmlirloader.cpp
index 59853a85e5..c80f9b4bd1 100644
--- a/src/qml/qml/qqmlirloader.cpp
+++ b/src/qml/qml/qqmlirloader.cpp
@@ -49,6 +49,12 @@ void QQmlIRLoader::load()
createPragma(type)->functionSignatureBehavior = value;
};
+ const auto createNativeMethodPragma = [&](
+ Pragma::PragmaType type,
+ Pragma::NativeMethodBehaviorValue value) {
+ createPragma(type)->nativeMethodBehavior = value;
+ };
+
if (unit->flags & QV4::CompiledData::Unit::IsSingleton)
createPragma(Pragma::Singleton);
if (unit->flags & QV4::CompiledData::Unit::IsStrict)
@@ -65,6 +71,9 @@ void QQmlIRLoader::load()
if (unit->flags & QV4::CompiledData::Unit::FunctionSignaturesEnforced)
createFunctionSignaturePragma(Pragma::FunctionSignatureBehavior, Pragma::Enforced);
+ if (unit->flags & QV4::CompiledData::Unit::NativeMethodsAcceptThisObject)
+ createNativeMethodPragma(Pragma::NativeMethodBehavior, Pragma::AcceptThisObject);
+
for (uint i = 0; i < qmlUnit->nObjects; ++i) {
const QV4::CompiledData::Object *serializedObject = qmlUnit->objectAt(i);
QmlIR::Object *object = loadObject(serializedObject);
diff --git a/src/qml/qml/qqmlobjectorgadget_p.h b/src/qml/qml/qqmlobjectorgadget_p.h
index ee7c8de65f..ddce295c9d 100644
--- a/src/qml/qml/qqmlobjectorgadget_p.h
+++ b/src/qml/qml/qqmlobjectorgadget_p.h
@@ -38,6 +38,7 @@ public:
void metacall(QMetaObject::Call type, int index, void **argv) const;
bool isNull() const { return ptr.isNull(); }
+ QObject *qObject() const { return ptr.isT1() ? ptr.asT1() : nullptr; }
private:
QBiPointer<QObject, void> ptr;
diff --git a/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsAcceptThisObject.qml b/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsAcceptThisObject.qml
new file mode 100644
index 0000000000..2af2e04352
--- /dev/null
+++ b/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsAcceptThisObject.qml
@@ -0,0 +1,33 @@
+pragma NativeMethodBehavior: AcceptThisObject
+import ValueTypes
+import StaticTest
+import QtQml
+
+QtObject {
+ id: self
+
+ property base v1
+ property derived v2
+
+ property var valueTypeMethod: v1.report
+
+ property var qtMethod: Qt.rect
+
+ property rect badRect: self.qtMethod(1, 2, 3, 4)
+ property rect goodRect1: qtMethod.call(undefined, 1, 2, 3, 4)
+ property rect goodRect2: qtMethod.call(Qt, 1, 2, 3, 4)
+
+ property string badString: self.valueTypeMethod()
+ property string goodString1: valueTypeMethod.call(undefined)
+ property string goodString2: valueTypeMethod.call(v1)
+ property string goodString3: valueTypeMethod.call(v2)
+
+ property string goodString4: toString.call(Qt)
+ property string badString2: toString.call(goodRect1)
+
+ property var mm: OriginalSingleton.mm
+ property int badInt: self.mm()
+ property int goodInt1: mm.call(undefined)
+ property int goodInt2: mm.call(OriginalSingleton)
+ property int goodInt3: mm.call(DerivedSingleton)
+}
diff --git a/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsRejectThisObject.qml b/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsRejectThisObject.qml
new file mode 100644
index 0000000000..aab4bf8451
--- /dev/null
+++ b/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsRejectThisObject.qml
@@ -0,0 +1,32 @@
+import ValueTypes
+import StaticTest
+import QtQml
+
+QtObject {
+ id: self
+
+ property base v1
+ property derived v2
+
+ property var valueTypeMethod: v1.report
+
+ property var qtMethod: Qt.rect
+
+ property rect badRect: self.qtMethod(1, 2, 3, 4)
+ property rect goodRect1: qtMethod.call(undefined, 1, 2, 3, 4)
+ property rect goodRect2: qtMethod.call(Qt, 1, 2, 3, 4)
+
+ property string badString: self.valueTypeMethod()
+ property string goodString1: valueTypeMethod.call(undefined)
+ property string goodString2: valueTypeMethod.call(v1)
+ property string goodString3: valueTypeMethod.call(v2)
+
+ property string goodString4: toString.call(Qt)
+ property string badString2: toString.call(goodRect1)
+
+ property var mm: OriginalSingleton.mm
+ property int badInt: self.mm()
+ property int goodInt1: mm.call(undefined)
+ property int goodInt2: mm.call(OriginalSingleton)
+ property int goodInt3: mm.call(DerivedSingleton)
+}
diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h
index 9ba5132bb4..0777ed103d 100644
--- a/tests/auto/qml/qqmllanguage/testtypes.h
+++ b/tests/auto/qml/qqmllanguage/testtypes.h
@@ -2305,6 +2305,7 @@ signals:
void abcChanged(const QString &);
public:
+ Q_INVOKABLE int mm() { return 5; }
QString abc() const { return m_abc; }
void setAbc(const QString &abc)
{
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
index efe89f61ac..fee65fcb17 100644
--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
@@ -403,6 +403,9 @@ private slots:
void nullIsNull();
void multiRequired();
+ void objectAndGadgetMethodCallsRejectThisObject();
+ void objectAndGadgetMethodCallsAcceptThisObject();
+
private:
QQmlEngine engine;
QStringList defaultImportPathList;
@@ -7763,6 +7766,89 @@ void tst_qqmllanguage::multiRequired()
qPrintable(url.toString() + ":5 Required property description was not initialized\n"));
}
+void tst_qqmllanguage::objectAndGadgetMethodCallsRejectThisObject()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("objectAndGadgetMethodCallsRejectThisObject.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ const QList<int> lines = { 15, 19, 21, 22, 24, 25, 28, 31 };
+ for (int line : lines) {
+ const QString message
+ = ".*:%1: Calling C.. methods with 'this' objects different from the one "
+ "they were retrieved from is broken, due to historical reasons. The "
+ "original object is used as 'this' object. You can allow the given "
+ "'this' object to be used by setting "
+ "'pragma NativeMethodBehavior: AcceptThisObject'"_L1.arg(QString::number(line));
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(message));
+ }
+
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QCOMPARE(o->property("badRect"), QRectF(1, 2, 3, 4));
+ QCOMPARE(o->property("goodRect1"), QRectF(1, 2, 3, 4));
+ QCOMPARE(o->property("goodRect2"), QRectF(1, 2, 3, 4));
+
+ QCOMPARE(o->property("badString"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString1"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString2"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString3"), QStringLiteral("27"));
+
+ QVERIFY(o->property("goodString4").value<QString>().startsWith("QObject_QML_"_L1));
+ QVERIFY(o->property("badString2").value<QString>().startsWith("QObject_QML_"_L1));
+
+ QCOMPARE(o->property("badInt"), 5);
+ QCOMPARE(o->property("goodInt1"), 5);
+ QCOMPARE(o->property("goodInt2"), 5);
+ QCOMPARE(o->property("goodInt3"), 5);
+}
+
+void tst_qqmllanguage::objectAndGadgetMethodCallsAcceptThisObject()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("objectAndGadgetMethodCallsAcceptThisObject.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ QTest::ignoreMessage(
+ QtWarningMsg, QRegularExpression(
+ "objectAndGadgetMethodCallsAcceptThisObject.qml:16: Error: "
+ "Cannot call method QtObject::rect on QObject_QML_"));
+ QTest::ignoreMessage(
+ QtWarningMsg, QRegularExpression(
+ "objectAndGadgetMethodCallsAcceptThisObject.qml:20: Error: "
+ "Cannot call method BaseValueType::report on QObject_QML_"));
+ QTest::ignoreMessage(
+ QtWarningMsg, QRegularExpression(
+ "objectAndGadgetMethodCallsAcceptThisObject.qml:26: Error: "
+ "Cannot call method toString on QRectF"));
+ QTest::ignoreMessage(
+ QtWarningMsg, QRegularExpression(
+ "objectAndGadgetMethodCallsAcceptThisObject.qml:29: Error: "
+ "Cannot call method OriginalSingleton::mm on QObject_QML_"));
+
+
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QCOMPARE(o->property("badRect"), QRectF());
+ QCOMPARE(o->property("goodRect1"), QRectF(1, 2, 3, 4));
+ QCOMPARE(o->property("goodRect2"), QRectF(1, 2, 3, 4));
+
+ QCOMPARE(o->property("badString"), QString());
+ QCOMPARE(o->property("goodString1"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString2"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString3"), QStringLiteral("28"));
+
+ QVERIFY(o->property("goodString4").value<QString>().startsWith("QtObject"_L1));
+ QCOMPARE(o->property("badString2"), QString());
+
+ QCOMPARE(o->property("badInt"), 0);
+ QCOMPARE(o->property("goodInt1"), 5);
+ QCOMPARE(o->property("goodInt2"), 5);
+ QCOMPARE(o->property("goodInt3"), 5);
+}
+
QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc"