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
4#include "qv4qobjectwrapper_p.h"
5
6#include <private/qjsvalue_p.h>
7
8#include <private/qqmlbinding_p.h>
9#include <private/qqmlbuiltinfunctions_p.h>
10#include <private/qqmlengine_p.h>
11#include <private/qqmlobjectorgadget_p.h>
12#include <private/qqmlpropertybinding_p.h>
13#include <private/qqmlscriptstring_p.h>
14#include <private/qqmlsignalnames_p.h>
15#include <private/qqmltypewrapper_p.h>
16#include <private/qqmlvaluetypewrapper_p.h>
17#include <private/qqmlvmemetaobject_p.h>
18
19#include <private/qv4arraybuffer_p.h>
20#include <private/qv4compileddata_p.h>
21#include <private/qv4dateobject_p.h>
22#include <private/qv4functionobject_p.h>
23#include <private/qv4identifiertable_p.h>
24#include <private/qv4jscall_p.h>
25#include <private/qv4jsonobject_p.h>
26#include <private/qv4lookup_p.h>
27#include <private/qv4mm_p.h>
28#include <private/qv4regexpobject_p.h>
29#include <private/qv4runtime_p.h>
30#include <private/qv4scopedvalue_p.h>
31#include <private/qv4sequenceobject_p.h>
32#include <private/qv4variantobject_p.h>
33
34#include <QtCore/qjsonarray.h>
35#include <QtCore/qjsonobject.h>
36#include <QtCore/qjsonvalue.h>
37#include <QtCore/qloggingcategory.h>
38#include <QtCore/qmetaobject.h>
39#include <QtCore/qqueue.h>
40#include <QtCore/qtimer.h>
41#include <QtCore/qtypes.h>
42#include <QtCore/qvarlengtharray.h>
43
44#include <vector>
45
46#if QT_CONFIG(qml_itemmodel)
47#include <QtCore/qabstractitemmodel.h>
48#endif
49
50QT_BEGIN_NAMESPACE
51
52Q_LOGGING_CATEGORY(lcBuiltinsBindingRemoval, "qt.qml.binding.removal", QtWarningMsg)
53Q_LOGGING_CATEGORY(lcObjectConnect, "qt.qml.object.connect", QtWarningMsg)
54Q_LOGGING_CATEGORY(lcOverloadResolution, "qt.qml.overloadresolution", QtWarningMsg)
55Q_LOGGING_CATEGORY(lcMethodBehavior, "qt.qml.method.behavior")
56Q_LOGGING_CATEGORY(lcSignalHandler, "qt.qml.signalhandler")
57
58// The code in this file does not violate strict aliasing, but GCC thinks it does
59// so turn off the warnings for us to have a clean build
60QT_WARNING_DISABLE_GCC("-Wstrict-aliasing")
61
62using namespace Qt::StringLiterals;
63
64namespace QV4 {
65
66QPair<QObject *, int> QObjectMethod::extractQtMethod(const FunctionObject *function)
67{
68 ExecutionEngine *v4 = function->engine();
69 if (v4) {
70 Scope scope(v4);
71 Scoped<QObjectMethod> method(scope, function->as<QObjectMethod>());
72 if (method)
73 return qMakePair(value1: method->object(), value2: method->methodIndex());
74 }
75
76 return qMakePair(value1: (QObject *)nullptr, value2: -1);
77}
78
79static QPair<QObject *, int> extractQtSignal(const Value &value)
80{
81 if (value.isObject()) {
82 ExecutionEngine *v4 = value.as<Object>()->engine();
83 Scope scope(v4);
84 ScopedFunctionObject function(scope, value);
85 if (function)
86 return QObjectMethod::extractQtMethod(function);
87
88 Scoped<QmlSignalHandler> handler(scope, value);
89 if (handler)
90 return qMakePair(value1: handler->object(), value2: handler->signalIndex());
91 }
92
93 return qMakePair(value1: (QObject *)nullptr, value2: -1);
94}
95
96static Heap::ReferenceObject::Flags referenceFlags(
97 ExecutionEngine *v4,
98 const QQmlPropertyData &property)
99{
100 Heap::ReferenceObject::Flags flags = Heap::ReferenceObject::NoFlag;
101 if (CppStackFrame *stackFrame = v4->currentStackFrame) {
102 if (stackFrame->v4Function->executableCompilationUnit()->valueTypesAreCopied())
103 flags |= Heap::ReferenceObject::EnforcesLocation;
104 }
105
106 if (property.isWritable())
107 flags |= Heap::ReferenceObject::CanWriteBack;
108
109 return flags;
110}
111
112static ReturnedValue loadProperty(
113 ExecutionEngine *v4, Heap::Object *wrapper,
114 QObject *object, const QQmlPropertyData &property)
115{
116 Q_ASSERT(!property.isFunction());
117 Scope scope(v4);
118
119 const QMetaType propMetaType = property.propType();
120 if (property.isQObject()) {
121 QObject *rv = nullptr;
122 property.readProperty(target: object, property: &rv);
123 if (propMetaType.flags().testFlag(flag: QMetaType::IsConst))
124 return QObjectWrapper::wrapConst(engine: v4, object: rv);
125 else
126 return QObjectWrapper::wrap(engine: v4, object: rv);
127 }
128
129 if (property.isQList() && propMetaType.flags().testFlag(flag: QMetaType::IsQmlList))
130 return QmlListWrapper::create(engine: v4, object, propId: property.coreIndex(), propType: propMetaType);
131
132 const auto encodeSimple = [&](auto v) {
133 property.readProperty(target: object, property: &v);
134 return Encode(v);
135 };
136
137 const auto encodeInt = [&](auto v) {
138 property.readProperty(target: object, property: &v);
139 return Encode(int(v));
140 };
141
142 const auto encodeDouble = [&](auto v) {
143 property.readProperty(target: object, property: &v);
144 return Encode(double(v));
145 };
146
147 const auto encodeDate = [&](auto v) {
148 property.readProperty(target: object, property: &v);
149 return Encode(v4->newDateObject(
150 v, wrapper, property.coreIndex(), referenceFlags(v4: scope.engine, property)));
151 };
152
153 const auto encodeString = [&](auto v) {
154 property.readProperty(target: object, property: &v);
155 return v4->newString(v)->asReturnedValue();
156 };
157
158 const auto encodeSequence = [&](QMetaSequence metaSequence) {
159 // Pass nullptr as data. It's lazy-loaded.
160 return QV4::SequencePrototype::newSequence(
161 engine: v4, type: propMetaType, metaSequence, data: nullptr,
162 object: wrapper, propertyIndex: property.coreIndex(), flags: referenceFlags(v4: scope.engine, property));
163 };
164
165
166 switch (property.isEnum() ? propMetaType.underlyingType().id() : propMetaType.id()) {
167 case QMetaType::UnknownType:
168 case QMetaType::Void:
169 return Encode::undefined();
170 case QMetaType::Nullptr:
171 case QMetaType::VoidStar:
172 return Encode::null();
173 case QMetaType::Int:
174 return encodeSimple(int());
175 case QMetaType::Bool:
176 return encodeSimple(bool());
177 case QMetaType::QString:
178 return encodeString(QString());
179 case QMetaType::QByteArray: {
180 QByteArray v;
181 property.readProperty(target: object, property: &v);
182 return v4->newArrayBuffer(array: v)->asReturnedValue();
183 }
184 case QMetaType::QChar:
185 return encodeString(QChar());
186 case QMetaType::Char16:
187 return encodeString(char16_t());
188 case QMetaType::UInt:
189 return encodeSimple(uint());
190 case QMetaType::Float:
191 return encodeSimple(float());
192 case QMetaType::Double:
193 return encodeSimple(double());
194 case QMetaType::Short:
195 return encodeInt(short());
196 case QMetaType::UShort:
197 return encodeInt(ushort());
198 case QMetaType::Char:
199 return encodeInt(char());
200 case QMetaType::UChar:
201 return encodeInt(uchar());
202 case QMetaType::SChar:
203 return encodeInt(qint8());
204 case QMetaType::Long:
205 return encodeDouble(long());
206 case QMetaType::ULong:
207 return encodeDouble(ulong());
208 case QMetaType::LongLong:
209 return encodeDouble(qlonglong());
210 case QMetaType::ULongLong:
211 return encodeDouble(qulonglong());
212 case QMetaType::QDateTime:
213 return encodeDate(QDateTime());
214 case QMetaType::QDate:
215 return encodeDate(QDate());
216 case QMetaType::QTime:
217 return encodeDate(QTime());
218#if QT_CONFIG(regularexpression)
219 case QMetaType::QRegularExpression: {
220 QRegularExpression v;
221 property.readProperty(target: object, property: &v);
222 return Encode(v4->newRegExpObject(re: v));
223 }
224#endif
225 case QMetaType::QVariantMap: {
226 QVariantMap v;
227 property.readProperty(target: object, property: &v);
228 return scope.engine->fromData(
229 type: propMetaType, ptr: &v, parent: wrapper, property: property.coreIndex(), flags: referenceFlags(v4, property));
230 }
231 case QMetaType::QJsonValue: {
232 QJsonValue v;
233 property.readProperty(target: object, property: &v);
234 return QV4::JsonObject::fromJsonValue(engine: v4, value: v);
235 }
236 case QMetaType::QJsonObject: {
237 QJsonObject v;
238 property.readProperty(target: object, property: &v);
239 return QV4::JsonObject::fromJsonObject(engine: v4, object: v);
240 }
241 case QMetaType::QJsonArray:
242 return encodeSequence(QMetaSequence::fromContainer<QJsonArray>());
243 case QMetaType::QStringList:
244 return encodeSequence(QMetaSequence::fromContainer<QStringList>());
245 case QMetaType::QVariantList:
246 return encodeSequence(QMetaSequence::fromContainer<QVariantList>());
247 case QMetaType::QUrl: {
248 // ### Qt7: We really want this to be a JS URL object, but that would break things.
249 QUrl v;
250 property.readProperty(target: object, property: &v);
251 return Encode(v4->newVariantObject(type: propMetaType, data: &v));
252 }
253 case QMetaType::QPixmap:
254 case QMetaType::QImage: {
255 // Scarce value types
256 QVariant v(propMetaType);
257 property.readProperty(target: object, property: v.data());
258 return Encode(v4->newVariantObject(type: propMetaType, data: v.constData()));
259 }
260 default:
261 break;
262 }
263
264 if (propMetaType == QMetaType::fromType<QJSValue>()) {
265 QJSValue v;
266 property.readProperty(target: object, property: &v);
267 return QJSValuePrivate::convertToReturnedValue(e: v4, jsval: v);
268 }
269
270 if (property.isQVariant()) {
271 // We have to read the property even if it's a lazy-loaded reference object.
272 // Without reading it, we wouldn't know its inner type.
273 QVariant v;
274 property.readProperty(target: object, property: &v);
275 return scope.engine->fromVariant(
276 variant: v, parent: wrapper, property: property.coreIndex(),
277 flags: referenceFlags(v4: scope.engine, property) | Heap::ReferenceObject::IsVariant);
278 }
279
280 if (!propMetaType.isValid()) {
281 QMetaProperty p = object->metaObject()->property(index: property.coreIndex());
282 qWarning(msg: "QMetaProperty::read: Unable to handle unregistered datatype '%s' for property "
283 "'%s::%s'", p.typeName(), object->metaObject()->className(), p.name());
284 return Encode::undefined();
285 }
286
287 // TODO: For historical reasons we don't enforce locations for reference objects here.
288 // Once we do, we can eager load and use the fromVariant() below.
289 // Then the extra checks for value types and sequences can be dropped.
290
291 if (QQmlMetaType::isValueType(type: propMetaType)) {
292 if (const QMetaObject *valueTypeMetaObject
293 = QQmlMetaType::metaObjectForValueType(type: propMetaType)) {
294 // Lazy loaded value type reference. Pass nullptr as data.
295 return QQmlValueTypeWrapper::create(
296 engine: v4, data: nullptr, metaObject: valueTypeMetaObject, type: propMetaType, object: wrapper,
297 property: property.coreIndex(), flags: referenceFlags(v4: scope.engine, property));
298 }
299 }
300
301 // See if it's a sequence type.
302 const QQmlType qmlType = QQmlMetaType::qmlListType(metaType: propMetaType);
303 if (qmlType.isSequentialContainer())
304 return encodeSequence(qmlType.listMetaSequence());
305
306 QVariant v(propMetaType);
307 property.readProperty(target: object, property: v.data());
308 return scope.engine->fromVariant(
309 variant: v, parent: wrapper, property: property.coreIndex(), flags: referenceFlags(v4: scope.engine, property));
310}
311
312void QObjectWrapper::initializeBindings(ExecutionEngine *engine)
313{
314 engine->functionPrototype()->defineDefaultProperty(QStringLiteral("connect"), code: method_connect);
315 engine->functionPrototype()->defineDefaultProperty(QStringLiteral("disconnect"), code: method_disconnect);
316}
317
318const QQmlPropertyData *QObjectWrapper::findProperty(
319 const QQmlRefPointer<QQmlContextData> &qmlContext, String *name,
320 Flags flags, QQmlPropertyData *local) const
321{
322 return findProperty(o: d()->object(), qmlContext, name, flags, local);
323}
324
325const QQmlPropertyData *QObjectWrapper::findProperty(
326 QObject *o, const QQmlRefPointer<QQmlContextData> &qmlContext,
327 String *name, Flags flags, QQmlPropertyData *local)
328{
329 Q_UNUSED(flags);
330
331 QQmlData *ddata = QQmlData::get(object: o, create: false);
332 const QQmlPropertyData *result = nullptr;
333 if (ddata && ddata->propertyCache)
334 result = ddata->propertyCache->property(key: name, object: o, context: qmlContext);
335 else
336 result = QQmlPropertyCache::property(o, name, qmlContext, local);
337 return result;
338}
339
340ReturnedValue QObjectWrapper::getProperty(
341 ExecutionEngine *engine, Heap::Object *wrapper, QObject *object,
342 const QQmlPropertyData *property, Flags flags)
343{
344 QQmlData::flushPendingBinding(object, coreIndex: property->coreIndex());
345
346 if (property->isFunction() && !property->isVarProperty()) {
347 if (property->isVMEFunction()) {
348 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: object);
349 Q_ASSERT(vmemo);
350 return vmemo->vmeMethod(index: property->coreIndex());
351 } else if (property->isV4Function()) {
352 return QObjectMethod::create(
353 engine, wrapper: (flags & AttachMethods) ? wrapper : nullptr, index: property->coreIndex());
354 } else if (property->isSignalHandler()) {
355 QmlSignalHandler::initProto(v4: engine);
356 return engine->memoryManager->allocate<QmlSignalHandler>(
357 args&: object, args: property->coreIndex())->asReturnedValue();
358 } else {
359 return QObjectMethod::create(
360 engine, wrapper: (flags & AttachMethods) ? wrapper : nullptr, index: property->coreIndex());
361 }
362 }
363
364 QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(e: engine->qmlEngine()) : nullptr;
365
366 if (ep && ep->propertyCapture && !property->isConstant())
367 if (!property->isBindable() || ep->propertyCapture->expression->mustCaptureBindableProperty())
368 ep->propertyCapture->captureProperty(object, property->coreIndex(), property->notifyIndex());
369
370 if (property->isVarProperty()) {
371 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: object);
372 Q_ASSERT(vmemo);
373 return vmemo->vmeProperty(index: property->coreIndex());
374 } else {
375 return loadProperty(v4: engine, wrapper, object, property: *property);
376 }
377}
378
379static OptionalReturnedValue getDestroyOrToStringMethod(
380 ExecutionEngine *v4, String *name, Heap::Object *qobj, bool *hasProperty = nullptr)
381{
382 int index = 0;
383 if (name->equals(other: v4->id_destroy()))
384 index = QObjectMethod::DestroyMethod;
385 else if (name->equals(other: v4->id_toString()))
386 index = QObjectMethod::ToStringMethod;
387 else
388 return OptionalReturnedValue();
389
390 if (hasProperty)
391 *hasProperty = true;
392 return OptionalReturnedValue(QObjectMethod::create(engine: v4, wrapper: qobj, index));
393}
394
395static OptionalReturnedValue getPropertyFromImports(
396 ExecutionEngine *v4, String *name, const QQmlRefPointer<QQmlContextData> &qmlContext,
397 QObject *qobj, bool *hasProperty = nullptr)
398{
399 if (!qmlContext || !qmlContext->imports())
400 return OptionalReturnedValue();
401
402 if (hasProperty)
403 *hasProperty = true;
404
405 if (QQmlTypeLoader *typeLoader = v4->typeLoader()) {
406 QQmlTypeNameCache::Result r = qmlContext->imports()->query(key: name, typeLoader);
407
408 if (!r.isValid())
409 return OptionalReturnedValue();
410
411 if (r.scriptIndex != -1) {
412 return OptionalReturnedValue(Encode::undefined());
413 } else if (r.type.isValid()) {
414 return OptionalReturnedValue(
415 QQmlTypeWrapper::create(v4, qobj,r.type, Heap::QQmlTypeWrapper::ExcludeEnums));
416 } else if (r.importNamespace) {
417 return OptionalReturnedValue(QQmlTypeWrapper::create(
418 v4, qobj, qmlContext->imports(), r.importNamespace,
419 Heap::QQmlTypeWrapper::ExcludeEnums));
420 }
421 Q_UNREACHABLE_RETURN(OptionalReturnedValue());
422 } else {
423 return OptionalReturnedValue();
424 }
425}
426
427ReturnedValue QObjectWrapper::getQmlProperty(
428 const QQmlRefPointer<QQmlContextData> &qmlContext, String *name,
429 QObjectWrapper::Flags flags, bool *hasProperty) const
430{
431 // Keep this code in sync with ::virtualResolveLookupGetter
432
433 if (QQmlData::wasDeleted(object: d()->object())) {
434 if (hasProperty)
435 *hasProperty = false;
436 return Encode::undefined();
437 }
438
439 ExecutionEngine *v4 = engine();
440
441 if (auto methodValue = getDestroyOrToStringMethod(v4, name, qobj: d(), hasProperty))
442 return *methodValue;
443
444 QQmlPropertyData local;
445 const QQmlPropertyData *result = findProperty(qmlContext, name, flags, local: &local);
446
447 if (!result) {
448 // Check for attached properties
449 if ((flags & IncludeImports) && name->startsWithUpper()) {
450 if (auto importProperty = getPropertyFromImports(
451 v4, name, qmlContext, qobj: d()->object(), hasProperty))
452 return *importProperty;
453 }
454 return Object::virtualGet(m: this, id: name->propertyKey(), receiver: this, hasProperty);
455 }
456
457 QQmlData *ddata = QQmlData::get(object: d()->object(), create: false);
458
459 if ((flags & CheckRevision) && result->hasRevision()) {
460 if (ddata && ddata->propertyCache && !ddata->propertyCache->isAllowedInRevision(data: result)) {
461 if (hasProperty)
462 *hasProperty = false;
463 return Encode::undefined();
464 }
465 }
466
467 if (hasProperty)
468 *hasProperty = true;
469
470 return getProperty(engine: v4, wrapper: d(), object: d()->object(), property: result, flags);
471}
472
473ReturnedValue QObjectWrapper::getQmlProperty(
474 ExecutionEngine *engine, const QQmlRefPointer<QQmlContextData> &qmlContext,
475 Heap::Object *wrapper, QObject *object, String *name, QObjectWrapper::Flags flags,
476 bool *hasProperty, const QQmlPropertyData **property)
477{
478 if (QQmlData::wasDeleted(object)) {
479 if (hasProperty)
480 *hasProperty = false;
481 return Encode::null();
482 }
483
484 if (auto methodValue = getDestroyOrToStringMethod(v4: engine, name, qobj: wrapper, hasProperty))
485 return *methodValue;
486
487 QQmlData *ddata = QQmlData::get(object, create: false);
488 QQmlPropertyData local;
489 const QQmlPropertyData *result = findProperty(o: object, qmlContext, name, flags, local: &local);
490
491 if (result) {
492 if ((flags & QObjectWrapper::CheckRevision) && result->hasRevision()) {
493 if (ddata && ddata->propertyCache
494 && !ddata->propertyCache->isAllowedInRevision(data: result)) {
495 if (hasProperty)
496 *hasProperty = false;
497 return Encode::undefined();
498 }
499 }
500
501 if (hasProperty)
502 *hasProperty = true;
503
504 if (property && result != &local)
505 *property = result;
506
507 return getProperty(engine, wrapper, object, property: result, flags);
508 } else {
509 // Check if this object is already wrapped.
510 if (!ddata || (ddata->jsWrapper.isUndefined() &&
511 (ddata->jsEngineId == 0 || // Nobody owns the QObject
512 !ddata->hasTaintedV4Object))) { // Someone else has used the QObject, but it isn't tainted
513
514 // Not wrapped. Last chance: try query QObjectWrapper's prototype.
515 // If it can't handle this, then there is no point
516 // to wrap the QObject just to look at an empty set of JS props.
517 Object *proto = QObjectWrapper::defaultPrototype(e: engine);
518 return proto->get(name, hasProperty);
519 }
520 }
521
522 // If we get here, we must already be wrapped (which implies a ddata).
523 // There's no point wrapping again, as there wouldn't be any new props.
524 Q_ASSERT(ddata);
525
526 Scope scope(engine);
527 Scoped<QObjectWrapper> rewrapped(scope, wrap(engine, object));
528 if (!rewrapped) {
529 if (hasProperty)
530 *hasProperty = false;
531 return Encode::null();
532 }
533 return rewrapped->getQmlProperty(qmlContext, name, flags, hasProperty);
534}
535
536
537bool QObjectWrapper::setQmlProperty(
538 ExecutionEngine *engine, const QQmlRefPointer<QQmlContextData> &qmlContext, QObject *object,
539 String *name, QObjectWrapper::Flags flags, const Value &value)
540{
541 if (QQmlData::wasDeleted(object))
542 return false;
543
544 QQmlPropertyData local;
545 const QQmlPropertyData *result = QQmlPropertyCache::property(object, name, qmlContext, &local);
546 if (!result)
547 return false;
548
549 if ((flags & QObjectWrapper::CheckRevision) && result->hasRevision()) {
550 QQmlData *ddata = QQmlData::get(object);
551 if (ddata && ddata->propertyCache && !ddata->propertyCache->isAllowedInRevision(data: result))
552 return false;
553 }
554
555 setProperty(engine, object, property: result, value);
556 return true;
557}
558
559/*!
560 \internal
561 If an QObjectWrapper is created via wrap, then it needs to be stored somewhere.
562 Otherwise, the garbage collector will immediately collect it if it is already
563 past the "mark QObjectWrapper's" phase (note that QObjectWrapper are marked
564 by iterating over a list of all QObjectWrapper, and then checking if the
565 wrapper fulfills some conditions).
566 However, sometimes we don't really want to keep a reference to the wrapper,
567 but just want to make sure that it exists (and we know that the wrapper
568 already fulfills the conditions to be kept alive). Then ensureWrapper
569 can be used, which creates the wrapper and ensures that it is also
570 marked.
571 */
572void QObjectWrapper::ensureWrapper(ExecutionEngine *engine, QObject *object)
573{
574 QV4::Scope scope(engine);
575 QV4::Scoped<QV4::QObjectWrapper> wrapper {scope, QV4::QObjectWrapper::wrap(engine, object)};
576 QV4::WriteBarrier::markCustom(engine, markFunction: [&wrapper](QV4::MarkStack *ms) {
577 wrapper->mark(markStack: ms);
578 });
579}
580
581void QObjectWrapper::setProperty(
582 ExecutionEngine *engine, QObject *object,
583 const QQmlPropertyData *property, const Value &value)
584{
585 if (!property->isWritable() && !property->isQList()) {
586 QString error = QLatin1String("Cannot assign to read-only property \"") +
587 property->name(object) + QLatin1Char('\"');
588 engine->throwTypeError(message: error);
589 return;
590 }
591
592 Scope scope(engine);
593 if (ScopedFunctionObject f(scope, value); f) {
594 if (f->as<QQmlTypeWrapper>()) {
595 // Ignore. It's probably a singleton or an attached type.
596 } else if (!f->isBinding()) {
597 const bool isAliasToAllowed = [&]() {
598 if (property->isAlias()) {
599 const QQmlPropertyIndex originalIndex(property->coreIndex(), -1);
600 auto [targetObject, targetIndex] = QQmlPropertyPrivate::findAliasTarget(baseObject: object, baseIndex: originalIndex);
601 Q_ASSERT(targetObject);
602 const QQmlPropertyCache *targetCache
603 = QQmlData::get(object: targetObject)->propertyCache.data();
604 Q_ASSERT(targetCache);
605 const QQmlPropertyData *targetProperty
606 = targetCache->property(index: targetIndex.coreIndex());
607 object = targetObject;
608 property = targetProperty;
609 return targetProperty->isVarProperty() || targetProperty->propType() == QMetaType::fromType<QJSValue>();
610 } else {
611 return false;
612 }
613 }();
614 if (!isAliasToAllowed && !property->isVarProperty()
615 && property->propType() != QMetaType::fromType<QJSValue>()) {
616 // assigning a JS function to a non var or QJSValue property or is not allowed.
617 QString error = QLatin1String("Cannot assign JavaScript function to ");
618 if (!QMetaType(property->propType()).name())
619 error += QLatin1String("[unknown property type]");
620 else
621 error += QLatin1String(QMetaType(property->propType()).name());
622 scope.engine->throwError(message: error);
623 return;
624 }
625 } else {
626
627 QQmlRefPointer<QQmlContextData> callingQmlContext = scope.engine->callingQmlContext();
628 Scoped<QQmlBindingFunction> bindingFunction(scope, (const Value &)f);
629 Scoped<JavaScriptFunctionObject> f(scope, bindingFunction->bindingFunction());
630 ScopedContext ctx(scope, f->scope());
631
632 // binding assignment.
633 if (property->isBindable()) {
634 const QQmlPropertyIndex idx(property->coreIndex(), /*not a value type*/-1);
635 auto [targetObject, targetIndex] = QQmlPropertyPrivate::findAliasTarget(baseObject: object, baseIndex: idx);
636 QUntypedPropertyBinding binding;
637 if (f->isBoundFunction()) {
638 auto boundFunction = static_cast<BoundFunction *>(f.getPointer());
639 binding = QQmlPropertyBinding::createFromBoundFunction(pd: property, function: boundFunction, obj: object, ctxt: callingQmlContext,
640 scope: ctx, target: targetObject, targetIndex);
641 } else {
642 binding = QQmlPropertyBinding::create(pd: property, function: f->function(), obj: object, ctxt: callingQmlContext,
643 scope: ctx, target: targetObject, targetIndex);
644 }
645 QUntypedBindable bindable;
646 void *argv = {&bindable};
647 // indirect metacall in case interceptors are installed
648 targetObject->metaObject()->metacall(targetObject, QMetaObject::BindableProperty, targetIndex.coreIndex(), &argv);
649 bool ok = bindable.setBinding(binding);
650 if (!ok) {
651 auto error = QStringLiteral("Failed to set binding on %1::%2.").
652 arg(args: QString::fromUtf8(utf8: object->metaObject()->className()), args: property->name(object));
653 scope.engine->throwError(message: error);
654 }
655 } else {
656 QQmlBinding *newBinding = QQmlBinding::create(property, function: f->function(), obj: object, ctxt: callingQmlContext, scope: ctx);
657 newBinding->setSourceLocation(bindingFunction->currentLocation());
658 if (f->isBoundFunction())
659 newBinding->setBoundFunction(static_cast<BoundFunction *>(f.getPointer()));
660 newBinding->setTarget(object, *property, valueType: nullptr);
661 QQmlPropertyPrivate::setBinding(binding: newBinding);
662 }
663 return;
664 }
665 }
666
667 if (Q_UNLIKELY(lcBuiltinsBindingRemoval().isInfoEnabled())) {
668 if (auto binding = QQmlPropertyPrivate::binding(object, index: QQmlPropertyIndex(property->coreIndex()))) {
669 const auto stackFrame = engine->currentStackFrame;
670 switch (binding->kind()) {
671 case QQmlAbstractBinding::QmlBinding: {
672 const auto qmlBinding = static_cast<const QQmlBinding*>(binding);
673 qCInfo(lcBuiltinsBindingRemoval,
674 "Overwriting binding on %s::%s at %s:%d that was initially bound at %s",
675 object->metaObject()->className(), qPrintable(property->name(object)),
676 qPrintable(stackFrame->source()), stackFrame->lineNumber(),
677 qPrintable(qmlBinding->expressionIdentifier()));
678 break;
679 }
680 case QQmlAbstractBinding::ValueTypeProxy:
681 case QQmlAbstractBinding::PropertyToPropertyBinding: {
682 qCInfo(lcBuiltinsBindingRemoval,
683 "Overwriting binding on %s::%s at %s:%d",
684 object->metaObject()->className(), qPrintable(property->name(object)),
685 qPrintable(stackFrame->source()), stackFrame->lineNumber());
686 break;
687 }
688 }
689 }
690 }
691 QQmlPropertyPrivate::removeBinding(o: object, index: QQmlPropertyIndex(property->coreIndex()));
692
693 if (property->isVarProperty()) {
694 // allow assignment of "special" values (null, undefined, function) to var properties
695 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: object);
696 Q_ASSERT(vmemo);
697 vmemo->setVMEProperty(index: property->coreIndex(), v: value);
698 return;
699 }
700
701#define PROPERTY_STORE(cpptype, value) \
702 cpptype o = value; \
703 int status = -1; \
704 int flags = 0; \
705 void *argv[] = { &o, 0, &status, &flags }; \
706 QMetaObject::metacall(object, QMetaObject::WriteProperty, property->coreIndex(), argv);
707
708 const QMetaType propType = property->propType();
709 // functions are already handled, except for the QJSValue case
710 Q_ASSERT(!value.as<FunctionObject>()
711 || value.as<QV4::QQmlTypeWrapper>()
712 || propType == QMetaType::fromType<QJSValue>());
713
714 if (value.isNull() && property->isQObject()) {
715 PROPERTY_STORE(QObject*, nullptr);
716 } else if (value.isUndefined() && property->isResettable()) {
717 void *a[] = { nullptr };
718 QMetaObject::metacall(object, QMetaObject::ResetProperty, property->coreIndex(), a);
719 } else if (value.isUndefined() && propType == QMetaType::fromType<QVariant>()) {
720 PROPERTY_STORE(QVariant, QVariant());
721 } else if (value.isUndefined() && propType == QMetaType::fromType<QJsonValue>()) {
722 PROPERTY_STORE(QJsonValue, QJsonValue(QJsonValue::Undefined));
723 } else if (propType == QMetaType::fromType<QJSValue>()) {
724 PROPERTY_STORE(QJSValue, QJSValuePrivate::fromReturnedValue(value.asReturnedValue()));
725 } else if (value.isUndefined() && propType != QMetaType::fromType<QQmlScriptString>()) {
726 QString error = QLatin1String("Cannot assign [undefined] to ");
727 if (!propType.name())
728 error += QLatin1String("[unknown property type]");
729 else
730 error += QLatin1String(propType.name());
731 scope.engine->throwError(message: error);
732 return;
733 } else if (propType == QMetaType::fromType<int>() && value.isNumber()) {
734 PROPERTY_STORE(int, value.toInt32());
735 } else if (propType == QMetaType::fromType<qreal>() && value.isNumber()) {
736 PROPERTY_STORE(qreal, qreal(value.asDouble()));
737 } else if (propType == QMetaType::fromType<float>() && value.isNumber()) {
738 PROPERTY_STORE(float, float(value.asDouble()));
739 } else if (propType == QMetaType::fromType<double>() && value.isNumber()) {
740 PROPERTY_STORE(double, double(value.asDouble()));
741 } else if (propType == QMetaType::fromType<QString>() && value.isString()) {
742 PROPERTY_STORE(QString, value.toQStringNoThrow());
743 } else if (property->isVarProperty()) {
744 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: object);
745 Q_ASSERT(vmemo);
746 vmemo->setVMEProperty(index: property->coreIndex(), v: value);
747 } else if (propType == QMetaType::fromType<QQmlScriptString>()
748 && (value.isUndefined() || value.isPrimitive())) {
749 QQmlScriptString ss(value.toQStringNoThrow(), nullptr /* context */, object);
750 if (value.isNumber()) {
751 ss.d->numberValue = value.toNumber();
752 ss.d->isNumberLiteral = true;
753 } else if (value.isString()) {
754 ss.d->script = CompiledData::Binding::escapedString(string: ss.d->script);
755 ss.d->isStringLiteral = true;
756 }
757 PROPERTY_STORE(QQmlScriptString, ss);
758 } else {
759 QVariant v;
760 if (property->isQList() && propType.flags().testFlag(flag: QMetaType::IsQmlList))
761 v = ExecutionEngine::toVariant(value, typeHint: QMetaType::fromType<QList<QObject *> >());
762 else
763 v = ExecutionEngine::toVariant(value, typeHint: propType);
764
765 QQmlRefPointer<QQmlContextData> callingQmlContext = scope.engine->callingQmlContext();
766 if (!QQmlPropertyPrivate::write(object, *property, v, callingQmlContext)) {
767 const char *valueType = (v.userType() == QMetaType::UnknownType)
768 ? "an unknown type"
769 : QMetaType(v.userType()).name();
770
771 const char *targetTypeName = propType.name();
772 if (!targetTypeName)
773 targetTypeName = "an unregistered type";
774
775 QString error = QLatin1String("Cannot assign ") +
776 QLatin1String(valueType) +
777 QLatin1String(" to ") +
778 QLatin1String(targetTypeName);
779 scope.engine->throwError(message: error);
780 return;
781 }
782 }
783}
784
785ReturnedValue QObjectWrapper::wrap_slowPath(ExecutionEngine *engine, QObject *object)
786{
787 Q_ASSERT(!QQmlData::wasDeleted(object));
788
789 QQmlData *ddata = QQmlData::get(object, create: true);
790 if (!ddata)
791 return Encode::undefined();
792
793 Scope scope(engine);
794
795 if (ddata->jsWrapper.isUndefined() &&
796 (ddata->jsEngineId == engine->m_engineId || // We own the QObject
797 ddata->jsEngineId == 0 || // No one owns the QObject
798 !ddata->hasTaintedV4Object)) { // Someone else has used the QObject, but it isn't tainted
799
800 ScopedValue rv(scope, create(engine, object));
801 ddata->jsWrapper.set(engine: scope.engine, value: rv);
802 ddata->jsEngineId = engine->m_engineId;
803 return rv->asReturnedValue();
804
805 } else {
806 // If this object is tainted, we have to check to see if it is in our
807 // tainted object list
808 ScopedObject alternateWrapper(scope, (Object *)nullptr);
809 if (engine->m_multiplyWrappedQObjects && ddata->hasTaintedV4Object)
810 alternateWrapper = engine->m_multiplyWrappedQObjects->value(key: object);
811
812 // If our tainted handle doesn't exist or has been collected, and there isn't
813 // a handle in the ddata, we can assume ownership of the ddata->jsWrapper
814 if (ddata->jsWrapper.isUndefined() && !alternateWrapper) {
815 ScopedValue result(scope, create(engine, object));
816 ddata->jsWrapper.set(engine: scope.engine, value: result);
817 ddata->jsEngineId = engine->m_engineId;
818 return result->asReturnedValue();
819 }
820
821 if (!alternateWrapper) {
822 alternateWrapper = create(engine, object);
823 if (!engine->m_multiplyWrappedQObjects)
824 engine->m_multiplyWrappedQObjects = new MultiplyWrappedQObjectMap;
825 engine->m_multiplyWrappedQObjects->insert(key: object, value: alternateWrapper->d());
826 ddata->hasTaintedV4Object = true;
827 }
828
829 return alternateWrapper.asReturnedValue();
830 }
831}
832
833ReturnedValue QObjectWrapper::wrapConst_slowPath(ExecutionEngine *engine, QObject *object)
834{
835 const QObject *constObject = object;
836
837 QQmlData *ddata = QQmlData::get(object, create: true);
838
839 Scope scope(engine);
840 ScopedObject constWrapper(scope);
841 if (engine->m_multiplyWrappedQObjects && ddata->hasConstWrapper)
842 constWrapper = engine->m_multiplyWrappedQObjects->value(key: constObject);
843
844 if (!constWrapper) {
845 constWrapper = create(engine, object);
846 constWrapper->setInternalClass(constWrapper->internalClass()->cryopreserved());
847 if (!engine->m_multiplyWrappedQObjects)
848 engine->m_multiplyWrappedQObjects = new MultiplyWrappedQObjectMap;
849 engine->m_multiplyWrappedQObjects->insert(key: constObject, value: constWrapper->d());
850 ddata->hasConstWrapper = true;
851 }
852
853 return constWrapper.asReturnedValue();
854}
855
856void QObjectWrapper::markWrapper(QObject *object, MarkStack *markStack)
857{
858 if (QQmlData::wasDeleted(object))
859 return;
860
861 QQmlData *ddata = QQmlData::get(object);
862 if (!ddata)
863 return;
864
865 const ExecutionEngine *engine = markStack->engine();
866 if (ddata->jsEngineId == engine->m_engineId)
867 ddata->jsWrapper.markOnce(markStack);
868 else if (engine->m_multiplyWrappedQObjects && ddata->hasTaintedV4Object)
869 engine->m_multiplyWrappedQObjects->mark(key: object, markStack);
870 if (ddata->hasConstWrapper) {
871 Q_ASSERT(engine->m_multiplyWrappedQObjects);
872 engine->m_multiplyWrappedQObjects->mark(key: static_cast<const QObject *>(object), markStack);
873 }
874}
875
876void QObjectWrapper::setProperty(ExecutionEngine *engine, int propertyIndex, const Value &value)
877{
878 setProperty(engine, object: d()->object(), propertyIndex, value);
879}
880
881void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, int propertyIndex, const Value &value)
882{
883 Q_ASSERT(propertyIndex < 0xffff);
884 Q_ASSERT(propertyIndex >= 0);
885
886 if (QQmlData::wasDeleted(object))
887 return;
888 QQmlData *ddata = QQmlData::get(object, /*create*/false);
889 if (!ddata)
890 return;
891
892 Q_ASSERT(ddata->propertyCache);
893 const QQmlPropertyData *property = ddata->propertyCache->property(index: propertyIndex);
894 Q_ASSERT(property); // We resolved this property earlier, so it better exist!
895 return setProperty(engine, object, property, value);
896}
897
898bool QObjectWrapper::virtualIsEqualTo(Managed *a, Managed *b)
899{
900 Q_ASSERT(a->as<QObjectWrapper>());
901 const QObjectWrapper *aobjectWrapper = static_cast<QObjectWrapper *>(a);
902 if (const QQmlTypeWrapper *qmlTypeWrapper = b->as<QQmlTypeWrapper>())
903 return qmlTypeWrapper->object() == aobjectWrapper->object();
904
905 // We can have a const and a non-const wrapper for the same object.
906 const QObjectWrapper *bobjectWrapper = b->as<QObjectWrapper>();
907 return bobjectWrapper && aobjectWrapper->object() == bobjectWrapper->object();
908}
909
910ReturnedValue QObjectWrapper::create(ExecutionEngine *engine, QObject *object)
911{
912 if (QQmlPropertyCache::ConstPtr cache = QQmlData::ensurePropertyCache(object)) {
913 ReturnedValue result = Encode::null();
914 void *args[] = { &result, &engine };
915 if (cache->callJSFactoryMethod(object, args))
916 return result;
917 }
918 return (engine->memoryManager->allocate<QObjectWrapper>(args&: object))->asReturnedValue();
919}
920
921ReturnedValue QObjectWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
922{
923 if (!id.isString())
924 return Object::virtualGet(m, id, receiver, hasProperty);
925
926 const QObjectWrapper *that = static_cast<const QObjectWrapper*>(m);
927 Scope scope(that);
928 ScopedString n(scope, id.asStringOrSymbol());
929 QQmlRefPointer<QQmlContextData> qmlContext = that->engine()->callingQmlContext();
930 return that->getQmlProperty(qmlContext, name: n, flags: IncludeImports | AttachMethods, hasProperty);
931}
932
933bool QObjectWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver)
934{
935 if (!id.isString())
936 return Object::virtualPut(m, id, value, receiver);
937
938 Scope scope(m);
939 QObjectWrapper *that = static_cast<QObjectWrapper*>(m);
940 ScopedString name(scope, id.asStringOrSymbol());
941
942 if (that->internalClass()->isFrozen()) {
943 QString error = QLatin1String("Cannot assign to property \"") +
944 name->toQString() + QLatin1String("\" of read-only object");
945 scope.engine->throwError(message: error);
946 return false;
947 }
948
949 if (scope.hasException() || QQmlData::wasDeleted(object: that->d()->object()))
950 return false;
951
952 QQmlRefPointer<QQmlContextData> qmlContext = scope.engine->callingQmlContext();
953 if (!setQmlProperty(engine: scope.engine, qmlContext, object: that->d()->object(), name, flags: NoFlag, value)) {
954 QQmlData *ddata = QQmlData::get(object: that->d()->object());
955 // Types created by QML are not extensible at run-time, but for other QObjects we can store them
956 // as regular JavaScript properties, like on JavaScript objects.
957 if (ddata && ddata->context) {
958 QString error = QLatin1String("Cannot assign to non-existent property \"") +
959 name->toQString() + QLatin1Char('\"');
960 scope.engine->throwError(message: error);
961 return false;
962 } else {
963 return Object::virtualPut(m, id, value, receiver);
964 }
965 }
966
967 return true;
968}
969
970PropertyAttributes QObjectWrapper::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p)
971{
972 if (id.isString()) {
973 const QObjectWrapper *that = static_cast<const QObjectWrapper*>(m);
974 const QObject *thatObject = that->d()->object();
975 if (!QQmlData::wasDeleted(object: thatObject)) {
976 Scope scope(m);
977 ScopedString n(scope, id.asStringOrSymbol());
978 QQmlRefPointer<QQmlContextData> qmlContext = scope.engine->callingQmlContext();
979 QQmlPropertyData local;
980 if (that->findProperty(qmlContext, name: n, flags: NoFlag, local: &local)
981 || n->equals(other: scope.engine->id_destroy()) || n->equals(other: scope.engine->id_toString())) {
982 if (p) {
983 // ### probably not the fastest implementation
984 bool hasProperty;
985 p->value = that->getQmlProperty(
986 qmlContext, name: n, flags: IncludeImports | AttachMethods, hasProperty: &hasProperty);
987 }
988 return Attr_Data;
989 }
990 }
991 }
992
993 return Object::virtualGetOwnProperty(m, id, p);
994}
995
996struct QObjectWrapperOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
997{
998 int propertyIndex = 0;
999 ~QObjectWrapperOwnPropertyKeyIterator() override = default;
1000 PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
1001
1002private:
1003 QSet<QByteArray> m_alreadySeen;
1004};
1005
1006PropertyKey QObjectWrapperOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
1007{
1008 // Used to block access to QObject::destroyed() and QObject::deleteLater() from QML
1009 static const int destroyedIdx1 = QObject::staticMetaObject.indexOfSignal(signal: "destroyed(QObject*)");
1010 static const int destroyedIdx2 = QObject::staticMetaObject.indexOfSignal(signal: "destroyed()");
1011 static const int deleteLaterIdx = QObject::staticMetaObject.indexOfSlot(slot: "deleteLater()");
1012
1013 const QObjectWrapper *that = static_cast<const QObjectWrapper*>(o);
1014
1015 QObject *thatObject = that->d()->object();
1016 if (thatObject && !QQmlData::wasDeleted(object: thatObject)) {
1017 const QMetaObject *mo = thatObject->metaObject();
1018 // These indices don't apply to gadgets, so don't block them.
1019 const bool preventDestruction = mo->superClass() || mo == &QObject::staticMetaObject;
1020 const int propertyCount = mo->propertyCount();
1021 if (propertyIndex < propertyCount) {
1022 ExecutionEngine *thatEngine = that->engine();
1023 Scope scope(thatEngine);
1024 const QMetaProperty property = mo->property(index: propertyIndex);
1025 ScopedString propName(scope, thatEngine->newString(s: QString::fromUtf8(utf8: property.name())));
1026 ++propertyIndex;
1027 if (attrs)
1028 *attrs= Attr_Data;
1029 if (pd) {
1030 QQmlPropertyData local;
1031 local.load(property);
1032 pd->value = that->getProperty(
1033 engine: thatEngine, wrapper: that->d(), object: thatObject, property: &local,
1034 flags: QObjectWrapper::AttachMethods);
1035 }
1036 return propName->toPropertyKey();
1037 }
1038 const int methodCount = mo->methodCount();
1039 while (propertyIndex < propertyCount + methodCount) {
1040 Q_ASSERT(propertyIndex >= propertyCount);
1041 int index = propertyIndex - propertyCount;
1042 const QMetaMethod method = mo->method(index);
1043 ++propertyIndex;
1044 if (method.access() == QMetaMethod::Private || (preventDestruction && (index == deleteLaterIdx || index == destroyedIdx1 || index == destroyedIdx2)))
1045 continue;
1046 // filter out duplicates due to overloads:
1047 if (m_alreadySeen.contains(value: method.name()))
1048 continue;
1049 else
1050 m_alreadySeen.insert(value: method.name());
1051 ExecutionEngine *thatEngine = that->engine();
1052 Scope scope(thatEngine);
1053 ScopedString methodName(scope, thatEngine->newString(s: QString::fromUtf8(ba: method.name())));
1054 if (attrs)
1055 *attrs = Attr_Data;
1056 if (pd) {
1057 QQmlPropertyData local;
1058 local.load(method);
1059 pd->value = that->getProperty(
1060 engine: thatEngine, wrapper: that->d(), object: thatObject, property: &local,
1061 flags: QObjectWrapper::AttachMethods);
1062 }
1063 return methodName->toPropertyKey();
1064 }
1065 }
1066
1067 return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
1068}
1069
1070OwnPropertyKeyIterator *QObjectWrapper::virtualOwnPropertyKeys(const Object *m, Value *target)
1071{
1072 *target = *m;
1073 return new QObjectWrapperOwnPropertyKeyIterator;
1074}
1075
1076ReturnedValue QObjectWrapper::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup)
1077{
1078 // Keep this code in sync with ::getQmlProperty
1079 PropertyKey id = engine->identifierTable->asPropertyKey(str: engine->currentStackFrame->v4Function->compilationUnit->
1080 runtimeStrings[lookup->nameIndex]);
1081 if (!id.isString())
1082 return Object::virtualResolveLookupGetter(object, engine, lookup);
1083 Scope scope(engine);
1084
1085 const QObjectWrapper *This = static_cast<const QObjectWrapper *>(object);
1086 ScopedString name(scope, id.asStringOrSymbol());
1087 QQmlRefPointer<QQmlContextData> qmlContext = engine->callingQmlContext();
1088
1089 QObject * const qobj = This->d()->object();
1090
1091 if (QQmlData::wasDeleted(object: qobj))
1092 return Encode::undefined();
1093
1094 QQmlData *ddata = QQmlData::get(object: qobj, create: false);
1095 if (auto methodValue = getDestroyOrToStringMethod(v4: engine, name, qobj: This->d())) {
1096 Scoped<QObjectMethod> method(scope, *methodValue);
1097 setupQObjectMethodLookup(
1098 lookup, ddata: ddata ? ddata : QQmlData::get(object: qobj, create: true), propertyData: nullptr, self: This, method: method->d());
1099 lookup->getter = Lookup::getterQObjectMethod;
1100 return method.asReturnedValue();
1101 }
1102
1103 if (!ddata || !ddata->propertyCache) {
1104 QQmlPropertyData local;
1105 const QQmlPropertyData *property = QQmlPropertyCache::property(
1106 qobj, name, qmlContext, &local);
1107 return property
1108 ? getProperty(engine, wrapper: This->d(), object: qobj, property,
1109 flags: lookup->forCall ? NoFlag : AttachMethods)
1110 : Encode::undefined();
1111 }
1112 const QQmlPropertyData *property = ddata->propertyCache->property(key: name.getPointer(), object: qobj, context: qmlContext);
1113
1114 if (!property) {
1115 // Check for attached properties
1116 if (name->startsWithUpper()) {
1117 if (auto importProperty = getPropertyFromImports(v4: engine, name, qmlContext, qobj))
1118 return *importProperty;
1119 }
1120 return Object::virtualResolveLookupGetter(object, engine, lookup);
1121 }
1122
1123 if (property->isFunction()
1124 && !property->isVarProperty()
1125 && !property->isVMEFunction() // Handled by QObjectLookup
1126 && !property->isSignalHandler()) { // TODO: Optimize SignalHandler, too
1127 QV4::Heap::QObjectMethod *method = nullptr;
1128 setupQObjectMethodLookup(lookup, ddata, propertyData: property, self: This, method);
1129 lookup->getter = Lookup::getterQObjectMethod;
1130 return lookup->getter(lookup, engine, *object);
1131 }
1132
1133 setupQObjectLookup(lookup, ddata, propertyData: property, self: This);
1134 lookup->getter = Lookup::getterQObject;
1135 return lookup->getter(lookup, engine, *object);
1136}
1137
1138ReturnedValue QObjectWrapper::lookupAttached(
1139 Lookup *l, ExecutionEngine *engine, const Value &object)
1140{
1141 if (&QObjectWrapper::lookupAttached == &Lookup::getterGeneric) {
1142 // Certain compilers, e.g. MSVC, will "helpfully" deduplicate methods that are completely
1143 // equal. As a result, the pointers are the same, which wreaks havoc on the logic that
1144 // decides how to retrieve the property.
1145 qFatal(msg: "Your C++ compiler is broken.");
1146 }
1147
1148 // This getter marks the presence of a lookup for an attached object.
1149 // It falls back to the generic lookup when run through the interpreter, but AOT-compiled
1150 // code can get clever with it.
1151 return Lookup::getterGeneric(l, engine, object);
1152}
1153
1154bool QObjectWrapper::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup,
1155 const Value &value)
1156{
1157 return Object::virtualResolveLookupSetter(object, engine, lookup, value);
1158}
1159
1160int QObjectWrapper::virtualMetacall(Object *object, QMetaObject::Call call, int index, void **a)
1161{
1162 QObjectWrapper *wrapper = object->as<QObjectWrapper>();
1163 Q_ASSERT(wrapper);
1164
1165 if (QObject *qObject = wrapper->object())
1166 return QMetaObject::metacall(qObject, call, index, a);
1167
1168 return 0;
1169}
1170
1171QString QObjectWrapper::objectToString(
1172 ExecutionEngine *engine, const QMetaObject *metaObject, QObject *object)
1173{
1174 if (!metaObject)
1175 return QLatin1String("null");
1176
1177 if (!object)
1178 return QString::fromUtf8(utf8: metaObject->className()) + QLatin1String("(0x0)");
1179
1180 const int id = metaObject->indexOfMethod(method: "toString()");
1181 if (id >= 0) {
1182 const QMetaMethod method = metaObject->method(index: id);
1183 const QMetaType returnType = method.returnMetaType();
1184 QVariant result(returnType);
1185 method.invoke(object, returnValue: QGenericReturnArgument(returnType.name(), result.data()));
1186 if (result.metaType() == QMetaType::fromType<QString>())
1187 return result.toString();
1188 QV4::Scope scope(engine);
1189 QV4::ScopedValue value(scope, engine->fromVariant(result));
1190 return value->toQString();
1191 }
1192
1193 QString result;
1194 result += QString::fromUtf8(utf8: metaObject->className()) +
1195 QLatin1String("(0x") + QString::number(quintptr(object), base: 16);
1196 QString objectName = object->objectName();
1197 if (!objectName.isEmpty())
1198 result += QLatin1String(", \"") + objectName + QLatin1Char('\"');
1199 result += QLatin1Char(')');
1200 return result;
1201}
1202
1203struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase
1204{
1205 PersistentValue function;
1206 PersistentValue thisObject;
1207 QMetaMethod signal;
1208
1209 QObjectSlotDispatcher()
1210 : QtPrivate::QSlotObjectBase(&impl)
1211 {}
1212
1213 static void impl(int which, QSlotObjectBase *this_, QObject *receiver, void **metaArgs, bool *ret)
1214 {
1215 switch (which) {
1216 case Destroy: {
1217 delete static_cast<QObjectSlotDispatcher*>(this_);
1218 }
1219 break;
1220 case Call: {
1221 if (QQmlData::wasDeleted(object: receiver))
1222 break;
1223
1224 QObjectSlotDispatcher *This = static_cast<QObjectSlotDispatcher*>(this_);
1225 ExecutionEngine *v4 = This->function.engine();
1226 // Might be that we're still connected to a signal that's emitted long
1227 // after the engine died. We don't track connections in a global list, so
1228 // we need this safeguard.
1229 if (!v4)
1230 break;
1231
1232 QQmlMetaObject::ArgTypeStorage<9> storage;
1233 QQmlMetaObject::methodParameterTypes(method: This->signal, argStorage: &storage, unknownTypeError: nullptr);
1234
1235 int argCount = storage.size();
1236
1237 Scope scope(v4);
1238 ScopedFunctionObject f(scope, This->function.value());
1239
1240 JSCallArguments jsCallData(scope, argCount);
1241 *jsCallData.thisObject = This->thisObject.isUndefined() ? v4->globalObject->asReturnedValue() : This->thisObject.value();
1242 for (int ii = 0; ii < argCount; ++ii) {
1243 QMetaType type = storage[ii];
1244 if (type == QMetaType::fromType<QVariant>()) {
1245 jsCallData.args[ii] = v4->fromVariant(*((QVariant *)metaArgs[ii + 1]));
1246 } else {
1247 jsCallData.args[ii] = v4->fromVariant(QVariant(type, metaArgs[ii + 1]));
1248 }
1249 }
1250
1251 f->call(data: jsCallData);
1252 if (scope.hasException()) {
1253 QQmlError error = v4->catchExceptionAsQmlError();
1254 if (error.description().isEmpty()) {
1255 ScopedString name(scope, f->name());
1256 error.setDescription(QStringLiteral("Unknown exception occurred during evaluation of connected function: %1").arg(a: name->toQString()));
1257 }
1258 if (QQmlEngine *qmlEngine = v4->qmlEngine()) {
1259 QQmlEnginePrivate::get(e: qmlEngine)->warning(error);
1260 } else {
1261 QMessageLogger(error.url().toString().toLatin1().constData(),
1262 error.line(), nullptr).warning().noquote()
1263 << error.toString();
1264 }
1265 }
1266 }
1267 break;
1268 case Compare: {
1269 QObjectSlotDispatcher *connection = static_cast<QObjectSlotDispatcher*>(this_);
1270 if (connection->function.isUndefined()) {
1271 *ret = false;
1272 return;
1273 }
1274
1275 // This is tricky. Normally the metaArgs[0] pointer is a pointer to the _function_
1276 // for the new-style QObject::connect. Here we use the engine pointer as sentinel
1277 // to distinguish those type of QSlotObjectBase connections from our QML connections.
1278 ExecutionEngine *v4 = reinterpret_cast<ExecutionEngine*>(metaArgs[0]);
1279 if (v4 != connection->function.engine()) {
1280 *ret = false;
1281 return;
1282 }
1283
1284 Scope scope(v4);
1285 ScopedValue function(scope, *reinterpret_cast<Value*>(metaArgs[1]));
1286 ScopedValue thisObject(scope, *reinterpret_cast<Value*>(metaArgs[2]));
1287 QObject *receiverToDisconnect = reinterpret_cast<QObject*>(metaArgs[3]);
1288 int slotIndexToDisconnect = *reinterpret_cast<int*>(metaArgs[4]);
1289
1290 if (slotIndexToDisconnect != -1) {
1291 // This is a QObject function wrapper
1292 if (connection->thisObject.isUndefined() == thisObject->isUndefined() &&
1293 (connection->thisObject.isUndefined() || RuntimeHelpers::strictEqual(x: *connection->thisObject.valueRef(), y: thisObject))) {
1294
1295 ScopedFunctionObject f(scope, connection->function.value());
1296 QPair<QObject *, int> connectedFunctionData = QObjectMethod::extractQtMethod(function: f);
1297 if (connectedFunctionData.first == receiverToDisconnect &&
1298 connectedFunctionData.second == slotIndexToDisconnect) {
1299 *ret = true;
1300 return;
1301 }
1302 }
1303 } else {
1304 // This is a normal JS function
1305 if (RuntimeHelpers::strictEqual(x: *connection->function.valueRef(), y: function) &&
1306 connection->thisObject.isUndefined() == thisObject->isUndefined() &&
1307 (connection->thisObject.isUndefined() || RuntimeHelpers::strictEqual(x: *connection->thisObject.valueRef(), y: thisObject))) {
1308 *ret = true;
1309 return;
1310 }
1311 }
1312
1313 *ret = false;
1314 }
1315 break;
1316 case NumOperations:
1317 break;
1318 }
1319 };
1320};
1321
1322ReturnedValue QObjectWrapper::method_connect(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1323{
1324 Scope scope(b);
1325
1326 if (argc == 0)
1327 THROW_GENERIC_ERROR("Function.prototype.connect: no arguments given");
1328
1329 QPair<QObject *, int> signalInfo = extractQtSignal(value: *thisObject);
1330 QObject *signalObject = signalInfo.first;
1331 int signalIndex = signalInfo.second; // in method range, not signal range!
1332
1333 if (signalIndex < 0)
1334 THROW_GENERIC_ERROR("Function.prototype.connect: this object is not a signal");
1335
1336 if (!signalObject)
1337 THROW_GENERIC_ERROR("Function.prototype.connect: cannot connect to deleted QObject");
1338
1339 auto signalMetaMethod = signalObject->metaObject()->method(index: signalIndex);
1340 if (signalMetaMethod.methodType() != QMetaMethod::Signal)
1341 THROW_GENERIC_ERROR("Function.prototype.connect: this object is not a signal");
1342
1343 ScopedFunctionObject f(scope);
1344 ScopedValue object (scope, Encode::undefined());
1345
1346 if (argc == 1) {
1347 f = argv[0];
1348 } else if (argc >= 2) {
1349 object = argv[0];
1350 f = argv[1];
1351 }
1352
1353 if (!f)
1354 THROW_GENERIC_ERROR("Function.prototype.connect: target is not a function");
1355
1356 if (!object->isUndefined() && !object->isObject())
1357 THROW_GENERIC_ERROR("Function.prototype.connect: target this is not an object");
1358
1359 QObjectSlotDispatcher *slot = new QObjectSlotDispatcher;
1360 slot->signal = signalMetaMethod;
1361
1362 slot->thisObject.set(engine: scope.engine, value: object);
1363 slot->function.set(engine: scope.engine, value: f);
1364
1365 if (QQmlData *ddata = QQmlData::get(object: signalObject)) {
1366 if (const QQmlPropertyCache *propertyCache = ddata->propertyCache.data()) {
1367 QQmlPropertyPrivate::flushSignal(sender: signalObject, signal_index: propertyCache->methodIndexToSignalIndex(index: signalIndex));
1368 }
1369 }
1370
1371 QPair<QObject *, int> functionData = QObjectMethod::extractQtMethod(function: f); // align with disconnect
1372 QObject *receiver = nullptr;
1373
1374 if (functionData.first)
1375 receiver = functionData.first;
1376 else if (auto qobjectWrapper = object->as<QV4::QObjectWrapper>())
1377 receiver = qobjectWrapper->object();
1378 else if (auto typeWrapper = object->as<QV4::QQmlTypeWrapper>())
1379 receiver = typeWrapper->object();
1380
1381 if (receiver) {
1382 QObjectPrivate::connect(sender: signalObject, signal_index: signalIndex, receiver, slotObj: slot, type: Qt::AutoConnection);
1383 } else {
1384 qCInfo(lcObjectConnect,
1385 "Could not find receiver of the connection, using sender as receiver. Disconnect "
1386 "explicitly (or delete the sender) to make sure the connection is removed.");
1387 QObjectPrivate::connect(sender: signalObject, signal_index: signalIndex, receiver: signalObject, slotObj: slot, type: Qt::AutoConnection);
1388 }
1389
1390 RETURN_UNDEFINED();
1391}
1392
1393ReturnedValue QObjectWrapper::method_disconnect(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1394{
1395 Scope scope(b);
1396
1397 if (argc == 0)
1398 THROW_GENERIC_ERROR("Function.prototype.disconnect: no arguments given");
1399
1400 QPair<QObject *, int> signalInfo = extractQtSignal(value: *thisObject);
1401 QObject *signalObject = signalInfo.first;
1402 int signalIndex = signalInfo.second;
1403
1404 if (signalIndex == -1)
1405 THROW_GENERIC_ERROR("Function.prototype.disconnect: this object is not a signal");
1406
1407 if (!signalObject)
1408 THROW_GENERIC_ERROR("Function.prototype.disconnect: cannot disconnect from deleted QObject");
1409
1410 if (signalIndex < 0 || signalObject->metaObject()->method(index: signalIndex).methodType() != QMetaMethod::Signal)
1411 THROW_GENERIC_ERROR("Function.prototype.disconnect: this object is not a signal");
1412
1413 ScopedFunctionObject functionValue(scope);
1414 ScopedValue functionThisValue(scope, Encode::undefined());
1415
1416 if (argc == 1) {
1417 functionValue = argv[0];
1418 } else if (argc >= 2) {
1419 functionThisValue = argv[0];
1420 functionValue = argv[1];
1421 }
1422
1423 if (!functionValue)
1424 THROW_GENERIC_ERROR("Function.prototype.disconnect: target is not a function");
1425
1426 if (!functionThisValue->isUndefined() && !functionThisValue->isObject())
1427 THROW_GENERIC_ERROR("Function.prototype.disconnect: target this is not an object");
1428
1429 QPair<QObject *, int> functionData = QObjectMethod::extractQtMethod(function: functionValue);
1430
1431 void *a[] = {
1432 scope.engine,
1433 functionValue.ptr,
1434 functionThisValue.ptr,
1435 functionData.first,
1436 &functionData.second
1437 };
1438
1439 QObject *receiver = nullptr;
1440
1441 if (functionData.first)
1442 receiver = functionData.first;
1443 else if (auto qobjectWrapper = functionThisValue->as<QV4::QObjectWrapper>())
1444 receiver = qobjectWrapper->object();
1445 else if (auto typeWrapper = functionThisValue->as<QV4::QQmlTypeWrapper>())
1446 receiver = typeWrapper->object();
1447
1448 if (receiver) {
1449 QObjectPrivate::disconnect(sender: signalObject, signal_index: signalIndex, receiver,
1450 slot: reinterpret_cast<void **>(&a));
1451 } else {
1452 QObjectPrivate::disconnect(sender: signalObject, signal_index: signalIndex, receiver: signalObject,
1453 slot: reinterpret_cast<void **>(&a));
1454 }
1455
1456 RETURN_UNDEFINED();
1457}
1458
1459static void markChildQObjectsRecursively(QObject *parent, MarkStack *markStack)
1460{
1461 QQueue<QObject *> queue;
1462 queue.append(l: parent->children());
1463
1464 while (!queue.isEmpty()) {
1465 QObject *child = queue.dequeue();
1466 if (!child)
1467 continue;
1468 QObjectWrapper::markWrapper(object: child, markStack);
1469 queue.append(l: child->children());
1470 }
1471}
1472
1473void Heap::QObjectWrapper::markObjects(Heap::Base *that, MarkStack *markStack)
1474{
1475 QObjectWrapper *This = static_cast<QObjectWrapper *>(that);
1476
1477 if (QObject *o = This->object()) {
1478 if (QQmlData *ddata = QQmlData::get(object: o)) {
1479 if (ddata->hasVMEMetaObject) {
1480 if (QQmlVMEMetaObject *vme
1481 = static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(o)->metaObject)) {
1482 vme->mark(markStack);
1483 }
1484 }
1485
1486 if (ddata->hasConstWrapper) {
1487 Scope scope(that->internalClass->engine);
1488 Q_ASSERT(scope.engine->m_multiplyWrappedQObjects);
1489
1490 Scoped<QV4::QObjectWrapper> constWrapper(
1491 scope,
1492 scope.engine->m_multiplyWrappedQObjects->value(
1493 key: static_cast<const QObject *>(o)));
1494
1495 Q_ASSERT(constWrapper);
1496
1497 if (This == constWrapper->d()) {
1498 // We've got the const wrapper. Also mark the non-const one
1499 if (ddata->jsEngineId == scope.engine->m_engineId)
1500 ddata->jsWrapper.markOnce(markStack);
1501 else
1502 scope.engine->m_multiplyWrappedQObjects->mark(key: o, markStack);
1503 } else {
1504 // We've got the non-const wrapper. Also mark the const one.
1505 constWrapper->mark(markStack);
1506 }
1507 }
1508 }
1509
1510 // Children usually don't need to be marked, the gc keeps them alive.
1511 // But in the rare case of a "floating" QObject without a parent that
1512 // _gets_ marked (we've been called here!) then we also need to
1513 // propagate the marking down to the children recursively.
1514 if (!o->parent())
1515 markChildQObjectsRecursively(parent: o, markStack);
1516 }
1517
1518 Object::markObjects(base: that, stack: markStack);
1519}
1520
1521void QObjectWrapper::destroyObject(bool lastCall)
1522{
1523 Heap::QObjectWrapper *h = d();
1524 Q_ASSERT(h->internalClass);
1525
1526 if (QObject *o = h->object()) {
1527 QQmlData *ddata = QQmlData::get(object: o, create: false);
1528 if (ddata) {
1529 if (!o->parent() && !ddata->indestructible) {
1530 if (ddata && ddata->ownContext) {
1531 Q_ASSERT(ddata->ownContext.data() == ddata->context);
1532 ddata->ownContext->deepClearContextObject(contextObject: o);
1533 ddata->ownContext.reset();
1534 ddata->context = nullptr;
1535 }
1536
1537 // This object is notionally destroyed now. It might still live until the next
1538 // event loop iteration, but it won't need its connections, CU, or deferredData
1539 // anymore.
1540
1541 ddata->isQueuedForDeletion = true;
1542 ddata->disconnectNotifiers(doDelete: QQmlData::DeleteNotifyList::No);
1543 ddata->compilationUnit.reset();
1544
1545 qDeleteAll(c: ddata->deferredData);
1546 ddata->deferredData.clear();
1547
1548 if (lastCall)
1549 delete o;
1550 else
1551 o->deleteLater();
1552 } else {
1553 // If the object is C++-owned, we still have to release the weak reference we have
1554 // to it.
1555 ddata->jsWrapper.clear();
1556 if (lastCall && ddata->propertyCache)
1557 ddata->propertyCache.reset();
1558 }
1559 }
1560 }
1561
1562 h->destroy();
1563}
1564
1565
1566DEFINE_OBJECT_VTABLE(QObjectWrapper);
1567
1568namespace {
1569
1570template<typename A, typename B, typename C, typename D, typename E, typename F, typename G>
1571class MaxSizeOf7 {
1572 template<typename Z, typename X>
1573 struct SMax {
1574 char dummy[sizeof(Z) > sizeof(X) ? sizeof(Z) : sizeof(X)];
1575 };
1576public:
1577 static const size_t Size = sizeof(SMax<A, SMax<B, SMax<C, SMax<D, SMax<E, SMax<F, G> > > > > >);
1578};
1579
1580struct CallArgument {
1581 Q_DISABLE_COPY_MOVE(CallArgument);
1582
1583 CallArgument() = default;
1584 ~CallArgument() { cleanup(); }
1585
1586 inline void *dataPtr();
1587
1588 inline void initAsType(QMetaType type);
1589 inline bool fromValue(QMetaType type, ExecutionEngine *, const Value &);
1590 inline ReturnedValue toValue(ExecutionEngine *);
1591
1592private:
1593 // QVariantWrappedType denotes that we're storing a QVariant, but we mean
1594 // the type inside the QVariant, not QVariant itself.
1595 enum { QVariantWrappedType = -1 };
1596
1597 inline void cleanup();
1598
1599 template <class T, class M>
1600 bool fromContainerValue(const Value &object, M CallArgument::*member);
1601
1602 union {
1603 float floatValue;
1604 double doubleValue;
1605 quint32 intValue;
1606 bool boolValue;
1607 QObject *qobjectPtr;
1608 std::vector<int> *stdVectorIntPtr;
1609 std::vector<qreal> *stdVectorRealPtr;
1610 std::vector<bool> *stdVectorBoolPtr;
1611 std::vector<QString> *stdVectorQStringPtr;
1612 std::vector<QUrl> *stdVectorQUrlPtr;
1613#if QT_CONFIG(qml_itemmodel)
1614 std::vector<QModelIndex> *stdVectorQModelIndexPtr;
1615#endif
1616
1617 char allocData[MaxSizeOf7<QVariant,
1618 QString,
1619 QList<QObject *>,
1620 QJSValue,
1621 QJsonArray,
1622 QJsonObject,
1623 QJsonValue>::Size];
1624 qint64 q_for_alignment;
1625 };
1626
1627 // Pointers to allocData
1628 union {
1629 QString *qstringPtr;
1630 QByteArray *qbyteArrayPtr;
1631 QVariant *qvariantPtr;
1632 QList<QObject *> *qlistPtr;
1633 QJSValue *qjsValuePtr;
1634 QJsonArray *jsonArrayPtr;
1635 QJsonObject *jsonObjectPtr;
1636 QJsonValue *jsonValuePtr;
1637 };
1638
1639 int type = QMetaType::UnknownType;
1640};
1641}
1642
1643static ReturnedValue CallMethod(const QQmlObjectOrGadget &object, int index, QMetaType returnType, int argCount,
1644 const QMetaType *argTypes, ExecutionEngine *engine, CallData *callArgs,
1645 QMetaObject::Call callType = QMetaObject::InvokeMetaMethod)
1646{
1647 if (argCount > 0) {
1648 // Convert all arguments.
1649 QVarLengthArray<CallArgument, 9> args(argCount + 1);
1650 args[0].initAsType(type: returnType);
1651 for (int ii = 0; ii < argCount; ++ii) {
1652 if (!args[ii + 1].fromValue(type: argTypes[ii], engine,
1653 callArgs->args[ii].asValue<Value>())) {
1654 qWarning() << QString::fromLatin1(ba: "Could not convert argument %1 at").arg(a: ii);
1655 const StackTrace stack = engine->stackTrace();
1656 for (const StackFrame &frame : stack) {
1657 qWarning() << "\t" << frame.function + QLatin1Char('@') + frame.source
1658 + (frame.line > 0
1659 ? (QLatin1Char(':') + QString::number(frame.line))
1660 : QString());
1661
1662 }
1663
1664 const bool is_signal =
1665 object.metaObject()->method(index).methodType() == QMetaMethod::Signal;
1666 if (is_signal) {
1667 qWarning() << "Passing incompatible arguments to signals is not supported.";
1668 } else {
1669 return engine->throwTypeError(
1670 message: QLatin1String("Passing incompatible arguments to C++ functions from "
1671 "JavaScript is not allowed."));
1672 }
1673 }
1674 }
1675 QVarLengthArray<void *, 9> argData(args.size());
1676 for (int ii = 0; ii < args.size(); ++ii)
1677 argData[ii] = args[ii].dataPtr();
1678
1679 object.metacall(type: callType, index, argv: argData.data());
1680
1681 return args[0].toValue(engine);
1682
1683 } else if (returnType != QMetaType::fromType<void>()) {
1684
1685 CallArgument arg;
1686 arg.initAsType(type: returnType);
1687
1688 void *args[] = { arg.dataPtr() };
1689
1690 object.metacall(type: callType, index, argv: args);
1691
1692 return arg.toValue(engine);
1693
1694 } else {
1695
1696 void *args[] = { nullptr };
1697 object.metacall(type: callType, index, argv: args);
1698 return Encode::undefined();
1699
1700 }
1701}
1702
1703template<typename Retrieve>
1704int MatchVariant(QMetaType conversionMetaType, Retrieve &&retrieve) {
1705 if (conversionMetaType == QMetaType::fromType<QVariant>())
1706 return 0;
1707
1708 const QMetaType type = retrieve();
1709 if (type == conversionMetaType)
1710 return 0;
1711
1712 if (const QMetaObject *conversionMetaObject = conversionMetaType.metaObject()) {
1713 if (const QMetaObject *mo = type.metaObject(); mo && mo->inherits(metaObject: conversionMetaObject))
1714 return 1;
1715 }
1716
1717 if (QMetaType::canConvert(fromType: type, toType: conversionMetaType))
1718 return 5;
1719
1720 return 10;
1721};
1722
1723/*
1724 Returns the match score for converting \a actual to be of type \a conversionType. A
1725 zero score means "perfect match" whereas a higher score is worse.
1726
1727 The conversion table is copied out of the \l QScript::callQtMethod() function.
1728*/
1729static int MatchScore(const Value &actual, QMetaType conversionMetaType)
1730{
1731 const int conversionType = conversionMetaType.id();
1732 if (actual.isNumber()) {
1733 switch (conversionType) {
1734 case QMetaType::Double:
1735 return 0;
1736 case QMetaType::Float:
1737 return 1;
1738 case QMetaType::LongLong:
1739 case QMetaType::ULongLong:
1740 return 2;
1741 case QMetaType::Long:
1742 case QMetaType::ULong:
1743 return 3;
1744 case QMetaType::Int:
1745 case QMetaType::UInt:
1746 return 4;
1747 case QMetaType::Short:
1748 case QMetaType::UShort:
1749 return 5;
1750 break;
1751 case QMetaType::Char:
1752 case QMetaType::UChar:
1753 return 6;
1754 case QMetaType::QJsonValue:
1755 return 5;
1756 default:
1757 return 10;
1758 }
1759 } else if (actual.isString()) {
1760 switch (conversionType) {
1761 case QMetaType::QString:
1762 return 0;
1763 case QMetaType::QJsonValue:
1764 return 5;
1765 case QMetaType::QUrl:
1766 return 6; // we like to convert strings to URLs in QML
1767 default:
1768 return 10;
1769 }
1770 } else if (actual.isBoolean()) {
1771 switch (conversionType) {
1772 case QMetaType::Bool:
1773 return 0;
1774 case QMetaType::QJsonValue:
1775 return 5;
1776 default:
1777 return 10;
1778 }
1779 } else if (actual.as<DateObject>()) {
1780 switch (conversionType) {
1781 case QMetaType::QDateTime:
1782 return 0;
1783 case QMetaType::QDate:
1784 return 1;
1785 case QMetaType::QTime:
1786 return 2;
1787 default:
1788 return 10;
1789 }
1790 } else if (actual.as<RegExpObject>()) {
1791 switch (conversionType) {
1792#if QT_CONFIG(regularexpression)
1793 case QMetaType::QRegularExpression:
1794 return 0;
1795#endif
1796 default:
1797 return 10;
1798 }
1799 } else if (actual.as<ArrayBuffer>()) {
1800 switch (conversionType) {
1801 case QMetaType::QByteArray:
1802 return 0;
1803 default:
1804 return 10;
1805 }
1806 } else if (actual.as<ArrayObject>()) {
1807 switch (conversionType) {
1808 case QMetaType::QJsonArray:
1809 return 3;
1810 case QMetaType::QStringList:
1811 case QMetaType::QVariantList:
1812 return 5;
1813 case QMetaType::QVector4D:
1814 case QMetaType::QMatrix4x4:
1815 return 6;
1816 case QMetaType::QVector3D:
1817 return 7;
1818 default:
1819 return 10;
1820 }
1821 } else if (actual.isNull()) {
1822 switch (conversionType) {
1823 case QMetaType::Nullptr:
1824 case QMetaType::VoidStar:
1825 case QMetaType::QObjectStar:
1826 case QMetaType::QJsonValue:
1827 return 0;
1828 default: {
1829 if (conversionMetaType.flags().testFlag(flag: QMetaType::IsPointer))
1830 return 0;
1831 else
1832 return 10;
1833 }
1834 }
1835 } else if (const Object *obj = actual.as<Object>()) {
1836 if (const VariantObject *variantObject = obj->as<VariantObject>()) {
1837 return MatchVariant(conversionMetaType, retrieve: [variantObject]() {
1838 return variantObject->d()->data().metaType();
1839 });
1840 }
1841
1842 if (const QObjectWrapper *wrapper = obj->as<QObjectWrapper>()) {
1843 switch (conversionType) {
1844 case QMetaType::QObjectStar:
1845 return 0;
1846 default:
1847 if (conversionMetaType.flags() & QMetaType::PointerToQObject) {
1848 QObject *wrapped = wrapper->object();
1849 if (!wrapped)
1850 return 0;
1851 if (qmlobject_can_cpp_cast(object: wrapped, mo: conversionMetaType.metaObject()))
1852 return 0;
1853 }
1854 }
1855 return 10;
1856 }
1857
1858 if (const QQmlTypeWrapper *wrapper = obj->as<QQmlTypeWrapper>()) {
1859 const QQmlType type = wrapper->d()->type();
1860 if (type.isSingleton()) {
1861 const QMetaType metaType = type.typeId();
1862 if (metaType == conversionMetaType)
1863 return 0;
1864
1865 if (conversionMetaType.flags() & QMetaType::PointerToQObject
1866 && metaType.flags() & QMetaType::PointerToQObject
1867 && type.metaObject()->inherits(metaObject: conversionMetaType.metaObject())) {
1868 return 0;
1869 }
1870 } else if (QObject *object = wrapper->object()) {
1871 if (conversionMetaType.flags() & QMetaType::PointerToQObject
1872 && qmlobject_can_cpp_cast(object, mo: conversionMetaType.metaObject())) {
1873 return 0;
1874 }
1875 }
1876
1877 return 10;
1878 }
1879
1880 if (const Sequence *sequence = obj->as<Sequence>()) {
1881 if (SequencePrototype::metaTypeForSequence(object: sequence) == conversionMetaType)
1882 return 1;
1883 else
1884 return 10;
1885 }
1886
1887 if (const QQmlValueTypeWrapper *wrapper = obj->as<QQmlValueTypeWrapper>()) {
1888 return MatchVariant(conversionMetaType, retrieve: [wrapper]() {
1889 return wrapper->d()->isVariant()
1890 ? wrapper->toVariant().metaType()
1891 : wrapper->type();
1892 });
1893 }
1894
1895 if (conversionType == QMetaType::QJsonObject)
1896 return 5;
1897 if (conversionType == qMetaTypeId<QJSValue>())
1898 return 0;
1899 if (conversionType == QMetaType::QVariantMap)
1900 return 5;
1901 }
1902
1903 return 10;
1904}
1905
1906static int numDefinedArguments(CallData *callArgs)
1907{
1908 int numDefinedArguments = callArgs->argc();
1909 while (numDefinedArguments > 0
1910 && callArgs->args[numDefinedArguments - 1].type() == StaticValue::Undefined_Type) {
1911 --numDefinedArguments;
1912 }
1913 return numDefinedArguments;
1914}
1915
1916static bool requiresStrictArguments(const QQmlObjectOrGadget &object)
1917{
1918 const QMetaObject *metaObject = object.metaObject();
1919 const int indexOfClassInfo = metaObject->indexOfClassInfo(name: "QML.StrictArguments");
1920 return indexOfClassInfo != -1
1921 && metaObject->classInfo(index: indexOfClassInfo).value() == QByteArrayView("true");
1922}
1923
1924ReturnedValue QObjectMethod::callPrecise(
1925 const QQmlObjectOrGadget &object, const QQmlPropertyData &data, ExecutionEngine *engine,
1926 CallData *callArgs, QMetaObject::Call callType)
1927{
1928 QByteArray unknownTypeError;
1929
1930 QMetaType returnType = object.methodReturnType(data, unknownTypeError: &unknownTypeError);
1931
1932 if (!returnType.isValid()) {
1933 return engine->throwError(message: QLatin1String("Unknown method return type: ")
1934 + QLatin1String(unknownTypeError));
1935 }
1936
1937 auto handleTooManyArguments = [&](int expectedArguments) {
1938 if (requiresStrictArguments(object)) {
1939 engine->throwError(QStringLiteral("Too many arguments"));
1940 return false;
1941 }
1942
1943 const auto stackTrace = engine->stackTrace();
1944 if (stackTrace.isEmpty()) {
1945 qWarning().nospace().noquote()
1946 << "When matching arguments for "
1947 << object.className() << "::" << data.name(metaObject: object.metaObject()) << "():";
1948 } else {
1949 const StackFrame frame = stackTrace.first();
1950 qWarning().noquote() << frame.function + QLatin1Char('@') + frame.source
1951 + (frame.line > 0 ? (QLatin1Char(':') + QString::number(frame.line))
1952 : QString());
1953 }
1954
1955 qWarning().noquote() << QStringLiteral("Too many arguments, ignoring %1")
1956 .arg(a: callArgs->argc() - expectedArguments);
1957 return true;
1958 };
1959
1960 const int definedArgumentCount = numDefinedArguments(callArgs);
1961
1962 if (data.hasArguments()) {
1963
1964 QQmlMetaObject::ArgTypeStorage<9> storage;
1965
1966 bool ok = false;
1967 if (data.isConstructor())
1968 ok = object.constructorParameterTypes(index: data.coreIndex(), dummy: &storage, unknownTypeError: &unknownTypeError);
1969 else
1970 ok = object.methodParameterTypes(index: data.coreIndex(), argStorage: &storage, unknownTypeError: &unknownTypeError);
1971
1972 if (!ok) {
1973 return engine->throwError(message: QLatin1String("Unknown method parameter type: ")
1974 + QLatin1String(unknownTypeError));
1975 }
1976
1977 if (storage.size() > callArgs->argc()) {
1978 QString error = QLatin1String("Insufficient arguments");
1979 return engine->throwError(message: error);
1980 }
1981
1982 if (storage.size() < definedArgumentCount) {
1983 if (!handleTooManyArguments(storage.size()))
1984 return Encode::undefined();
1985
1986 }
1987
1988 return CallMethod(object, index: data.coreIndex(), returnType, argCount: storage.size(), argTypes: storage.constData(), engine, callArgs, callType);
1989
1990 } else {
1991 if (definedArgumentCount > 0 && !handleTooManyArguments(0))
1992 return Encode::undefined();
1993
1994 return CallMethod(object, index: data.coreIndex(), returnType, argCount: 0, argTypes: nullptr, engine, callArgs, callType);
1995 }
1996}
1997
1998/*
1999Resolve the overloaded method to call. The algorithm works conceptually like this:
2000 1. Resolve the set of overloads it is *possible* to call.
2001 Impossible overloads include those that have too many parameters or have parameters
2002 of unknown type.
2003 2. Filter the set of overloads to only contain those with the closest number of
2004 parameters.
2005 For example, if we are called with 3 parameters and there are 2 overloads that
2006 take 2 parameters and one that takes 3, eliminate the 2 parameter overloads.
2007 3. Find the best remaining overload based on its match score.
2008 If two or more overloads have the same match score, return the last one. The match
2009 score is constructed by adding the matchScore() result for each of the parameters.
2010*/
2011const QQmlPropertyData *QObjectMethod::resolveOverloaded(
2012 const QQmlObjectOrGadget &object, const QQmlPropertyData *methods, int methodCount,
2013 ExecutionEngine *engine, CallData *callArgs)
2014{
2015 const int argumentCount = callArgs->argc();
2016 const int definedArgumentCount = numDefinedArguments(callArgs);
2017
2018 const QQmlPropertyData *best = nullptr;
2019 int bestParameterScore = INT_MAX;
2020 int bestMaxMatchScore = INT_MAX;
2021 int bestSumMatchScore = INT_MAX;
2022
2023 Scope scope(engine);
2024 ScopedValue v(scope);
2025
2026 for (int i = 0; i < methodCount; ++i) {
2027 const QQmlPropertyData *attempt = methods + i;
2028
2029 if (lcOverloadResolution().isDebugEnabled()) {
2030 const QQmlPropertyData &candidate = methods[i];
2031 const QMetaMethod m = candidate.isConstructor()
2032 ? object.metaObject()->constructor(index: candidate.coreIndex())
2033 : object.metaObject()->method(index: candidate.coreIndex());
2034 qCDebug(lcOverloadResolution) << "::: considering signature" << m.methodSignature();
2035 }
2036
2037 // QQmlV4Function overrides anything that doesn't provide the exact number of arguments
2038 int methodParameterScore = 1;
2039 // QQmlV4Function overrides the "no idea" option, which is 10
2040 int maxMethodMatchScore = 9;
2041 // QQmlV4Function cannot provide a best sum of match scores as we don't match the arguments
2042 int sumMethodMatchScore = bestSumMatchScore;
2043
2044 if (!attempt->isV4Function()) {
2045 QQmlMetaObject::ArgTypeStorage<9> storage;
2046 int methodArgumentCount = 0;
2047 if (attempt->hasArguments()) {
2048 if (attempt->isConstructor()) {
2049 if (!object.constructorParameterTypes(index: attempt->coreIndex(), dummy: &storage, unknownTypeError: nullptr)) {
2050 qCDebug(lcOverloadResolution, "rejected, could not get ctor argument types");
2051 continue;
2052 }
2053 } else {
2054 if (!object.methodParameterTypes(index: attempt->coreIndex(), argStorage: &storage, unknownTypeError: nullptr)) {
2055 qCDebug(lcOverloadResolution, "rejected, could not get ctor argument types");
2056 continue;
2057 }
2058 }
2059 methodArgumentCount = storage.size();
2060 }
2061
2062 if (methodArgumentCount > argumentCount) {
2063 qCDebug(lcOverloadResolution, "rejected, insufficient arguments");
2064 continue; // We don't have sufficient arguments to call this method
2065 }
2066
2067 methodParameterScore = (definedArgumentCount == methodArgumentCount)
2068 ? 0
2069 : (definedArgumentCount - methodArgumentCount + 1);
2070 if (methodParameterScore > bestParameterScore) {
2071 qCDebug(lcOverloadResolution) << "rejected, score too bad. own" << methodParameterScore << "vs best:" << bestParameterScore;
2072 continue; // We already have a better option
2073 }
2074
2075 maxMethodMatchScore = 0;
2076 sumMethodMatchScore = 0;
2077 for (int ii = 0; ii < methodArgumentCount; ++ii) {
2078 const int score = MatchScore(actual: (v = Value::fromStaticValue(staticValue: callArgs->args[ii])),
2079 conversionMetaType: storage[ii]);
2080 maxMethodMatchScore = qMax(a: maxMethodMatchScore, b: score);
2081 sumMethodMatchScore += score;
2082 }
2083 }
2084
2085 if (bestParameterScore > methodParameterScore || bestMaxMatchScore > maxMethodMatchScore
2086 || (bestParameterScore == methodParameterScore
2087 && bestMaxMatchScore == maxMethodMatchScore
2088 && bestSumMatchScore > sumMethodMatchScore)) {
2089 best = attempt;
2090 bestParameterScore = methodParameterScore;
2091 bestMaxMatchScore = maxMethodMatchScore;
2092 bestSumMatchScore = sumMethodMatchScore;
2093 qCDebug(lcOverloadResolution) << "updated best" << "bestParameterScore" << bestParameterScore << "\n"
2094 << "bestMaxMatchScore" << bestMaxMatchScore << "\n"
2095 << "bestSumMatchScore" << bestSumMatchScore << "\n";
2096 } else {
2097 qCDebug(lcOverloadResolution) << "did not update best\n"
2098 << "bestParameterScore" << bestParameterScore << "\t"
2099 << "methodParameterScore" << methodParameterScore << "\n"
2100 << "bestMaxMatchScore" << bestMaxMatchScore << "\t"
2101 << "maxMethodMatchScore" << maxMethodMatchScore << "\n"
2102 << "bestSumMatchScore" << bestSumMatchScore << "\t"
2103 << "sumMethodMatchScore" << sumMethodMatchScore << "\n";
2104 }
2105
2106 if (bestParameterScore == 0 && bestMaxMatchScore == 0) {
2107 qCDebug(lcOverloadResolution, "perfect match");
2108 break; // We can't get better than that
2109 }
2110
2111 };
2112
2113 if (best && best->isValid()) {
2114 return best;
2115 } else {
2116 QString error = QLatin1String("Unable to determine callable overload. Candidates are:");
2117 for (int i = 0; i < methodCount; ++i) {
2118 const QQmlPropertyData &candidate = methods[i];
2119 const QMetaMethod m = candidate.isConstructor()
2120 ? object.metaObject()->constructor(index: candidate.coreIndex())
2121 : object.metaObject()->method(index: candidate.coreIndex());
2122 error += u"\n " + QString::fromUtf8(ba: m.methodSignature());
2123 }
2124
2125 engine->throwError(message: error);
2126 return nullptr;
2127 }
2128}
2129
2130static bool ExactMatch(QMetaType passed, QMetaType required, const void *data)
2131{
2132 if (required == QMetaType::fromType<QVariant>()
2133 || required == QMetaType::fromType<QJSValue>()
2134 || required == QMetaType::fromType<QJSManagedValue>()) {
2135 return true;
2136 }
2137
2138 if (data) {
2139 if (passed == QMetaType::fromType<QVariant>())
2140 passed = static_cast<const QVariant *>(data)->metaType();
2141 else if (passed == QMetaType::fromType<QJSPrimitiveValue>())
2142 passed = static_cast<const QJSPrimitiveValue *>(data)->metaType();
2143 }
2144
2145 if (passed == required)
2146 return true;
2147
2148 if (required == QMetaType::fromType<QJSPrimitiveValue>()) {
2149 switch (passed.id()) {
2150 case QMetaType::UnknownType:
2151 case QMetaType::Nullptr:
2152 case QMetaType::Bool:
2153 case QMetaType::Int:
2154 case QMetaType::Double:
2155 case QMetaType::QString:
2156 return true;
2157 default:
2158 break;
2159 }
2160 }
2161
2162 return false;
2163}
2164
2165bool QObjectMethod::isExactMatch(
2166 const QMetaMethod &method, void **argv, int argc, const QMetaType *types)
2167{
2168 if (types[0].isValid() && !ExactMatch(passed: method.returnMetaType(), required: types[0], data: nullptr))
2169 return false;
2170
2171 if (method.parameterCount() != argc)
2172 return false;
2173
2174 for (int i = 0; i < argc; ++i) {
2175 if (!ExactMatch(passed: types[i + 1], required: method.parameterMetaType(index: i), data: argv[i + 1]))
2176 return false;
2177 }
2178
2179 return true;
2180}
2181
2182const QQmlPropertyData *QObjectMethod::resolveOverloaded(
2183 const QQmlPropertyData *methods, int methodCount,
2184 void **argv, int argc, const QMetaType *types)
2185{
2186 // We only accept exact matches here. Everything else goes through the JavaScript conversion.
2187 for (int i = 0; i < methodCount; ++i) {
2188 const QQmlPropertyData *attempt = methods + i;
2189 if (isExactMatch(method: attempt->metaMethod(), argv, argc, types))
2190 return attempt;
2191 }
2192
2193 return nullptr;
2194}
2195
2196void CallArgument::cleanup()
2197{
2198 switch (type) {
2199 case QMetaType::QString:
2200 qstringPtr->~QString();
2201 break;
2202 case QMetaType::QByteArray:
2203 qbyteArrayPtr->~QByteArray();
2204 break;
2205 case QMetaType::QVariant:
2206 case QVariantWrappedType:
2207 qvariantPtr->~QVariant();
2208 break;
2209 case QMetaType::QJsonArray:
2210 jsonArrayPtr->~QJsonArray();
2211 break;
2212 case QMetaType::QJsonObject:
2213 jsonObjectPtr->~QJsonObject();
2214 break;
2215 case QMetaType::QJsonValue:
2216 jsonValuePtr->~QJsonValue();
2217 break;
2218 default:
2219 if (type == qMetaTypeId<QJSValue>()) {
2220 qjsValuePtr->~QJSValue();
2221 break;
2222 }
2223
2224 if (type == qMetaTypeId<QList<QObject *> >()) {
2225 qlistPtr->~QList<QObject *>();
2226 break;
2227 }
2228
2229 // The sequence types need no cleanup because we don't own them.
2230
2231 break;
2232 }
2233}
2234
2235void *CallArgument::dataPtr()
2236{
2237 switch (type) {
2238 case QMetaType::UnknownType:
2239 return nullptr;
2240 case QVariantWrappedType:
2241 return qvariantPtr->data();
2242 default:
2243 if (type == qMetaTypeId<std::vector<int>>())
2244 return stdVectorIntPtr;
2245 if (type == qMetaTypeId<std::vector<qreal>>())
2246 return stdVectorRealPtr;
2247 if (type == qMetaTypeId<std::vector<bool>>())
2248 return stdVectorBoolPtr;
2249 if (type == qMetaTypeId<std::vector<QString>>())
2250 return stdVectorQStringPtr;
2251 if (type == qMetaTypeId<std::vector<QUrl>>())
2252 return stdVectorQUrlPtr;
2253#if QT_CONFIG(qml_itemmodel)
2254 if (type == qMetaTypeId<std::vector<QModelIndex>>())
2255 return stdVectorQModelIndexPtr;
2256#endif
2257 break;
2258 }
2259
2260 return (void *)&allocData;
2261}
2262
2263void CallArgument::initAsType(QMetaType metaType)
2264{
2265 if (type != QMetaType::UnknownType)
2266 cleanup();
2267
2268 type = metaType.id();
2269 switch (type) {
2270 case QMetaType::Void:
2271 type = QMetaType::UnknownType;
2272 break;
2273 case QMetaType::UnknownType:
2274 case QMetaType::Int:
2275 case QMetaType::UInt:
2276 case QMetaType::Bool:
2277 case QMetaType::Double:
2278 case QMetaType::Float:
2279 break;
2280 case QMetaType::QObjectStar:
2281 qobjectPtr = nullptr;
2282 break;
2283 case QMetaType::QString:
2284 qstringPtr = new (&allocData) QString();
2285 break;
2286 case QMetaType::QVariant:
2287 qvariantPtr = new (&allocData) QVariant();
2288 break;
2289 case QMetaType::QJsonArray:
2290 jsonArrayPtr = new (&allocData) QJsonArray();
2291 break;
2292 case QMetaType::QJsonObject:
2293 jsonObjectPtr = new (&allocData) QJsonObject();
2294 break;
2295 case QMetaType::QJsonValue:
2296 jsonValuePtr = new (&allocData) QJsonValue();
2297 break;
2298 default: {
2299 if (metaType == QMetaType::fromType<QJSValue>()) {
2300 qjsValuePtr = new (&allocData) QJSValue();
2301 break;
2302 }
2303
2304 if (metaType == QMetaType::fromType<QList<QObject *>>()) {
2305 qlistPtr = new (&allocData) QList<QObject *>();
2306 break;
2307 }
2308
2309 type = QVariantWrappedType;
2310 qvariantPtr = new (&allocData) QVariant(metaType, (void *)nullptr);
2311 break;
2312 }
2313 }
2314}
2315
2316template <class T, class M>
2317bool CallArgument::fromContainerValue(const Value &value, M CallArgument::*member)
2318{
2319 if (const Sequence *sequence = value.as<Sequence>()) {
2320 if (T* ptr = static_cast<T *>(SequencePrototype::getRawContainerPtr(
2321 object: sequence, typeHint: QMetaType(type)))) {
2322 (this->*member) = ptr;
2323 return true;
2324 }
2325 }
2326 (this->*member) = nullptr;
2327 return false;
2328}
2329
2330bool CallArgument::fromValue(QMetaType metaType, ExecutionEngine *engine, const Value &value)
2331{
2332 if (type != QMetaType::UnknownType)
2333 cleanup();
2334
2335 type = metaType.id();
2336
2337 switch (type) {
2338 case QMetaType::Int:
2339 intValue = quint32(value.toInt32());
2340 return true;
2341 case QMetaType::UInt:
2342 intValue = quint32(value.toUInt32());
2343 return true;
2344 case QMetaType::Bool:
2345 boolValue = value.toBoolean();
2346 return true;
2347 case QMetaType::Double:
2348 doubleValue = double(value.toNumber());
2349 return true;
2350 case QMetaType::Float:
2351 floatValue = float(value.toNumber());
2352 return true;
2353 case QMetaType::QString:
2354 if (value.isNullOrUndefined())
2355 qstringPtr = new (&allocData) QString();
2356 else
2357 qstringPtr = new (&allocData) QString(value.toQStringNoThrow());
2358 return true;
2359 case QMetaType::QByteArray:
2360 qbyteArrayPtr = new (&allocData) QByteArray();
2361 ExecutionEngine::metaTypeFromJS(value, type: metaType, data: qbyteArrayPtr);
2362 return true;
2363 case QMetaType::QObjectStar:
2364 if (const QObjectWrapper *qobjectWrapper = value.as<QObjectWrapper>()) {
2365 qobjectPtr = qobjectWrapper->object();
2366 return true;
2367 }
2368
2369 if (const QQmlTypeWrapper *qmlTypeWrapper = value.as<QQmlTypeWrapper>()) {
2370 if (qmlTypeWrapper->isSingleton()) {
2371 // Convert via QVariant below.
2372 // TODO: Can't we just do qobjectPtr = qmlTypeWrapper->object() instead?
2373 break;
2374 } else if (QObject *obj = qmlTypeWrapper->object()) {
2375 // attached object case
2376 qobjectPtr = obj;
2377 return true;
2378 }
2379
2380 // If this is a plain type wrapper without an instance,
2381 // then we got a namespace, and that's a type error
2382 type = QMetaType::UnknownType;
2383 return false;
2384 }
2385
2386 qobjectPtr = nullptr;
2387 return value.isNullOrUndefined(); // null and undefined are nullptr
2388 case QMetaType::QVariant:
2389 qvariantPtr = new (&allocData) QVariant(ExecutionEngine::toVariant(value, typeHint: QMetaType {}));
2390 return true;
2391 case QMetaType::QJsonArray: {
2392 Scope scope(engine);
2393 ScopedObject o(scope, value);
2394 jsonArrayPtr = new (&allocData) QJsonArray(JsonObject::toJsonArray(o));
2395 return true;
2396 }
2397 case QMetaType::QJsonObject: {
2398 Scope scope(engine);
2399 ScopedObject o(scope, value);
2400 jsonObjectPtr = new (&allocData) QJsonObject(JsonObject::toJsonObject(o));
2401 return true;
2402 }
2403 case QMetaType::QJsonValue:
2404 jsonValuePtr = new (&allocData) QJsonValue(JsonObject::toJsonValue(value));
2405 return true;
2406 case QMetaType::Void:
2407 type = QMetaType::UnknownType;
2408 // TODO: This only doesn't leak because a default constructed QVariant doesn't allocate.
2409 *qvariantPtr = QVariant();
2410 return true;
2411 default:
2412 if (type == qMetaTypeId<QJSValue>()) {
2413 qjsValuePtr = new (&allocData) QJSValue;
2414 QJSValuePrivate::setValue(jsval: qjsValuePtr, v: value.asReturnedValue());
2415 return true;
2416 }
2417
2418 if (type == qMetaTypeId<QList<QObject*> >()) {
2419 qlistPtr = new (&allocData) QList<QObject *>();
2420 Scope scope(engine);
2421 ScopedArrayObject array(scope, value);
2422 if (array) {
2423 Scoped<QObjectWrapper> qobjectWrapper(scope);
2424
2425 uint length = array->getLength();
2426 qlistPtr->reserve(size: length);
2427 for (uint ii = 0; ii < length; ++ii) {
2428 QObject *o = nullptr;
2429 qobjectWrapper = array->get(idx: ii);
2430 if (!!qobjectWrapper)
2431 o = qobjectWrapper->object();
2432 qlistPtr->append(t: o);
2433 }
2434 return true;
2435 }
2436
2437 if (const auto sequence = value.as<QV4::Sequence>()) {
2438 QV4::ReferenceObject::readReference(ref: sequence->d());
2439 uint length = sequence->size();
2440 if (sequence->d()->listType() == QMetaType::fromType<QList<QObject *>>()) {
2441 *qlistPtr = *static_cast<QList<QObject *> *>(sequence->getRawContainerPtr());
2442 } else {
2443 qlistPtr->reserve(size: length);
2444 Scoped<QObjectWrapper> qobjectWrapper(scope);
2445 for (uint ii = 0; ii < length; ++ii) {
2446 QObject *o = nullptr;
2447 qobjectWrapper = sequence->get(idx: ii);
2448 if (!!qobjectWrapper)
2449 o = qobjectWrapper->object();
2450 qlistPtr->append(t: o);
2451 }
2452 }
2453 return true;
2454 }
2455
2456 if (const QObjectWrapper *qobjectWrapper = value.as<QObjectWrapper>()) {
2457 qlistPtr->append(t: qobjectWrapper->object());
2458 return true;
2459 }
2460
2461 if (const QmlListWrapper *listWrapper = value.as<QmlListWrapper>()) {
2462 *qlistPtr = listWrapper->d()->property()->toList<QList<QObject *>>();
2463 return true;
2464 }
2465
2466 qlistPtr->append(t: nullptr);
2467 return value.isNullOrUndefined();
2468 }
2469
2470 if (metaType.flags() & (QMetaType::PointerToQObject | QMetaType::PointerToGadget)) {
2471 // You can assign null or undefined to any pointer. The result is a nullptr.
2472 if (value.isNullOrUndefined()) {
2473 qvariantPtr = new (&allocData) QVariant(metaType, nullptr);
2474 return true;
2475 }
2476 break;
2477 }
2478
2479 if (type == qMetaTypeId<std::vector<int>>()) {
2480 if (fromContainerValue<std::vector<int>>(value, member: &CallArgument::stdVectorIntPtr))
2481 return true;
2482 } else if (type == qMetaTypeId<std::vector<qreal>>()) {
2483 if (fromContainerValue<std::vector<qreal>>(value, member: &CallArgument::stdVectorRealPtr))
2484 return true;
2485 } else if (type == qMetaTypeId<std::vector<bool>>()) {
2486 if (fromContainerValue<std::vector<bool>>(value, member: &CallArgument::stdVectorBoolPtr))
2487 return true;
2488 } else if (type == qMetaTypeId<std::vector<QString>>()) {
2489 if (fromContainerValue<std::vector<QString>>(value, member: &CallArgument::stdVectorQStringPtr))
2490 return true;
2491 } else if (type == qMetaTypeId<std::vector<QUrl>>()) {
2492 if (fromContainerValue<std::vector<QUrl>>(value, member: &CallArgument::stdVectorQUrlPtr))
2493 return true;
2494#if QT_CONFIG(qml_itemmodel)
2495 } else if (type == qMetaTypeId<std::vector<QModelIndex>>()) {
2496 if (fromContainerValue<std::vector<QModelIndex>>(
2497 value, member: &CallArgument::stdVectorQModelIndexPtr)) {
2498 return true;
2499 }
2500#endif
2501 }
2502 break;
2503 }
2504
2505 // Convert via QVariant through the QML engine.
2506 qvariantPtr = new (&allocData) QVariant(metaType);
2507 type = QVariantWrappedType;
2508
2509 if (ExecutionEngine::metaTypeFromJS(value, type: metaType, data: qvariantPtr->data()))
2510 return true;
2511
2512 const QVariant v = ExecutionEngine::toVariant(value, typeHint: metaType);
2513 return QMetaType::convert(fromType: v.metaType(), from: v.constData(), toType: metaType, to: qvariantPtr->data());
2514}
2515
2516ReturnedValue CallArgument::toValue(ExecutionEngine *engine)
2517{
2518 switch (type) {
2519 case QMetaType::Int:
2520 return Encode(int(intValue));
2521 case QMetaType::UInt:
2522 return Encode((uint)intValue);
2523 case QMetaType::Bool:
2524 return Encode(boolValue);
2525 case QMetaType::Double:
2526 return Encode(doubleValue);
2527 case QMetaType::Float:
2528 return Encode(floatValue);
2529 case QMetaType::QString:
2530 return Encode(engine->newString(s: *qstringPtr));
2531 case QMetaType::QByteArray:
2532 return Encode(engine->newArrayBuffer(array: *qbyteArrayPtr));
2533 case QMetaType::QObjectStar:
2534 if (qobjectPtr)
2535 QQmlData::get(object: qobjectPtr, create: true)->setImplicitDestructible();
2536 return QObjectWrapper::wrap(engine, object: qobjectPtr);
2537 case QMetaType::QJsonArray:
2538 return JsonObject::fromJsonArray(engine, array: *jsonArrayPtr);
2539 case QMetaType::QJsonObject:
2540 return JsonObject::fromJsonObject(engine, object: *jsonObjectPtr);
2541 case QMetaType::QJsonValue:
2542 return JsonObject::fromJsonValue(engine, value: *jsonValuePtr);
2543 case QMetaType::QVariant:
2544 case QVariantWrappedType: {
2545 Scope scope(engine);
2546 ScopedValue rv(scope, scope.engine->fromVariant(*qvariantPtr));
2547 Scoped<QObjectWrapper> qobjectWrapper(scope, rv);
2548 if (!!qobjectWrapper) {
2549 if (QObject *object = qobjectWrapper->object())
2550 QQmlData::get(object, create: true)->setImplicitDestructible();
2551 }
2552 return rv->asReturnedValue();
2553 }
2554 default:
2555 break;
2556 }
2557
2558 if (type == qMetaTypeId<QJSValue>()) {
2559 // The QJSValue can be passed around via dataPtr()
2560 QJSValuePrivate::manageStringOnV4Heap(e: engine, jsval: qjsValuePtr);
2561 return QJSValuePrivate::asReturnedValue(jsval: qjsValuePtr);
2562 }
2563
2564 if (type == qMetaTypeId<QList<QObject *> >()) {
2565 // XXX Can this be made more by using Array as a prototype and implementing
2566 // directly against QList<QObject*>?
2567 QList<QObject *> &list = *qlistPtr;
2568 Scope scope(engine);
2569 ScopedArrayObject array(scope, engine->newArrayObject());
2570 array->arrayReserve(n: list.size());
2571 ScopedValue v(scope);
2572 for (int ii = 0; ii < list.size(); ++ii)
2573 array->arrayPut(index: ii, value: (v = QObjectWrapper::wrap(engine, object: list.at(i: ii))));
2574 array->setArrayLengthUnchecked(list.size());
2575 return array.asReturnedValue();
2576 }
2577
2578 return Encode::undefined();
2579}
2580
2581ReturnedValue QObjectMethod::create(ExecutionEngine *engine, Heap::Object *wrapper, int index)
2582{
2583 Scope valueScope(engine);
2584 Scoped<QObjectMethod> method(
2585 valueScope,
2586 engine->memoryManager->allocate<QObjectMethod>(args&: engine, args&: wrapper, args&: index));
2587 return method.asReturnedValue();
2588}
2589
2590ReturnedValue QObjectMethod::create(
2591 ExecutionEngine *engine, Heap::QQmlValueTypeWrapper *valueType, int index)
2592{
2593 Scope valueScope(engine);
2594 Scoped<QObjectMethod> method(
2595 valueScope,
2596 engine->memoryManager->allocate<QObjectMethod>(args&: engine, args&: valueType, args&: index));
2597 return method.asReturnedValue();
2598}
2599
2600ReturnedValue QObjectMethod::create(
2601 ExecutionEngine *engine, Heap::QObjectMethod *cloneFrom,
2602 Heap::Object *wrapper, Heap::Object *object)
2603{
2604 Scope valueScope(engine);
2605
2606 Scoped<QQmlValueTypeWrapper> valueTypeWrapper(valueScope);
2607 if (cloneFrom->wrapper) {
2608 Scoped<QQmlValueTypeWrapper> ref(valueScope, cloneFrom->wrapper);
2609 if (ref) {
2610 valueTypeWrapper = QQmlValueTypeWrapper::create(engine, cloneFrom: ref->d(), object: wrapper);
2611 } else {
2612 // We cannot re-attach a plain QQmlValueTypeWrapper because don't we know what
2613 // value we should operate on. Without knowledge of the property the value
2614 // was read from, we cannot load the value from the given object.
2615 return Encode::undefined();
2616 }
2617 }
2618
2619 Scoped<QObjectMethod> method(
2620 valueScope,
2621 engine->memoryManager->allocate<QV4::QObjectMethod>(
2622 args&: engine, args: valueTypeWrapper ? valueTypeWrapper->d() : object, args&: cloneFrom->index));
2623
2624 method->d()->methodCount = cloneFrom->methodCount;
2625
2626 Q_ASSERT(method->d()->methods == nullptr);
2627 switch (cloneFrom->methodCount) {
2628 case 0:
2629 Q_ASSERT(cloneFrom->methods == nullptr);
2630 break;
2631 case 1:
2632 Q_ASSERT(cloneFrom->methods
2633 == reinterpret_cast<QQmlPropertyData *>(&cloneFrom->_singleMethod));
2634 method->d()->methods = reinterpret_cast<QQmlPropertyData *>(&method->d()->_singleMethod);
2635 *method->d()->methods = *cloneFrom->methods;
2636 break;
2637 default:
2638 Q_ASSERT(cloneFrom->methods != nullptr);
2639 method->d()->methods = new QQmlPropertyData[cloneFrom->methodCount];
2640 memcpy(dest: method->d()->methods, src: cloneFrom->methods,
2641 n: cloneFrom->methodCount * sizeof(QQmlPropertyData));
2642 break;
2643 }
2644
2645 return method.asReturnedValue();
2646}
2647
2648void Heap::QObjectMethod::init(QV4::ExecutionEngine *engine, Object *object, int methodIndex)
2649{
2650 Heap::FunctionObject::init(engine);
2651 wrapper.set(e: engine, newVal: object);
2652 index = methodIndex;
2653}
2654
2655const QMetaObject *Heap::QObjectMethod::metaObject() const
2656{
2657 Scope scope(internalClass->engine);
2658
2659 if (Scoped<QV4::QQmlValueTypeWrapper> valueWrapper(scope, wrapper); valueWrapper)
2660 return valueWrapper->metaObject();
2661 if (QObject *self = object())
2662 return self->metaObject();
2663
2664 return nullptr;
2665}
2666
2667QObject *Heap::QObjectMethod::object() const
2668{
2669 Scope scope(internalClass->engine);
2670
2671 if (Scoped<QV4::QObjectWrapper> objectWrapper(scope, wrapper); objectWrapper)
2672 return objectWrapper->object();
2673 if (Scoped<QV4::QQmlTypeWrapper> typeWrapper(scope, wrapper); typeWrapper)
2674 return typeWrapper->object();
2675 return nullptr;
2676}
2677
2678bool Heap::QObjectMethod::isDetached() const
2679{
2680 if (!wrapper)
2681 return true;
2682
2683 QV4::Scope scope(internalClass->engine);
2684 if (Scoped<QV4::QQmlValueTypeWrapper> valueWrapper(scope, wrapper); valueWrapper)
2685 return valueWrapper->d()->object() == nullptr;
2686
2687 return false;
2688}
2689
2690bool Heap::QObjectMethod::isAttachedTo(QObject *o) const
2691{
2692 QV4::Scope scope(internalClass->engine);
2693 if (Scoped<QV4::QObjectWrapper> objectWrapper(scope, wrapper); objectWrapper)
2694 return objectWrapper->object() == o;
2695 if (Scoped<QV4::QQmlTypeWrapper> typeWrapper(scope, wrapper); typeWrapper)
2696 return typeWrapper->object() == o;
2697
2698 if (Scoped<QV4::QQmlValueTypeWrapper> valueWrapper(scope, wrapper); valueWrapper) {
2699 QV4::Scope scope(wrapper->internalClass->engine);
2700 if (QV4::Scoped<QV4::QObjectWrapper> qobject(scope, valueWrapper->d()->object()); qobject)
2701 return qobject->object() == o;
2702 if (QV4::Scoped<QV4::QQmlTypeWrapper> type(scope, valueWrapper->d()->object()); type)
2703 return type->object() == o;
2704
2705 // Attached to some nested value type or sequence object
2706 return false;
2707 }
2708
2709 return false;
2710}
2711
2712Heap::QObjectMethod::ThisObjectMode Heap::QObjectMethod::checkThisObject(
2713 const QMetaObject *thisMeta) const
2714{
2715 // Check that the metaobject matches.
2716
2717 if (!thisMeta) {
2718 // You can only get a detached method via a lookup, and then you have a thisObject.
2719 Q_ASSERT(wrapper);
2720 return Included;
2721 }
2722
2723 const auto check = [&](const QMetaObject *included) {
2724 const auto stackFrame = internalClass->engine->currentStackFrame;
2725 if (stackFrame && !stackFrame->v4Function->executableCompilationUnit()
2726 ->nativeMethodsAcceptThisObjects()) {
2727 qCWarning(lcMethodBehavior,
2728 "%s:%d: Calling C++ methods with 'this' objects different from the one "
2729 "they were retrieved from is broken, due to historical reasons. The "
2730 "original object is used as 'this' object. You can allow the given "
2731 "'this' object to be used by setting "
2732 "'pragma NativeMethodBehavior: AcceptThisObject'",
2733 qPrintable(stackFrame->source()), stackFrame->lineNumber());
2734 return Included;
2735 }
2736
2737 // destroy() and toString() can be called on all QObjects, but not on gadgets.
2738 if (index < 0)
2739 return thisMeta->inherits(metaObject: &QObject::staticMetaObject) ? Explicit : Invalid;
2740
2741 // Find the base type the method belongs to.
2742 int methodOffset = included->methodOffset();
2743 while (true) {
2744 if (included == thisMeta)
2745 return Explicit;
2746
2747 if (methodOffset <= index)
2748 return thisMeta->inherits(metaObject: included) ? Explicit : Invalid;
2749
2750 included = included->superClass();
2751 Q_ASSERT(included);
2752 methodOffset -= QMetaObjectPrivate::get(metaobject: included)->methodCount;
2753 };
2754
2755 Q_UNREACHABLE_RETURN(Invalid);
2756 };
2757
2758 if (const QMetaObject *meta = metaObject())
2759 return check(meta);
2760
2761 // If the QObjectMethod is detached, we can only have gotten here via a lookup.
2762 // The lookup checks that the QQmlPropertyCache matches.
2763 return Explicit;
2764}
2765
2766QString Heap::QObjectMethod::name() const
2767{
2768 if (index == QV4::QObjectMethod::DestroyMethod)
2769 return QStringLiteral("destroy");
2770 else if (index == QV4::QObjectMethod::ToStringMethod)
2771 return QStringLiteral("toString");
2772
2773 const QMetaObject *mo = metaObject();
2774 if (!mo)
2775 return QString();
2776
2777 int methodOffset = mo->methodOffset();
2778 while (methodOffset > index) {
2779 mo = mo->superClass();
2780 methodOffset -= QMetaObjectPrivate::get(metaobject: mo)->methodCount;
2781 }
2782
2783 return "%1::%2"_L1.arg(args: QLatin1StringView{mo->className()},
2784 args: QLatin1StringView{mo->method(index).name()});
2785}
2786
2787void Heap::QObjectMethod::ensureMethodsCache(const QMetaObject *thisMeta)
2788{
2789 if (methods) {
2790 Q_ASSERT(methodCount > 0);
2791 return;
2792 }
2793
2794 const QMetaObject *mo = metaObject();
2795
2796 if (!mo)
2797 mo = thisMeta;
2798
2799 Q_ASSERT(mo);
2800
2801 int methodOffset = mo->methodOffset();
2802 while (methodOffset > index) {
2803 mo = mo->superClass();
2804 methodOffset -= QMetaObjectPrivate::get(metaobject: mo)->methodCount;
2805 }
2806 QVarLengthArray<QQmlPropertyData, 9> resolvedMethods;
2807 QQmlPropertyData dummy;
2808 QMetaMethod method = mo->method(index);
2809 dummy.load(method);
2810 dummy.setMetaObject(mo);
2811 resolvedMethods.append(t: dummy);
2812 // Look for overloaded methods
2813 QByteArray methodName = method.name();
2814 for (int ii = index - 1; ii >= methodOffset; --ii) {
2815 if (methodName == mo->method(index: ii).name()) {
2816 method = mo->method(index: ii);
2817 dummy.load(method);
2818 resolvedMethods.append(t: dummy);
2819 }
2820 }
2821 if (resolvedMethods.size() > 1) {
2822 methods = new QQmlPropertyData[resolvedMethods.size()];
2823 memcpy(dest: methods, src: resolvedMethods.data(), n: resolvedMethods.size()*sizeof(QQmlPropertyData));
2824 methodCount = resolvedMethods.size();
2825 } else {
2826 methods = reinterpret_cast<QQmlPropertyData *>(&_singleMethod);
2827 *methods = resolvedMethods.at(idx: 0);
2828 methodCount = 1;
2829 }
2830
2831 Q_ASSERT(methodCount > 0);
2832}
2833
2834ReturnedValue QObjectMethod::method_toString(ExecutionEngine *engine, QObject *o) const
2835{
2836 return engine->newString(
2837 s: QObjectWrapper::objectToString(
2838 engine, metaObject: o ? o->metaObject() : d()->metaObject(), object: o))->asReturnedValue();
2839}
2840
2841ReturnedValue QObjectMethod::method_destroy(
2842 ExecutionEngine *engine, QObject *o, const Value *args, int argc) const
2843{
2844 if (!o)
2845 return Encode::undefined();
2846
2847 if (QQmlData::keepAliveDuringGarbageCollection(object: o))
2848 return engine->throwError(QStringLiteral("Invalid attempt to destroy() an indestructible object"));
2849
2850 int delay = 0;
2851 if (argc > 0)
2852 delay = args[0].toUInt32();
2853
2854 if (delay > 0)
2855 QTimer::singleShot(msec: delay, receiver: o, SLOT(deleteLater()));
2856 else
2857 o->deleteLater();
2858
2859 return Encode::undefined();
2860}
2861
2862ReturnedValue QObjectMethod::virtualCall(
2863 const FunctionObject *m, const Value *thisObject, const Value *argv, int argc)
2864{
2865 const QObjectMethod *This = static_cast<const QObjectMethod*>(m);
2866 return This->callInternal(thisObject, argv, argc);
2867}
2868
2869void QObjectMethod::virtualCallWithMetaTypes(
2870 const FunctionObject *m, QObject *thisObject, void **argv, const QMetaType *types, int argc)
2871{
2872 const QObjectMethod *This = static_cast<const QObjectMethod*>(m);
2873 This->callInternalWithMetaTypes(thisObject, argv, types, argc);
2874}
2875
2876ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value *argv, int argc) const
2877{
2878 ExecutionEngine *v4 = engine();
2879
2880 const QMetaObject *thisMeta = nullptr;
2881
2882 QObject *o = nullptr;
2883 Heap::QQmlValueTypeWrapper *valueWrapper = nullptr;
2884 if (const QObjectWrapper *w = thisObject->as<QObjectWrapper>()) {
2885 thisMeta = w->metaObject();
2886 o = w->object();
2887 } else if (const QQmlTypeWrapper *w = thisObject->as<QQmlTypeWrapper>()) {
2888 thisMeta = w->metaObject();
2889 o = w->object();
2890 } else if (const QQmlValueTypeWrapper *w = thisObject->as<QQmlValueTypeWrapper>()) {
2891 thisMeta = w->metaObject();
2892 valueWrapper = w->d();
2893 }
2894
2895 Heap::QObjectMethod::ThisObjectMode mode = Heap::QObjectMethod::Invalid;
2896 if (o && o == d()->object()) {
2897 mode = Heap::QObjectMethod::Explicit;
2898 // Nothing to do; objects are the same. This should be common
2899 } else if (valueWrapper && valueWrapper == d()->wrapper) {
2900 mode = Heap::QObjectMethod::Explicit;
2901 // Nothing to do; gadgets are the same. This should be somewhat common
2902 } else {
2903 mode = d()->checkThisObject(thisMeta);
2904 if (mode == Heap::QObjectMethod::Invalid) {
2905 v4->throwError(message: QLatin1String("Cannot call method %1 on %2").arg(
2906 args: d()->name(), args: thisObject->toQStringNoThrow()));
2907 return Encode::undefined();
2908 }
2909 }
2910
2911 QQmlObjectOrGadget object = [&](){
2912 if (mode == Heap::QObjectMethod::Included) {
2913 QV4::Scope scope(v4);
2914 if (QV4::Scoped<QV4::QObjectWrapper> qobject(scope, d()->wrapper); qobject)
2915 return QQmlObjectOrGadget(qobject->object());
2916 if (QV4::Scoped<QV4::QQmlTypeWrapper> type(scope, d()->wrapper); type)
2917 return QQmlObjectOrGadget(type->object());
2918 if (QV4::Scoped<QV4::QQmlValueTypeWrapper> value(scope, d()->wrapper); value) {
2919 valueWrapper = value->d();
2920 return QQmlObjectOrGadget(valueWrapper->metaObject(), valueWrapper->gadgetPtr());
2921 }
2922 Q_UNREACHABLE();
2923 } else {
2924 if (o)
2925 return QQmlObjectOrGadget(o);
2926
2927 Q_ASSERT(valueWrapper);
2928 if (!valueWrapper->enforcesLocation())
2929 QV4::ReferenceObject::readReference(ref: valueWrapper);
2930 return QQmlObjectOrGadget(thisMeta, valueWrapper->gadgetPtr());
2931 }
2932 }();
2933
2934 if (object.isNull())
2935 return Encode::undefined();
2936
2937 if (d()->index == DestroyMethod)
2938 return method_destroy(engine: v4, o: object.qObject(), args: argv, argc);
2939 else if (d()->index == ToStringMethod)
2940 return method_toString(engine: v4, o: object.qObject());
2941
2942 d()->ensureMethodsCache(thisMeta);
2943
2944 Scope scope(v4);
2945 JSCallData cData(thisObject, argv, argc);
2946 CallData *callData = cData.callData(scope);
2947
2948 const QQmlPropertyData *method = d()->methods;
2949
2950 // If we call the method, we have to write back any value type references afterwards.
2951 // The method might change the value.
2952 const auto doCall = [&](const auto &call) {
2953 if (!method->isConstant()) {
2954 if (valueWrapper && valueWrapper->isReference()) {
2955 ScopedValue rv(scope, call());
2956 valueWrapper->writeBack();
2957 return rv->asReturnedValue();
2958 }
2959 }
2960
2961 return call();
2962 };
2963
2964 if (d()->methodCount != 1) {
2965 Q_ASSERT(d()->methodCount > 0);
2966 method = resolveOverloaded(object, methods: d()->methods, methodCount: d()->methodCount, engine: v4, callArgs: callData);
2967 if (method == nullptr)
2968 return Encode::undefined();
2969 }
2970
2971 if (method->isV4Function()) {
2972 return doCall([&]() {
2973 ScopedValue rv(scope, Value::undefinedValue());
2974 QQmlV4Function func(callData, rv, v4);
2975 QQmlV4FunctionPtr funcptr = &func;
2976
2977 void *args[] = { nullptr, &funcptr };
2978 object.metacall(type: QMetaObject::InvokeMetaMethod, index: method->coreIndex(), argv: args);
2979
2980 return rv->asReturnedValue();
2981 });
2982 }
2983
2984 return doCall([&]() { return callPrecise(object, data: *method, engine: v4, callArgs: callData); });
2985}
2986
2987struct ToStringMetaMethod
2988{
2989 constexpr int parameterCount() const { return 0; }
2990 constexpr QMetaType returnMetaType() const { return QMetaType::fromType<QString>(); }
2991 constexpr QMetaType parameterMetaType(int) const { return QMetaType(); }
2992};
2993
2994void QObjectMethod::callInternalWithMetaTypes(
2995 QObject *thisObject, void **argv, const QMetaType *types, int argc) const
2996{
2997 ExecutionEngine *v4 = engine();
2998
2999 const QMetaObject *thisMeta = nullptr;
3000 Heap::QQmlValueTypeWrapper *valueWrapper = nullptr;
3001
3002 if (thisObject) {
3003 thisMeta = thisObject->metaObject();
3004 } else {
3005 Q_ASSERT(Value::fromHeapObject(d()->wrapper).as<QQmlValueTypeWrapper>());
3006 valueWrapper = d()->wrapper.cast<Heap::QQmlValueTypeWrapper>();
3007 thisMeta = valueWrapper->metaObject();
3008 }
3009
3010 QQmlObjectOrGadget object = [&](){
3011 if (thisObject)
3012 return QQmlObjectOrGadget(thisObject);
3013
3014 Scope scope(v4);
3015 Scoped<QQmlValueTypeWrapper> wrapper(scope, d()->wrapper);
3016 Q_ASSERT(wrapper);
3017
3018 Heap::QQmlValueTypeWrapper *valueWrapper = wrapper->d();
3019 if (!valueWrapper->enforcesLocation())
3020 QV4::ReferenceObject::readReference(ref: valueWrapper);
3021 return QQmlObjectOrGadget(thisMeta, valueWrapper->gadgetPtr());
3022 }();
3023
3024 if (object.isNull())
3025 return;
3026
3027 if (d()->index == DestroyMethod) {
3028 // method_destroy will use at most one argument
3029 QV4::convertAndCall(
3030 engine: v4, thisObject, a: argv, types, argc: std::min(a: argc, b: 1),
3031 call: [this, v4, object](const Value *thisObject, const Value *argv, int argc) {
3032 Q_UNUSED(thisObject);
3033 return method_destroy(engine: v4, o: object.qObject(), args: argv, argc);
3034 });
3035 return;
3036 }
3037
3038 if (d()->index == ToStringMethod) {
3039 const ToStringMetaMethod metaMethod;
3040 QV4::coerceAndCall(
3041 engine: v4, typedFunction: &metaMethod, argv, types, argc,
3042 call: [v4, thisMeta, object](void **argv, int) {
3043 *static_cast<QString *>(argv[0])
3044 = QObjectWrapper::objectToString(engine: v4, metaObject: thisMeta, object: object.qObject());
3045 });
3046 return;
3047 }
3048
3049 d()->ensureMethodsCache(thisMeta);
3050
3051 const QQmlPropertyData *method = d()->methods;
3052 if (d()->methodCount != 1) {
3053 Q_ASSERT(d()->methodCount > 0);
3054 method = resolveOverloaded(methods: d()->methods, methodCount: d()->methodCount, argv, argc, types);
3055 }
3056
3057 if (!method || method->isV4Function()) {
3058 QV4::convertAndCall(
3059 engine: v4, thisObject, a: argv, types, argc,
3060 call: [this](const Value *thisObject, const Value *argv, int argc) {
3061 return callInternal(thisObject, argv, argc);
3062 });
3063 } else {
3064 const QMetaMethod metaMethod = method->metaMethod();
3065 QV4::coerceAndCall(
3066 engine: v4, typedFunction: &metaMethod, argv, types, argc,
3067 call: [v4, object, valueWrapper, method](void **argv, int argc) {
3068 Q_UNUSED(argc);
3069
3070 // If we call the method, we have to write back any value type references afterwards.
3071 // The method might change the value.
3072 object.metacall(type: QMetaObject::InvokeMetaMethod, index: method->coreIndex(), argv);
3073 if (!method->isConstant()) {
3074 if (valueWrapper && valueWrapper->isReference())
3075 valueWrapper->writeBack();
3076 }
3077
3078 // If the method returns a QObject* we need to track it on the JS heap
3079 // (if it's destructible).
3080 QObject *qobjectPtr = nullptr;
3081 const QMetaType resultType = method->propType();
3082 if (argv[0]) {
3083 if (resultType.flags() & QMetaType::PointerToQObject) {
3084 qobjectPtr = *static_cast<QObject **>(argv[0]);
3085 } else if (resultType == QMetaType::fromType<QVariant>()) {
3086 const QVariant *result = static_cast<const QVariant *>(argv[0]);
3087 const QMetaType variantType = result->metaType();
3088 if (variantType.flags() & QMetaType::PointerToQObject)
3089 qobjectPtr = *static_cast<QObject *const *>(result->data());
3090 }
3091 }
3092
3093 if (qobjectPtr) {
3094 QQmlData *ddata = QQmlData::get(object: qobjectPtr, create: true);
3095 if (!ddata->explicitIndestructibleSet) {
3096 ddata->indestructible = false;
3097 QObjectWrapper::ensureWrapper(engine: v4, object: qobjectPtr);
3098 }
3099 }
3100 });
3101 }
3102}
3103
3104DEFINE_OBJECT_VTABLE(QObjectMethod);
3105
3106void Heap::QmlSignalHandler::init(QObject *object, int signalIndex)
3107{
3108 Object::init();
3109 this->signalIndex = signalIndex;
3110 setObject(object);
3111}
3112
3113DEFINE_OBJECT_VTABLE(QmlSignalHandler);
3114
3115ReturnedValue QmlSignalHandler::call(const Value *thisObject, const Value *argv, int argc) const
3116{
3117 const QString handlerName = QQmlSignalNames::signalNameToHandlerName(
3118 signal: object()->metaObject()->method(index: signalIndex()).name());
3119 qCWarning(lcSignalHandler).noquote()
3120 << QStringLiteral("Property '%1' of object %2 is a signal handler. You should "
3121 "not call it directly. Make it a proper function and call "
3122 "that or emit the signal.")
3123 .arg(args: handlerName, args: thisObject->toQStringNoThrow());
3124
3125 Scope scope(engine());
3126 Scoped<QObjectMethod> method(
3127 scope, QObjectMethod::create(
3128 engine: scope.engine,
3129 wrapper: static_cast<Heap::QObjectWrapper *>(nullptr),
3130 index: signalIndex()));
3131
3132 return method->call(thisObject, argv, argc);
3133}
3134
3135void QmlSignalHandler::initProto(ExecutionEngine *engine)
3136{
3137 if (engine->signalHandlerPrototype()->d_unchecked())
3138 return;
3139
3140 Scope scope(engine);
3141 ScopedObject o(scope, engine->newObject());
3142 ScopedString connect(scope, engine->newIdentifier(QStringLiteral("connect")));
3143 ScopedString disconnect(scope, engine->newIdentifier(QStringLiteral("disconnect")));
3144 o->put(name: connect, v: ScopedValue(scope, engine->functionPrototype()->get(name: connect)));
3145 o->put(name: disconnect, v: ScopedValue(scope, engine->functionPrototype()->get(name: disconnect)));
3146
3147 engine->jsObjects[ExecutionEngine::SignalHandlerProto] = o->d();
3148}
3149
3150
3151MultiplyWrappedQObjectMap::Iterator MultiplyWrappedQObjectMap::erase(
3152 MultiplyWrappedQObjectMap::Iterator it)
3153{
3154 const QObjectBiPointer key = it.key();
3155 const QObject *obj = key.isT1() ? key.asT1() : key.asT2();
3156 disconnect(sender: obj, signal: &QObject::destroyed, receiver: this, slot: &MultiplyWrappedQObjectMap::removeDestroyedObject);
3157 return QHash<QObjectBiPointer, WeakValue>::erase(it);
3158}
3159
3160void MultiplyWrappedQObjectMap::removeDestroyedObject(QObject *object)
3161{
3162 QHash<QObjectBiPointer, WeakValue>::remove(key: object);
3163 QHash<QObjectBiPointer, WeakValue>::remove(key: static_cast<const QObject *>(object));
3164}
3165
3166} // namespace QV4
3167
3168QT_END_NAMESPACE
3169
3170#include "moc_qv4qobjectwrapper_p.cpp"
3171

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qml/jsruntime/qv4qobjectwrapper.cpp