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

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