757 lines · 30.2 KB · C++
RawDownload
////  ScriptEngineV8_cast.cpp//  libraries/script-engine/src/v8////  Created by Heather Anderson 12/9/2021//  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 "ScriptEngineV8.h" #include <QtCore/QJsonArray>#include <QtCore/QJsonObject>#include <QtCore/QJsonValue>#include "libplatform/libplatform.h"#include "v8.h" #include "../ScriptEngineCast.h"#include "../ScriptValueIterator.h" #include "ScriptObjectV8Proxy.h"#include "ScriptValueV8Wrapper.h"#include "ScriptEngineLoggingV8.h" void ScriptEngineV8::setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) {    ScriptValueV8Wrapper* unwrappedPrototype = ScriptValueV8Wrapper::unwrap(prototype);    if (unwrappedPrototype) {        const V8ScriptValue& scriptPrototype = unwrappedPrototype->toV8Value();        _customTypeProtect.lockForWrite();        _customPrototypes.insert(metaTypeId, scriptPrototype);        _customTypeProtect.unlock();    }} void ScriptEngineV8::registerCustomType(int type,                                              ScriptEngine::MarshalFunction marshalFunc,                                              ScriptEngine::DemarshalFunction demarshalFunc){    _customTypeProtect.lockForWrite();     // storing it in a map for our own benefit    CustomMarshal& customType = _customTypes.insert(type, CustomMarshal()).value();    customType.demarshalFunc = demarshalFunc;    customType.marshalFunc = marshalFunc;    _customTypeProtect.unlock();} Q_DECLARE_METATYPE(ScriptValue);Q_DECLARE_METATYPE(QVariantMap); static ScriptValue StringListToScriptValue(ScriptEngine* engine, const QStringList& src) {    int len = src.length();    ScriptValue dest = engine->newArray(len);    for (int idx = 0; idx < len; ++idx) {        dest.setProperty(idx, engine->newValue(src.at(idx)));    }    return dest;} static bool StringListFromScriptValue(const ScriptValue& src, QStringList& dest) {    if(!src.isArray()) return false;    int len = src.property("length").toInteger();    dest.clear();    for (int idx = 0; idx < len; ++idx) {        dest.append(src.property(idx).toString());    }    return true;} static ScriptValue VariantListToScriptValue(ScriptEngine* engine, const QVariantList& src) {    int len = src.length();    ScriptValue dest = engine->newArray(len);    for (int idx = 0; idx < len; ++idx) {        dest.setProperty(idx, engine->newVariant(src.at(idx)));    }    return dest;} static bool VariantListFromScriptValue(const ScriptValue& src, QVariantList& dest) {    if(!src.isArray()) return false;    int len = src.property("length").toInteger();    dest.clear();    for (int idx = 0; idx < len; ++idx) {        dest.append(src.property(idx).toVariant());    }    return true;} static ScriptValue VariantMapToScriptValue(ScriptEngine* engine, const QVariantMap& src) {    ScriptValue dest = engine->newObject();    for (QVariantMap::const_iterator iter = src.cbegin(); iter != src.cend(); ++iter) {        dest.setProperty(iter.key(), engine->newVariant(iter.value()));    }    return dest;} static bool VariantMapFromScriptValue(const ScriptValue& src, QVariantMap& dest) {    dest.clear();    ScriptValueIteratorPointer iter = src.newIterator();    while (iter->hasNext()) {        iter->next();        dest.insert(iter->name(), iter->value().toVariant());    }    return true;} static ScriptValue VariantHashToScriptValue(ScriptEngine* engine, const QVariantHash& src) {    ScriptValue dest = engine->newObject();    for (QVariantHash::const_iterator iter = src.cbegin(); iter != src.cend(); ++iter) {        dest.setProperty(iter.key(), engine->newVariant(iter.value()));    }    return dest;} static bool VariantHashFromScriptValue(const ScriptValue& src, QVariantHash& dest) {    dest.clear();    ScriptValueIteratorPointer iter = src.newIterator();    while (iter->hasNext()) {        iter->next();        dest.insert(iter->name(), iter->value().toVariant());    }    return true;} static ScriptValue JsonValueToScriptValue(ScriptEngine* engine, const QJsonValue& src) {    return engine->newVariant(src.toVariant());} static bool JsonValueFromScriptValue(const ScriptValue& src, QJsonValue& dest) {    dest = QJsonValue::fromVariant(src.toVariant());    return true;} static ScriptValue JsonObjectToScriptValue(ScriptEngine* engine, const QJsonObject& src) {    QVariantMap map = src.toVariantMap();    ScriptValue dest = engine->newObject();    for (QVariantMap::const_iterator iter = map.cbegin(); iter != map.cend(); ++iter) {        dest.setProperty(iter.key(), engine->newVariant(iter.value()));    }    return dest;} static bool JsonObjectFromScriptValue(const ScriptValue& src, QJsonObject& dest) {    QVariantMap map;    ScriptValueIteratorPointer iter = src.newIterator();    while (iter->hasNext()) {        iter->next();        map.insert(iter->name(), iter->value().toVariant());    }    dest = QJsonObject::fromVariantMap(map);    return true;} static ScriptValue JsonArrayToScriptValue(ScriptEngine* engine, const QJsonArray& src) {    QVariantList list = src.toVariantList();    int len = list.length();    ScriptValue dest = engine->newArray(len);    for (int idx = 0; idx < len; ++idx) {        dest.setProperty(idx, engine->newVariant(list.at(idx)));    }    return dest;} static bool JsonArrayFromScriptValue(const ScriptValue& src, QJsonArray& dest) {    if(!src.isArray()) return false;    QVariantList list;    int len = src.property("length").toInteger();    for (int idx = 0; idx < len; ++idx) {        list.append(src.property(idx).toVariant());    }    dest = QJsonArray::fromVariantList(list);    return true;} // QMetaType::QJsonArray void ScriptEngineV8::registerSystemTypes() {    scriptRegisterMetaType<QStringList, StringListToScriptValue, StringListFromScriptValue>(static_cast<ScriptEngine*>(this));    scriptRegisterMetaType<QVariantList, VariantListToScriptValue, VariantListFromScriptValue>(this);    scriptRegisterMetaType<QVariantMap, VariantMapToScriptValue, VariantMapFromScriptValue>(this);    scriptRegisterMetaType<QVariantHash, VariantHashToScriptValue, VariantHashFromScriptValue>(this);    scriptRegisterMetaType<QJsonValue, JsonValueToScriptValue, JsonValueFromScriptValue>(this);    scriptRegisterMetaType<QJsonObject, JsonObjectToScriptValue, JsonObjectFromScriptValue>(this);    scriptRegisterMetaType<QJsonArray, JsonArrayToScriptValue, JsonArrayFromScriptValue>(this);} int ScriptEngineV8::computeCastPenalty(const V8ScriptValue& v8Val, int destTypeId) {    Q_ASSERT(_v8Isolate->IsCurrent());    v8::HandleScope handleScope(_v8Isolate);    v8::Local<v8::Context> context = getContext();    v8::Context::Scope contextScope(context);     const v8::Local<v8::Value> val = v8Val.constGet();    if (val->IsNumber()) {        switch (destTypeId){            case QMetaType::Bool:                // Conversion to bool is acceptable, but numbers are preferred                return 5;                break;            case QMetaType::UInt:            case QMetaType::ULong:            case QMetaType::Int:            case QMetaType::Long:            case QMetaType::Short:            case QMetaType::Double:            case QMetaType::Float:            case QMetaType::ULongLong:            case QMetaType::LongLong:            case QMetaType::UShort:                // Perfect case. JS doesn't have separate integer and floating point type                return 0;                break;            case QMetaType::QString:            case QMetaType::QByteArray:            case QMetaType::QDateTime:            case QMetaType::QDate:                // Conversion to string should be avoided, it's probably not what we want                return 100;                break;            default:                // Other, not predicted cases                return 5;        }    } else if (val->IsString() || val->IsDate() || val->IsRegExp()) {        switch (destTypeId){            case QMetaType::Bool:                // Conversion from to bool should be avoided if possible, it's probably not what we want                return 100;            case QMetaType::UInt:            case QMetaType::ULong:            case QMetaType::Int:            case QMetaType::Long:            case QMetaType::Short:            case QMetaType::Double:            case QMetaType::Float:            case QMetaType::ULongLong:            case QMetaType::LongLong:            case QMetaType::UShort:                // Conversion from to number should be avoided if possible, it's probably not what we want                return 100;            case QMetaType::QString:                // Perfect case                return 0;            case QMetaType::QByteArray:            case QMetaType::QDateTime:            case QMetaType::QDate:                // String to string should be slightly preferred                return 5;            default:                return 5;        }    } else if (val->IsBoolean()) {        switch (destTypeId){            case QMetaType::Bool:                // Perfect case                return 0;                break;            case QMetaType::UInt:            case QMetaType::ULong:            case QMetaType::Int:            case QMetaType::Long:            case QMetaType::Short:            case QMetaType::Double:            case QMetaType::Float:            case QMetaType::ULongLong:            case QMetaType::LongLong:            case QMetaType::UShort:                // Using function with bool parameter should be preferred over converted bool to nimber                return 5;                break;            case QMetaType::QString:            case QMetaType::QByteArray:            case QMetaType::QDateTime:            case QMetaType::QDate:                // Bool probably shouldn't be converted to string if there are better alternatives                return 50;                break;            default:                return 5;        }    }    return 0;} bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& dest, int destTypeId) {    Q_ASSERT(_v8Isolate->IsCurrent());    v8::HandleScope handleScope(_v8Isolate);    v8::Local<v8::Context> context = getContext();    v8::Context::Scope contextScope(context);    const v8::Local<v8::Value> val = v8Val.constGet();     // if we're not particularly interested in a specific type, try to detect if we're dealing with a registered type    if (destTypeId == QMetaType::UnknownType) {        QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val);        if (obj) {            for (const QMetaObject* metaObject = obj->metaObject(); metaObject; metaObject = metaObject->superClass()) {                QByteArray typeName = QByteArray(metaObject->className()) + "*";                int typeId = QMetaType::type(typeName.constData());                if (typeId != QMetaType::UnknownType) {                    destTypeId = typeId;                    break;                }            }        }    }     if (destTypeId == qMetaTypeId<ScriptValue>()) {        dest = QVariant::fromValue(ScriptValue(new ScriptValueV8Wrapper(this, v8Val)));        return true;    }     QString errorMessage("");    // do we have a registered handler for this type?    ScriptEngine::DemarshalFunction demarshalFunc = nullptr;    {        _customTypeProtect.lockForRead();        CustomMarshalMap::const_iterator lookup = _customTypes.find(destTypeId);        if (lookup != _customTypes.cend()) {            demarshalFunc = lookup.value().demarshalFunc;        }        _customTypeProtect.unlock();    }    if (demarshalFunc) {        dest = QVariant();        ScriptValue wrappedVal(new ScriptValueV8Wrapper(this, v8Val));        bool success = demarshalFunc(wrappedVal, dest);        if(!success) dest = QVariant();        return success;    } else {        switch (destTypeId) {            case QMetaType::UnknownType:                if (val->IsUndefined()) {                    dest = QVariant();                    break;                }                if (val->IsNull()) {                    dest = QVariant::fromValue(nullptr);                    break;                }                if (val->IsBoolean()) {                    dest = QVariant::fromValue(val->ToBoolean(_v8Isolate)->Value());                    break;                }                if (val->IsString()) {                    v8::String::Utf8Value string(_v8Isolate, val);                    Q_ASSERT(*string != nullptr);                    dest = QVariant::fromValue(QString(*string));                    break;                }                if (val->IsNumber()) {                    dest = QVariant::fromValue(val->ToNumber(context).ToLocalChecked()->Value());                    break;                }                {                    QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val);                    if (obj) {                        dest = QVariant::fromValue(obj);                        break;                    }                }                {                    QVariant var = ScriptVariantV8Proxy::unwrap(v8Val);                    if (var.isValid()) {                        dest = var;                        break;                    }                }                if (val->IsArray()) {                    if (convertJSArrayToVariant(v8::Local<v8::Array>::Cast(val), dest)) {                        return true;                    }                }                // This is for generic JS objects                if (val->IsObject()) {                    if (convertJSObjectToVariant(v8::Local<v8::Object>::Cast(val), dest)) {                        break;                    }                }                errorMessage = QString() + "Conversion failure: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked()))                               + "to variant. Destination type: " + QMetaType::typeName(destTypeId) +" details: "+ scriptValueDebugDetailsV8(v8Val);                qCDebug(scriptengine_v8) << errorMessage;                 // V8TODO: this doesn't seem to be necessary anymore but I'm keeping it until all the API is tested                //dest = val->ToVariant();                break;            case QMetaType::Bool:                dest = QVariant::fromValue(val->ToBoolean(_v8Isolate)->Value());                break;            case QMetaType::QDateTime:            case QMetaType::QDate:                if (val->IsDate()){                    double timeMs = v8::Date::Cast(*val)->NumberValue(context).ToChecked();                    dest = QVariant::fromValue(QDateTime::fromMSecsSinceEpoch(timeMs));                } else if (val->IsNumber()) {                    dest = QVariant::fromValue(QDateTime::fromMSecsSinceEpoch(val->ToNumber(context).ToLocalChecked()->Value()));                } else {                    return false;                }                break;            case QMetaType::UInt:            case QMetaType::ULong:                if ( val->IsArray() || val->IsObject() ){                    return false;                }                dest = QVariant::fromValue(val->ToUint32(context).ToLocalChecked()->Value());                break;            case QMetaType::Int:            case QMetaType::Long:            case QMetaType::Short:                if ( val->IsArray() || val->IsObject() ){                    return false;                }                dest = QVariant::fromValue(val->ToInt32(context).ToLocalChecked()->Value());                break;            case QMetaType::Double:            case QMetaType::Float:            case QMetaType::ULongLong:            case QMetaType::LongLong:                if ( val->IsArray() || val->IsObject() ){                    return false;                }                dest = QVariant::fromValue(val->ToNumber(context).ToLocalChecked()->Value());                break;            case QMetaType::QString:            case QMetaType::QByteArray:                {                    v8::String::Utf8Value string(_v8Isolate, val);                    Q_ASSERT(*string != nullptr);                    dest = QVariant::fromValue(QString(*string));                }                break;            case QMetaType::UShort:                if ( val->IsArray() || val->IsObject() ){                    return false;                }                dest = QVariant::fromValue(static_cast<uint16_t>(val->ToUint32(context).ToLocalChecked()->Value()));                break;            case QMetaType::QObjectStar:                {                    // V8TODO: This is to diagnose a really weird segfault where it looks like only half of the QPointer is set                    // to null upon object deletion, it doesn't seem to happen anymore, but I'm keeping it for now                    uint64_t ptrVal = (uint64_t)(ScriptObjectV8Proxy::unwrap(v8Val));                    if ((uint32_t)(ptrVal) == 0 && ptrVal != 0) {                        qCDebug(scriptengine_v8) << "ScriptEngineV8::castValueToVariant pointer bug happened";                        //Q_ASSERT(false);                        return false;                    }                }                dest = QVariant::fromValue(ScriptObjectV8Proxy::unwrap(v8Val));                break;            case QMetaType::QVariant:                if (val->IsUndefined()) {                    dest = QVariant();                    return true;                }                if (val->IsNull()) {                    dest = QVariant::fromValue(nullptr);                    return true;                }                if (val->IsBoolean()) {                    dest = QVariant::fromValue(val->BooleanValue(_v8Isolate));                    return true;                }                if (val->IsString()) {                    v8::String::Utf8Value string(_v8Isolate, val);                    Q_ASSERT(*string != nullptr);                    dest = QVariant::fromValue(QString(*string));                    return true;                }                if (val->IsNumber()) {                    dest = QVariant::fromValue(val->ToNumber(context).ToLocalChecked()->Value());                    return true;                }                {                    QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val);                    if (obj) {                        dest = QVariant::fromValue(obj);                        return true;                    }                }                {                    QVariant var = ScriptVariantV8Proxy::unwrap(v8Val);                    if (var.isValid()) {                        dest = var;                        return true;                    }                }                if (val->IsArray()) {                    if (convertJSArrayToVariant(v8::Local<v8::Array>::Cast(val), dest)) {                        return true;                    }                }                if (val->IsObject()) {                    if (convertJSObjectToVariant(v8::Local<v8::Object>::Cast(val), dest)) {                        return true;

Lines 1–500 of 757.