1613 lines · 71.6 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"#include "ScriptEngineLoggingV8.h" Q_DECLARE_METATYPE(ScriptValue) Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>)Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>)  // These values are put into internal fields of V8 objects, to signalize what kind of data is the pointer in another// internal field pointing to. Proxy unwrapping functions recognize proxies by checking for these values in internal field 0// of V8 object. // Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1static const void *internalPointsToQObjectProxy = (void *)0x13370000;// Internal field value of object pointing to ScriptObjectV8Proxy is changed to this value upon proxy's deletionstatic const void *internalPointsToDeletedQObjectProxy = (void *)0x13370010;static const void *internalPointsToQVariantProxy = (void *)0x13371000;//static const void *internalPointsToSignalProxy = (void *)0x13372000;static const void *internalPointsToMethodProxy = (void *)0x13373000;// This is used to pass object in ScriptVariantV8Proxy to methods of prototype object, for example passing AnimationPointer to AnimationObject// Object is then converted using scriptvalue_cast for use inside the prototypestatic const void *internalPointsToQVariantInProxy = (void *)0x13374000; // 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(); }     // Name of the file in which message was generated. Empty string when no file name is available.    virtual int currentLineNumber() const override { return _parent->currentLineNumber(); }     // Number of the line on which message was generated. -1 if there line number is not available.    virtual QString currentFileName() const override { return _parent->currentFileName(); }     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) {    Q_ASSERT(_engine != nullptr);    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();            if (proxy) {                return V8ScriptValue(engine, proxy.get()->toV8Value());            }        }    }     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;            qCCritical(scriptengine_v8) << "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 V8ScriptValue(engine, proxy.get()->toV8Value());            }        }        // 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;            // Lookup needs to be done twice, because _qobjectWrapperMapProtect must not be locked when disconnecting signals            QSharedPointer<ScriptObjectV8Proxy> proxy;            {                QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect);                auto lookupV8 = enginePtr->_qobjectWrapperMapV8.find(object);                if (lookupV8 != enginePtr->_qobjectWrapperMapV8.end()) {                    proxy = lookupV8.value();                }            }            if (proxy) {                for (auto signal : proxy->_signalInstances) {                    if (signal) {                        signal->disconnectAllScriptSignalProxies();                    }                }            }             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());} 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);    auto v8Value = val.constGet();    Q_ASSERT(!v8Value.IsEmpty());     if (v8Value->IsNullOrUndefined()) {        return nullptr;    }    if (!v8Value->IsObject()) {        return nullptr;    }    v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(v8Value);    if (v8Object->InternalFieldCount() != 3) {        return nullptr;    }    if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) {        qCDebug(scriptengine_v8) << "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);     if (value->IsNullOrUndefined()) {        return nullptr;    }    if (!value->IsObject()) {        return nullptr;    }    v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(value);    if (v8Object->InternalFieldCount() != 3) {        return nullptr;    }    if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) {        qCDebug(scriptengine_v8) << "Cannot unwrap proxy - internal fields don't point to object proxy";        return nullptr;    }     ScriptObjectV8Proxy* proxy = reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));    if (proxy) {        Q_ASSERT(!proxy->_wasDestroyed);    }     return proxy;} QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) {    ScriptObjectV8Proxy* proxy = unwrapProxy(val);    return proxy ? proxy->toQObject() : nullptr;} ScriptObjectV8Proxy::~ScriptObjectV8Proxy() {    for (auto signal : _signalInstances) {        if (signal) {            signal->disconnectAllScriptSignalProxies();        }    }    _wasDestroyed = true;    if (_ownsObject) {        auto isolate = _engine->getIsolate();        v8::Locker locker(isolate);        v8::Isolate::Scope isolateScope(isolate);        v8::HandleScope handleScope(isolate);        _v8Object.Reset();        QObject* qobject = _object;        if(qobject) qobject->deleteLater();    } else {        auto isolate = _engine->getIsolate();        v8::Locker locker(isolate);        v8::Isolate::Scope isolateScope(isolate);        v8::HandleScope handleScope(isolate);        if (_object)            qCDebug(scriptengine_v8) << "Deleting object proxy: " << name();        Q_ASSERT(!_v8Object.Get(isolate)->IsNullOrUndefined());        // This prevents unwrap function from unwrapping proxy that was deleted        _v8Object.Get(isolate)->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToDeletedQObjectProxy));        _v8Object.Reset();    }} void ScriptObjectV8Proxy::investigate() {    QObject* qobject = _object;    if (!qobject) {        QStringList backtrace = _engine->currentContext()->backtrace();        qCDebug(scriptengine_v8) << "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());    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);     const QMetaObject* metaObject = qobject->metaObject();     auto objectTemplate = _engine->getObjectProxyTemplate();     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;         // 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(prop.name(), idx)).value();        _propNameMap.insert(prop.name(), &propDef);        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<QString, 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::Private) continue;         bool isSignal = false;        QByteArray szName = method.name();         switch (method.methodType()) {            case QMetaMethod::Constructor:                continue;            case QMetaMethod::Signal:                if (szName == "destroyed") {                    continue;                }                if (szName == "objectNameChanged") {                    continue;                }                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(szName);        if (isSignal) {            if (nameLookup == methodNames.end()) {                SignalDef& signalDef = _signals.insert(idx, SignalDef(szName, idx)).value();                signalDef.name = szName;                signalDef.signal = method;                _signalNameMap.insert(szName, &signalDef);                methodNames.insert(szName, 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) {                qCCritical(scriptengine_v8) << "Method " << metaObject->className() << "::" << name.toQString() << " has QMetaType::UnknownType return value";            }            for (int i = 0; i < method.parameterCount(); i++) {                if (method.parameterType(i) == QMetaType::UnknownType) {                    qCCritical(scriptengine_v8) << "Parameter " << i << "in method " << metaObject->className() << "::" << name.toQString() << " is of type QMetaType::UnknownType";                }            }            if (nameLookup == methodNames.end()) {                MethodDef& methodDef = _methods.insert(idx, MethodDef(szName, idx)).value();                methodDef.name = szName;                methodDef.numMaxParams = parameterCount;                methodDef.methods.append(method);                _methodNameMap.insert(szName, &methodDef);                methodNames.insert(szName, idx);            } else {                int originalMethodId = nameLookup.value();                MethodDefMap::iterator methodLookup = _methods.find(originalMethodId);                Q_ASSERT(methodLookup != _methods.end());                MethodDef& methodDef = methodLookup.value();                if(methodDef.numMaxParams < parameterCount) methodDef.numMaxParams = parameterCount;                methodDef.methods.append(method);            }        }    }     v8::Local<v8::Object> v8Object = objectTemplate->NewInstance(context).ToLocalChecked();     v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy));    v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this));     _v8Object.Reset(_engine->getIsolate(), v8Object);    if (_ownsObject) {        _v8Object.SetWeak(this, weakHandleCallback, v8::WeakCallbackType::kParameter);    }     // Properties added later will be stored in this object    v8::Local<v8::Object> propertiesObject = v8::Object::New(_engine->getIsolate());    v8Object->SetInternalField(2, propertiesObject);     // Add all the methods objects as properties - this allows adding properties to a given method later. Is used by Script.request.    for (auto i = _methods.begin(); i != _methods.end(); i++) {        V8ScriptValue method = ScriptMethodV8Proxy::newMethod(_engine, qobject, V8ScriptValue(_engine, v8Object),                                                              i.value().methods, i.value().numMaxParams);        if(!propertiesObject->Set(context, v8::String::NewFromUtf8(isolate, i.value().name.toStdString().c_str()).ToLocalChecked(), method.get()).FromMaybe(false)) {            Q_ASSERT(false);        }    }} void ScriptObjectV8Proxy::weakHandleCallback(const v8::WeakCallbackInfo<ScriptObjectV8Proxy>& info) {    auto proxy = info.GetParameter();    proxy->_v8Object.Reset();    info.GetParameter()->_object->deleteLater();} 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);    v8::Local<v8::Context> context = _engine->getContext();    v8::Context::Scope contextScope(context);    QString nameStr(*v8::String::Utf8Value(isolate, name.constGet()));    // V8TODO: registering methods on V8 side would make API calls a lot faster     // check for methods    MethodNameMap::const_iterator method = _methodNameMap.find(nameStr);    if (method != _methodNameMap.cend()) {        const MethodDef* methodDef = method.value();        *id = methodDef->_id | METHOD_TYPE;        return flags & (HandlesReadAccess | HandlesWriteAccess);    }     // check for properties    PropertyNameMap::const_iterator prop = _propNameMap.find(nameStr);    if (prop != _propNameMap.cend()) {        const PropertyDef* propDef = prop.value();        *id = propDef->_id | PROPERTY_TYPE;        return flags & (HandlesReadAccess | HandlesWriteAccess);    }     // check for signals    // V8TODO: this should use _signalNameMap QHash for faster search    for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) {        if (!(trans.value().name == nameStr)) 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());    v8::String::Utf8Value utf8Value(info.GetIsolate(), name);    v8::Local<v8::Value> objectV8 = info.This();    ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8);    if (!proxy) {        qCDebug(scriptengine_v8) << "Proxy object not found when getting: " << *utf8Value;        return;    }    V8ScriptValue object(proxy->_engine, objectV8);

Lines 1–500 of 1613.