void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo<v8::Value>& arguments) { v8::Isolate *isolate = arguments.GetIsolate(); Q_ASSERT(isolate == _engine->getIsolate()); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); ContextScopeV8 contextScopeV8(_engine); auto context = _engine->getContext(); v8::Context::Scope contextScope(context); QObject* qobject = _object; if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } int scriptNumArgs = arguments.Length(); int numArgs = std::min(scriptNumArgs, _numMaxParams); const int scriptValueTypeId = qMetaTypeId<ScriptValue>(); int parameterConversionFailureId = 0; int parameterConversionFailureCount = 0; int num_metas = _metas.size(); QVector< QList<ScriptValue> > qScriptArgLists; QVector< QVector <QGenericArgument> > qGenArgsVectors; QVector< QList<QVariant> > qVarArgLists; qScriptArgLists.resize(num_metas); qGenArgsVectors.resize(num_metas); qVarArgLists.resize(num_metas); bool isValidMetaSelected = false; int bestMeta = 0; int bestConversionPenaltyScore = 0; for (int i = 0; i < num_metas; i++) { const QMetaMethod& meta = _metas[i]; int methodNumArgs = meta.parameterCount(); if (methodNumArgs != numArgs) { continue; } qGenArgsVectors[i].resize(10); int conversionPenaltyScore = 0; int conversionFailures = 0; for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); if (methodArgTypeId == QMetaType::UnknownType) { QString methodName = fullName(); qCDebug(scriptengine_v8) << "One of the arguments is QMetaType::UnknownType for method " << methodName; Q_ASSERT(false); } v8::Local<v8::Value> argVal = arguments[arg]; if (methodArgTypeId == scriptValueTypeId) { qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, argVal)))); qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); } else if (methodArgTypeId == QMetaType::QVariant) { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { conversionFailures++; } else { qVarArgLists[i].append(varArgVal); qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); } } else { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { conversionFailures++; } else { qVarArgLists[i].append(varArgVal); const QVariant& converted = qVarArgLists[i].back(); conversionPenaltyScore += _engine->computeCastPenalty(V8ScriptValue(_engine, argVal), methodArgTypeId); // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant // A const_cast is needed because calling data() would detach the QVariant. qGenArgsVectors[i][arg] = QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData())); } } } if (conversionFailures) { if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { parameterConversionFailureCount = conversionFailures; parameterConversionFailureId = meta.methodIndex(); } continue; } if (!isValidMetaSelected) { isValidMetaSelected = true; bestMeta = i; bestConversionPenaltyScore = conversionPenaltyScore; } if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { bestMeta = i; bestConversionPenaltyScore = conversionPenaltyScore; } } if (isValidMetaSelected) { // V8TODO: is this the correct wrapper? ScriptContextV8Wrapper ourContext(_engine, &arguments, context, _engine->currentContext()->parentContext()); ScriptContextGuard guard(&ourContext); const QMetaMethod& meta = _metas[bestMeta]; int returnTypeId = meta.returnType(); QVector <QGenericArgument> &qGenArgs = qGenArgsVectors[bestMeta]; // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's // nothing in there for return values so these need to be explicitly runtime-registered! if (returnTypeId == QMetaType::UnknownType) { QString methodName = fullName(); qCDebug(scriptengine_v8) << "returnTypeId == QMetaType::UnknownType for method " << methodName; _engine->logBacktrace(""); //Q_ASSERT(false); } if (returnTypeId == QMetaType::UnknownType) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } else if (returnTypeId == QMetaType::Void) { bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); } return; } else if (returnTypeId == scriptValueTypeId) { ScriptValue result; bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } V8ScriptValue v8Result = ScriptValueV8Wrapper::fullUnwrap(_engine, result); arguments.GetReturnValue().Set(v8Result.get()); return; } else { // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant const char* typeName = meta.typeName(); QVariant qRetVal(returnTypeId, static_cast<void*>(NULL)); QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData())); bool success = meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } V8ScriptValue v8Result = _engine->castVariantToValue(qRetVal); arguments.GetReturnValue().Set(v8Result.get()); return; } } // we failed to convert the call to C++, try to create a somewhat sane error message if (parameterConversionFailureCount == 0) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); int methodNumArgs = meta.parameterCount(); Q_ASSERT(methodNumArgs == numArgs); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); v8::Local<v8::Value> argVal = arguments[arg]; if (methodArgTypeId != scriptValueTypeId) { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); QByteArray argTypeName = _engine->valueType(V8ScriptValue(_engine, argVal)).toLatin1(); QString errorMessage = QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName); qCDebug(scriptengine_v8) << errorMessage << "\n Backtrace:" << _engine->currentContext()->backtrace(); isolate->ThrowError(v8::String::NewFromUtf8(isolate, errorMessage.toStdString().c_str()).ToLocalChecked()); return; } } } QString errorMessage = QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()); qCDebug(scriptengine_v8) << errorMessage; isolate->ThrowError(v8::String::NewFromUtf8(isolate, errorMessage.toStdString().c_str()).ToLocalChecked()); Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? return;} ScriptSignalV8Proxy::ScriptSignalV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QMetaMethod& meta) : _engine(engine), _object(object), _meta(meta), _metaCallId(discoverMetaCallIdx()) { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); auto context = _engine->getContext(); v8::Context::Scope contextScope(context); _objectLifetime.Reset(isolate, lifetime.get()); _objectLifetime.SetWeak(this, weakHandleCallback, v8::WeakCallbackType::kParameter); _v8Context.Reset(isolate, context); _engine->_signalProxySetLock.lockForWrite(); _engine->_signalProxySet.insert(this); _engine->_signalProxySetLock.unlock();} ScriptSignalV8Proxy::~ScriptSignalV8Proxy() { if (!_cleanup) { disconnectAllScriptSignalProxies(); } auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); _objectLifetime.Reset(); _v8Context.Reset();#ifdef OVERTE_SCRIPT_USE_AFTER_DELETE_GUARD Q_ASSERT(!_engine->_wasDestroyed);#endif _engine->_signalProxySetLock.lockForWrite(); _engine->_signalProxySet.remove(this); _engine->_signalProxySetLock.unlock();} void ScriptSignalV8Proxy::weakHandleCallback(const v8::WeakCallbackInfo<ScriptSignalV8Proxy>& info) { auto proxy = info.GetParameter(); proxy->_objectLifetime.Reset(); proxy->deleteLater();} QString ScriptSignalV8Proxy::fullName() const { Q_ASSERT(_object); if (!_object) return ""; QString objectName = _object->objectName(); if (!objectName.isEmpty()) { return QString("%1.%2").arg(objectName, _meta.name()); } return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name());} // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for itint ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { id = ScriptSignalV8ProxyBase::qt_metacall(call, id, arguments); if (id != 0 || call != QMetaObject::InvokeMetaMethod) { return id; } #ifdef SCRIPT_EVENT_PERFORMANCE_STATISTICS _callCounter++; if (_callCounter % 1000 == 0) { qCDebug(scriptengine_v8) << "Script engine: " << _engine->manager()->getFilename() << " Signal proxy " << fullName() << " call count: " << _callCounter << " total time: " << _totalCallTime_s; } QElapsedTimer callTimer; callTimer.start();#endif auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); QList<Connection> connections; withReadLock([&]{ connections = _connections; }); // V8TODO: this may cause deadlocks on connect/disconnect, so the connect/disconnect procedure needs to be reworked. // It should probably add events to a separate list that would be processed before and after all the events for the signal. { v8::HandleScope handleScope(isolate); // V8TODO: should we use function creation context, or context in which connect happened? auto context = _engine->getContext(); v8::Context::Scope contextScope(context); v8::Local<v8::Value> args[Q_METAMETHOD_INVOKE_MAX_ARGS]; int numArgs = _meta.parameterCount(); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = _meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); QVariant argValue(methodArgTypeId, arguments[arg + 1]); args[arg] = _engine->castVariantToValue(argValue).get(); } for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { Connection& conn = *iter; { auto functionContext = context; Q_ASSERT(!conn.callback.get().IsEmpty()); Q_ASSERT(!conn.callback.get()->IsUndefined()); if (conn.callback.get()->IsNull()) { qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::qt_metacall: Connection callback is Null"; _engine->popContext(); continue; } if (!conn.callback.get()->IsFunction()) { auto stringV8 = conn.callback.get()->ToDetailString(functionContext).ToLocalChecked(); QString error = *v8::String::Utf8Value(_engine->getIsolate(), stringV8); qCDebug(scriptengine_v8) << error; Q_ASSERT(false); } v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(conn.callback.get()); v8::Local<v8::Value> v8This; if (conn.thisValue.get()->IsObject()) { v8This = conn.thisValue.get(); } else { v8This = functionContext->Global(); } v8::TryCatch tryCatch(isolate); callback->Call(functionContext, v8This, numArgs, args); if (tryCatch.HasCaught()) { QString errorMessage(QString("Signal proxy ") + fullName() + " connection call failed: \"" + _engine->formatErrorMessageFromTryCatch(tryCatch) + "\nThis provided: " + QString::number(conn.thisValue.get()->IsObject())); v8::Local<v8::Message> exceptionMessage = tryCatch.Message(); int errorLineNumber = -1; if (!exceptionMessage.IsEmpty()) { errorLineNumber = exceptionMessage->GetLineNumber(context).FromJust(); } if (_engine->_manager) { _engine->_manager->scriptErrorMessage(errorMessage, getFileNameFromTryCatch(tryCatch, isolate, context), errorLineNumber); } else { qDebug(scriptengine_v8) << errorMessage; } _engine->setUncaughtException(tryCatch, "Error in signal proxy"); } } } } #ifdef SCRIPT_EVENT_PERFORMANCE_STATISTICS _totalCallTime_s += callTimer.elapsed() / 1000.0f;#endif return -1;} int ScriptSignalV8Proxy::discoverMetaCallIdx() { const QMetaObject* ourMeta = metaObject(); return ourMeta->methodCount();} ScriptSignalV8Proxy::ConnectionList::iterator ScriptSignalV8Proxy::findConnection(V8ScriptValue thisObject, V8ScriptValue callback) { auto iterOut = resultWithReadLock<ScriptSignalV8Proxy::ConnectionList::iterator>([&]{ v8::Locker locker(_engine->getIsolate()); v8::Isolate::Scope isolateScope(_engine->getIsolate()); v8::HandleScope handleScope(_engine->getIsolate()); v8::Context::Scope contextScope(_engine->getContext()); ConnectionList::iterator iter; for (iter = _connections.begin(); iter != _connections.end(); ++iter) { Connection& conn = *iter; if (conn.callback.constGet()->StrictEquals(callback.constGet()) && conn.thisValue.constGet()->StrictEquals(thisObject.constGet())) { break; } } return iter; }); return iterOut;} void ScriptSignalV8Proxy::connect(ScriptValue arg0, ScriptValue arg1) { v8::Isolate *isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); auto context = _engine->getContext(); v8::Context::Scope contextScope(context); QObject* qobject = _object; if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } // untangle the arguments V8ScriptValue callback(_engine, v8::Null(isolate)); V8ScriptValue callbackThis(_engine, v8::Null(isolate)); if (arg1.isFunction()) { auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); auto unwrappedArg1 = ScriptValueV8Wrapper::unwrap(arg1); if (!unwrappedArg0 || !unwrappedArg1) { Q_ASSERT(false); return; } callbackThis = unwrappedArg0->toV8Value(); callback = unwrappedArg1->toV8Value(); } else { auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); if (!unwrappedArg0) { Q_ASSERT(false); return; } callback = unwrappedArg0->toV8Value(); } if (!callback.get()->IsFunction()) { isolate->ThrowError("Function expected as argument to 'connect'"); return; } // are we already connected? { ConnectionList::iterator lookup = findConnection(callbackThis, callback); if (lookup != _connections.end()) { return; // already exists } } // add a reference to ourselves to the destination callback Q_ASSERT(!callback.get().IsEmpty()); Q_ASSERT(!callback.get()->IsUndefined()); Q_ASSERT(callback.get()->IsFunction()); v8::Local<v8::Function> destFunction = v8::Local<v8::Function>::Cast(callback.get()); v8::Local<v8::String> destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); v8::Local<v8::Value> destData; // V8TODO: I'm not sure which context to use here //auto destFunctionContext = destFunction->CreationContext(); auto destFunctionContext = context; Q_ASSERT(thisObject().isObject()); V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)); ScriptSignalV8Proxy* thisProxy = dynamic_cast<ScriptSignalV8Proxy*>(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)->toQObject()); Q_ASSERT(thisProxy); qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: " << thisProxy->fullName() << " fullName: " << fullName(); if (!destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData)) { Q_ASSERT(false); } if (destData->IsArray()) { v8::Local<v8::Array> destArray = v8::Local<v8::Array>::Cast(destData); int length = destArray->Length(); // V8TODO: Maybe copying array is unnecessary? v8::Local<v8::Array> newArray = v8::Array::New(isolate, length + 1); bool foundIt = false; for (int idx = 0; idx < length && !foundIt; ++idx) { v8::Local<v8::Value> entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); { qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: entry details: " << _engine->scriptValueDebugDetailsV8(V8ScriptValue(_engine, entry)); Q_ASSERT(entry->IsObject()); V8ScriptValue v8EntryObject(_engine, entry); Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)); // For debugging ScriptSignalV8Proxy* entryProxy = dynamic_cast<ScriptSignalV8Proxy*>(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)->toQObject()); Q_ASSERT(entryProxy); qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: entry proxy: " << entryProxy->fullName(); } if (!newArray->Set(destFunctionContext, idx, entry).FromMaybe(false)) { Q_ASSERT(false); } if (entry->StrictEquals(v8ThisObject.get())) { foundIt = true; } } if (!foundIt) { if (!newArray->Set(destFunctionContext, length, v8ThisObject.get()).FromMaybe(false)) { Q_ASSERT(false); } if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { Q_ASSERT(false); } } } else { v8::Local<v8::Array> newArray = v8::Array::New(isolate, 1); if (!newArray->Set(destFunctionContext, 0, v8ThisObject.get()).FromMaybe(false)) { Q_ASSERT(false); } if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { Q_ASSERT(false); } } // add this to our internal list of connections Connection newConnection(callbackThis, callback); withWriteLock([&]{ _connections.append(newConnection); }); // inform Qt that we're connecting to this signal if (!_isConnected) { auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); Q_ASSERT(result); _isConnected = true; }} void ScriptSignalV8Proxy::disconnect(ScriptValue arg0, ScriptValue arg1) { QObject* qobject = _object; v8::Isolate *isolate = _engine->getIsolate(); if (!_cleanup) { if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } } v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); auto context = _engine->getContext(); v8::Context::Scope contextScope(context);Lines 1001–1500 of 1613. Previous lines Load more
void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo<v8::Value>& arguments) { v8::Isolate *isolate = arguments.GetIsolate(); Q_ASSERT(isolate == _engine->getIsolate()); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); ContextScopeV8 contextScopeV8(_engine); auto context = _engine->getContext(); v8::Context::Scope contextScope(context); QObject* qobject = _object; if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } int scriptNumArgs = arguments.Length(); int numArgs = std::min(scriptNumArgs, _numMaxParams); const int scriptValueTypeId = qMetaTypeId<ScriptValue>(); int parameterConversionFailureId = 0; int parameterConversionFailureCount = 0; int num_metas = _metas.size(); QVector< QList<ScriptValue> > qScriptArgLists; QVector< QVector <QGenericArgument> > qGenArgsVectors; QVector< QList<QVariant> > qVarArgLists; qScriptArgLists.resize(num_metas); qGenArgsVectors.resize(num_metas); qVarArgLists.resize(num_metas); bool isValidMetaSelected = false; int bestMeta = 0; int bestConversionPenaltyScore = 0; for (int i = 0; i < num_metas; i++) { const QMetaMethod& meta = _metas[i]; int methodNumArgs = meta.parameterCount(); if (methodNumArgs != numArgs) { continue; } qGenArgsVectors[i].resize(10); int conversionPenaltyScore = 0; int conversionFailures = 0; for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); if (methodArgTypeId == QMetaType::UnknownType) { QString methodName = fullName(); qCDebug(scriptengine_v8) << "One of the arguments is QMetaType::UnknownType for method " << methodName; Q_ASSERT(false); } v8::Local<v8::Value> argVal = arguments[arg]; if (methodArgTypeId == scriptValueTypeId) { qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, argVal)))); qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); } else if (methodArgTypeId == QMetaType::QVariant) { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { conversionFailures++; } else { qVarArgLists[i].append(varArgVal); qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); } } else { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { conversionFailures++; } else { qVarArgLists[i].append(varArgVal); const QVariant& converted = qVarArgLists[i].back(); conversionPenaltyScore += _engine->computeCastPenalty(V8ScriptValue(_engine, argVal), methodArgTypeId); // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant // A const_cast is needed because calling data() would detach the QVariant. qGenArgsVectors[i][arg] = QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData())); } } } if (conversionFailures) { if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { parameterConversionFailureCount = conversionFailures; parameterConversionFailureId = meta.methodIndex(); } continue; } if (!isValidMetaSelected) { isValidMetaSelected = true; bestMeta = i; bestConversionPenaltyScore = conversionPenaltyScore; } if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { bestMeta = i; bestConversionPenaltyScore = conversionPenaltyScore; } } if (isValidMetaSelected) { // V8TODO: is this the correct wrapper? ScriptContextV8Wrapper ourContext(_engine, &arguments, context, _engine->currentContext()->parentContext()); ScriptContextGuard guard(&ourContext); const QMetaMethod& meta = _metas[bestMeta]; int returnTypeId = meta.returnType(); QVector <QGenericArgument> &qGenArgs = qGenArgsVectors[bestMeta]; // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's // nothing in there for return values so these need to be explicitly runtime-registered! if (returnTypeId == QMetaType::UnknownType) { QString methodName = fullName(); qCDebug(scriptengine_v8) << "returnTypeId == QMetaType::UnknownType for method " << methodName; _engine->logBacktrace(""); //Q_ASSERT(false); } if (returnTypeId == QMetaType::UnknownType) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } else if (returnTypeId == QMetaType::Void) { bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); } return; } else if (returnTypeId == scriptValueTypeId) { ScriptValue result; bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } V8ScriptValue v8Result = ScriptValueV8Wrapper::fullUnwrap(_engine, result); arguments.GetReturnValue().Set(v8Result.get()); return; } else { // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant const char* typeName = meta.typeName(); QVariant qRetVal(returnTypeId, static_cast<void*>(NULL)); QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData())); bool success = meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } V8ScriptValue v8Result = _engine->castVariantToValue(qRetVal); arguments.GetReturnValue().Set(v8Result.get()); return; } } // we failed to convert the call to C++, try to create a somewhat sane error message if (parameterConversionFailureCount == 0) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); int methodNumArgs = meta.parameterCount(); Q_ASSERT(methodNumArgs == numArgs); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); v8::Local<v8::Value> argVal = arguments[arg]; if (methodArgTypeId != scriptValueTypeId) { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); QByteArray argTypeName = _engine->valueType(V8ScriptValue(_engine, argVal)).toLatin1(); QString errorMessage = QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName); qCDebug(scriptengine_v8) << errorMessage << "\n Backtrace:" << _engine->currentContext()->backtrace(); isolate->ThrowError(v8::String::NewFromUtf8(isolate, errorMessage.toStdString().c_str()).ToLocalChecked()); return; } } } QString errorMessage = QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()); qCDebug(scriptengine_v8) << errorMessage; isolate->ThrowError(v8::String::NewFromUtf8(isolate, errorMessage.toStdString().c_str()).ToLocalChecked()); Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? return; } ScriptSignalV8Proxy::ScriptSignalV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QMetaMethod& meta) : _engine(engine), _object(object), _meta(meta), _metaCallId(discoverMetaCallIdx()) { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); auto context = _engine->getContext(); v8::Context::Scope contextScope(context); _objectLifetime.Reset(isolate, lifetime.get()); _objectLifetime.SetWeak(this, weakHandleCallback, v8::WeakCallbackType::kParameter); _v8Context.Reset(isolate, context); _engine->_signalProxySetLock.lockForWrite(); _engine->_signalProxySet.insert(this); _engine->_signalProxySetLock.unlock(); } ScriptSignalV8Proxy::~ScriptSignalV8Proxy() { if (!_cleanup) { disconnectAllScriptSignalProxies(); } auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); _objectLifetime.Reset(); _v8Context.Reset(); #ifdef OVERTE_SCRIPT_USE_AFTER_DELETE_GUARD Q_ASSERT(!_engine->_wasDestroyed); #endif _engine->_signalProxySetLock.lockForWrite(); _engine->_signalProxySet.remove(this); _engine->_signalProxySetLock.unlock(); } void ScriptSignalV8Proxy::weakHandleCallback(const v8::WeakCallbackInfo<ScriptSignalV8Proxy>& info) { auto proxy = info.GetParameter(); proxy->_objectLifetime.Reset(); proxy->deleteLater(); } QString ScriptSignalV8Proxy::fullName() const { Q_ASSERT(_object); if (!_object) return ""; QString objectName = _object->objectName(); if (!objectName.isEmpty()) { return QString("%1.%2").arg(objectName, _meta.name()); } return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name()); } // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { id = ScriptSignalV8ProxyBase::qt_metacall(call, id, arguments); if (id != 0 || call != QMetaObject::InvokeMetaMethod) { return id; } #ifdef SCRIPT_EVENT_PERFORMANCE_STATISTICS _callCounter++; if (_callCounter % 1000 == 0) { qCDebug(scriptengine_v8) << "Script engine: " << _engine->manager()->getFilename() << " Signal proxy " << fullName() << " call count: " << _callCounter << " total time: " << _totalCallTime_s; } QElapsedTimer callTimer; callTimer.start(); #endif auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); QList<Connection> connections; withReadLock([&]{ connections = _connections; }); // V8TODO: this may cause deadlocks on connect/disconnect, so the connect/disconnect procedure needs to be reworked. // It should probably add events to a separate list that would be processed before and after all the events for the signal. { v8::HandleScope handleScope(isolate); // V8TODO: should we use function creation context, or context in which connect happened? auto context = _engine->getContext(); v8::Context::Scope contextScope(context); v8::Local<v8::Value> args[Q_METAMETHOD_INVOKE_MAX_ARGS]; int numArgs = _meta.parameterCount(); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = _meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); QVariant argValue(methodArgTypeId, arguments[arg + 1]); args[arg] = _engine->castVariantToValue(argValue).get(); } for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { Connection& conn = *iter; { auto functionContext = context; Q_ASSERT(!conn.callback.get().IsEmpty()); Q_ASSERT(!conn.callback.get()->IsUndefined()); if (conn.callback.get()->IsNull()) { qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::qt_metacall: Connection callback is Null"; _engine->popContext(); continue; } if (!conn.callback.get()->IsFunction()) { auto stringV8 = conn.callback.get()->ToDetailString(functionContext).ToLocalChecked(); QString error = *v8::String::Utf8Value(_engine->getIsolate(), stringV8); qCDebug(scriptengine_v8) << error; Q_ASSERT(false); } v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(conn.callback.get()); v8::Local<v8::Value> v8This; if (conn.thisValue.get()->IsObject()) { v8This = conn.thisValue.get(); } else { v8This = functionContext->Global(); } v8::TryCatch tryCatch(isolate); callback->Call(functionContext, v8This, numArgs, args); if (tryCatch.HasCaught()) { QString errorMessage(QString("Signal proxy ") + fullName() + " connection call failed: \"" + _engine->formatErrorMessageFromTryCatch(tryCatch) + "\nThis provided: " + QString::number(conn.thisValue.get()->IsObject())); v8::Local<v8::Message> exceptionMessage = tryCatch.Message(); int errorLineNumber = -1; if (!exceptionMessage.IsEmpty()) { errorLineNumber = exceptionMessage->GetLineNumber(context).FromJust(); } if (_engine->_manager) { _engine->_manager->scriptErrorMessage(errorMessage, getFileNameFromTryCatch(tryCatch, isolate, context), errorLineNumber); } else { qDebug(scriptengine_v8) << errorMessage; } _engine->setUncaughtException(tryCatch, "Error in signal proxy"); } } } } #ifdef SCRIPT_EVENT_PERFORMANCE_STATISTICS _totalCallTime_s += callTimer.elapsed() / 1000.0f; #endif return -1; } int ScriptSignalV8Proxy::discoverMetaCallIdx() { const QMetaObject* ourMeta = metaObject(); return ourMeta->methodCount(); } ScriptSignalV8Proxy::ConnectionList::iterator ScriptSignalV8Proxy::findConnection(V8ScriptValue thisObject, V8ScriptValue callback) { auto iterOut = resultWithReadLock<ScriptSignalV8Proxy::ConnectionList::iterator>([&]{ v8::Locker locker(_engine->getIsolate()); v8::Isolate::Scope isolateScope(_engine->getIsolate()); v8::HandleScope handleScope(_engine->getIsolate()); v8::Context::Scope contextScope(_engine->getContext()); ConnectionList::iterator iter; for (iter = _connections.begin(); iter != _connections.end(); ++iter) { Connection& conn = *iter; if (conn.callback.constGet()->StrictEquals(callback.constGet()) && conn.thisValue.constGet()->StrictEquals(thisObject.constGet())) { break; } } return iter; }); return iterOut; } void ScriptSignalV8Proxy::connect(ScriptValue arg0, ScriptValue arg1) { v8::Isolate *isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); auto context = _engine->getContext(); v8::Context::Scope contextScope(context); QObject* qobject = _object; if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } // untangle the arguments V8ScriptValue callback(_engine, v8::Null(isolate)); V8ScriptValue callbackThis(_engine, v8::Null(isolate)); if (arg1.isFunction()) { auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); auto unwrappedArg1 = ScriptValueV8Wrapper::unwrap(arg1); if (!unwrappedArg0 || !unwrappedArg1) { Q_ASSERT(false); return; } callbackThis = unwrappedArg0->toV8Value(); callback = unwrappedArg1->toV8Value(); } else { auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); if (!unwrappedArg0) { Q_ASSERT(false); return; } callback = unwrappedArg0->toV8Value(); } if (!callback.get()->IsFunction()) { isolate->ThrowError("Function expected as argument to 'connect'"); return; } // are we already connected? { ConnectionList::iterator lookup = findConnection(callbackThis, callback); if (lookup != _connections.end()) { return; // already exists } } // add a reference to ourselves to the destination callback Q_ASSERT(!callback.get().IsEmpty()); Q_ASSERT(!callback.get()->IsUndefined()); Q_ASSERT(callback.get()->IsFunction()); v8::Local<v8::Function> destFunction = v8::Local<v8::Function>::Cast(callback.get()); v8::Local<v8::String> destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); v8::Local<v8::Value> destData; // V8TODO: I'm not sure which context to use here //auto destFunctionContext = destFunction->CreationContext(); auto destFunctionContext = context; Q_ASSERT(thisObject().isObject()); V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)); ScriptSignalV8Proxy* thisProxy = dynamic_cast<ScriptSignalV8Proxy*>(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)->toQObject()); Q_ASSERT(thisProxy); qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: " << thisProxy->fullName() << " fullName: " << fullName(); if (!destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData)) { Q_ASSERT(false); } if (destData->IsArray()) { v8::Local<v8::Array> destArray = v8::Local<v8::Array>::Cast(destData); int length = destArray->Length(); // V8TODO: Maybe copying array is unnecessary? v8::Local<v8::Array> newArray = v8::Array::New(isolate, length + 1); bool foundIt = false; for (int idx = 0; idx < length && !foundIt; ++idx) { v8::Local<v8::Value> entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); { qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: entry details: " << _engine->scriptValueDebugDetailsV8(V8ScriptValue(_engine, entry)); Q_ASSERT(entry->IsObject()); V8ScriptValue v8EntryObject(_engine, entry); Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)); // For debugging ScriptSignalV8Proxy* entryProxy = dynamic_cast<ScriptSignalV8Proxy*>(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)->toQObject()); Q_ASSERT(entryProxy); qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: entry proxy: " << entryProxy->fullName(); } if (!newArray->Set(destFunctionContext, idx, entry).FromMaybe(false)) { Q_ASSERT(false); } if (entry->StrictEquals(v8ThisObject.get())) { foundIt = true; } } if (!foundIt) { if (!newArray->Set(destFunctionContext, length, v8ThisObject.get()).FromMaybe(false)) { Q_ASSERT(false); } if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { Q_ASSERT(false); } } } else { v8::Local<v8::Array> newArray = v8::Array::New(isolate, 1); if (!newArray->Set(destFunctionContext, 0, v8ThisObject.get()).FromMaybe(false)) { Q_ASSERT(false); } if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { Q_ASSERT(false); } } // add this to our internal list of connections Connection newConnection(callbackThis, callback); withWriteLock([&]{ _connections.append(newConnection); }); // inform Qt that we're connecting to this signal if (!_isConnected) { auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); Q_ASSERT(result); _isConnected = true; } } void ScriptSignalV8Proxy::disconnect(ScriptValue arg0, ScriptValue arg1) { QObject* qobject = _object; v8::Isolate *isolate = _engine->getIsolate(); if (!_cleanup) { if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } } v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); auto context = _engine->getContext(); v8::Context::Scope contextScope(context);