1613 lines · 71.6 KB · C++
RawDownload
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