868 lines · 33.7 KB · C++
RawDownload
////  ScriptValueV8Wrapper.cpp//  libraries/script-engine/src/v8////  Created by Heather Anderson on 5/16/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 "ScriptValueV8Wrapper.h" #include "ScriptValueIteratorV8Wrapper.h" #include "../ScriptEngineLogging.h"#include "ScriptEngineLoggingV8.h"  void ScriptValueV8Wrapper::enqueueRelease() {    _engine->scheduleValueWrapperForDeletion(this);} void ScriptValueV8Wrapper::release() {    // Check if ScriptValueV8Wrapper::release was called from inside ScriptValueV8Wrapper functions, and if so, delete it later    // This prevents access-after-delete crashes when ScriptValueV8Wrapper::release is called from inside JS executed in    // ScriptValueV8Wrapper::call, ScriptValueV8Wrapper::construct and others    if (lock.tryLockForWrite()) {        lock.unlock();        delete this;    } else {        _engine->scheduleValueWrapperForDeletion(this);    }} ScriptValueProxy* ScriptValueV8Wrapper::copy() const {    //V8TODO: check if the value needs to be copied or just wrapper    v8::Isolate *isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    v8::Context::Scope contextScope(_engine->getContext());    // V8TODO: I'm not sure if this part is right:    ScriptValueV8Wrapper *copiedWrapper = new ScriptValueV8Wrapper(_engine, _value);    return copiedWrapper;} ScriptValueV8Wrapper* ScriptValueV8Wrapper::unwrap(const ScriptValue& val) {    return dynamic_cast<ScriptValueV8Wrapper*>(val.ptr());} V8ScriptValue ScriptValueV8Wrapper::fullUnwrap(const ScriptValue& value) const {    ScriptValueV8Wrapper* unwrapped = unwrap(value);    if (unwrapped) {        if (unwrapped->engine().get() != _engine) {            return _engine->castVariantToValue(unwrapped->toVariant());        } else {            return unwrapped->toV8Value();        }    }    QVariant varValue = value.toVariant();    return _engine->castVariantToValue(varValue);} V8ScriptValue ScriptValueV8Wrapper::fullUnwrap(ScriptEngineV8* engine, const ScriptValue& value) {    ScriptValueV8Wrapper* unwrapped = unwrap(value);    if (unwrapped) {        if (unwrapped->engine().get() != engine) {            return engine->castVariantToValue(unwrapped->toVariant());        } else {            return unwrapped->toV8Value();        }    }    QVariant varValue = value.toVariant();    return engine->castVariantToValue(varValue);} ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const ScriptValueList& args) {    Q_ASSERT(_engine == _value.getEngine());    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    V8ScriptValue v8This = fullUnwrap(thisObject);    Q_ASSERT(args.length() <= Q_METAMETHOD_INVOKE_MAX_ARGS);    v8::Local<v8::Value> v8Args[Q_METAMETHOD_INVOKE_MAX_ARGS];    int argIndex = 0;    for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) {        v8Args[argIndex++] = fullUnwrap(*iter).get();    }    Q_ASSERT(_value.get()->IsFunction());    v8::Local<v8::Function> v8Function = v8::Local<v8::Function>::Cast(_value.get());    v8::TryCatch tryCatch(isolate);    v8::Local<v8::Value> recv;    if (v8This.get()->IsObject()) {        recv = v8This.get();    }else{        recv = context->Global();    }     lock.lockForRead();    auto maybeResult = v8Function->Call(context, recv, args.length(), v8Args);    lock.unlock();    if (tryCatch.HasCaught()) {        QString errorMessage(QString("Function call failed: \"") + _engine->formatErrorMessageFromTryCatch(tryCatch));        if (_engine->_manager) {            v8::Local<v8::Message> exceptionMessage = tryCatch.Message();            int errorLineNumber = -1;            if (!exceptionMessage.IsEmpty()) {                errorLineNumber = exceptionMessage->GetLineNumber(context).FromJust();            }            _engine->_manager->scriptErrorMessage(errorMessage, getFileNameFromTryCatch(tryCatch, isolate, context),                                                  errorLineNumber);        } else {            qDebug(scriptengine_v8) << errorMessage;        }    }    v8::Local<v8::Value> result;    Q_ASSERT(_engine == _value.getEngine());    if (maybeResult.ToLocal(&result)) {        return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, result)));    } else {        auto currentContext = _engine->currentContext();        QString errorMessage("JS function call failed: " + currentContext->backtrace().join("\n"));        if (_engine->_manager) {            _engine->_manager->scriptErrorMessage(errorMessage, currentContext->currentFileName(), currentContext->currentLineNumber());        } else {            qDebug(scriptengine_v8) << errorMessage;        }        return _engine->undefinedValue();    }} ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const ScriptValue& arguments) {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    v8::Context::Scope contextScope(_engine->getContext());    V8ScriptValue v8This = fullUnwrap(thisObject);    V8ScriptValue v8Args = fullUnwrap(arguments);    // V8TODO should there be a v8 try-catch here?    // IsFunction check should be here probably    // V8TODO I'm not sure in what format arguments are yet, backtrace will show how it is used    // V8TODO: this seems to never be used?    Q_ASSERT(false);    return _engine->undefinedValue();    /*v8::Local<v8::Function> v8Function = v8::Local<v8::Function>::Cast(_value.get());    auto maybeResult = v8Function->Call(_engine->getContext(), v8This, v8Args);    v8::Local<v8::Value> result;    if (maybeResult.ToLocal(&result)) {        return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine->getContext(), result)));    } else {        //V8TODO Add more details        qCWarning(scriptengine_v8) << "JS function call failed";        return _engine->undefinedValue();    }*/} ScriptValue ScriptValueV8Wrapper::construct(const ScriptValueList& args) {    //V8TODO: there is CallAsContructor in V8    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    Q_ASSERT(args.length() <= Q_METAMETHOD_INVOKE_MAX_ARGS);    v8::Local<v8::Value> v8Args[Q_METAMETHOD_INVOKE_MAX_ARGS];    int argIndex = 0;    for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) {        v8Args[argIndex++] = fullUnwrap(*iter).get();    }    //V8TODO: should there be a v8 try-catch here?    //V8TODO: Can something else than a function be callable in this way in JS?    if (!_value.get()->IsFunction()) {        qCWarning(scriptengine_v8) << "ScriptValueV8Wrapper::construct: value is not a function";        return _engine->undefinedValue();    }     v8::Local<v8::Function> v8Function = v8::Local<v8::Function>::Cast(_value.get());    // V8TODO: I'm not sure if this is correct, maybe use CallAsConstructor instead?    // Maybe it's CallAsConstructor for function and NewInstance for class?    lock.lockForRead();    auto maybeResult = v8Function->NewInstance(context, args.length(), v8Args);    lock.unlock();    v8::Local<v8::Object> result;    if (maybeResult.ToLocal(&result)) {        return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, result)));    } else {        //V8TODO Add more details        qCWarning(scriptengine_v8) << "JS function call failed";        return _engine->undefinedValue();    }} // V8TODO: this seems to never be used?ScriptValue ScriptValueV8Wrapper::construct(const ScriptValue& arguments) {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    v8::Context::Scope contextScope(_engine->getContext());    // V8TODO I'm not sure in what format arguments are yet, backtrace will show how it is used    Q_ASSERT(false);    return _engine->undefinedValue();    //V8ScriptValue unwrapped = fullUnwrap(arguments);    //V8ScriptValue result = _value.construct(unwrapped);    //return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result)));} // V8TODO: check how data() is used and if it needs fixingScriptValue ScriptValueV8Wrapper::data() const {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    // Private properties are an experimental feature for now on V8, so we are using regular value for now    if (_value.constGet()->IsObject()) {        auto v8Object = v8::Local<v8::Object>::Cast(_value.constGet());         v8::Local<v8::Value> data;         //bool createData = false;         if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "__data").ToLocalChecked()).ToLocal(&data)) {             data = v8::Undefined(isolate);             Q_ASSERT(false);             //createData = true;         }         /*else {             if (data->IsUndefined()) {                 createData = true;             }         }         if (createData) {             qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data(): Data object doesn't exist, creating new one";             // Create data object if it's non-existent or invalid             data = v8::Object::New(isolate);             if( !v8Object->Set(_engine->getContext(), v8::String::NewFromUtf8(isolate, "__data").ToLocalChecked(), data).FromMaybe(false)) {                 qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data(): Data object couldn't be created";                 Q_ASSERT(false);             }         }*/         V8ScriptValue result(_engine, data);         return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result)));    } else {        qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data() was called on a value that is not an object";        Q_ASSERT(false);    }    //V8TODO I'm not sure how this would work in V8    //V8ScriptValue result = _value.data();    //return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result)));    return _engine->nullValue();} ScriptEnginePointer ScriptValueV8Wrapper::engine() const {    if (!_engine) {        return ScriptEnginePointer();    }#ifdef OVERTE_SCRIPT_USE_AFTER_DELETE_GUARD    Q_ASSERT(!_engine->_wasDestroyed);#endif    return _engine->shared_from_this();} ScriptValueIteratorPointer ScriptValueV8Wrapper::newIterator() const {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    v8::Context::Scope contextScope(_engine->getContext());    ScriptValueIteratorPointer iterator = std::make_shared<ScriptValueIteratorV8Wrapper>(_engine, _value);    return iterator;} bool ScriptValueV8Wrapper::hasProperty(const QString& name) const {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    //V8TODO: does function return true on IsObject too?    if (_value.constGet()->IsObject()) {    //V8TODO: what about flags?        v8::Local<v8::Value> resultLocal;        v8::Local<v8::String> key = v8::String::NewFromUtf8(isolate, name.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked();        const v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(_value.constGet());        //V8TODO: Which context?        if (object->Get(context, key).ToLocal(&resultLocal)) {            return true;        } else {            return false;        }    }    return false;}   ScriptValue ScriptValueV8Wrapper::property(const QString& name, const ScriptValue::ResolveFlags &mode) const {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    if (_value.constGet()->IsNullOrUndefined()) {        return _engine->undefinedValue();    }    if (_value.constGet()->IsObject()) {    //V8TODO: what about flags?        v8::Local<v8::Value> resultLocal;        v8::Local<v8::String> key = v8::String::NewFromUtf8(_engine->getIsolate(), name.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked();        const v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(_value.constGet());        //V8TODO: Which context?        lock.lockForRead();        if (object->Get(context, key).ToLocal(&resultLocal)) {            V8ScriptValue result(_engine, resultLocal);            lock.unlock();            return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result)));        } else {            QString parentValueQString("");            v8::Local<v8::String> parentValueString;            if (_value.constGet()->ToDetailString(context).ToLocal(&parentValueString)) {                QString(*v8::String::Utf8Value(isolate, parentValueString));            }            qCDebug(scriptengine_v8) << "Failed to get property, parent of value: " << name << ", parent type: " << QString(*v8::String::Utf8Value(isolate, _value.constGet()->TypeOf(isolate))) << " parent value: " << parentValueQString;        }    }    if (name == QString("x")) {        printf("x");    }    return _engine->undefinedValue();} ScriptValue ScriptValueV8Wrapper::property(quint32 arrayIndex, const ScriptValue::ResolveFlags& mode) const {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    v8::Context::Scope contextScope(_engine->getContext());    if (_value.constGet()->IsNullOrUndefined()) {        qCDebug(scriptengine_v8) << "Failed to get property, parent of value: " << arrayIndex << " is not a V8 object, reported type: " << QString(*v8::String::Utf8Value(isolate, _value.constGet()->TypeOf(isolate)));        return _engine->undefinedValue();    }    if (_value.constGet()->IsObject()) {    //V8TODO: what about flags?        v8::Local<v8::Value> resultLocal;        const v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(_value.constGet());        lock.lockForRead();        if (object->Get(_value.constGetContext(), arrayIndex).ToLocal(&resultLocal)) {            V8ScriptValue result(_engine, resultLocal);            lock.unlock();            return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result)));        }        lock.unlock();    }    qCDebug(scriptengine_v8) << "Failed to get property, parent of value: " << arrayIndex << " is not a V8 object, reported type: " << QString(*v8::String::Utf8Value(isolate, _value.constGet()->TypeOf(isolate)));    return _engine->undefinedValue();} ScriptValue ScriptValueV8Wrapper::prototype() const {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    v8::Context::Scope contextScope(_engine->getContext());     auto value = _value.constGet();    if (!value->IsObject()) {        return _engine->undefinedValue();    }    auto object = v8::Local<v8::Object>::Cast(value);    auto prototype = object->GetPrototype();     V8ScriptValue result(_engine, prototype);    return ScriptValue(new ScriptValueV8Wrapper(_engine, result));} void ScriptValueV8Wrapper::setData(const ScriptValue& value) {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    V8ScriptValue unwrapped = fullUnwrap(value);    // Private properties are an experimental feature for now on V8, so we are using regular value for now    if (_value.constGet()->IsNullOrUndefined()) {        qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::setData() was called on a value that is null or undefined";        return;    }    if (_value.constGet()->IsObject()) {        auto v8Object = v8::Local<v8::Object>::Cast(_value.constGet());        if( !v8Object->Set(context, v8::String::NewFromUtf8(isolate, "__data").ToLocalChecked(), unwrapped.constGet()).FromMaybe(false)) {            qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data(): Data object couldn't be created";            Q_ASSERT(false);        }    } else {        qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data() was called on a value that is not an object";        Q_ASSERT(false);    }} void ScriptValueV8Wrapper::setProperty(const QString& name, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) {    Q_ASSERT(flags != ScriptValue::PropertyGetter || flags != ScriptValue::PropertySetter);    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    V8ScriptValue unwrapped = fullUnwrap(value);    if (_value.constGet()->IsNullOrUndefined()) {        qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::setProperty() was called on a value that is null or undefined";        return;    }    if(_value.constGet()->IsObject()) {        v8::Local<v8::String> key = v8::String::NewFromUtf8(isolate, name.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked();        Q_ASSERT(_value.get()->IsObject());        auto object = v8::Local<v8::Object>::Cast(_value.get());        lock.lockForRead();        v8::Maybe<bool> retVal = object->Set(isolate->GetCurrentContext(), key, unwrapped.constGet());        lock.unlock();        if (retVal.IsJust() ? !retVal.FromJust() : true){            qCDebug(scriptengine_v8) << "Failed to set property";        }    } else {        v8::Local<v8::String> details;        QString detailsString("");        if(_value.get()->ToDetailString(context).ToLocal(&details)) {            v8::String::Utf8Value utf8Value(isolate,details);            detailsString = *utf8Value;        }        qCDebug(scriptengine_v8) << "Failed to set property:" + name + " - parent is not an object. Parent details: " + " Type: " + QString(*v8::String::Utf8Value(isolate, _value.constGet()->TypeOf(isolate)));        qCDebug(scriptengine_v8) << _engine->currentContext()->backtrace();    }    //V8TODO: what about flags?    //_value.setProperty(name, unwrapped, (V8ScriptValue::PropertyFlags)(int)flags);} void ScriptValueV8Wrapper::setProperty(quint32 arrayIndex, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    V8ScriptValue unwrapped = fullUnwrap(value);    if (_value.constGet()->IsNullOrUndefined()) {        qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::setProperty() was called on a value that is null or undefined";        return;    }    if(_value.constGet()->IsObject()) {        auto object = v8::Local<v8::Object>::Cast(_value.get());        //V8TODO: I don't know which context to use here        lock.lockForRead();        v8::Maybe<bool> retVal(object->Set(context, arrayIndex, unwrapped.constGet()));        lock.unlock();        if (retVal.IsJust() ? !retVal.FromJust() : true){            qCDebug(scriptengine_v8) << "Failed to set property";        }    } else {        qCDebug(scriptengine_v8) << "Failed to set property: " + QString(arrayIndex) + " - parent is not an object";    }    //V8TODO: what about flags?    //_value.setProperty(arrayIndex, unwrapped, (V8ScriptValue::PropertyFlags)(int)flags);} void ScriptValueV8Wrapper::setPrototype(const ScriptValue& prototype) {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    auto context = _engine->getContext();    v8::Context::Scope contextScope(context);    ScriptValueV8Wrapper* unwrappedPrototype = unwrap(prototype);    if (unwrappedPrototype) {        if(unwrappedPrototype->toV8Value().constGet()->IsNullOrUndefined() && _value.constGet()->IsNullOrUndefined()) {            qCDebug(scriptengine_v8) << "Failed to assign prototype - one of values is null or undefined";        }        if(unwrappedPrototype->toV8Value().constGet()->IsObject() && _value.constGet()->IsObject()) {            auto object = v8::Local<v8::Object>::Cast(_value.get());            //V8TODO: I don't know which context to use here            lock.lockForRead();            v8::Maybe<bool> retVal = object->SetPrototype(context, unwrappedPrototype->toV8Value().constGet());            lock.unlock();            if (retVal.IsJust() ? !retVal.FromJust() : true){                qCDebug(scriptengine_v8) << "Failed to assign prototype";            }        } else {            qCDebug(scriptengine_v8) << "Failed to assign prototype - one of values is not an object";        }    }} bool ScriptValueV8Wrapper::strictlyEquals(const ScriptValue& other) const {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);    v8::Context::Scope contextScope(_engine->getContext());    ScriptValueV8Wrapper* unwrappedOther = unwrap(other);    return unwrappedOther ? _value.constGet()->StrictEquals(unwrappedOther->toV8Value().constGet()) : false;} inline QList<QString> ScriptValueV8Wrapper::getPropertyNames() const {    auto isolate = _engine->getIsolate();    Q_ASSERT(isolate->IsCurrent());    v8::HandleScope handleScope(isolate);

Lines 1–500 of 868.