ksuprynowiczFixes for code review586bd7f4 years agoHistory
802 lines · 33.6 KB · C++
RawDownload
////  ScriptObjectQtProxy.cpp//  libraries/script-engine/src/qtscript////  Created by Heather Anderson on 12/5/21.//  Copyright 2021 Vircadia contributors.//  Copyright 2022 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "ScriptObjectQtProxy.h" #include <QtCore/QList>#include <QtCore/QSharedPointer> #include <QtScript/QScriptContext> #include "../ScriptEngineLogging.h" #include "ScriptContextQtWrapper.h"#include "ScriptValueQtWrapper.h" Q_DECLARE_METATYPE(QScriptContext*)Q_DECLARE_METATYPE(ScriptValue)Q_DECLARE_METATYPE(QScriptValue) Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectQtProxy>)Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantQtProxy>) // Used strictly to replace the "this" object value for property access.  May expand to a full context element// if we find it necessary to, but hopefully not neededclass ScriptPropertyContextQtWrapper final : public ScriptContext {public:  // construction    inline ScriptPropertyContextQtWrapper(const ScriptValue& object, ScriptContext* parentContext) :        _parent(parentContext), _object(object) {} public:  // ScriptContext implementation    virtual int argumentCount() const override { return _parent->argumentCount(); }    virtual ScriptValue argument(int index) const override { return _parent->argument(index); }    virtual QStringList backtrace() const override { return _parent->backtrace(); }    virtual ScriptValue callee() const override { return _parent->callee(); }    virtual ScriptEnginePointer engine() const override { return _parent->engine(); }    virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); }    virtual ScriptContextPointer parentContext() const override { return _parent->parentContext(); }    virtual ScriptValue thisObject() const override { return _object; }    virtual ScriptValue throwError(const QString& text) override { return _parent->throwError(text); }    virtual ScriptValue throwValue(const ScriptValue& value) override { return _parent->throwValue(value); } private:  // storage    ScriptContext* _parent;    const ScriptValue& _object;}; QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObject* object,                                             ScriptEngine::ValueOwnership ownership,                                             const ScriptEngine::QObjectWrapOptions& options) {    QScriptEngine* qengine = static_cast<QScriptEngine*>(engine);     // do we already have a valid wrapper for this QObject?    {        QMutexLocker guard(&engine->_qobjectWrapperMapProtect);        ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);        if (lookup != engine->_qobjectWrapperMap.end()) {            QSharedPointer<ScriptObjectQtProxy> proxy = lookup.value().lock();            if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));;        }    }        bool ownsObject;    switch (ownership) {        case ScriptEngine::QtOwnership:            ownsObject = false;            break;        case ScriptEngine::ScriptOwnership:            ownsObject = true;            break;        case ScriptEngine::AutoOwnership:            ownsObject = !object->parent();            break;        default:            ownsObject = false;            qCritical() << "Wrong ScriptEngine::ValueOwnership value: " << ownership;            break;    }     // create the wrapper    auto proxy = QSharedPointer<ScriptObjectQtProxy>::create(engine, object, ownsObject, options);     {        QMutexLocker guard(&engine->_qobjectWrapperMapProtect);         // check again to see if someone else created the wrapper while we were busy        ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);        if (lookup != engine->_qobjectWrapperMap.end()) {            QSharedPointer<ScriptObjectQtProxy> proxy = lookup.value().lock();            if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));;        }         // register the wrapper with the engine and make sure it cleans itself up        engine->_qobjectWrapperMap.insert(object, proxy);        QPointer<ScriptEngineQtScript> enginePtr = engine;        object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() {            if (!enginePtr) return;            QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect);            ScriptEngineQtScript::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object);            if (lookup != enginePtr->_qobjectWrapperMap.end()) {                enginePtr->_qobjectWrapperMap.erase(lookup);            }        });    }     return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));} ScriptObjectQtProxy* ScriptObjectQtProxy::unwrapProxy(const QScriptValue& val) {    QScriptClass* scriptClass = val.scriptClass();    return scriptClass ? dynamic_cast<ScriptObjectQtProxy*>(scriptClass) : nullptr;} QObject* ScriptObjectQtProxy::unwrap(const QScriptValue& val) {    if (val.isQObject()) {        return val.toQObject();    }    ScriptObjectQtProxy* proxy = unwrapProxy(val);    return proxy ? proxy->toQtValue() : nullptr;} ScriptObjectQtProxy::~ScriptObjectQtProxy() {    if (_ownsObject) {        QObject* qobject = _object;        if(qobject) qobject->deleteLater();    }} void ScriptObjectQtProxy::investigate() {    QObject* qobject = _object;    Q_ASSERT(qobject);    if (!qobject) return;     const QMetaObject* metaObject = qobject->metaObject();     // discover properties    int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0;    int num = metaObject->propertyCount();    for (int idx = startIdx; idx < num; ++idx) {        QMetaProperty prop = metaObject->property(idx);        if (!prop.isScriptable()) continue;         // always exclude child objects (at least until we decide otherwise)        int metaTypeId = prop.userType();        if (metaTypeId != QMetaType::UnknownType) {            QMetaType metaType(metaTypeId);            if (metaType.flags() & QMetaType::PointerToQObject) {                continue;            }        }         PropertyDef& propDef = _props.insert(idx, PropertyDef()).value();        propDef.name = _engine->toStringHandle(QString::fromLatin1(prop.name()));        propDef.flags = QScriptValue::Undeletable | QScriptValue::PropertyGetter | QScriptValue::PropertySetter |                        QScriptValue::QObjectMember;        if (prop.isConstant()) propDef.flags |= QScriptValue::ReadOnly;    }     // discover methods    startIdx = (_wrapOptions & ScriptEngine::ExcludeSuperClassMethods) ? metaObject->methodOffset() : 0;    num = metaObject->methodCount();    QHash<QScriptString, int> methodNames;    for (int idx = startIdx; idx < num; ++idx) {        QMetaMethod method = metaObject->method(idx);                // perhaps keep this comment?  Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible        /* if (method.access() != QMetaMethod::Public) continue;*/         bool isSignal = false;        QByteArray szName = method.name();         switch (method.methodType()) {            case QMetaMethod::Constructor:                continue;            case QMetaMethod::Signal:                isSignal = true;                break;            case QMetaMethod::Slot:                if (_wrapOptions & ScriptEngine::ExcludeSlots) {                    continue;                }                if (szName == "deleteLater") {                    continue;                }                break;            default:                break;        }         QScriptString name = _engine->toStringHandle(QString::fromLatin1(szName));        auto nameLookup = methodNames.find(name);        if (isSignal) {            if (nameLookup == methodNames.end()) {                SignalDef& signalDef = _signals.insert(idx, SignalDef()).value();                signalDef.name = name;                signalDef.signal = method;                methodNames.insert(name, idx);            } else {                int originalMethodId = nameLookup.value();                SignalDefMap::iterator signalLookup = _signals.find(originalMethodId);                Q_ASSERT(signalLookup != _signals.end());                SignalDef& signalDef = signalLookup.value();                Q_ASSERT(signalDef.signal.parameterCount() != method.parameterCount());                if (signalDef.signal.parameterCount() < method.parameterCount()) {                    signalDef.signal = method;                }            }        } else {            int parameterCount = method.parameterCount();            if(method.returnType() == QMetaType::UnknownType) {                qCritical(scriptengine) << "Method " << metaObject->className() << "::" << name << " has QMetaType::UnknownType return value";            }            for (int i = 0; i < method.parameterCount(); i++) {                if (method.parameterType(i) == QMetaType::UnknownType) {                    qCritical(scriptengine) << "Parameter " << i << "in method " << metaObject->className() << "::" << name << " is of type QMetaType::UnknownType";                }            }            if (nameLookup == methodNames.end()) {                MethodDef& methodDef = _methods.insert(idx, MethodDef()).value();                methodDef.name = name;                methodDef.numMaxParms = parameterCount;                methodDef.methods.append(method);                methodNames.insert(name, idx);            } else {                int originalMethodId = nameLookup.value();                MethodDefMap::iterator methodLookup = _methods.find(originalMethodId);                Q_ASSERT(methodLookup != _methods.end());                MethodDef& methodDef = methodLookup.value();                if(methodDef.numMaxParms < parameterCount) methodDef.numMaxParms = parameterCount;                methodDef.methods.append(method);            }        }    }} QString ScriptObjectQtProxy::name() const {    Q_ASSERT(_object);    if (!_object) return "";    return _object ? _object->objectName() : "";    QString objectName = _object->objectName();    if (!objectName.isEmpty()) return objectName;    return _object->metaObject()->className();} QScriptClass::QueryFlags ScriptObjectQtProxy::queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) {    // check for properties    for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) {        const PropertyDef& propDef = trans.value();        if (propDef.name != name) continue;        *id = trans.key() | PROPERTY_TYPE;        return flags & (HandlesReadAccess | HandlesWriteAccess);    }     // check for methods    for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) {        if (trans.value().name != name) continue;        *id = trans.key() | METHOD_TYPE;        return flags & (HandlesReadAccess | HandlesWriteAccess);    }     // check for signals    for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) {        if (trans.value().name != name) continue;        *id = trans.key() | SIGNAL_TYPE;        return flags & (HandlesReadAccess | HandlesWriteAccess);    }     return QueryFlags();} QScriptValue::PropertyFlags ScriptObjectQtProxy::propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) {    QObject* qobject = _object;    if (!qobject) {        return QScriptValue::PropertyFlags();    }     switch (id & TYPE_MASK) {        case PROPERTY_TYPE: {            PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK);            if (lookup == _props.cend()) return QScriptValue::PropertyFlags();            const PropertyDef& propDef = lookup.value();            return propDef.flags;        }        case METHOD_TYPE: {            MethodDefMap::const_iterator lookup = _methods.find(id & ~TYPE_MASK);            if (lookup == _methods.cend()) return QScriptValue::PropertyFlags();            return QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::QObjectMember;        }        case SIGNAL_TYPE: {            SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK);            if (lookup == _signals.cend()) return QScriptValue::PropertyFlags();            return QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::QObjectMember;        }    }    return QScriptValue::PropertyFlags();} QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QScriptString& name, uint id) {    QObject* qobject = _object;    if (!qobject) {        QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext();        currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object");        return QScriptValue();    }     const QMetaObject* metaObject = qobject->metaObject();     switch (id & TYPE_MASK) {        case PROPERTY_TYPE: {            int propId = id & ~TYPE_MASK;            PropertyDefMap::const_iterator lookup = _props.find(propId);            if (lookup == _props.cend()) return QScriptValue();             QMetaProperty prop = metaObject->property(propId);            ScriptValue scriptThis = ScriptValue(new ScriptValueQtWrapper(_engine, object));            ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext());            ScriptContextGuard guard(&ourContext);             QVariant varValue = prop.read(qobject);            return _engine->castVariantToValue(varValue);        }        case METHOD_TYPE: {            int methodId = id & ~TYPE_MASK;            MethodDefMap::const_iterator lookup = _methods.find(methodId);            if (lookup == _methods.cend()) return QScriptValue();            const MethodDef& methodDef = lookup.value();            for (auto iter = methodDef.methods.begin(); iter != methodDef.methods.end(); iter++ ) {                if((*iter).returnType() == QMetaType::UnknownType) {                    qDebug(scriptengine) << "Method with QMetaType::UnknownType " << metaObject->className() << " " << (*iter).name();                }            }            return static_cast<QScriptEngine*>(_engine)->newObject(                new ScriptMethodQtProxy(_engine, qobject, object, methodDef.methods, methodDef.numMaxParms));        }        case SIGNAL_TYPE: {            int signalId = id & ~TYPE_MASK;            SignalDefMap::const_iterator defLookup = _signals.find(signalId);            if (defLookup == _signals.cend()) return QScriptValue();             InstanceMap::const_iterator instLookup = _signalInstances.find(signalId);            if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) {                instLookup = _signalInstances.insert(signalId,                    new ScriptSignalQtProxy(_engine, qobject, object, defLookup.value().signal));                Q_ASSERT(instLookup != _signalInstances.cend());            }            ScriptSignalQtProxy* proxy = instLookup.value();             QScriptEngine::QObjectWrapOptions options = QScriptEngine::ExcludeSuperClassContents |                                                        QScriptEngine::ExcludeDeleteLater |                                                        QScriptEngine::PreferExistingWrapperObject;            return static_cast<QScriptEngine*>(_engine)->newQObject(proxy, QScriptEngine::ScriptOwnership, options);        }    }    return QScriptValue();} void ScriptObjectQtProxy::setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) {    if (!(id & PROPERTY_TYPE)) return;    QObject* qobject = _object;    if (!qobject) {        QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext();        currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object");        return;    }     int propId = id & ~TYPE_MASK;    PropertyDefMap::const_iterator lookup = _props.find(propId);    if (lookup == _props.cend()) return;    const PropertyDef& propDef = lookup.value();    if (propDef.flags & QScriptValue::ReadOnly) return;     const QMetaObject* metaObject = qobject->metaObject();    QMetaProperty prop = metaObject->property(propId);     ScriptValue scriptThis = ScriptValue(new ScriptValueQtWrapper(_engine, object));    ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext());    ScriptContextGuard guard(&ourContext);     int propTypeId = prop.userType();    Q_ASSERT(propTypeId != QMetaType::UnknownType);    QVariant varValue;    if(!_engine->castValueToVariant(value, varValue, propTypeId)) {        QByteArray propTypeName = QMetaType(propTypeId).name();        QByteArray valTypeName = _engine->valueType(value).toLatin1();        QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext();        currentContext->throwError(QScriptContext::TypeError, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName));        return;    }    prop.write(qobject, varValue);} ScriptVariantQtProxy::ScriptVariantQtProxy(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue scriptProto, ScriptObjectQtProxy* proto) :    QScriptClass(engine), _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) {    _name = QString::fromLatin1(variant.typeName());} QScriptValue ScriptVariantQtProxy::newVariant(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue proto) {    QScriptEngine* qengine = static_cast<QScriptEngine*>(engine);    ScriptObjectQtProxy* protoProxy = ScriptObjectQtProxy::unwrapProxy(proto);    if (!protoProxy) {        Q_ASSERT(protoProxy);        return qengine->newVariant(variant);    }    auto proxy = QSharedPointer<ScriptVariantQtProxy>::create(engine, variant, proto, protoProxy);    return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));} ScriptVariantQtProxy* ScriptVariantQtProxy::unwrapProxy(const QScriptValue& val) {    QScriptClass* scriptClass = val.scriptClass();    return scriptClass ? dynamic_cast<ScriptVariantQtProxy*>(scriptClass) : nullptr;} QVariant ScriptVariantQtProxy::unwrap(const QScriptValue& val) {    ScriptVariantQtProxy* proxy = unwrapProxy(val);    return proxy ? proxy->toQtValue() : QVariant();} QString ScriptMethodQtProxy::fullName() const {    Q_ASSERT(_object);    if (!_object) return "";    Q_ASSERT(!_metas.isEmpty());    const QMetaMethod& firstMethod = _metas.front();    QString objectName = _object->objectName();    if (!objectName.isEmpty()) {        return QString("%1.%2").arg(objectName, firstMethod.name());    }    return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name());} bool ScriptMethodQtProxy::supportsExtension(Extension extension) const {    switch (extension) {        case Callable:            return true;        default:            return false;    }} QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& argument) {    if (extension != Callable) return QVariant();    QScriptContext* context = qvariant_cast<QScriptContext*>(argument);     QObject* qobject = _object;    if (!qobject) {        context->throwError(QScriptContext::ReferenceError, "Referencing deleted native object");        return QVariant();    }     int scriptNumArgs = context->argumentCount();    int numArgs = std::min(scriptNumArgs, _numMaxParms);        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);            Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);            QScriptValue argVal = context->argument(arg);            if (methodArgTypeId == scriptValueTypeId) {                qScriptArgLists[i].append(ScriptValue(new ScriptValueQtWrapper(_engine, argVal)));                qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back());            } else if (methodArgTypeId == QMetaType::QVariant) {                qVarArgLists[i].append(argVal.toVariant());                qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back());            } else {                QVariant varArgVal;                if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) {                    conversionFailures++;

Lines 1–500 of 802.