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