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