Julian GroßFix copyright headersae7d2293 years agoHistory
1550 lines · 73.5 KB · C++
RawDownload
////  ScriptObjectV8Proxy.cpp//  libraries/script-engine/src/v8////  Created by Heather Anderson on 12/5/21.//  Modified for V8 by dr Karol Suprynowicz on 2022/10/08//  Copyright 2021 Vircadia contributors.//  Copyright 2022-2023 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//  SPDX-License-Identifier: Apache-2.0// #include "ScriptObjectV8Proxy.h" #include <QElapsedTimer>#include <QtCore/QList>#include <QtCore/QSharedPointer> #include "../ScriptEngineLogging.h" #include "ScriptContextV8Wrapper.h"#include "ScriptValueV8Wrapper.h" //V8TODO: is this needed for anything? It could cause trouble with multithreading if V8ScriptContext is v8::Persistent//Q_DECLARE_METATYPE(V8ScriptContext*) Q_DECLARE_METATYPE(ScriptValue)//V8TODO: default constructor would be needed//Q_DECLARE_METATYPE(V8ScriptValue) Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>)Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>) // Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1static const void *internalPointsToQObjectProxy = (void *)0x13370000;static const void *internalPointsToQVariantProxy = (void *)0x13371000;//static const void *internalPointsToSignalProxy = (void *)0x13372000;static const void *internalPointsToMethodProxy = (void *)0x13373000; // 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 ScriptPropertyContextV8Wrapper final : public ScriptContext {public:  // construction    inline ScriptPropertyContextV8Wrapper(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;}; ScriptObjectV8Proxy::ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options) :    _engine(engine), _wrapOptions(options), _ownsObject(ownsObject), _object(object) {    //_v8ObjectTemplate(engine->getIsolate(), v8::ObjectTemplate::New(engine->getIsolate())    investigate();} V8ScriptValue ScriptObjectV8Proxy::newQObject(ScriptEngineV8* engine, QObject* object,                                             ScriptEngine::ValueOwnership ownership,                                             const ScriptEngine::QObjectWrapOptions& options) {    // do we already have a valid wrapper for this QObject?    {        QMutexLocker guard(&engine->_qobjectWrapperMapProtect);        ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);        if (lookup != engine->_qobjectWrapperMap.end()) {            QSharedPointer<ScriptObjectV8Proxy> proxy = lookup.value().lock();            // V8TODO: is conversion to QVariant and back needed?            if (proxy) {                return V8ScriptValue(engine, proxy.get()->toV8Value());            }            //if (proxy) return engine->newObject(proxy.get(), engine->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<ScriptObjectV8Proxy>::create(engine, object, ownsObject, options);     {        QMutexLocker guard(&engine->_qobjectWrapperMapProtect);         // check again to see if someone else created the wrapper while we were busy        ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);        if (lookup != engine->_qobjectWrapperMap.end()) {            QSharedPointer<ScriptObjectV8Proxy> proxy = lookup.value().lock();            //if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));;            if (proxy) {                return V8ScriptValue(engine, proxy.get()->toV8Value());            }        }        // V8TODO add a V8 callback that removes pointer for the script engine owned ob from the map so that it gets deleted        // register the wrapper with the engine and make sure it cleans itself up        engine->_qobjectWrapperMap.insert(object, proxy);        engine->_qobjectWrapperMapV8.insert(object, proxy);        QPointer<ScriptEngineV8> enginePtr = engine;        object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() {            if (!enginePtr) return;            QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect);            ScriptEngineV8::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object);            if (lookup != enginePtr->_qobjectWrapperMap.end()) {                enginePtr->_qobjectWrapperMap.erase(lookup);            }            auto lookupV8 = enginePtr->_qobjectWrapperMapV8.find(object);            if (lookupV8 != enginePtr->_qobjectWrapperMapV8.end()) {                enginePtr->_qobjectWrapperMapV8.erase(lookupV8);            }        });    }     return V8ScriptValue(engine, proxy.get()->toV8Value());    //return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));} ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val) {    auto isolate = val.getEngine()->getIsolate();    v8::Locker locker(isolate);    v8::Isolate::Scope isolateScope(isolate);    v8::HandleScope handleScope(isolate);    v8::Local<v8::Context> context = val.constGetContext();    v8::Context::Scope contextScope(context);    //V8TODO This shouldn't cause problems but I'm not sure if it's ok    //v8::HandleScope handleScope(const_cast<v8::Isolate*>(val.constGetIsolate()));    auto v8Value = val.constGet();    if (v8Value->IsNullOrUndefined()) {        return nullptr;    }    if (!v8Value->IsObject()) {        //qDebug(scriptengine) << "Cannot unwrap proxy - value is not an object";        return nullptr;    }    v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(v8Value);    if (v8Object->InternalFieldCount() != 3) {        //qDebug(scriptengine) << "Cannot unwrap proxy - wrong number of internal fields";        return nullptr;    }    if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) {        qDebug(scriptengine) << "Cannot unwrap proxy - internal fields don't point to object proxy";        return nullptr;    }    return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));} ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(v8::Isolate* isolate, v8::Local<v8::Value> &value) {    v8::Locker locker(isolate);    v8::Isolate::Scope isolateScope(isolate);    v8::HandleScope handleScope(isolate);    //V8TODO: shouldn't there be context scope here?    //v8::Local<v8::Context> context = val.constGetContext();    //v8::Context::Scope contextScope(context);    if (value->IsNullOrUndefined()) {        //qDebug(scriptengine) << "Cannot unwrap proxy - value is not an object";        return nullptr;    }    if (!value->IsObject()) {        //qDebug(scriptengine) << "Cannot unwrap proxy - value is not an object";        return nullptr;    }    v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(value);    if (v8Object->InternalFieldCount() != 3) {        //qDebug(scriptengine) << "Cannot unwrap proxy - wrong number of internal fields";        return nullptr;    }    if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) {        qDebug(scriptengine) << "Cannot unwrap proxy - internal fields don't point to object proxy";        return nullptr;    }    return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));} QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) {    ScriptObjectV8Proxy* proxy = unwrapProxy(val);    return proxy ? proxy->toQObject() : nullptr;} ScriptObjectV8Proxy::~ScriptObjectV8Proxy() {    if(_object) qDebug(scriptengine) << "Deleting object proxy: " << name();    if (_ownsObject) {        QObject* qobject = _object;        if(qobject) qobject->deleteLater();    }} void ScriptObjectV8Proxy::investigate() {    QObject* qobject = _object;    if (!qobject) {        QStringList backtrace = _engine->currentContext()->backtrace();        qDebug(scriptengine) << "ScriptObjectV8Proxy::investigate: Object pointer is NULL, " << backtrace;    }    if (!qobject) return;     auto isolate = _engine->getIsolate();    v8::Locker locker(isolate);    v8::Isolate::Scope isolateScope(isolate);    v8::HandleScope handleScope(_engine->getIsolate());    v8::Context::Scope contextScope(_engine->getContext());        //auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate());    auto objectTemplate = v8::ObjectTemplate::New(_engine->getIsolate());    objectTemplate->SetInternalFieldCount(3);    objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set, nullptr, nullptr, v8GetPropertyNames));        const QMetaObject* metaObject = qobject->metaObject();     //qDebug(scriptengine) << "Investigate: " << metaObject->className();    if (QString("ConsoleScriptingInterface") == metaObject->className()) {        printf("ConsoleScriptingInterface");    }    // 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;         //qDebug(scriptengine) << "Investigate: " << metaObject->className() << " Property: " << prop.name();        // 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;            }        }         auto v8Name = v8::String::NewFromUtf8(_engine->getIsolate(), prop.name()).ToLocalChecked();        PropertyDef& propDef = _props.insert(idx, PropertyDef(_engine, v8Name)).value();        propDef.flags = ScriptValue::Undeletable | ScriptValue::PropertyGetter | ScriptValue::PropertySetter |                        ScriptValue::QObjectMember;        if (prop.isConstant()) propDef.flags |= ScriptValue::ReadOnly;    }     // discover methods    startIdx = (_wrapOptions & ScriptEngine::ExcludeSuperClassMethods) ? metaObject->methodOffset() : 0;    num = metaObject->methodCount();    QHash<V8ScriptString, int> methodNames;    for (int idx = startIdx; idx < num; ++idx) {        QMetaMethod method = metaObject->method(idx);        //qDebug(scriptengine) << "Investigate: " << metaObject->className() << " Method: " << method.name();                // 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;        }         auto nameString = v8::String::NewFromUtf8(_engine->getIsolate(), szName.data(), v8::NewStringType::kNormal, szName.length()).ToLocalChecked();        V8ScriptString name(_engine, nameString);        auto nameLookup = methodNames.find(name);        if (isSignal) {            if (nameLookup == methodNames.end()) {                SignalDef& signalDef = _signals.insert(idx, SignalDef(_engine, name.get())).value();                signalDef.name = name;                signalDef.signal = method;                //qDebug(scriptengine) << "Utf8Value 1: " << QString(*v8::String::Utf8Value(const_cast<v8::Isolate*>(_engine->getIsolate()), nameString));                //qDebug(scriptengine) << "Utf8Value 2: " << QString(*v8::String::Utf8Value(const_cast<v8::Isolate*>(_engine->getIsolate()), name.constGet()));                //qDebug(scriptengine) << "toQString: " << name.toQString();                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.toQString() << " 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.toQString() << " is of type QMetaType::UnknownType";                }            }            if (nameLookup == methodNames.end()) {                MethodDef& methodDef = _methods.insert(idx, MethodDef(_engine, name.get())).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);            }        }    }     v8::Local<v8::Object> v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked();     v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy));    v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this));    // Properties added later will be stored in this object    v8::Local<v8::Object> propertiesObject = v8::Object::New(_engine->getIsolate());    v8Object->SetInternalField(2, propertiesObject);    _v8Object.Reset(_engine->getIsolate(), v8Object);     // Add all the methods objects as properties - this allows adding properties to a given method later. Is used by Script.request.    // V8TODO: Should these be deleted when the script-owned object is destroyed? It needs checking if script-owned objects will be garbage-collected, or will self-referencing prevent it.    for (auto i = _methods.begin(); i != _methods.end(); i++) {        V8ScriptValue method = ScriptMethodV8Proxy::newMethod(_engine, qobject, V8ScriptValue(_engine, v8Object),                                                              i.value().methods, i.value().numMaxParms);        if(!propertiesObject->Set(_engine->getContext(), i.value().name.constGet(), method.get()).FromMaybe(false)) {            Q_ASSERT(false);        }    } } QString ScriptObjectV8Proxy::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();} ScriptObjectV8Proxy::QueryFlags ScriptObjectV8Proxy::queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id) {    auto isolate = _engine->getIsolate();    v8::Locker locker(isolate);    v8::Isolate::Scope isolateScope(isolate);    v8::HandleScope handleScope(isolate);    // V8TODO: this might be inefficient when there's large number of properties    v8::Local<v8::Context> context = _engine->getContext();    v8::Context::Scope contextScope(context);    v8::String::Utf8Value nameStr(isolate, name.constGet());     // check for methods    for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) {        v8::String::Utf8Value methodNameStr(isolate, trans.value().name.constGet());        //qDebug(scriptengine) << "queryProperty : " << *nameStr << " method: " << *methodNameStr;        if (!(trans.value().name == name)) continue;        *id = trans.key() | METHOD_TYPE;        return flags & (HandlesReadAccess | HandlesWriteAccess);    }     // 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 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();} ScriptValue::PropertyFlags ScriptObjectV8Proxy::propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) {    QObject* qobject = _object;    if (!qobject) {        return ScriptValue::PropertyFlags();    }     switch (id & TYPE_MASK) {        case PROPERTY_TYPE: {            PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK);            if (lookup == _props.cend()) return ScriptValue::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 ScriptValue::PropertyFlags();            return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember;        }        case SIGNAL_TYPE: {            SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK);            if (lookup == _signals.cend()) return ScriptValue::PropertyFlags();            return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember;        }    }    return ScriptValue::PropertyFlags();} void ScriptObjectV8Proxy::v8Get(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {    v8::HandleScope handleScope(info.GetIsolate());    //V8TODO: should there be a context scope here?    v8::String::Utf8Value utf8Value(info.GetIsolate(), name);    //qDebug(scriptengine) << "Get: " << *utf8Value;    v8::Local<v8::Value> objectV8 = info.This();    ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8);    if (!proxy) {        qDebug(scriptengine) << "Proxy object not found when getting: " << *utf8Value;        return;    }    V8ScriptValue object(proxy->_engine, objectV8);    Q_ASSERT(name->IsString());    V8ScriptString nameString(proxy->_engine, v8::Local<v8::String>::Cast(name));    uint id;    QueryFlags flags = proxy->queryProperty(object, nameString, HandlesReadAccess, &id);    if (flags) {        V8ScriptValue value = proxy->property(object, nameString, id);        info.GetReturnValue().Set(value.get());    } else {        v8::Local<v8::Value> property;        if(info.This()->GetInternalField(2).As<v8::Object>()->Get(proxy->_engine->getContext(), name).ToLocal(&property)) {            info.GetReturnValue().Set(property);        } else {            qDebug(scriptengine) << "Value not found: " << *utf8Value;        }    }} void ScriptObjectV8Proxy::v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info) {    v8::HandleScope handleScope(info.GetIsolate());    //V8TODO: should there be a context scope here?    v8::String::Utf8Value utf8Value(info.GetIsolate(), name);    //qDebug(scriptengine) << "Set: " << *utf8Value;    v8::Local<v8::Value> objectV8 = info.This();    ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8);    if (!proxy) {        qDebug(scriptengine) << "Proxy object not found when setting: " << *utf8Value;        return;    }    V8ScriptValue object(proxy->_engine, objectV8);    Q_ASSERT(name->IsString());    V8ScriptString nameString(proxy->_engine, v8::Local<v8::String>::Cast(name));    //V8ScriptString nameString(info.GetIsolate(), name->ToString(proxy->_engine->getContext()).ToLocalChecked());    uint id;    QueryFlags flags = proxy->queryProperty(object, nameString, HandlesWriteAccess, &id);    if (flags) {        proxy->setProperty(object, nameString, id, V8ScriptValue(proxy->_engine, value));        info.GetReturnValue().Set(value);    } else {        // V8TODO: Should it be v8::Object or v8::Local<v8::Object>?        if (info.This()->GetInternalField(2).As<v8::Object>()->Set(proxy->_engine->getContext(), name, value).FromMaybe(false)) {            info.GetReturnValue().Set(value);        } else {            qDebug(scriptengine) << "Set failed: " << *utf8Value;        }    }} void ScriptObjectV8Proxy::v8GetPropertyNames(const v8::PropertyCallbackInfo<v8::Array>& info) {    qDebug(scriptengine) << "ScriptObjectV8Proxy::v8GetPropertyNames called";    v8::HandleScope handleScope(info.GetIsolate());    auto context = info.GetIsolate()->GetCurrentContext();    v8::Context::Scope contextScope(context);

Lines 1–500 of 1550.