Showing the changes to libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp (1 of 11 files). Show all
Unified · Split
| @@ -53,6 +53,18 @@ private: // storage · 52 unmodified lines | ||
| 53 | 53 | QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObject* object, |
| 54 | 54 | ScriptEngine::ValueOwnership ownership, |
| 55 | 55 | const ScriptEngine::QObjectWrapOptions& options) { |
| 56 | + QScriptEngine* qengine = static_cast<QScriptEngine*>(engine); | |
| 57 | + | |
| 58 | + // do we already have a valid wrapper for this QObject? | |
| 59 | + { | |
| 60 | + QMutexLocker guard(&engine->_qobjectWrapperMapProtect); | |
| 61 | + ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); | |
| 62 | + if (lookup != engine->_qobjectWrapperMap.end()) { | |
| 63 | + QSharedPointer<ScriptObjectQtProxy> proxy = lookup.value().lock(); | |
| 64 | + if (proxy) return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; | |
| 65 | + } | |
| 66 | + } | |
| 67 | + | |
| 56 | 68 | bool ownsObject; |
| 57 | 69 | switch (ownership) { |
| 58 | 70 | case ScriptEngine::QtOwnership: |
| @@ -65,8 +77,33 @@ QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObje · 6 unmodified lines | ||
| 65 | 77 | ownsObject = !object->parent(); |
| 66 | 78 | break; |
| 67 | 79 | } |
| 68 | − QScriptEngine* qengine = static_cast<QScriptEngine*>(engine); | |
| 80 | + | |
| 81 | + // create the wrapper | |
| 69 | 82 | auto proxy = QSharedPointer<ScriptObjectQtProxy>::create(engine, object, ownsObject, options); |
| 83 | + | |
| 84 | + { | |
| 85 | + QMutexLocker guard(&engine->_qobjectWrapperMapProtect); | |
| 86 | + | |
| 87 | + // check again to see if someone else created the wrapper while we were busy | |
| 88 | + ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); | |
| 89 | + if (lookup != engine->_qobjectWrapperMap.end()) { | |
| 90 | + QSharedPointer<ScriptObjectQtProxy> proxy = lookup.value().lock(); | |
| 91 | + if (proxy) return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; | |
| 92 | + } | |
| 93 | + | |
| 94 | + // register the wrapper with the engine and make sure it cleans itself up | |
| 95 | + engine->_qobjectWrapperMap.insert(object, proxy); | |
| 96 | + QPointer<ScriptEngineQtScript> enginePtr = engine; | |
| 97 | + object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { | |
| 98 | + if (!enginePtr) return; | |
| 99 | + QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); | |
| 100 | + ScriptEngineQtScript::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object); | |
| 101 | + if (lookup != enginePtr->_qobjectWrapperMap.end()) { | |
| 102 | + enginePtr->_qobjectWrapperMap.erase(lookup); | |
| 103 | + } | |
| 104 | + }); | |
| 105 | + } | |
| 106 | + | |
| 70 | 107 | return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); |
| 71 | 108 | } |
| 72 | 109 | |
| @@ -96,7 +133,6 @@ void ScriptObjectQtProxy::investigate() { · 23 unmodified lines | ||
| 96 | 133 | if (!qobject) return; |
| 97 | 134 | |
| 98 | 135 | const QMetaObject* metaObject = qobject->metaObject(); |
| 99 | − _name = QString::fromLatin1(metaObject->className()); | |
| 100 | 136 | |
| 101 | 137 | // discover properties |
| 102 | 138 | int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0; |
| @@ -185,6 +221,15 @@ void ScriptObjectQtProxy::investigate() { · 82 unmodified lines | ||
| 185 | 221 | } |
| 186 | 222 | } |
| 187 | 223 | |
| 224 | +QString ScriptObjectQtProxy::name() const { | |
| 225 | + Q_ASSERT(_object); | |
| 226 | + if (!_object) return ""; | |
| 227 | + return _object ? _object->objectName() : ""; | |
| 228 | + QString objectName = _object->objectName(); | |
| 229 | + if (!objectName.isEmpty()) return objectName; | |
| 230 | + return _object->metaObject()->className(); | |
| 231 | +} | |
| 232 | + | |
| 188 | 233 | QScriptClass::QueryFlags ScriptObjectQtProxy::queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) { |
| 189 | 234 | // check for properties |
| 190 | 235 | for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { |
| @@ -268,7 +313,7 @@ QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QSc · 77 unmodified lines | ||
| 268 | 313 | MethodDefMap::const_iterator lookup = _methods.find(methodId); |
| 269 | 314 | if (lookup == _methods.cend()) return QScriptValue(); |
| 270 | 315 | return static_cast<QScriptEngine*>(_engine)->newObject( |
| 271 | − new ScriptMethodQtProxy(_engine, qobject, object, name, lookup.value().methods)); | |
| 316 | + new ScriptMethodQtProxy(_engine, qobject, object, lookup.value().methods)); | |
| 272 | 317 | } |
| 273 | 318 | case SIGNAL_TYPE: { |
| 274 | 319 | int signalId = id & ~TYPE_MASK; |
| @@ -278,7 +323,7 @@ QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QSc · 3 unmodified lines | ||
| 278 | 323 | InstanceMap::const_iterator instLookup = _signalInstances.find(signalId); |
| 279 | 324 | if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) { |
| 280 | 325 | instLookup = _signalInstances.insert(signalId, |
| 281 | − new ScriptSignalQtProxy(_engine, qobject, object, name, defLookup.value().signal)); | |
| 326 | + new ScriptSignalQtProxy(_engine, qobject, object, defLookup.value().signal)); | |
| 282 | 327 | Q_ASSERT(instLookup != _signalInstances.cend()); |
| 283 | 328 | } |
| 284 | 329 | ScriptSignalQtProxy* proxy = instLookup.value(); |
| @@ -315,10 +360,11 @@ void ScriptObjectQtProxy::setProperty(QScriptValue& object, const QScriptString& · 30 unmodified lines | ||
| 315 | 360 | ScriptContextGuard guard(&ourContext); |
| 316 | 361 | |
| 317 | 362 | int propTypeId = prop.userType(); |
| 363 | + Q_ASSERT(propTypeId != QMetaType::UnknownType); | |
| 318 | 364 | QVariant varValue; |
| 319 | 365 | if(!_engine->castValueToVariant(value, varValue, propTypeId)) { |
| 320 | 366 | QByteArray propTypeName = QMetaType(propTypeId).name(); |
| 321 | − QByteArray valTypeName = value.toVariant().typeName(); | |
| 367 | + QByteArray valTypeName = _engine->valueType(value).toLatin1(); | |
| 322 | 368 | QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); |
| 323 | 369 | currentContext->throwError(QScriptContext::TypeError, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName)); |
| 324 | 370 | return; |
| @@ -352,6 +398,18 @@ QVariant ScriptVariantQtProxy::unwrap(const QScriptValue& val) { · 27 unmodified lines | ||
| 352 | 398 | return proxy ? proxy->toQtValue() : QVariant(); |
| 353 | 399 | } |
| 354 | 400 | |
| 401 | +QString ScriptMethodQtProxy::fullName() const { | |
| 402 | + Q_ASSERT(_object); | |
| 403 | + if (!_object) return ""; | |
| 404 | + Q_ASSERT(!_metas.isEmpty()); | |
| 405 | + const QMetaMethod& firstMethod = _metas.front(); | |
| 406 | + QString objectName = _object->objectName(); | |
| 407 | + if (!objectName.isEmpty()) { | |
| 408 | + return QString("%1.%2").arg(objectName, firstMethod.name()); | |
| 409 | + } | |
| 410 | + return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name()); | |
| 411 | +} | |
| 412 | + | |
| 355 | 413 | bool ScriptMethodQtProxy::supportsExtension(Extension extension) const { |
| 356 | 414 | switch (extension) { |
| 357 | 415 | case Callable: |
| @@ -377,6 +435,9 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 19 unmodified lines | ||
| 377 | 435 | |
| 378 | 436 | const int scriptValueTypeId = qMetaTypeId<ScriptValue>(); |
| 379 | 437 | |
| 438 | + int parameterConversionFailureId = 0; | |
| 439 | + int parameterConversionFailureCount = 0; | |
| 440 | + | |
| 380 | 441 | for (auto iter = _metas.cbegin(); iter != _metas.end(); ++iter) { |
| 381 | 442 | const QMetaMethod& meta = *iter; |
| 382 | 443 | int methodNumArgs = meta.parameterCount(); |
| @@ -387,10 +448,10 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 4 unmodified lines | ||
| 387 | 448 | QList<ScriptValue> qScriptArgList; |
| 388 | 449 | QList<QVariant> qVarArgList; |
| 389 | 450 | QGenericArgument qGenArgs[10]; |
| 390 | − int arg; | |
| 391 | − bool conversionFailed = false; | |
| 392 | − for (arg = 0; arg < numArgs && !conversionFailed; ++arg) { | |
| 451 | + int conversionFailures = 0; | |
| 452 | + for (int arg = 0; arg < numArgs; ++arg) { | |
| 393 | 453 | int methodArgTypeId = meta.parameterType(arg); |
| 454 | + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); | |
| 394 | 455 | QScriptValue argVal = context->argument(arg); |
| 395 | 456 | if (methodArgTypeId == scriptValueTypeId) { |
| 396 | 457 | qScriptArgList.append(ScriptValue(new ScriptValueQtWrapper(_engine, argVal))); |
| @@ -401,24 +462,23 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 4 unmodified lines | ||
| 401 | 462 | } else { |
| 402 | 463 | QVariant varArgVal; |
| 403 | 464 | if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { |
| 404 | − conversionFailed = true; | |
| 405 | − break; | |
| 406 | − /*QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); | |
| 407 | − QByteArray argTypeName = argVal.toVariant().typeName(); | |
| 408 | − context->throwError(QScriptContext::TypeError, | |
| 409 | − QString("Cannot convert %1 to %2").arg(argTypeName, methodTypeName)); | |
| 410 | − return QVariant();*/ | |
| 465 | + conversionFailures++; | |
| 466 | + } else { | |
| 467 | + qVarArgList.append(varArgVal); | |
| 468 | + const QVariant& converted = qVarArgList.back(); | |
| 469 | + | |
| 470 | + // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant | |
| 471 | + // A const_cast is needed because calling data() would detach the QVariant. | |
| 472 | + qGenArgs[arg] = | |
| 473 | + QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData())); | |
| 411 | 474 | } |
| 412 | − qVarArgList.append(varArgVal); | |
| 413 | − const QVariant& converted = qVarArgList.back(); | |
| 414 | − | |
| 415 | − // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant | |
| 416 | − // A const_cast is needed because calling data() would detach the QVariant. | |
| 417 | − qGenArgs[arg] = | |
| 418 | − QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData())); | |
| 419 | 475 | } |
| 420 | 476 | } |
| 421 | − if (conversionFailed) { | |
| 477 | + if (conversionFailures) { | |
| 478 | + if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { | |
| 479 | + parameterConversionFailureCount = conversionFailures; | |
| 480 | + parameterConversionFailureId = meta.methodIndex(); | |
| 481 | + } | |
| 422 | 482 | continue; |
| 423 | 483 | } |
| 424 | 484 | |
| @@ -426,11 +486,18 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 1 unmodified line | ||
| 426 | 486 | ScriptContextGuard guard(&ourContext); |
| 427 | 487 | |
| 428 | 488 | int returnTypeId = meta.returnType(); |
| 429 | − if (returnTypeId == QMetaType::Void) { | |
| 489 | + | |
| 490 | + // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's | |
| 491 | + // nothing in there for return values so these need to be explicitly runtime-registered! | |
| 492 | + Q_ASSERT(returnTypeId != QMetaType::UnknownType); | |
| 493 | + if (returnTypeId == QMetaType::UnknownType) { | |
| 494 | + context->throwError(QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName())); | |
| 495 | + return QVariant(); | |
| 496 | + } else if (returnTypeId == QMetaType::Void) { | |
| 430 | 497 | bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], |
| 431 | 498 | qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); |
| 432 | 499 | if (!success) { |
| 433 | − context->throwError("Native call failed"); | |
| 500 | + context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); | |
| 434 | 501 | } |
| 435 | 502 | return QVariant(); |
| 436 | 503 | } else if (returnTypeId == scriptValueTypeId) { |
| @@ -439,7 +506,7 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 2 unmodified lines | ||
| 439 | 506 | qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], |
| 440 | 507 | qGenArgs[7], qGenArgs[8], qGenArgs[9]); |
| 441 | 508 | if (!success) { |
| 442 | − context->throwError("Native call failed"); | |
| 509 | + context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); | |
| 443 | 510 | return QVariant(); |
| 444 | 511 | } |
| 445 | 512 | QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result); |
| @@ -454,15 +521,53 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 8 unmodified lines | ||
| 454 | 521 | meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], |
| 455 | 522 | qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); |
| 456 | 523 | if (!success) { |
| 457 | − context->throwError("Native call failed"); | |
| 524 | + context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); | |
| 458 | 525 | return QVariant(); |
| 459 | 526 | } |
| 460 | 527 | QScriptValue qResult = _engine->castVariantToValue(qRetVal); |
| 461 | 528 | return QVariant::fromValue(qResult); |
| 462 | 529 | } |
| 463 | 530 | } |
| 464 | − context->throwError("Native call failed: could not locate an overload with the requested arguments"); | |
| 531 | + | |
| 532 | + // we failed to convert the call to C++, try to create a somewhat sane error message | |
| 533 | + if (parameterConversionFailureCount == 0) { | |
| 534 | + context->throwError(QString("Native call of %1 failed: unexpected parameter count").arg(fullName())); | |
| 535 | + return QVariant(); | |
| 536 | + } | |
| 537 | + | |
| 538 | + const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); | |
| 539 | + int methodNumArgs = meta.parameterCount(); | |
| 540 | + Q_ASSERT(methodNumArgs == numArgs); | |
| 541 | + | |
| 542 | + for (int arg = 0; arg < numArgs; ++arg) { | |
| 543 | + int methodArgTypeId = meta.parameterType(arg); | |
| 544 | + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); | |
| 545 | + QScriptValue argVal = context->argument(arg); | |
| 546 | + if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { | |
| 547 | + QVariant varArgVal; | |
| 548 | + if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { | |
| 549 | + QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); | |
| 550 | + QByteArray argTypeName = _engine->valueType(argVal).toLatin1(); | |
| 551 | + context->throwError(QScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") | |
| 552 | + .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); | |
| 553 | + return QVariant(); | |
| 554 | + } | |
| 555 | + } | |
| 556 | + } | |
| 557 | + | |
| 558 | + Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? | |
| 465 | 559 | return QVariant(); |
| 560 | + context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName())); | |
| 561 | +} | |
| 562 | + | |
| 563 | +QString ScriptSignalQtProxy::fullName() const { | |
| 564 | + Q_ASSERT(_object); | |
| 565 | + if (!_object) return ""; | |
| 566 | + QString objectName = _object->objectName(); | |
| 567 | + if (!objectName.isEmpty()) { | |
| 568 | + return QString("%1.%2").arg(objectName, _meta.name()); | |
| 569 | + } | |
| 570 | + return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name()); | |
| 466 | 571 | } |
| 467 | 572 | |
| 468 | 573 | // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it |
| @@ -476,6 +581,7 @@ int ScriptSignalQtProxy::qt_metacall(QMetaObject::Call call, int id, void** argu · 7 unmodified lines | ||
| 476 | 581 | int numArgs = _meta.parameterCount(); |
| 477 | 582 | for (int arg = 0; arg < numArgs; ++arg) { |
| 478 | 583 | int methodArgTypeId = _meta.parameterType(arg); |
| 584 | + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); | |
| 479 | 585 | QVariant argValue(methodArgTypeId, arguments[arg+1]); |
| 480 | 586 | args.append(_engine->castVariantToValue(argValue)); |
| 481 | 587 | } |