aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime/qv4qobjectwrapper.cpp
diff options
context:
space:
mode:
authorUlf Hermann <[email protected]>2023-09-19 16:43:11 +0200
committerUlf Hermann <[email protected]>2023-09-28 16:23:04 +0200
commit4f1b9156a48e44cf1f127a4563d0ac69ab436f12 (patch)
treee4092d3574468a0af89efc39145e6fd0348230ae /src/qml/jsruntime/qv4qobjectwrapper.cpp
parentf9271771cf6179a8b51ca90a2f863d6983c8fe0c (diff)
QML: Implement QObjectMethod::virtualCallWithMetaTypes
We can use the same mechanism we have in place when calling typed JavaScript functions. The type coercion is generalized and moved to qv4jscall_p.h. We also use the correct JavaScript coercion in the rare fallback case where the types are actually different. Fixes: QTBUG-113258 Change-Id: I30404ee0122433b47227b2fc0dc4b0e3862a99c7 Reviewed-by: Qt CI Bot <[email protected]> Reviewed-by: Fabian Kosmale <[email protected]>
Diffstat (limited to 'src/qml/jsruntime/qv4qobjectwrapper.cpp')
-rw-r--r--src/qml/jsruntime/qv4qobjectwrapper.cpp189
1 files changed, 188 insertions, 1 deletions
diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp
index 2b2a8c2ea4..176d696e9c 100644
--- a/src/qml/jsruntime/qv4qobjectwrapper.cpp
+++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp
@@ -33,6 +33,7 @@
#include <private/qqmlscriptstring_p.h>
#include <private/qv4compileddata_p.h>
#include <private/qqmlpropertybinding_p.h>
+#include <private/qqmlpropertycachemethodarguments_p.h>
#include <QtQml/qjsvalue.h>
#include <QtCore/qjsonarray.h>
@@ -1969,7 +1970,69 @@ static const QQmlPropertyData *ResolveOverloaded(
}
}
+static bool ExactMatch(QMetaType passed, QMetaType required, const void *data)
+{
+ if (required == QMetaType::fromType<QVariant>()
+ || required == QMetaType::fromType<QJSValue>()
+ || required == QMetaType::fromType<QJSManagedValue>()) {
+ return true;
+ }
+
+ if (data) {
+ if (passed == QMetaType::fromType<QVariant>())
+ passed = static_cast<const QVariant *>(data)->metaType();
+ else if (passed == QMetaType::fromType<QJSPrimitiveValue>())
+ passed = static_cast<const QJSPrimitiveValue *>(data)->metaType();
+ }
+
+ if (passed == required)
+ return true;
+
+ if (required == QMetaType::fromType<QJSPrimitiveValue>()) {
+ switch (passed.id()) {
+ case QMetaType::UnknownType:
+ case QMetaType::Nullptr:
+ case QMetaType::Bool:
+ case QMetaType::Int:
+ case QMetaType::Double:
+ case QMetaType::QString:
+ return true;
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+static const QQmlPropertyData *ResolveOverloaded(
+ const QQmlPropertyData *methods, int methodCount,
+ void **argv, int argc, const QMetaType *types)
+{
+ // We only accept exact matches here. Everything else goes through the JavaScript conversion.
+ for (int i = 0; i < methodCount; ++i) {
+ const QQmlPropertyData *attempt = methods + i;
+ if (types[0].isValid() && !ExactMatch(attempt->propType(), types[0], nullptr))
+ continue;
+
+ const QMetaMethod method = attempt->metaMethod();
+ if (method.parameterCount() != argc)
+ continue;
+
+ bool valid = true;
+ for (int i = 0; i < argc; ++i) {
+ if (!ExactMatch(types[i + 1], method.parameterMetaType(i), argv[i + 1])) {
+ valid = false;
+ break;
+ }
+ }
+ if (valid)
+ return attempt;
+ }
+
+ return nullptr;
+}
void CallArgument::cleanup()
{
@@ -2565,6 +2628,7 @@ void Heap::QObjectMethod::ensureMethodsCache(const QMetaObject *thisMeta)
QQmlPropertyData dummy;
QMetaMethod method = mo->method(index);
dummy.load(method);
+ dummy.setMetaObject(mo);
resolvedMethods.append(dummy);
// Look for overloaded methods
QByteArray methodName = method.name();
@@ -2616,12 +2680,20 @@ ReturnedValue QObjectMethod::method_destroy(
return Encode::undefined();
}
-ReturnedValue QObjectMethod::virtualCall(const FunctionObject *m, const Value *thisObject, const Value *argv, int argc)
+ReturnedValue QObjectMethod::virtualCall(
+ const FunctionObject *m, const Value *thisObject, const Value *argv, int argc)
{
const QObjectMethod *This = static_cast<const QObjectMethod*>(m);
return This->callInternal(thisObject, argv, argc);
}
+void QObjectMethod::virtualCallWithMetaTypes(
+ const FunctionObject *m, QObject *thisObject, void **argv, const QMetaType *types, int argc)
+{
+ const QObjectMethod *This = static_cast<const QObjectMethod*>(m);
+ This->callInternalWithMetaTypes(thisObject, argv, types, argc);
+}
+
ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value *argv, int argc) const
{
ExecutionEngine *v4 = engine();
@@ -2733,6 +2805,121 @@ ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value *
return doCall([&]() { return CallPrecise(object, *method, v4, callData); });
}
+struct ToStringMetaMethod
+{
+ constexpr int parameterCount() const { return 0; }
+ constexpr QMetaType returnMetaType() const { return QMetaType::fromType<QString>(); }
+ constexpr QMetaType parameterMetaType(int) const { return QMetaType(); }
+};
+
+void QObjectMethod::callInternalWithMetaTypes(
+ QObject *thisObject, void **argv, const QMetaType *types, int argc) const
+{
+ ExecutionEngine *v4 = engine();
+
+ const QMetaObject *thisMeta = nullptr;
+ Heap::QQmlValueTypeWrapper *valueWrapper = nullptr;
+
+ if (thisObject) {
+ thisMeta = thisObject->metaObject();
+ } else {
+ Q_ASSERT(Value::fromHeapObject(d()->wrapper).as<QQmlValueTypeWrapper>());
+ valueWrapper = d()->wrapper.cast<Heap::QQmlValueTypeWrapper>();
+ thisMeta = valueWrapper->metaObject();
+ }
+
+ QQmlObjectOrGadget object = [&](){
+ if (thisObject)
+ return QQmlObjectOrGadget(thisObject);
+
+ Scope scope(v4);
+ Scoped<QQmlValueTypeWrapper> wrapper(scope, d()->wrapper);
+ Q_ASSERT(wrapper);
+
+ Heap::QQmlValueTypeWrapper *valueWrapper = wrapper->d();
+ if (!valueWrapper->enforcesLocation())
+ QV4::ReferenceObject::readReference(valueWrapper);
+ return QQmlObjectOrGadget(thisMeta, valueWrapper->gadgetPtr());
+ }();
+
+ if (object.isNull())
+ return;
+
+ if (d()->index == DestroyMethod) {
+ // method_destroy will use at most one argument
+ QV4::convertAndCall(
+ v4, thisObject, argv, types, std::min(argc, 1),
+ [this, v4, object](const Value *thisObject, const Value *argv, int argc) {
+ Q_UNUSED(thisObject);
+ return method_destroy(v4, object.qObject(), argv, argc);
+ });
+ return;
+ }
+
+ if (d()->index == ToStringMethod) {
+ const ToStringMetaMethod metaMethod;
+ QV4::coerceAndCall(
+ v4, &metaMethod, argv, types, argc,
+ [v4, thisMeta, object](void **argv, int) {
+ *static_cast<QString *>(argv[0])
+ = QObjectWrapper::objectToString(v4, thisMeta, object.qObject());
+ });
+ return;
+ }
+
+ d()->ensureMethodsCache(thisMeta);
+
+ const QQmlPropertyData *method = d()->methods;
+ if (d()->methodCount != 1) {
+ Q_ASSERT(d()->methodCount > 0);
+ method = ResolveOverloaded(d()->methods, d()->methodCount, argv, argc, types);
+ }
+
+ if (!method || method->isV4Function()) {
+ QV4::convertAndCall(
+ v4, thisObject, argv, types, argc,
+ [this](const Value *thisObject, const Value *argv, int argc) {
+ return callInternal(thisObject, argv, argc);
+ });
+ } else {
+ const QMetaMethod metaMethod = method->metaMethod();
+ QV4::coerceAndCall(
+ v4, &metaMethod, argv, types, argc,
+ [v4, object, valueWrapper, method](void **argv, int argc) {
+ Q_UNUSED(argc);
+
+ // If we call the method, we have to write back any value type references afterwards.
+ // The method might change the value.
+ object.metacall(QMetaObject::InvokeMetaMethod, method->coreIndex(), argv);
+ if (!method->isConstant()) {
+ if (valueWrapper && valueWrapper->isReference())
+ valueWrapper->writeBack();
+ }
+
+ // If the method returns a QObject* we need to track it on the JS heap
+ // (if it's destructible).
+ QObject *qobjectPtr = nullptr;
+ const QMetaType resultType = method->propType();
+ if (resultType.flags() & QMetaType::PointerToQObject) {
+ qobjectPtr = *static_cast<QObject **>(argv[0]);
+ } else if (resultType == QMetaType::fromType<QVariant>()) {
+ const QVariant *result = static_cast<const QVariant *>(argv[0]);
+ const QMetaType variantType = result->metaType();
+ if (variantType.flags() & QMetaType::PointerToQObject)
+ qobjectPtr = *static_cast<QObject *const *>(result->data());
+ }
+
+ if (qobjectPtr) {
+ QQmlData *ddata = QQmlData::get(qobjectPtr, true);
+ if (!ddata->explicitIndestructibleSet) {
+ ddata->indestructible = false;
+ QObjectWrapper::wrap(v4, qobjectPtr);
+ }
+ }
+ });
+ }
+}
+
DEFINE_OBJECT_VTABLE(QObjectMethod);