Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qv4qobjectwrapper.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
6
7#include <private/qjsvalue_p.h>
8#include <private/qjsmanagedvalue_p.h>
9
10#include <private/qqmlbinding_p.h>
11#include <private/qqmlbuiltinfunctions_p.h>
12#include <private/qqmlengine_p.h>
13#include <private/qqmlobjectorgadget_p.h>
14#include <private/qqmlpropertybinding_p.h>
15#include <private/qqmlscriptstring_p.h>
16#include <private/qqmlsignalnames_p.h>
17#include <private/qqmltypewrapper_p.h>
18#include <private/qqmlvaluetypewrapper_p.h>
19#include <private/qqmlvmemetaobject_p.h>
20
21#include <private/qv4arraybuffer_p.h>
22#include <private/qv4arrayobject_p.h>
23#include <private/qv4compileddata_p.h>
24#include <private/qv4dateobject_p.h>
25#include <private/qv4functionobject_p.h>
26#include <private/qv4identifiertable_p.h>
27#include <private/qv4jscall_p.h>
28#include <private/qv4jsonobject_p.h>
29#include <private/qv4lookup_p.h>
30#include <private/qv4mm_p.h>
31#include <private/qv4regexpobject_p.h>
32#include <private/qv4runtime_p.h>
33#include <private/qv4scopedvalue_p.h>
34#include <private/qv4sequenceobject_p.h>
35#include <private/qv4variantobject_p.h>
36
37#include <QtCore/qjsonarray.h>
38#include <QtCore/qjsonobject.h>
39#include <QtCore/qjsonvalue.h>
40#include <QtCore/qloggingcategory.h>
41#include <QtCore/qmetaobject.h>
42#include <QtCore/qqueue.h>
43#include <QtCore/qtimer.h>
44#include <QtCore/qtypes.h>
45#include <QtCore/qvarlengtharray.h>
46
47#include <vector>
48
49#if QT_CONFIG(qml_itemmodel)
50#include <QtCore/qabstractitemmodel.h>
51#endif
52
53extern "C" {
54
56
57/*! \internal
58 Called right before a QML-to-C++ method call is dispatched, but only
59 when qt_v4NativeCallHookEnabled is set. An attached debugger can set
60 a breakpoint here. \a receiverMeta is the meta object of the call
61 receiver.
62*/
63Q_QML_EXPORT Q_DECL_COLD_FUNCTION void qt_v4AboutToCallNativeMethodHook(const QMetaObject *receiverMeta)
64{
65 Q_UNUSED(receiverMeta);
66}
67
68} // extern "C"
69
71
72Q_LOGGING_CATEGORY(lcBuiltinsBindingRemoval, "qt.qml.binding.removal", QtWarningMsg)
73Q_STATIC_LOGGING_CATEGORY(lcObjectConnect, "qt.qml.object.connect", QtWarningMsg)
74Q_STATIC_LOGGING_CATEGORY(lcOverloadResolution, "qt.qml.overloadresolution", QtWarningMsg)
75Q_STATIC_LOGGING_CATEGORY(lcMethodBehavior, "qt.qml.method.behavior")
76Q_STATIC_LOGGING_CATEGORY(lcSignalHandler, "qt.qml.signalhandler")
77
78// The code in this file does not violate strict aliasing, but GCC thinks it does
79// so turn off the warnings for us to have a clean build
80QT_WARNING_DISABLE_GCC("-Wstrict-aliasing")
81
82using namespace Qt::StringLiterals;
83
84namespace QV4 {
85
87{
89 if (v4) {
90 Scope scope(v4);
92 if (method)
94 }
95
96 return std::make_pair((QObject *)nullptr, -1);
97}
98
99static std::pair<QObject *, int> extractQtSignal(const Value &value)
100{
101 if (value.isObject()) {
102 ExecutionEngine *v4 = value.as<Object>()->engine();
103 Scope scope(v4);
104 ScopedFunctionObject function(scope, value);
105 if (function)
106 return QObjectMethod::extractQtMethod(function);
107
108 Scoped<QmlSignalHandler> handler(scope, value);
109 if (handler)
110 return std::make_pair(handler->object(), handler->signalIndex());
111 }
112
113 return std::make_pair((QObject *)nullptr, -1);
114}
115
117 ExecutionEngine *v4,
118 const QQmlPropertyData &property)
119{
120 Heap::ReferenceObject::Flags flags = Heap::ReferenceObject::NoFlag;
121 if (CppStackFrame *stackFrame = v4->currentStackFrame) {
122 if (stackFrame->v4Function->executableCompilationUnit()->valueTypesAreCopied())
123 flags |= Heap::ReferenceObject::EnforcesLocation;
124 }
125
126 if (property.isWritable())
127 flags |= Heap::ReferenceObject::CanWriteBack;
128
129 return flags;
130}
131
135{
137 Scope scope(v4);
138
140 if (property.isQObject()) {
141 QObject *rv = nullptr;
144 return QObjectWrapper::wrapConst(v4, rv);
145 else
146 return QObjectWrapper::wrap(v4, rv);
147 }
148
151
152 const auto encodeSimple = [&](auto v) {
154 return Encode(v);
155 };
156
157 const auto encodeInt = [&](auto v) {
159 return Encode(int(v));
160 };
161
162 const auto encodeDouble = [&](auto v) {
164 return Encode(double(v));
165 };
166
167 const auto encodeDate = [&](auto v) {
169 return Encode(v4->newDateObject(
171 };
172
173 const auto encodeString = [&](auto v) {
175 return v4->newString(v)->asReturnedValue();
176 };
177
178 const auto encodeSequence = [&](QMetaSequence metaSequence) {
179 // Pass nullptr as data. It's lazy-loaded.
181 v4, propMetaType, metaSequence, nullptr,
183 };
184
185
188 case QMetaType::Void:
189 return Encode::undefined();
190 case QMetaType::Nullptr:
191 case QMetaType::VoidStar:
192 return Encode::null();
193 case QMetaType::Int:
194 return encodeSimple(int());
195 case QMetaType::Bool:
196 return encodeSimple(bool());
197 case QMetaType::QString:
198 return encodeString(QString());
199 case QMetaType::QByteArray: {
203 }
204 case QMetaType::QChar:
205 return encodeString(QChar());
206 case QMetaType::Char16:
207 return encodeString(char16_t());
208 case QMetaType::UInt:
209 return encodeSimple(uint());
210 case QMetaType::Float:
211 return encodeSimple(float());
212 case QMetaType::Double:
213 return encodeSimple(double());
214 case QMetaType::Short:
215 return encodeInt(short());
216 case QMetaType::UShort:
217 return encodeInt(ushort());
218 case QMetaType::Char:
219 return encodeInt(char());
220 case QMetaType::UChar:
221 return encodeInt(uchar());
222 case QMetaType::SChar:
223 return encodeInt(qint8());
224 case QMetaType::Long:
225 return encodeDouble(long());
226 case QMetaType::ULong:
227 return encodeDouble(ulong());
228 case QMetaType::LongLong:
229 return encodeDouble(qlonglong());
230 case QMetaType::ULongLong:
231 return encodeDouble(qulonglong());
232 case QMetaType::QDateTime:
233 return encodeDate(QDateTime());
234 case QMetaType::QDate:
235 return encodeDate(QDate());
236 case QMetaType::QTime:
237 return encodeDate(QTime());
238#if QT_CONFIG(regularexpression)
242 return Encode(v4->newRegExpObject(v));
243 }
244#endif
245 case QMetaType::QVariantMap: {
248 return scope.engine->fromData(
250 }
251 case QMetaType::QVariantHash: {
254 return scope.engine->fromData(
256 }
257 case QMetaType::QJsonValue: {
260 return QV4::JsonObject::fromJsonValue(v4, v);
261 }
262 case QMetaType::QJsonObject: {
265 return QV4::JsonObject::fromJsonObject(v4, v);
266 }
267 case QMetaType::QJsonArray:
273 case QMetaType::QUrl: {
274 // ### Qt7: We really want this to be a JS URL object, but that would break things.
275 QUrl v;
278 }
279 case QMetaType::QPixmap:
280 case QMetaType::QImage: {
281 // Scarce value types
285 }
286 default:
287 break;
288 }
289
291 QJSValue v;
294 }
295
296 if (property.isQVariant()) {
297 // We have to read the property even if it's a lazy-loaded reference object.
298 // Without reading it, we wouldn't know its inner type.
299 QVariant v;
301 return scope.engine->fromVariant(
304 }
305
306 if (!propMetaType.isValid()) {
308 qWarning("QMetaProperty::read: Unable to handle unregistered datatype '%s' for property "
309 "'%s::%s'", p.typeName(), object->metaObject()->className(), p.name());
310 return Encode::undefined();
311 }
312
313 // TODO: For historical reasons we don't enforce locations for reference objects here.
314 // Once we do, we can eager load and use the fromVariant() below.
315 // Then the extra checks for value types and sequences can be dropped.
316
320 // Lazy loaded value type reference. Pass nullptr as data.
324 }
325 }
326
327 // See if it's a sequence type.
331
334 return scope.engine->fromVariant(
336}
337
343
350
365
369{
371
373 if (property->isVMEFunction()) {
376 return vmemo->vmeMethod(property->coreIndex());
377 } else if (property->isV4Function()) {
378 return QObjectMethod::create(
379 engine, (flags & AttachMethods) ? wrapper : nullptr, property->coreIndex());
380 } else if (property->isSignalHandler()) {
384 } else {
385 return QObjectMethod::create(
386 engine, (flags & AttachMethods) ? wrapper : nullptr, property->coreIndex());
387 }
388 }
389
391
392 if (ep && ep->propertyCapture && !property->isConstant()) {
397 }
398 }
399
400 if (property->isVarProperty()) {
404 } else {
406 }
407}
408
410 ExecutionEngine *v4, String *name, Heap::Object *qobj, bool *hasProperty = nullptr)
411{
412 int index = 0;
413 if (name->equals(v4->id_destroy()))
415 else if (name->equals(v4->id_toString()))
417 else
418 return OptionalReturnedValue();
419
420 if (hasProperty)
421 *hasProperty = true;
423}
424
426 ExecutionEngine *v4, String *name, const QQmlRefPointer<QQmlContextData> &qmlContext,
427 QObject *qobj, bool *hasProperty = nullptr)
428{
429 if (!qmlContext || !qmlContext->imports())
430 return OptionalReturnedValue();
431
432 if (hasProperty)
433 *hasProperty = true;
434
435 if (QQmlTypeLoader *typeLoader = v4->typeLoader()) {
436 QQmlTypeNameCache::Result r = qmlContext->imports()->query(name, typeLoader);
437
438 if (!r.isValid())
439 return OptionalReturnedValue();
440
441 if (r.scriptIndex != -1) {
442 return OptionalReturnedValue(Encode::undefined());
443 } else if (r.type.isValid()) {
444 return OptionalReturnedValue(
445 QQmlTypeWrapper::create(v4, qobj,r.type, Heap::QQmlTypeWrapper::ExcludeEnums));
446 } else if (r.importNamespace) {
447 return OptionalReturnedValue(QQmlTypeWrapper::create(
448 v4, qobj, qmlContext->imports(), r.importNamespace,
449 Heap::QQmlTypeWrapper::ExcludeEnums));
450 }
451 Q_UNREACHABLE_RETURN(OptionalReturnedValue());
452 } else {
453 return OptionalReturnedValue();
454 }
455}
456
460{
461 // Keep this code in sync with ::virtualResolveLookupGetter
462
463 if (QQmlData::wasDeleted(d()->object())) {
464 if (hasProperty)
465 *hasProperty = false;
466 return Encode::undefined();
467 }
468
470
472 return *methodValue;
473
476
477 if (!result) {
478 // Check for attached properties
482 return *importProperty;
483 }
484 return Object::virtualGet(this, name->propertyKey(), this, hasProperty);
485 }
486
487 QQmlData *ddata = QQmlData::get(d()->object(), false);
488
489 if ((flags & CheckRevision) && result->hasRevision()) {
491 if (hasProperty)
492 *hasProperty = false;
493 return Encode::undefined();
494 }
495 }
496
497 if (hasProperty)
498 *hasProperty = true;
499
500 return getProperty(v4, d(), d()->object(), result, flags);
501}
502
507{
508 if (QQmlData::wasDeleted(object)) {
509 if (hasProperty)
510 *hasProperty = false;
511 return Encode::null();
512 }
513
515 return *methodValue;
516
517 QQmlData *ddata = QQmlData::get(object, false);
520
521 if (result) {
525 if (hasProperty)
526 *hasProperty = false;
527 return Encode::undefined();
528 }
529 }
530
531 if (hasProperty)
532 *hasProperty = true;
533
534 if (property && result != &local)
535 *property = result;
536
538 } else {
539 // Check if this object is already wrapped.
540 if (!ddata || (ddata->jsWrapper.isUndefined() &&
541 (ddata->jsEngineId == 0 || // Nobody owns the QObject
542 !ddata->hasTaintedV4Object))) { // Someone else has used the QObject, but it isn't tainted
543
544 // Not wrapped. Last chance: try query QObjectWrapper's prototype.
545 // If it can't handle this, then there is no point
546 // to wrap the QObject just to look at an empty set of JS props.
548 return proto->get(name, hasProperty);
549 }
550 }
551
552 // If we get here, we must already be wrapped (which implies a ddata).
553 // There's no point wrapping again, as there wouldn't be any new props.
555
558 if (!rewrapped) {
559 if (hasProperty)
560 *hasProperty = false;
561 return Encode::null();
562 }
564}
565
566
588
589/*!
590 \internal
591 If an QObjectWrapper is created via wrap, then it needs to be stored somewhere.
592 Otherwise, the garbage collector will immediately collect it if it is already
593 past the "mark QObjectWrapper's" phase (note that QObjectWrapper are marked
594 by iterating over a list of all QObjectWrapper, and then checking if the
595 wrapper fulfills some conditions).
596 However, sometimes we don't really want to keep a reference to the wrapper,
597 but just want to make sure that it exists (and we know that the wrapper
598 already fulfills the conditions to be kept alive). Then ensureWrapper
599 can be used, which creates the wrapper and ensures that it is also
600 marked.
601 */
610
613 const QQmlPropertyData *property, const Value &value)
614{
615 if (!property->isWritable() && !property->isQList()) {
616 QString error = QLatin1String("Cannot assign to read-only property \"") +
619 return;
620 }
621
624 if (f->as<QQmlTypeWrapper>()) {
625 // Ignore. It's probably a singleton or an attached type.
626 } else if (!f->isBinding()) {
627 const bool isAliasToAllowed = [&]() {
628 if (property->isAlias()) {
640 } else {
641 return false;
642 }
643 }();
646 // assigning a JS function to a non var or QJSValue property or is not allowed.
647 QString error = QLatin1String("Cannot assign JavaScript function to ");
648 if (!QMetaType(property->propType()).name())
649 error += QLatin1String("[unknown property type]");
650 else
653 return;
654 }
655 } else {
656
661
662 // binding assignment.
663 if (property->acceptsQBinding()) {
664 const QQmlPropertyIndex idx(property->coreIndex(), /*not a value type*/-1);
667 if (f->isBoundFunction()) {
668 auto boundFunction = static_cast<BoundFunction *>(f.getPointer());
671 } else {
674
675 }
677 void *argv = {&bindable};
678 // indirect metacall in case interceptors are installed
681 if (!ok) {
682 auto error = QStringLiteral("Failed to set binding on %1::%2.").
685 }
686 } else {
689 if (f->isBoundFunction())
691 newBinding->setTarget(object, *property, nullptr);
693 }
694 return;
695 }
696 }
697
701 binding && !binding->isSticky()) {
702 const auto stackFrame = engine->currentStackFrame;
703 switch (binding->kind()) {
705 const auto qmlBinding = static_cast<const QQmlBinding*>(binding);
707 "Overwriting binding on %s::%s at %s:%d that was initially bound at %s",
711 break;
712 }
716 "Overwriting binding on %s::%s at %s:%d",
719 break;
720 }
721 }
722 }
723 }
726
727 if (property->isVarProperty()) {
728 // allow assignment of "special" values (null, undefined, function) to var properties
732 return;
733 }
734
735#define PROPERTY_STORE(cpptype, value)
736 cpptype o = value;
737 int status = -1;
738 int flags = 0;
739 void *argv[] = { &o, 0, &status, &flags };
740 QMetaObject::metacall(object, QMetaObject::WriteProperty, property->coreIndex(), argv);
741
743 // functions are already handled, except for the QJSValue case
747
748 if (value.isNull() && property->isQObject()) {
749 PROPERTY_STORE(QObject*, nullptr);
750 } else if (value.isUndefined() && property->isResettable()) {
751 void *a[] = { nullptr };
753 } else if (value.isUndefined() && propType == QMetaType::fromType<QVariant>()) {
755 } else if (value.isUndefined() && propType == QMetaType::fromType<QJsonValue>()) {
757 } else if (propType == QMetaType::fromType<QJSValue>()) {
760 QString error = QLatin1String("Cannot assign [undefined] to ");
761 if (!propType.name())
762 error += QLatin1String("[unknown property type]");
763 else
766 return;
767 } else if (propType == QMetaType::fromType<int>() && value.isNumber()) {
769 } else if (propType == QMetaType::fromType<qreal>() && value.isNumber()) {
771 } else if (propType == QMetaType::fromType<float>() && value.isNumber()) {
772 PROPERTY_STORE(float, float(value.asDouble()));
773 } else if (propType == QMetaType::fromType<double>() && value.isNumber()) {
774 PROPERTY_STORE(double, double(value.asDouble()));
775 } else if (propType == QMetaType::fromType<QString>() && value.isString()) {
777 } else if (property->isVarProperty()) {
782 && (value.isUndefined() || value.isPrimitive())) {
783 QQmlScriptString ss(value.toQStringNoThrow(), nullptr /* context */, object);
784 if (value.isNumber()) {
786 ss.d->isNumberLiteral = true;
787 } else if (value.isString()) {
789 ss.d->isStringLiteral = true;
790 }
792 } else {
793 QVariant v;
796 else
798
801 const char *valueType = (v.userType() == QMetaType::UnknownType)
802 ? "an unknown type"
803 : QMetaType(v.userType()).name();
804
805 const char *targetTypeName = propType.name();
806 if (!targetTypeName)
807 targetTypeName = "an unregistered type";
808
809 QString error = QLatin1String("Cannot assign ") +
811 QLatin1String(" to ") +
814 return;
815 }
816 }
817}
818
820{
822
823 QQmlData *ddata = QQmlData::get(object, true);
824 if (!ddata)
825 return Encode::undefined();
826
828
829 if (ddata->jsWrapper.isUndefined() &&
830 (ddata->jsEngineId == engine->m_engineId || // We own the QObject
831 ddata->jsEngineId == 0 || // No one owns the QObject
832 !ddata->hasTaintedV4Object)) { // Someone else has used the QObject, but it isn't tainted
833
837 return rv->asReturnedValue();
838
839 } else {
840 // If this object is tainted, we have to check to see if it is in our
841 // tainted object list
845
846 // If our tainted handle doesn't exist or has been collected, and there isn't
847 // a handle in the ddata, we can assume ownership of the ddata->jsWrapper
852 return result->asReturnedValue();
853 }
854
855 if (!alternateWrapper) {
861 }
862
864 }
865}
866
868{
869 const QObject *constObject = object;
870
871 QQmlData *ddata = QQmlData::get(object, true);
872
877
878 if (!constWrapper) {
884 ddata->hasConstWrapper = true;
885 }
886
888}
889
907
912
914{
915 Q_ASSERT(propertyIndex < 0xffff);
917
919 return;
920 QQmlData *ddata = QQmlData::get(object, /*create*/false);
921 if (!ddata)
922 return;
923
926 Q_ASSERT(property); // We resolved this property earlier, so it better exist!
928}
929
931{
933 const QObjectWrapper *aobjectWrapper = static_cast<QObjectWrapper *>(a);
936
937 // We can have a const and a non-const wrapper for the same object.
940}
941
952
964
966{
967 if (!id.isString())
968 return Object::virtualPut(m, id, value, receiver);
969
970 Scope scope(m);
971 QObjectWrapper *that = static_cast<QObjectWrapper*>(m);
973
974 if (that->internalClass()->isFrozen()) {
975 QString error = QLatin1String("Cannot assign to property \"") +
976 name->toQString() + QLatin1String("\" of read-only object");
978 return false;
979 }
980
982 return false;
983
987 // Types created by QML are not extensible at run-time, but for other QObjects we can store them
988 // as regular JavaScript properties, like on JavaScript objects.
989 if (ddata && ddata->context) {
990 QString error = QLatin1String("Cannot assign to non-existent property \"") +
991 name->toQString() + QLatin1Char('\"');
993 return false;
994 } else {
995 return Object::virtualPut(m, id, value, receiver);
996 }
997 }
998
999 return true;
1000}
1001
1003{
1004 if (id.isString()) {
1005 const QObjectWrapper *that = static_cast<const QObjectWrapper*>(m);
1006 const QObject *thatObject = that->d()->object();
1008 Scope scope(m);
1014 if (p) {
1015 // ### probably not the fastest implementation
1016 bool hasProperty;
1019 }
1020 return Attr_Data;
1021 }
1022 }
1023 }
1024
1025 return Object::virtualGetOwnProperty(m, id, p);
1026}
1027
1029{
1032 PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
1033
1034private:
1035 QSet<QByteArray> m_alreadySeen;
1036};
1037
1038PropertyKey QObjectWrapperOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
1039{
1040 // Used to block access to QObject::destroyed() and QObject::deleteLater() from QML
1041 static const int destroyedIdx1 = QObject::staticMetaObject.indexOfSignal("destroyed(QObject*)");
1042 static const int destroyedIdx2 = QObject::staticMetaObject.indexOfSignal("destroyed()");
1043 static const int deleteLaterIdx = QObject::staticMetaObject.indexOfSlot("deleteLater()");
1044
1045 const QObjectWrapper *that = static_cast<const QObjectWrapper*>(o);
1046
1047 QObject *thatObject = that->d()->object();
1048 if (thatObject && !QQmlData::wasDeleted(thatObject)) {
1049 const QMetaObject *mo = thatObject->metaObject();
1050 // These indices don't apply to gadgets, so don't block them.
1051 const bool preventDestruction = mo->superClass() || mo == &QObject::staticMetaObject;
1052 const int propertyCount = mo->propertyCount();
1053 if (propertyIndex < propertyCount) {
1054 ExecutionEngine *thatEngine = that->engine();
1055 Scope scope(thatEngine);
1056 const QMetaProperty property = mo->property(propertyIndex);
1057 ScopedString propName(scope, thatEngine->newString(QString::fromUtf8(property.name())));
1058 ++propertyIndex;
1059 if (attrs)
1060 *attrs= Attr_Data;
1061 if (pd) {
1062 QQmlPropertyData local;
1063 local.load(property);
1064 pd->value = that->getProperty(
1065 thatEngine, that->d(), thatObject, &local,
1066 QObjectWrapper::AttachMethods);
1067 }
1068 return propName->toPropertyKey();
1069 }
1070 const int methodCount = mo->methodCount();
1071 while (propertyIndex < propertyCount + methodCount) {
1072 Q_ASSERT(propertyIndex >= propertyCount);
1073 int index = propertyIndex - propertyCount;
1074 const QMetaMethod method = mo->method(index);
1075 ++propertyIndex;
1076 if (method.access() == QMetaMethod::Private || (preventDestruction && (index == deleteLaterIdx || index == destroyedIdx1 || index == destroyedIdx2)))
1077 continue;
1078 // filter out duplicates due to overloads:
1079 if (m_alreadySeen.contains(method.name()))
1080 continue;
1081 else
1082 m_alreadySeen.insert(method.name());
1083 ExecutionEngine *thatEngine = that->engine();
1084 Scope scope(thatEngine);
1085 ScopedString methodName(scope, thatEngine->newString(QString::fromUtf8(method.name())));
1086 if (attrs)
1087 *attrs = Attr_Data;
1088 if (pd) {
1089 QQmlPropertyData local;
1090 local.load(method);
1091 pd->value = that->getProperty(
1092 thatEngine, that->d(), thatObject, &local,
1093 QObjectWrapper::AttachMethods);
1094 }
1095 return methodName->toPropertyKey();
1096 }
1097 }
1098
1099 return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
1100}
1101
1107
1109{
1110 // Keep this code in sync with ::getQmlProperty
1113 if (!id.isString())
1116
1117 const QObjectWrapper *This = static_cast<const QObjectWrapper *>(object);
1120
1121 QObject * const qobj = This->d()->object();
1122
1123 if (QQmlData::wasDeleted(qobj))
1124 return Encode::undefined();
1125
1126 QQmlData *ddata = QQmlData::get(qobj, false);
1129 if (!ddata)
1130 ddata = QQmlData::get(qobj, true);
1136 return method.asReturnedValue();
1137 }
1138
1139 if (!ddata || !ddata->propertyCache) {
1143 if (!property)
1144 return Encode::undefined();
1151 } else {
1158 }
1159 return result->asReturnedValue();
1160 }
1162
1163 if (!property) {
1164 // Check for attached properties
1165 if (name->startsWithUpper()) {
1167 return *importProperty;
1168 }
1170 }
1171
1172 if (property->isFunction()
1173 && !property->isVarProperty()
1174 && !property->isVMEFunction() // Handled by QObjectLookup
1175 && !property->isSignalHandler()) { // TODO: Optimize SignalHandler, too
1176 QV4::Heap::QObjectMethod *method = nullptr;
1179 return lookup->getter(engine, *object);
1180 }
1181
1183
1185 return lookup->getter(engine, *object);
1186}
1187
1193
1195{
1198
1199 if (QObject *qObject = wrapper->object())
1200 return QMetaObject::metacall(qObject, call, index, a);
1201
1202 return 0;
1203}
1204
1207{
1208 if (!metaObject)
1209 return QLatin1String("null");
1210
1211 if (!object)
1212 return QString::fromUtf8(metaObject->className()) + QLatin1String("(0x0)");
1213
1214 const int id = metaObject->indexOfMethod("toString()");
1215 if (id >= 0) {
1221 return result.toString();
1224 return value->toQString();
1225 }
1226
1229 QLatin1String("(0x") + QString::number(quintptr(object), 16);
1231 if (!objectName.isEmpty())
1232 result += QLatin1String(", \"") + objectName + QLatin1Char('\"');
1233 result += QLatin1Char(')');
1234 return result;
1235}
1236
1238{
1243
1247
1248 static void impl(int which, QSlotObjectBase *this_, QObject *receiver, void **metaArgs, bool *ret)
1249 {
1250 switch (which) {
1251 case Destroy: {
1252 delete static_cast<QObjectSlotDispatcher*>(this_);
1253 }
1254 break;
1255 case Call: {
1256 if (QQmlData::wasDeleted(receiver))
1257 break;
1258
1259 QObjectSlotDispatcher *This = static_cast<QObjectSlotDispatcher*>(this_);
1260 ExecutionEngine *v4 = This->function.engine();
1261 // Might be that we're still connected to a signal that's emitted long
1262 // after the engine died. We don't track connections in a global list, so
1263 // we need this safeguard.
1264 if (!v4)
1265 break;
1266
1267 QQmlMetaObject::ArgTypeStorage<9> storage;
1268 QQmlMetaObject::methodParameterTypes(This->signal, &storage, nullptr);
1269
1270 const qsizetype argCount = std::min(storage.size(), This->maxNumArguments);
1271
1272 Scope scope(v4);
1273 ScopedFunctionObject f(scope, This->function.value());
1274
1275 JSCallArguments jsCallData(scope, argCount);
1276 *jsCallData.thisObject = This->thisObject.isUndefined()
1277 ? v4->globalObject->asReturnedValue()
1278 : This->thisObject.value();
1279 for (qsizetype ii = 0; ii < argCount; ++ii) {
1280 QMetaType type = storage[ii];
1281 if (type == QMetaType::fromType<QVariant>()) {
1282 jsCallData.args[ii] = v4->fromVariant(*((QVariant *)metaArgs[ii + 1]));
1283 } else {
1284 jsCallData.args[ii] = v4->fromVariant(QVariant(type, metaArgs[ii + 1]));
1285 }
1286 }
1287
1288 f->call(jsCallData);
1289 if (scope.hasException()) {
1290 QQmlError error = v4->catchExceptionAsQmlError();
1291 if (error.description().isEmpty()) {
1292 ScopedString name(scope, f->name());
1293 error.setDescription(QStringLiteral("Unknown exception occurred during evaluation of connected function: %1").arg(name->toQString()));
1294 }
1295 if (QQmlEngine *qmlEngine = v4->qmlEngine()) {
1296 QQmlEnginePrivate::get(qmlEngine)->warning(error);
1297 } else {
1298 QMessageLogger(error.url().toString().toLatin1().constData(),
1299 error.line(), nullptr).warning().noquote()
1300 << error.toString();
1301 }
1302 }
1303 }
1304 break;
1305 case Compare: {
1306 QObjectSlotDispatcher *connection = static_cast<QObjectSlotDispatcher*>(this_);
1307 if (connection->function.isUndefined()) {
1308 *ret = false;
1309 return;
1310 }
1311
1312 // This is tricky. Normally the metaArgs[0] pointer is a pointer to the _function_
1313 // for the new-style QObject::connect. Here we use the engine pointer as sentinel
1314 // to distinguish those type of QSlotObjectBase connections from our QML connections.
1315 ExecutionEngine *v4 = reinterpret_cast<ExecutionEngine*>(metaArgs[0]);
1316 if (v4 != connection->function.engine()) {
1317 *ret = false;
1318 return;
1319 }
1320
1321 Scope scope(v4);
1322 ScopedValue function(scope, *reinterpret_cast<Value*>(metaArgs[1]));
1323 ScopedValue thisObject(scope, *reinterpret_cast<Value*>(metaArgs[2]));
1324 QObject *receiverToDisconnect = reinterpret_cast<QObject*>(metaArgs[3]);
1325 int slotIndexToDisconnect = *reinterpret_cast<int*>(metaArgs[4]);
1326
1327 if (slotIndexToDisconnect != -1) {
1328 // This is a QObject function wrapper
1329 if (connection->thisObject.isUndefined() == thisObject->isUndefined() &&
1330 (connection->thisObject.isUndefined() || RuntimeHelpers::strictEqual(*connection->thisObject.valueRef(), thisObject))) {
1331
1332 ScopedFunctionObject f(scope, connection->function.value());
1333 std::pair<QObject *, int> connectedFunctionData = QObjectMethod::extractQtMethod(f);
1334 if (connectedFunctionData.first == receiverToDisconnect &&
1335 connectedFunctionData.second == slotIndexToDisconnect) {
1336 *ret = true;
1337 return;
1338 }
1339 }
1340 } else {
1341 // This is a normal JS function
1342 if (RuntimeHelpers::strictEqual(*connection->function.valueRef(), function) &&
1343 connection->thisObject.isUndefined() == thisObject->isUndefined() &&
1344 (connection->thisObject.isUndefined() || RuntimeHelpers::strictEqual(*connection->thisObject.valueRef(), thisObject))) {
1345 *ret = true;
1346 return;
1347 }
1348 }
1349
1350 *ret = false;
1351 }
1352 break;
1353 case NumOperations:
1354 break;
1355 }
1356 };
1357};
1358
1360{
1361 Scope scope(b);
1362
1363 if (argc == 0)
1364 THROW_GENERIC_ERROR("Function.prototype.connect: no arguments given");
1365
1368 int signalIndex = signalInfo.second; // in method range, not signal range!
1369
1370 if (signalIndex < 0)
1371 THROW_GENERIC_ERROR("Function.prototype.connect: this object is not a signal");
1372
1373 if (!signalObject)
1374 THROW_GENERIC_ERROR("Function.prototype.connect: cannot connect to deleted QObject");
1375
1378 THROW_GENERIC_ERROR("Function.prototype.connect: this object is not a signal");
1379
1382
1383 if (argc == 1) {
1384 f = argv[0];
1385 } else if (argc >= 2) {
1386 object = argv[0];
1387 f = argv[1];
1388 }
1389
1390 if (!f)
1391 THROW_GENERIC_ERROR("Function.prototype.connect: target is not a function");
1392
1393 if (!object->isUndefined() && !object->isObject())
1394 THROW_GENERIC_ERROR("Function.prototype.connect: target this is not an object");
1395
1398
1401
1405 }
1406 }
1407
1408 std::pair<QObject *, int> functionData = QObjectMethod::extractQtMethod(f); // align with disconnect
1409 QObject *receiver = nullptr;
1410
1411 if (functionData.first)
1413 else if (auto qobjectWrapper = object->as<QV4::QObjectWrapper>())
1415 else if (auto typeWrapper = object->as<QV4::QQmlTypeWrapper>())
1417
1418 if (receiver) {
1419 if (functionData.second == -1) {
1421 } else {
1422 // This means we are connecting to QObjectMethod which complains about extra arguments.
1423 Heap::QObjectMethod *d = static_cast<Heap::QObjectMethod *>(f->d());
1426 [](int a, const QQmlPropertyData &b) {
1427 return std::max(a, b.metaMethod().parameterCount());
1428 });
1429 }
1430
1432 } else {
1435 "Could not find receiver of the connection, using sender as receiver. Disconnect "
1436 "explicitly (or delete the sender) to make sure the connection is removed.");
1438 }
1439
1441}
1442
1444{
1445 Scope scope(b);
1446
1447 if (argc == 0)
1448 THROW_GENERIC_ERROR("Function.prototype.disconnect: no arguments given");
1449
1453
1454 if (signalIndex == -1)
1455 THROW_GENERIC_ERROR("Function.prototype.disconnect: this object is not a signal");
1456
1457 if (!signalObject)
1458 THROW_GENERIC_ERROR("Function.prototype.disconnect: cannot disconnect from deleted QObject");
1459
1461 THROW_GENERIC_ERROR("Function.prototype.disconnect: this object is not a signal");
1462
1465
1466 if (argc == 1) {
1467 functionValue = argv[0];
1468 } else if (argc >= 2) {
1470 functionValue = argv[1];
1471 }
1472
1473 if (!functionValue)
1474 THROW_GENERIC_ERROR("Function.prototype.disconnect: target is not a function");
1475
1477 THROW_GENERIC_ERROR("Function.prototype.disconnect: target this is not an object");
1478
1480
1481 void *a[] = {
1482 scope.engine,
1487 };
1488
1489 QObject *receiver = nullptr;
1490
1491 if (functionData.first)
1495 else if (auto typeWrapper = functionThisValue->as<QV4::QQmlTypeWrapper>())
1497
1498 if (receiver) {
1500 reinterpret_cast<void **>(&a));
1501 } else {
1503 reinterpret_cast<void **>(&a));
1504 }
1505
1507}
1508
1509static void markChildQObjectsRecursively(QObject *parent, MarkStack *markStack)
1510{
1511 QQueue<QObject *> queue;
1512 queue.append(parent->children());
1513
1514 while (!queue.isEmpty()) {
1515 QObject *child = queue.dequeue();
1516 if (!child)
1517 continue;
1518 QObjectWrapper::markWrapper(child, markStack);
1519 queue.append(child->children());
1520 }
1521}
1522
1523// returns true if and only if an object was recorded
1525 QObject *o, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
1526 MemoryManager::ObjectsForCompilationUnit *recorded)
1527{
1528 Q_ASSERT(compilationUnit);
1529 Q_ASSERT(o);
1530 Q_ASSERT(recorded);
1531
1532 const auto &baseUnit = compilationUnit->baseCompilationUnit();
1533 Q_ASSERT(baseUnit);
1534
1535 for (const auto &unit : recorded->compilationUnits) {
1536 if (baseUnit == unit) {
1537 recorded->objects.push_back(o);
1538 return true;
1539 }
1540 }
1541
1542 return false;
1543}
1544
1545// returns true if and only if an object was recorded
1547 QObject *o, const QQmlVMEMetaObject *vme,
1548 MemoryManager::ObjectsForCompilationUnit *recorded)
1549{
1550 for (; vme; vme = vme->parentVMEMetaObject()) {
1551 if (recordObjectForCompilationUnits(o, vme->compilationUnit(), recorded))
1552 return true;
1553 }
1554
1555 return false;
1556}
1557
1559{
1561
1562 QObject *o = static_cast<QObjectWrapper *>(that)->object();
1563 if (!o)
1564 return;
1565
1567 // Children usually don't need to be marked, the gc keeps them alive.
1568 // But in the rare case of a "floating" QObject without a parent that
1569 // _gets_ marked (we've been called here!) then we also need to
1570 // propagate the marking down to the children recursively.
1571 if (!o->parent())
1573 });
1574
1576 if (!ddata)
1577 return;
1578
1579 if (ddata->hasVMEMetaObject) {
1580 if (auto *vme = static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(o)->metaObject)) {
1581 vme->mark(markStack);
1583 // ddata->compilationUnit can be different from the VME compilation units
1584 // if the "topmost" instantiation of the object is inaddressible.
1588 }
1589 }
1590 }
1591 } else if (const auto &cu = ddata->compilationUnit) {
1592 // Objects without a VMEMetaObject are identified via QQmlData::compilationUnit.
1595 }
1596
1598 return;
1599
1600 // mark the const wrapper if our engine has interacted with it at some point
1603 scope, scope.engine->m_multiplyWrappedQObjects->value(static_cast<const QObject *>(o)));
1604
1605 if (!constWrapper)
1606 return;
1607
1608 if (that != constWrapper->d()) {
1609 // We've got the non-const wrapper. Also mark the const one.
1611 return;
1612 }
1613
1614 // We've got the const wrapper. Also mark the non-const one
1617 else
1619}
1620
1622{
1623 Heap::QObjectWrapper *h = d();
1625
1626 if (QObject *o = h->object()) {
1627 QQmlData *ddata = QQmlData::get(o, false);
1628 if (ddata) {
1629 if (!o->parent() && !ddata->indestructible) {
1630 if (ddata && ddata->ownContext) {
1634 ddata->context = nullptr;
1635 }
1636
1637 // This object is notionally destroyed now. It might still live until the next
1638 // event loop iteration, but it won't need its connections, CU, or deferredData
1639 // anymore.
1640
1641 ddata->isQueuedForDeletion = true;
1644
1647
1648 if (lastCall)
1649 delete o;
1650 else
1651 o->deleteLater();
1652 } else {
1653 // If the object is C++-owned, we still have to release the weak reference we have
1654 // to it. If the "main" wrapper is not ours, we should leave it alone, though.
1655 if (ddata->jsWrapper.as<QObjectWrapper>() == this)
1657 }
1658 }
1659 }
1660
1661 h->destroy();
1662}
1663
1664
1666
1667template<typename Retrieve>
1668int MatchVariant(QMetaType conversionMetaType, Retrieve &&retrieve) {
1669 if (conversionMetaType == QMetaType::fromType<QVariant>())
1670 return 0;
1671
1672 const QMetaType type = retrieve();
1673 if (type == conversionMetaType)
1674 return 0;
1675
1676 if (const QMetaObject *conversionMetaObject = conversionMetaType.metaObject()) {
1677 if (const QMetaObject *mo = type.metaObject(); mo && mo->inherits(conversionMetaObject))
1678 return 1;
1679 }
1680
1681 if (QMetaType::canConvert(type, conversionMetaType)) {
1682 if (conversionMetaType == QMetaType::fromType<QJSValue>()
1683 || conversionMetaType == QMetaType::fromType<double>()
1684 || conversionMetaType == QMetaType::fromType<QString>()) {
1685 // Unspecific conversions receive lower score. You can convert anything
1686 // to QString or double via toString() and valueOf(), respectively.
1687 // And anything can be wrapped into QJSValue, but that's inefficient.
1688 return 6;
1689 }
1690
1691 // We have an explicitly defined conversion method to a non-boring type.
1692 return 5;
1693 }
1694
1695 return 10;
1696};
1697
1698/*
1699 Returns the match score for converting \a actual to be of type \a conversionType. A
1700 zero score means "perfect match" whereas a higher score is worse.
1701
1702 The conversion table is copied out of the \l QScript::callQtMethod() function.
1703*/
1704static int MatchScore(const Value &actual, QMetaType conversionMetaType)
1705{
1706 const int conversionType = conversionMetaType.id();
1707 const auto convertibleScore = [&](QMetaType actualType) {
1708 // There are a number of things we can do in JavaScript to subvert this, but
1709 // if the conversion is not explicitly defined in C++, we don't want to prioritize it.
1710 if (!QMetaType::canConvert(actualType, conversionMetaType))
1711 return 10;
1712
1713 // You can convert anything to QJSValue, but that's inefficient.
1714 // If we have a better option, we should use it.
1715 if (conversionMetaType == QMetaType::fromType<QJSValue>())
1716 return 9;
1717
1718 // You can also convert anything to QVariant, but that's also suboptimal.
1719 // You can convert anything to string or double via toString() and valueOf().
1720 // Those are also rather unspecific.
1721 switch (conversionType) {
1722 case QMetaType::QVariant:
1723 case QMetaType::Double:
1724 case QMetaType::QString:
1725 return 9;
1726 default:
1727 break;
1728 }
1729
1730 // We have an explicitly defined conversion method to a non-boring type.
1731 return 8;
1732 };
1733
1734 if (actual.isNumber()) {
1735 switch (conversionType) {
1736 case QMetaType::Double:
1737 return 0;
1738 case QMetaType::Float:
1739 return 1;
1740 case QMetaType::LongLong:
1741 case QMetaType::ULongLong:
1742 return 2;
1743 case QMetaType::Long:
1744 case QMetaType::ULong:
1745 return 3;
1746 case QMetaType::Int:
1747 case QMetaType::UInt:
1748 return 4;
1749 case QMetaType::Short:
1750 case QMetaType::UShort:
1751 return 5;
1752 break;
1753 case QMetaType::Char:
1754 case QMetaType::UChar:
1755 return 6;
1756 case QMetaType::QJsonValue:
1757 return 5;
1758 default:
1759 return convertibleScore(actual.isInteger()
1760 ? QMetaType::fromType<int>()
1761 : QMetaType::fromType<double>());
1762 }
1763 } else if (actual.isString()) {
1764 switch (conversionType) {
1765 case QMetaType::QString:
1766 return 0;
1767 case QMetaType::QJsonValue:
1768 return 5;
1769 case QMetaType::QUrl:
1770 return 6; // we like to convert strings to URLs in QML
1771 case QMetaType::Double:
1772 case QMetaType::Float:
1773 case QMetaType::LongLong:
1774 case QMetaType::ULongLong:
1775 case QMetaType::Int:
1776 case QMetaType::UInt:
1777 case QMetaType::Short:
1778 case QMetaType::UShort:
1779 case QMetaType::Char:
1780 case QMetaType::UChar:
1781 // QMetaType can natively convert strings to numbers.
1782 // However, in the general case it's of course extremely lossy.
1783 return 10;
1784 default:
1785 return convertibleScore(QMetaType::fromType<QString>());
1786 }
1787 } else if (actual.isBoolean()) {
1788 switch (conversionType) {
1789 case QMetaType::Bool:
1790 return 0;
1791 case QMetaType::QJsonValue:
1792 return 5;
1793 default:
1794 return convertibleScore(QMetaType::fromType<bool>());
1795 }
1796 } else if (actual.as<DateObject>()) {
1797 switch (conversionType) {
1798 case QMetaType::QDateTime:
1799 return 0;
1800 case QMetaType::QDate:
1801 return 1;
1802 case QMetaType::QTime:
1803 return 2;
1804 default:
1805 return convertibleScore(QMetaType::fromType<QDateTime>());
1806 }
1807 } else if (actual.as<RegExpObject>()) {
1808 switch (conversionType) {
1809#if QT_CONFIG(regularexpression)
1810 case QMetaType::QRegularExpression:
1811 return 0;
1812 default:
1813 return convertibleScore(QMetaType::fromType<QRegularExpression>());
1814#else
1815 default:
1816 return convertibleScore(QMetaType());
1817#endif
1818 }
1819 } else if (actual.as<ArrayBuffer>()) {
1820 switch (conversionType) {
1821 case QMetaType::QByteArray:
1822 return 0;
1823 default:
1824 return convertibleScore(QMetaType::fromType<QByteArray>());
1825 }
1826 } else if (actual.as<ArrayObject>()) {
1827 switch (conversionType) {
1828 case QMetaType::QJsonArray:
1829 return 3;
1830 case QMetaType::QStringList:
1831 case QMetaType::QVariantList:
1832 return 5;
1833 case QMetaType::QVector4D:
1834 case QMetaType::QMatrix4x4:
1835 return 6;
1836 case QMetaType::QVector3D:
1837 return 7;
1838 default:
1839 return convertibleScore(QMetaType());
1840 }
1841 } else if (actual.isNull()) {
1842 switch (conversionType) {
1843 case QMetaType::Nullptr:
1844 case QMetaType::VoidStar:
1845 case QMetaType::QObjectStar:
1846 case QMetaType::QJsonValue:
1847 return 0;
1848 default: {
1849 if (conversionMetaType.flags().testFlag(QMetaType::IsPointer))
1850 return 0;
1851 else
1852 return convertibleScore(QMetaType());
1853 }
1854 }
1855 } else if (const Object *obj = actual.as<Object>()) {
1856 if (const VariantObject *variantObject = obj->as<VariantObject>()) {
1857 return MatchVariant(conversionMetaType, [variantObject]() {
1858 return variantObject->d()->data().metaType();
1859 });
1860 }
1861
1862 if (const QObjectWrapper *wrapper = obj->as<QObjectWrapper>()) {
1863 switch (conversionType) {
1864 case QMetaType::QObjectStar:
1865 return 0;
1866 default:
1867 if (conversionMetaType.flags() & QMetaType::PointerToQObject) {
1868 QObject *wrapped = wrapper->object();
1869 if (!wrapped)
1870 return 0;
1871 if (qmlobject_can_cpp_cast(wrapped, conversionMetaType.metaObject()))
1872 return 0;
1873 }
1874 }
1875
1876 return convertibleScore(QMetaType::fromType<QObject *>());
1877 }
1878
1879 if (const QQmlTypeWrapper *wrapper = obj->as<QQmlTypeWrapper>()) {
1880 const QQmlType type = wrapper->d()->type();
1881 if (type.isSingleton()) {
1882 const QMetaType metaType = type.typeId();
1883 if (metaType == conversionMetaType)
1884 return 0;
1885
1886 if (conversionMetaType.flags() & QMetaType::PointerToQObject
1887 && metaType.flags() & QMetaType::PointerToQObject
1888 && type.metaObject()->inherits(conversionMetaType.metaObject())) {
1889 return 0;
1890 }
1891 } else if (QObject *object = wrapper->object()) {
1892 if (conversionMetaType.flags() & QMetaType::PointerToQObject
1893 && qmlobject_can_cpp_cast(object, conversionMetaType.metaObject())) {
1894 return 0;
1895 }
1896 }
1897
1898 return convertibleScore(QMetaType());
1899 }
1900
1901 if (const Sequence *sequence = obj->as<Sequence>()) {
1902 const QMetaType sequenceType = SequencePrototype::metaTypeForSequence(sequence);
1903 if (sequenceType == conversionMetaType)
1904 return 1;
1905
1906 return convertibleScore(sequenceType);
1907 }
1908
1909 if (const QQmlValueTypeWrapper *wrapper = obj->as<QQmlValueTypeWrapper>()) {
1910 return MatchVariant(conversionMetaType, [wrapper]() {
1911 return wrapper->d()->isVariant()
1912 ? wrapper->toVariant().metaType()
1913 : wrapper->type();
1914 });
1915 }
1916
1917 if (conversionMetaType == QMetaType::fromType<QJSValue>())
1918 return 0;
1919
1920 switch (conversionType) {
1921 case QMetaType::QJsonObject:
1922 case QMetaType::QVariantMap:
1923 return 5;
1924 default:
1925 break;
1926 }
1927
1928 }
1929
1930 return convertibleScore(QMetaType());
1931}
1932
1933static int numDefinedArguments(CallData *callArgs)
1934{
1935 int numDefinedArguments = callArgs->argc();
1936 while (numDefinedArguments > 0
1937 && callArgs->args[numDefinedArguments - 1].type() == StaticValue::Undefined_Type) {
1938 --numDefinedArguments;
1939 }
1940 return numDefinedArguments;
1941}
1942
1943static bool requiresStrictArguments(const QQmlObjectOrGadget &object)
1944{
1945 const QMetaObject *metaObject = object.metaObject();
1946 const int indexOfClassInfo = metaObject->indexOfClassInfo("QML.StrictArguments");
1947 return indexOfClassInfo != -1
1948 && metaObject->classInfo(indexOfClassInfo).value() == QByteArrayView("true");
1949}
1950
1954{
1955 const auto constructReturnValue = [&](QMetaType returnType, void *returnValue) {
1956 // If we construct a value in place, we mustn't initialize the memory before that.
1959 };
1960
1961 const auto convertReturnValue
1963
1964 // In contrast to other invocations of convertAndCall() we do not want to create
1965 // a URL object from a QUrl here like metaTypeFromJS does. This is for compatibility.
1966 // URL objects are proper, specified, objects that behave different from our variant
1967 // objects when it comes to equality comparisons.
1969 ? engine->fromVariant(*reinterpret_cast<const QVariant *>(returnValue))
1971
1972 const auto typeFlags = returnType.flags();
1975 } else if (typeFlags & QMetaType::PointerToQObject) {
1976 // We consider QObjects returned from invokables as owned by the QML engine unless
1977 // explicitly configured otherwise.
1978 if (QObject *object = *reinterpret_cast<QObject **>(returnValue))
1980 }
1981 return result;
1982 };
1983
1984 const auto conversionErrorHandler = [&](QMetaType argumentType, void *argument, int ii) {
1985 // We've checked the number of arguments before
1986 Q_ASSERT(ii < callArgs->argc());
1987
1989 // We have an engine here. So we can conjure up a QJSManagedValue for anything.
1990 *static_cast<QJSManagedValue *>(argument)
1992 return true;
1993 }
1994
1996 && callArgs->args[ii].isUndefined()) {
1997 // TODO: QObjectMethod silently converts undefined to null when you pass undefined
1998 // to a method that accepts some QObject-derived. This is wrong because
1999 // undefined is certainly not null. We accept it for compatibility.
2000 *static_cast<QObject **>(argument) = nullptr;
2001 return true;
2002 }
2003
2004 // TODO: QObjectMethod is allowed to use QVariant conversion.
2005 // This is wrong because we can't see the converters at compile time.
2006 // It only exists for compatibility with old versions of Qt.
2010 return true;
2011
2012 qWarning() << QString::fromLatin1("Could not convert argument %1 from %2 to %3")
2013 .arg(ii)
2015 .arg(argumentType.name());
2016 const StackTrace stack = engine->stackTrace();
2017 for (const StackFrame &frame : stack) {
2018 qWarning() << "\t" << frame.function + QLatin1Char('@') + frame.source + (frame.line > 0
2019 ? (QLatin1Char(':') + QString::number(frame.line))
2020 : QString());
2021
2022 }
2023
2025 qWarning() << "Passing incompatible arguments to signals is not supported.";
2027 }
2028
2030 "Passing incompatible arguments to C++ functions from JavaScript is not allowed."));
2031 return false;
2032 };
2033
2035
2036 const auto handleTooManyArguments = [&](int expectedArguments) {
2038 engine->throwError(QStringLiteral("Too many arguments"));
2039 return false;
2040 }
2041
2042 const auto stackTrace = engine->stackTrace();
2043 if (stackTrace.isEmpty()) {
2045 << "When matching arguments for "
2046 << object.className() << "::" << data.name(object.metaObject()) << "():";
2047 } else {
2048 const StackFrame frame = stackTrace.first();
2050 + (frame.line > 0 ? (QLatin1Char(':') + QString::number(frame.line))
2051 : QString());
2052 }
2053
2054 qWarning().noquote() << QStringLiteral("Too many arguments, ignoring %1")
2056 return true;
2057 };
2058
2060
2061 if (data.hasArguments()) {
2063
2064 const bool ok = data.isConstructor()
2069 if (!ok)
2071
2072 const int expectedArgumentCount = storage.size() - 1;
2074 return engine->throwError(QLatin1String("Insufficient arguments"));
2075
2078 return Encode::undefined();
2079 }
2080
2081 return QV4::convertAndCall(
2084 [&](void **argData, const QMetaType *types, int argc) {
2085 Q_UNUSED(types);
2086 Q_UNUSED(argc);
2089 }
2090
2092 return Encode::undefined();
2093
2095 if (!returnType.isValid())
2097
2098 return QV4::convertAndCall(
2099 engine, &returnType, 1, nullptr, 0,
2100 [&](void **argData, const QMetaType *types, int argc) {
2101 Q_UNUSED(types);
2102 Q_UNUSED(argc);
2105}
2106
2107/*
2108Resolve the overloaded method to call. The algorithm works conceptually like this:
2109 1. Resolve the set of overloads it is *possible* to call.
2110 Impossible overloads include those that have too many parameters or have parameters
2111 of unknown type.
2112 2. Filter the set of overloads to only contain those with the closest number of
2113 parameters.
2114 For example, if we are called with 3 parameters and there are 2 overloads that
2115 take 2 parameters and one that takes 3, eliminate the 2 parameter overloads.
2116 3. Find the best remaining overload based on its match score.
2117 If two or more overloads have the same match score, return the last one. The match
2118 score is constructed by adding the matchScore() result for each of the parameters.
2119*/
2123{
2124 const int argumentCount = callArgs->argc();
2126
2127 const QQmlPropertyData *best = nullptr;
2131
2134
2135 for (int i = 0; i < methodCount; ++i) {
2136 const QQmlPropertyData *attempt = methods + i;
2137
2143 qCDebug(lcOverloadResolution) << "::: considering signature" << m.methodSignature();
2144 }
2145
2146 // QQmlV4Function overrides anything that doesn't provide the exact number of arguments
2147 int methodParameterScore = 1;
2148 // QQmlV4Function overrides the "no idea" option, which is 10
2149 int maxMethodMatchScore = 9;
2150 // QQmlV4Function cannot provide a best sum of match scores as we don't match the arguments
2152
2153 if (!attempt->isV4Function()) {
2155 int methodArgumentCount = 0;
2156 if (attempt->hasArguments()) {
2157 if (attempt->isConstructor()) {
2158 if (!object.constructorParameterTypes(*attempt, &storage, nullptr)) {
2159 qCDebug(lcOverloadResolution, "rejected, could not get ctor argument types");
2160 continue;
2161 }
2162 } else {
2163 if (!object.methodParameterTypes(*attempt, &storage, nullptr)) {
2164 qCDebug(lcOverloadResolution, "rejected, could not get ctor argument types");
2165 continue;
2166 }
2167 }
2169 }
2170
2172 qCDebug(lcOverloadResolution, "rejected, insufficient arguments");
2173 continue; // We don't have sufficient arguments to call this method
2174 }
2175
2177 ? 0
2180 qCDebug(lcOverloadResolution) << "rejected, score too bad. own" << methodParameterScore << "vs best:" << bestParameterScore;
2181 continue; // We already have a better option
2182 }
2183
2186 for (int ii = 0; ii < methodArgumentCount; ++ii) {
2187 const int score = MatchScore((v = Value::fromStaticValue(callArgs->args[ii])),
2188 storage[ii]);
2191 }
2192 }
2193
2198 best = attempt;
2202 qCDebug(lcOverloadResolution) << "updated best" << "bestParameterScore" << bestParameterScore << "\n"
2203 << "bestMaxMatchScore" << bestMaxMatchScore << "\n"
2204 << "bestSumMatchScore" << bestSumMatchScore << "\n";
2205 } else {
2206 qCDebug(lcOverloadResolution) << "did not update best\n"
2207 << "bestParameterScore" << bestParameterScore << "\t"
2208 << "methodParameterScore" << methodParameterScore << "\n"
2209 << "bestMaxMatchScore" << bestMaxMatchScore << "\t"
2210 << "maxMethodMatchScore" << maxMethodMatchScore << "\n"
2211 << "bestSumMatchScore" << bestSumMatchScore << "\t"
2212 << "sumMethodMatchScore" << sumMethodMatchScore << "\n";
2213 }
2214
2215 if (bestParameterScore == 0 && bestMaxMatchScore == 0) {
2216 qCDebug(lcOverloadResolution, "perfect match");
2217 break; // We can't get better than that
2218 }
2219
2220 };
2221
2222 if (best && best->isValid()) {
2223 return best;
2224 } else {
2225 QString error = QLatin1String("Unable to determine callable overload. Candidates are:");
2226 for (int i = 0; i < methodCount; ++i) {
2231 error += u"\n " + QString::fromUtf8(m.methodSignature());
2232 }
2233
2235 return nullptr;
2236 }
2237}
2238
2239static bool ExactMatch(QMetaType passed, QMetaType required, const void *data)
2240{
2241 if (required == QMetaType::fromType<QVariant>()
2242 || required == QMetaType::fromType<QJSValue>()
2243 || required == QMetaType::fromType<QJSManagedValue>()) {
2244 return true;
2245 }
2246
2247 if (data) {
2248 if (passed == QMetaType::fromType<QVariant>())
2249 passed = static_cast<const QVariant *>(data)->metaType();
2250 else if (passed == QMetaType::fromType<QJSPrimitiveValue>())
2251 passed = static_cast<const QJSPrimitiveValue *>(data)->metaType();
2252 }
2253
2254 if (passed == required)
2255 return true;
2256
2257 if (required == QMetaType::fromType<QJSPrimitiveValue>()) {
2258 switch (passed.id()) {
2259 case QMetaType::UnknownType:
2260 case QMetaType::Nullptr:
2261 case QMetaType::Bool:
2262 case QMetaType::Int:
2263 case QMetaType::Double:
2264 case QMetaType::QString:
2265 return true;
2266 default:
2267 break;
2268 }
2269 }
2270
2271 return false;
2272}
2273
2275 const QMetaMethod &method, void **argv, int argc, const QMetaType *types)
2276{
2277 if (types[0].isValid() && !ExactMatch(method.returnMetaType(), types[0], nullptr))
2278 return false;
2279
2280 if (method.parameterCount() != argc)
2281 return false;
2282
2283 for (int i = 0; i < argc; ++i) {
2284 if (!ExactMatch(types[i + 1], method.parameterMetaType(i), argv[i + 1]))
2285 return false;
2286 }
2287
2288 return true;
2289}
2290
2293 void **argv, int argc, const QMetaType *types)
2294{
2295 // We only accept exact matches here. Everything else goes through the JavaScript conversion.
2296 for (int i = 0; i < methodCount; ++i) {
2297 const QQmlPropertyData *attempt = methods + i;
2299 return attempt;
2300 }
2301
2302 return nullptr;
2303}
2304
2313
2323
2327{
2329
2331 if (cloneFrom->wrapper) {
2333 if (ref) {
2335 } else {
2336 // We cannot re-attach a plain QQmlValueTypeWrapper because don't we know what
2337 // value we should operate on. Without knowledge of the property the value
2338 // was read from, we cannot load the value from the given object.
2339 return Encode::undefined();
2340 }
2341 }
2342
2344 valueScope,
2347
2349
2350 Q_ASSERT(method->d()->methods == nullptr);
2351 switch (cloneFrom->methodCount) {
2352 case 0:
2353 Q_ASSERT(cloneFrom->methods == nullptr);
2354 break;
2355 case 1:
2357 == reinterpret_cast<QQmlPropertyData *>(&cloneFrom->_singleMethod));
2358 method->d()->methods = reinterpret_cast<QQmlPropertyData *>(&method->d()->_singleMethod);
2359 *method->d()->methods = *cloneFrom->methods;
2360 break;
2361 default:
2362 Q_ASSERT(cloneFrom->methods != nullptr);
2366 break;
2367 }
2368
2369 return method.asReturnedValue();
2370}
2371
2373{
2377}
2378
2380{
2382
2384 return valueWrapper->metaObject();
2385 if (QObject *self = object())
2386 return self->metaObject();
2387
2388 return nullptr;
2389}
2390
2392{
2394
2396 return objectWrapper->object();
2398 return typeWrapper->object();
2399 return nullptr;
2400}
2401
2402bool Heap::QObjectMethod::isDetached() const
2403{
2404 if (!wrapper)
2405 return true;
2406
2409 return valueWrapper->d()->object() == nullptr;
2410
2411 return false;
2412}
2413
2415{
2418 return objectWrapper->object() == o;
2420 return typeWrapper->object() == o;
2421
2425 return qobject->object() == o;
2427 return type->object() == o;
2428
2429 // Attached to some nested value type or sequence object
2430 return false;
2431 }
2432
2433 return false;
2434}
2435
2437 const QMetaObject *thisMeta) const
2438{
2439 // Check that the metaobject matches.
2440
2441 if (!thisMeta) {
2442 // You can only get a detached method via a lookup, and then you have a thisObject.
2444 return Included;
2445 }
2446
2447 const auto check = [&](const QMetaObject *included) {
2452 "%s:%d: Calling C++ methods with 'this' objects different from the one "
2453 "they were retrieved from is broken, due to historical reasons. The "
2454 "original object is used as 'this' object. You can allow the given "
2455 "'this' object to be used by setting "
2456 "'pragma NativeMethodBehavior: AcceptThisObject'",
2458 return Included;
2459 }
2460
2461 // destroy() and toString() can be called on all QObjects, but not on gadgets.
2462 if (index < 0)
2464
2465 // Find the base type the method belongs to.
2467 while (true) {
2468 if (included == thisMeta)
2469 return Explicit;
2470
2471 if (methodOffset <= index)
2473
2477 };
2478
2480 };
2481
2482 if (const QMetaObject *meta = metaObject())
2483 return check(meta);
2484
2485 // If the QObjectMethod is detached, we can only have gotten here via a lookup.
2486 // The lookup checks that the QQmlPropertyCache matches.
2487 return Explicit;
2488}
2489
2491{
2493 return QStringLiteral("destroy");
2494 else if (index == QV4::QObjectMethod::ToStringMethod)
2495 return QStringLiteral("toString");
2496
2497 const QMetaObject *mo = metaObject();
2498 if (!mo)
2499 return QString();
2500
2501 int methodOffset = mo->methodOffset();
2502 while (methodOffset > index) {
2503 mo = mo->superClass();
2505 }
2506
2507 return "%1::%2"_L1.arg(QLatin1StringView{mo->className()},
2509}
2510
2512{
2513 if (methods) {
2514 Q_ASSERT(methodCount > 0);
2515 return;
2516 }
2517
2518 const QMetaObject *mo = metaObject();
2519
2520 if (!mo)
2521 mo = thisMeta;
2522
2523 Q_ASSERT(mo);
2524
2525 int methodOffset = mo->methodOffset();
2526 while (methodOffset > index) {
2527 mo = mo->superClass();
2529 }
2533 dummy.load(method);
2536 // Look for overloaded methods
2538 for (int ii = index - 1; ii >= methodOffset; --ii) {
2539 if (methodName == mo->method(ii).name()) {
2540 method = mo->method(ii);
2541 dummy.load(method);
2543 }
2544 }
2545 if (resolvedMethods.size() > 1) {
2549 } else {
2550 methods = reinterpret_cast<QQmlPropertyData *>(&_singleMethod);
2552 methodCount = 1;
2553 }
2554
2555 Q_ASSERT(methodCount > 0);
2556}
2557
2564
2566 ExecutionEngine *engine, QObject *o, const Value *args, int argc) const
2567{
2568 method_destroy(engine, o, argc > 0 ? args[0].toInt32() : 0);
2569 return Encode::undefined();
2570}
2571
2573{
2574 if (!o)
2575 return true;
2576
2578 engine->throwError(QStringLiteral("Invalid attempt to destroy() an indestructible object"));
2579 return false;
2580 }
2581
2582 if (delay > 0)
2584 else
2585 o->deleteLater();
2586
2587 return true;
2588}
2589
2591 const FunctionObject *m, const Value *thisObject, const Value *argv, int argc)
2592{
2593 const QObjectMethod *This = static_cast<const QObjectMethod*>(m);
2595}
2596
2598 const FunctionObject *m, QObject *thisObject, void **argv, const QMetaType *types, int argc)
2599{
2600 const QObjectMethod *This = static_cast<const QObjectMethod*>(m);
2602}
2603
2605{
2607
2608 const QMetaObject *thisMeta = nullptr;
2609
2610 QObject *o = nullptr;
2612 if (const QObjectWrapper *w = thisObject->as<QObjectWrapper>()) {
2613 thisMeta = w->metaObject();
2614 o = w->object();
2615 } else if (const QQmlTypeWrapper *w = thisObject->as<QQmlTypeWrapper>()) {
2616 thisMeta = w->metaObject();
2617 o = w->object();
2618 } else if (const QQmlValueTypeWrapper *w = thisObject->as<QQmlValueTypeWrapper>()) {
2619 thisMeta = w->metaObject();
2620 valueWrapper = w->d();
2621 }
2622
2624 if (o && o == d()->object()) {
2626 // Nothing to do; objects are the same. This should be common
2627 } else if (valueWrapper && valueWrapper == d()->wrapper) {
2629 // Nothing to do; gadgets are the same. This should be somewhat common
2630 } else {
2632 if (mode == Heap::QObjectMethod::Invalid) {
2633 v4->throwError(QLatin1String("Cannot call method %1 on %2").arg(
2634 d()->name(), thisObject->toQStringNoThrow()));
2635 return Encode::undefined();
2636 }
2637 }
2638
2639 QQmlObjectOrGadget object = [&](){
2640 if (mode == Heap::QObjectMethod::Included) {
2641 QV4::Scope scope(v4);
2645 return QQmlObjectOrGadget(type->object());
2647 valueWrapper = value->d();
2649 }
2650 Q_UNREACHABLE();
2651 } else {
2652 if (o)
2653 return QQmlObjectOrGadget(o);
2654
2659 }
2660 }();
2661
2662 if (object.isNull())
2663 return Encode::undefined();
2664
2665 if (d()->index == DestroyMethod)
2666 return method_destroy(v4, object.qObject(), argv, argc);
2667 else if (d()->index == ToStringMethod) {
2669 const QMetaObject *meta = object.metaObject();
2670 if (meta && meta->indexOfMethod("toString()") >= 0)
2672 }
2673 return method_toString(v4, object.qObject());
2674 }
2675
2677
2678 Scope scope(v4);
2681
2682 const QQmlPropertyData *method = d()->methods;
2683
2684 // If we call the method, we have to write back any value type references afterwards.
2685 // The method might change the value.
2686 const auto doCall = [&](const auto &call) {
2687 if (!method->isConstant()) {
2691 return rv->asReturnedValue();
2692 }
2693 }
2694
2695 return call();
2696 };
2697
2698 if (d()->methodCount != 1) {
2699 Q_ASSERT(d()->methodCount > 0);
2701 if (method == nullptr)
2702 return Encode::undefined();
2703 }
2704
2707
2708 if (method->isV4Function()) {
2709 return doCall([&]() {
2713
2714 void *args[] = { nullptr, &funcptr };
2716
2717 return rv->asReturnedValue();
2718 });
2719 }
2720
2721 return doCall([&]() { return callPrecise(object, *method, v4, callData); });
2722}
2723
2725{
2726 constexpr int parameterCount() const { return 0; }
2727 constexpr QMetaType returnMetaType() const { return QMetaType::fromType<QString>(); }
2728 constexpr QMetaType parameterMetaType(int) const { return QMetaType(); }
2729};
2730
2732 QObject *thisObject, void **argv, const QMetaType *types, int argc) const
2733{
2735
2736 const QMetaObject *thisMeta = nullptr;
2738
2739 if (thisObject) {
2741 } else {
2745 }
2746
2747 QQmlObjectOrGadget object = [&](){
2748 if (thisObject)
2750
2751 Scope scope(v4);
2754
2759 }();
2760
2761 if (object.isNull())
2762 return;
2763
2764 if (d()->index == DestroyMethod) {
2765 // method_destroy will use at most one argument
2767 v4, thisObject, argv, types, std::min(argc, 1),
2768 [this, v4, object](const Value *thisObject, const Value *argv, int argc) {
2770 return method_destroy(v4, object.qObject(), argv, argc);
2771 });
2772 return;
2773 }
2774
2775 if (d()->index == ToStringMethod) {
2779 [v4, thisMeta, object](void **argv, int) {
2780 if (!argv[0])
2781 return;
2782 *static_cast<QString *>(argv[0])
2784 });
2785 return;
2786 }
2787
2789
2790 const QQmlPropertyData *method = d()->methods;
2791 if (d()->methodCount != 1) {
2792 Q_ASSERT(d()->methodCount > 0);
2794 }
2795
2796 if (!method || method->isV4Function()) {
2799 [this](const Value *thisObject, const Value *argv, int argc) {
2800 return callInternal(thisObject, argv, argc);
2801 });
2802 } else {
2806 [v4, object, valueWrapper, method](void **argv, int argc) {
2807 Q_UNUSED(argc);
2808
2809 // If we call the method, we have to write back any value type references afterwards.
2810 // The method might change the value.
2812 if (!method->isConstant()) {
2815 }
2816
2817 // If the method returns a QObject* we need to track it on the JS heap
2818 // (if it's destructible).
2819 QObject *qobjectPtr = nullptr;
2821 if (argv[0]) {
2823 qobjectPtr = *static_cast<QObject **>(argv[0]);
2824 } else if (resultType == QMetaType::fromType<QVariant>()) {
2825 const QVariant *result = static_cast<const QVariant *>(argv[0]);
2828 qobjectPtr = *static_cast<QObject *const *>(result->data());
2829 }
2830 }
2831
2832 if (qobjectPtr) {
2835 ddata->indestructible = false;
2837 }
2838 }
2839 });
2840 }
2841}
2842
2844
2845void Heap::QmlSignalHandler::init(QObject *object, int signalIndex)
2846{
2847 Object::init();
2848 this->signalIndex = signalIndex;
2849 setObject(object);
2850}
2851
2853
2855{
2859 << QStringLiteral("Property '%1' of object %2 is a signal handler. You should "
2860 "not call it directly. Make it a proper function and call "
2861 "that or emit the signal.")
2863
2864 Scope scope(engine());
2867 scope.engine,
2868 static_cast<Heap::QObjectWrapper *>(nullptr),
2869 signalIndex()));
2870
2871 return method->call(thisObject, argv, argc);
2872}
2873
2888
2889
2892{
2893 const QObjectBiPointer key = it.key();
2894 const QObject *obj = key.isT1() ? key.asT1() : key.asT2();
2895 disconnect(obj, &QObject::destroyed, this, &MultiplyWrappedQObjectMap::removeDestroyedObject);
2896 return QHash<QObjectBiPointer, WeakValue>::erase(it);
2897}
2898
2899void MultiplyWrappedQObjectMap::removeDestroyedObject(QObject *object)
2900{
2901 QHash<QObjectBiPointer, WeakValue>::remove(object);
2902 QHash<QObjectBiPointer, WeakValue>::remove(static_cast<const QObject *>(object));
2903}
2904
2905} // namespace QV4
2906
2907QT_END_NAMESPACE
2908
2909#include "moc_qv4qobjectwrapper_p.cpp"
QHash< QObjectBiPointer, QV4::WeakValue >::Iterator Iterator
Definition qjsvalue.h:24
static int MatchScore(const Value &actual, QMetaType conversionMetaType)
static std::pair< QObject *, int > extractQtSignal(const Value &value)
static bool requiresStrictArguments(const QQmlObjectOrGadget &object)
static bool ExactMatch(QMetaType passed, QMetaType required, const void *data)
static ReturnedValue loadProperty(ExecutionEngine *v4, Heap::Object *wrapper, QObject *object, const QQmlPropertyData &property)
static void markChildQObjectsRecursively(QObject *parent, MarkStack *markStack)
static bool recordVMEObjectForCompilationUnits(QObject *o, const QQmlVMEMetaObject *vme, MemoryManager::ObjectsForCompilationUnit *recorded)
int MatchVariant(QMetaType conversionMetaType, Retrieve &&retrieve)
static OptionalReturnedValue getDestroyOrToStringMethod(ExecutionEngine *v4, String *name, Heap::Object *qobj, bool *hasProperty=nullptr)
static Heap::ReferenceObject::Flags referenceFlags(ExecutionEngine *v4, const QQmlPropertyData &property)
static OptionalReturnedValue getPropertyFromImports(ExecutionEngine *v4, String *name, const QQmlRefPointer< QQmlContextData > &qmlContext, QObject *qobj, bool *hasProperty=nullptr)
static bool recordObjectForCompilationUnits(QObject *o, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, MemoryManager::ObjectsForCompilationUnit *recorded)
static int numDefinedArguments(CallData *callArgs)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
Q_QML_EXPORT Q_DECL_COLD_FUNCTION void qt_v4AboutToCallNativeMethodHook(const QMetaObject *receiverMeta)
Q_QML_EXPORT bool qt_v4NativeCallHookEnabled
#define PROPERTY_STORE(cpptype, value)
void init(QObject *object, int signalIndex)
static void impl(int which, QSlotObjectBase *this_, QObject *receiver, void **metaArgs, bool *ret)
PropertyKey next(const Object *o, Property *pd=nullptr, PropertyAttributes *attrs=nullptr) override
~QObjectWrapperOwnPropertyKeyIterator() override=default
constexpr QMetaType returnMetaType() const
constexpr QMetaType parameterMetaType(int) const
constexpr int parameterCount() const