329 lines · 15.2 KB · C
RawDownload
////  ScriptEngineV8.h//  libraries/script-engine/src/qtscript////  Created by Brad Hefta-Gaub on 12/14/13.//  Modified for V8 by dr Karol Suprynowicz on 2022/10/08//  Copyright 2013 High Fidelity, Inc.//  Copyright 2020 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// /// @addtogroup ScriptEngine/// @{ #ifndef hifi_ScriptEngineV8_h#define hifi_ScriptEngineV8_h #include <memory> #include <QtCore/QByteArray>#include <QtCore/QHash>#include <QtCore/QMetaEnum>#include <QtCore/QMutex>#include <QtCore/QObject>#include <QtCore/QPointer>#include <QtCore/QSharedPointer>#include <QtCore/QString>#include <QQueue>#include <v8-isolate.h>#include <v8-profiler.h> #include "libplatform/libplatform.h"#include "v8.h" #include "ScriptEngineDebugFlags.h"#include "../ScriptEngine.h"#include "../ScriptManager.h"#include "../ScriptException.h" #include "ArrayBufferClass.h" class ScriptContextV8Wrapper;class ScriptEngineV8;class ScriptManager;class ScriptObjectV8Proxy;class ScriptMethodV8Proxy;class ScriptValueV8Wrapper;class ScriptSignalV8Proxy; template <typename T> class V8ScriptValueTemplate;typedef V8ScriptValueTemplate<v8::Value> V8ScriptValue;typedef V8ScriptValueTemplate<v8::Script> V8ScriptProgram; using ScriptContextV8Pointer = std::shared_ptr<ScriptContextV8Wrapper>; const double GARBAGE_COLLECTION_TIME_LIMIT_S = 1.0; Q_DECLARE_METATYPE(ScriptEngine::FunctionSignature) /// [V8] Implements ScriptEngine for V8 and translates calls for QScriptEngineclass ScriptEngineV8 final : public ScriptEngine,                                   public std::enable_shared_from_this<ScriptEngineV8> {    Q_OBJECT public:  // construction    ScriptEngineV8(ScriptManager *manager = nullptr);    virtual ~ScriptEngineV8();     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////    // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can    //        properly ensure they are only called on the correct thread public:  // ScriptEngine implementation    class ScriptEngineScopeGuardV8 final : public ScriptEngineScopeGuard {    public:        explicit ScriptEngineScopeGuardV8(v8::Isolate *isolate) : _locker(isolate), _isolateScope(isolate) {};        ~ScriptEngineScopeGuardV8() override = default;    private:        v8::Locker _locker;        v8::Isolate::Scope _isolateScope;    };     std::unique_ptr<ScriptEngineScopeGuard> getScopeGuard() override;    virtual void abortEvaluation() override;    virtual void clearExceptions() override;    virtual ScriptContext* currentContext() const override;    Q_INVOKABLE virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) override;    Q_INVOKABLE virtual ScriptValue evaluate(const ScriptProgramPointer& program) override;    Q_INVOKABLE virtual ScriptValue evaluateInClosure(const ScriptValue& locals, const ScriptProgramPointer& program) override;    virtual ScriptValue globalObject() override;    virtual bool hasUncaughtException() const override;    virtual bool isEvaluating() const override;    virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) override;     virtual ScriptValue newArray(uint length = 0) override;    virtual ScriptValue newArrayBuffer(const QByteArray& message) override;    virtual ScriptValue newFunction(ScriptEngine::FunctionSignature fun, int length = 0) override;    virtual ScriptValue newObject() override;    virtual ScriptValue newMethod(QObject* object, V8ScriptValue lifetime,                               const QList<QMetaMethod>& metas, int numMaxParams);    virtual ScriptProgramPointer newProgram(const QString& sourceCode, const QString& fileName) override;    virtual ScriptValue newQObject(QObject *object, ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership,        const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions()) override;    virtual ScriptValue newValue(bool value) override;    virtual ScriptValue newValue(int value) override;    virtual ScriptValue newValue(uint value) override;    virtual ScriptValue newValue(double value) override;    virtual ScriptValue newValue(const QString& value) override;    virtual ScriptValue newValue(const QLatin1String& value) override;    virtual ScriptValue newValue(const char* value) override;    virtual ScriptValue newVariant(const QVariant& value) override;    virtual ScriptValue nullValue() override;     virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") override;     virtual bool raiseException(const QString& exception, const QString &reason = QString()) override;    virtual bool raiseException(const ScriptValue& exception, const QString &reason = QString()) override;    Q_INVOKABLE virtual void registerEnum(ScriptEngineScopeGuard* scopeGuard, const QString& enumName, QMetaEnum newEnum) override;    Q_INVOKABLE virtual void registerFunction(ScriptEngineScopeGuard* scopeGuard, const QString& name,                                              ScriptEngine::FunctionSignature fun,                                              int numArguments = -1) override;    Q_INVOKABLE virtual void registerFunction(ScriptEngineScopeGuard* scopeGuard, const QString& parent,                                              const QString& name,                                              ScriptEngine::FunctionSignature fun,                                              int numArguments = -1) override;    Q_INVOKABLE virtual void registerGetterSetter(ScriptEngineScopeGuard* scopeGuard, const QString& name,                                                  ScriptEngine::FunctionSignature getter,                                                  ScriptEngine::FunctionSignature setter,                                                  const QString& parent = QString("")) override;    Q_INVOKABLE virtual void registerGlobalObject(ScriptEngineScopeGuard* scopeGuard, const QString& name,                                                  QObject* object, ScriptEngine::ValueOwnership = ScriptEngine::QtOwnership) override;    virtual void setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) override;    virtual void setObjectName(const QString& name) override;    virtual bool setProperty(const char* name, const QVariant& value) override;    virtual void setProcessEventsInterval(int interval) override;    virtual QThread* thread() const override;    virtual void setThread(QThread* thread) override;    virtual ScriptValue undefinedValue() override;    virtual std::shared_ptr<ScriptException> uncaughtException() const override;    virtual void updateMemoryCost(const qint64& deltaSize) override;    virtual void requestCollectGarbage() override { _v8Isolate->MemoryPressureNotification(v8::MemoryPressureLevel::kCritical); }    virtual void processEvents() override;    virtual void compileTest() override;    virtual QString scriptValueDebugDetails(const ScriptValue &value) override;    QString scriptValueDebugDetailsV8(const V8ScriptValue &value);    virtual QString scriptValueDebugListMembers(const ScriptValue &value) override;    QString scriptValueDebugListMembersV8(const V8ScriptValue &v8Value);    virtual void logBacktrace(const QString &title = QString("")) override;    virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() override;    virtual void startCollectingObjectStatistics() override;    virtual void dumpHeapObjectStatistics() override;    virtual void startProfiling() override;    virtual void stopProfilingAndSave() override;    void scheduleValueWrapperForDeletion(ScriptValueV8Wrapper* wrapper) {_scriptValueWrappersToDelete.enqueue(wrapper);}    void deleteUnusedValueWrappers();    virtual void perManagerLoopIterationCleanup() override;    virtual void disconnectSignalProxies() override;     // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways    inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); } protected: // brought over from BaseScriptEngine      // if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it.    // note: this is used in cases where C++ code might call into JS API methods directly    bool raiseException(const V8ScriptValue& exception);     // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways    static bool IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method); public: // public non-interface methods for other QtScript-specific classes to use     /// registers a global object by name    Q_INVOKABLE void registerValue(ScriptEngineScopeGuardV8* scopeGuard, const QString& valueName, V8ScriptValue value);     // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety    // V8TODO    //inline ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } public: // not for public use, but I don't like how Qt strings this along with private friend functions    virtual ScriptValue create(int type, const void* ptr) override;    virtual QVariant convert(const ScriptValue& value, int typeId) override;    virtual void registerCustomType(int type, ScriptEngine::MarshalFunction marshalFunc,                                    ScriptEngine::DemarshalFunction demarshalFunc) override;    int computeCastPenalty(const V8ScriptValue& val, int destTypeId);    bool castValueToVariant(const V8ScriptValue& val, QVariant& dest, int destTypeId);     // Converts JS objects created in V8 to variants. Iterates over all properties and converts them to variants.    bool convertJSArrayToVariant(v8::Local<v8::Array> array, QVariant &dest);    bool convertJSObjectToVariant(v8::Local<v8::Object> object, QVariant &dest);    V8ScriptValue castVariantToValue(const QVariant& val);    QString valueType(const V8ScriptValue& val);    v8::Isolate* getIsolate() {        Q_ASSERT(_v8Isolate != nullptr);        return _v8Isolate;}    v8::Local<v8::Context> getContext();    const v8::Local<v8::Context> getConstContext() const;    QString formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch);    // Useful for debugging    virtual QStringList getCurrentScriptURLs() const override;     using ObjectWrapperMap = QMap<QObject*, QWeakPointer<ScriptObjectV8Proxy>>;    mutable QMutex _qobjectWrapperMapProtect;    ObjectWrapperMap _qobjectWrapperMap;    // Second map, from which wrappers are removed by script engine upon deletion    QMap<QObject*, QSharedPointer<ScriptObjectV8Proxy>> _qobjectWrapperMapV8;    // V8TODO: maybe just a single map can be used instead to increase performance?     // Sometimes ScriptValueV8Wrapper::release() is called from inside ScriptValueV8Wrapper.    // Then wrapper needs to be deleted in the event loop    QQueue<ScriptValueV8Wrapper*> _scriptValueWrappersToDelete;     // Used by ScriptObjectV8Proxy to create JS objects referencing C++ ones    v8::Local<v8::ObjectTemplate> getObjectProxyTemplate();    v8::Local<v8::ObjectTemplate> getMethodDataTemplate();    v8::Local<v8::ObjectTemplate> getFunctionDataTemplate();    v8::Local<v8::ObjectTemplate> getVariantDataTemplate();    v8::Local<v8::ObjectTemplate> getVariantProxyTemplate();     ScriptContextV8Pointer pushContext(v8::Local<v8::Context> context);    void popContext();    void storeGlobalObjectContents();#ifdef OVERTE_V8_MEMORY_DEBUG    void incrementScriptValueCounter() { scriptValueCount++; };    void decrementScriptValueCounter() { scriptValueCount--; };    void incrementScriptValueProxyCounter() { scriptValueProxyCount++; };    void decrementScriptValueProxyCounter() { scriptValueProxyCount--; };#endif protected:     void registerSystemTypes(); protected:    static QMutex _v8InitMutex;    static std::once_flag _v8InitOnceFlag;    static v8::Platform* getV8Platform();     void setUncaughtEngineException(const QString &message, const QString& info = QString());    void setUncaughtException(const v8::TryCatch &tryCatch, const QString& info = QString());    void setUncaughtException(std::shared_ptr<ScriptException> exception);     friend class ScriptSignalV8Proxy;     std::shared_ptr<ScriptException> _uncaughtException;      // V8TODO: clean up isolate when script engine is destroyed?    v8::Isolate* _v8Isolate;     struct CustomMarshal {        ScriptEngine::MarshalFunction marshalFunc;        ScriptEngine::DemarshalFunction demarshalFunc;    };    using CustomMarshalMap = QHash<int, CustomMarshal>;    using CustomPrototypeMap = QHash<int, V8ScriptValue>;     mutable QReadWriteLock _customTypeProtect { QReadWriteLock::Recursive };    CustomMarshalMap _customTypes;    CustomPrototypeMap _customPrototypes;    ScriptValue _nullValue;    ScriptValue _undefinedValue;    // Current context stack. Main context is first on the list and current one is last.    QList<ScriptContextV8Pointer> _contexts;    // V8TODO: release in destructor    v8::Persistent<v8::Object> _globalObjectContents;    bool areGlobalObjectContentsStored {false};     // Used by ScriptObjectV8Proxy to create JS objects referencing C++ ones    // V8TODO: release in destructor    v8::Persistent<v8::ObjectTemplate> _objectProxyTemplate;    v8::Persistent<v8::ObjectTemplate> _methodDataTemplate;    v8::Persistent<v8::ObjectTemplate> _functionDataTemplate;    v8::Persistent<v8::ObjectTemplate> _variantDataTemplate;    v8::Persistent<v8::ObjectTemplate> _variantProxyTemplate; public:    volatile int _memoryCorruptionIndicator = 12345678;private:    //V8TODO    //ArrayBufferClass* _arrayBufferClass;    // Counts how many nested evaluate calls are there at a given point    int _evaluatingCounter;#ifdef OVERTE_V8_MEMORY_DEBUG    std::atomic<size_t> scriptValueCount{0};    std::atomic<size_t> scriptValueProxyCount{0};#endif #ifdef OVERTE_SCRIPT_USE_AFTER_DELETE_GUARD    bool _wasDestroyed{false};#endif    // Pointers to profiling classes. These are valid only when profiler is running, otherwise null    // Smart pointer cannot be used here since profiler has private destructor    v8::CpuProfiler *_profiler{nullptr};    v8::ProfilerId _profilerId{0};     // Set of script signal proxy pointers. Used for disconnecting signals on cleanup.    // V8TODO: later it would be also worth to make sure that script proxies themselves get deleted together with script engine    QReadWriteLock _signalProxySetLock;    QSet<ScriptSignalV8Proxy*> _signalProxySet;     friend ScriptValueV8Wrapper;    friend ScriptSignalV8Proxy;}; // This class is used to automatically add context to script engine's context list that is used by C++ calls// An instance of it needs to be created in every V8 callback class ContextScopeV8 {public:    ContextScopeV8(ScriptEngineV8 *engine);    ~ContextScopeV8();private:    bool _isContextChangeNeeded;    ScriptEngineV8* _engine;}; QString getFileNameFromTryCatch(v8::TryCatch &tryCatch, v8::Isolate *isolate, v8::Local<v8::Context> &context ); #include "V8Types.h" #endif  // hifi_ScriptEngineV8_h /// @}