Overte C++ Documentation
ScriptEngineV8.h
1 //
2 // ScriptEngineV8.h
3 // libraries/script-engine/src/qtscript
4 //
5 // Created by Brad Hefta-Gaub on 12/14/13.
6 // Modified for V8 by dr Karol Suprynowicz on 2022/10/08
7 // Copyright 2013 High Fidelity, Inc.
8 // Copyright 2020 Vircadia contributors.
9 // Copyright 2022-2023 Overte e.V.
10 //
11 // Distributed under the Apache License, Version 2.0.
12 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13 // SPDX-License-Identifier: Apache-2.0
14 //
15 
18 
19 #ifndef hifi_ScriptEngineV8_h
20 #define hifi_ScriptEngineV8_h
21 
22 #include <memory>
23 
24 #include <QtCore/QByteArray>
25 #include <QtCore/QHash>
26 #include <QtCore/QMetaEnum>
27 #include <QtCore/QMutex>
28 #include <QtCore/QObject>
29 #include <QtCore/QPointer>
30 #include <QtCore/QSharedPointer>
31 #include <QtCore/QString>
32 #include <QQueue>
33 #include <v8-isolate.h>
34 #include <v8-profiler.h>
35 
36 #include "libplatform/libplatform.h"
37 #include "v8.h"
38 
39 #include "ScriptEngineDebugFlags.h"
40 #include "../ScriptEngine.h"
41 #include "../ScriptManager.h"
42 #include "../ScriptException.h"
43 
44 #include "ArrayBufferClass.h"
45 
47 class ScriptEngineV8;
48 class ScriptManager;
50 class ScriptMethodV8Proxy;
52 class ScriptSignalV8Proxy;
53 
54 template <typename T> class V8ScriptValueTemplate;
55 typedef V8ScriptValueTemplate<v8::Value> V8ScriptValue;
56 typedef V8ScriptValueTemplate<v8::Script> V8ScriptProgram;
57 
58 using ScriptContextV8Pointer = std::shared_ptr<ScriptContextV8Wrapper>;
59 
60 const double GARBAGE_COLLECTION_TIME_LIMIT_S = 1.0;
61 
62 Q_DECLARE_METATYPE(ScriptEngine::FunctionSignature)
63 
64 class ScriptEngineV8 final : public ScriptEngine,
66  public std::enable_shared_from_this<ScriptEngineV8> {
67  Q_OBJECT
68 
69 public: // construction
70  ScriptEngineV8(ScriptManager *manager = nullptr);
71  virtual ~ScriptEngineV8();
72 
74  // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can
75  // properly ensure they are only called on the correct thread
76 
77 public: // ScriptEngine implementation
78  class ScriptEngineScopeGuardV8 final : public ScriptEngineScopeGuard {
79  public:
80  explicit ScriptEngineScopeGuardV8(v8::Isolate *isolate) : _locker(isolate), _isolateScope(isolate) {};
81  ~ScriptEngineScopeGuardV8() override = default;
82  private:
83  v8::Locker _locker;
84  v8::Isolate::Scope _isolateScope;
85  };
86 
87  std::unique_ptr<ScriptEngineScopeGuard> getScopeGuard() override;
88  virtual void abortEvaluation() override;
89  virtual void clearExceptions() override;
90  virtual ScriptContext* currentContext() const override;
91  Q_INVOKABLE virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) override;
92  Q_INVOKABLE virtual ScriptValue evaluate(const ScriptProgramPointer& program) override;
93  Q_INVOKABLE virtual ScriptValue evaluateInClosure(const ScriptValue& locals, const ScriptProgramPointer& program) override;
94  virtual ScriptValue globalObject() override;
95  virtual bool hasUncaughtException() const override;
96  virtual bool isEvaluating() const override;
97  virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) override;
98 
99  virtual ScriptValue newArray(uint length = 0) override;
100  virtual ScriptValue newArrayBuffer(const QByteArray& message) override;
101  virtual ScriptValue newFunction(ScriptEngine::FunctionSignature fun, int length = 0) override;
102  virtual ScriptValue newObject() override;
103  virtual ScriptValue newMethod(QObject* object, V8ScriptValue lifetime,
104  const QList<QMetaMethod>& metas, int numMaxParams);
105  virtual ScriptProgramPointer newProgram(const QString& sourceCode, const QString& fileName) override;
106  virtual ScriptValue newQObject(QObject *object, ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership,
107  const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions()) override;
108  virtual ScriptValue newValue(bool value) override;
109  virtual ScriptValue newValue(int value) override;
110  virtual ScriptValue newValue(uint value) override;
111  virtual ScriptValue newValue(double value) override;
112  virtual ScriptValue newValue(const QString& value) override;
113  virtual ScriptValue newValue(const QLatin1String& value) override;
114  virtual ScriptValue newValue(const char* value) override;
115  virtual ScriptValue newVariant(const QVariant& value) override;
116  virtual ScriptValue nullValue() override;
117 
118  virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") override;
119 
120  virtual bool raiseException(const QString& exception, const QString &reason = QString()) override;
121  virtual bool raiseException(const ScriptValue& exception, const QString &reason = QString()) override;
122  Q_INVOKABLE virtual void registerEnum(ScriptEngineScopeGuard* scopeGuard, const QString& enumName, QMetaEnum newEnum) override;
123  Q_INVOKABLE virtual void registerFunction(ScriptEngineScopeGuard* scopeGuard, const QString& name,
124  ScriptEngine::FunctionSignature fun,
125  int numArguments = -1) override;
126  Q_INVOKABLE virtual void registerFunction(ScriptEngineScopeGuard* scopeGuard, const QString& parent,
127  const QString& name,
128  ScriptEngine::FunctionSignature fun,
129  int numArguments = -1) override;
130  Q_INVOKABLE virtual void registerGetterSetter(ScriptEngineScopeGuard* scopeGuard, const QString& name,
131  ScriptEngine::FunctionSignature getter,
132  ScriptEngine::FunctionSignature setter,
133  const QString& parent = QString("")) override;
134  Q_INVOKABLE virtual void registerGlobalObject(ScriptEngineScopeGuard* scopeGuard, const QString& name,
135  QObject* object, ScriptEngine::ValueOwnership = ScriptEngine::QtOwnership) override;
136  virtual void setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) override;
137  virtual void setObjectName(const QString& name) override;
138  virtual bool setProperty(const char* name, const QVariant& value) override;
139  virtual void setProcessEventsInterval(int interval) override;
140  virtual QThread* thread() const override;
141  virtual void setThread(QThread* thread) override;
142  virtual ScriptValue undefinedValue() override;
143  virtual std::shared_ptr<ScriptException> uncaughtException() const override;
144  virtual void updateMemoryCost(const qint64& deltaSize) override;
145  virtual void requestCollectGarbage() override { _v8Isolate->MemoryPressureNotification(v8::MemoryPressureLevel::kCritical); }
146  virtual void processEvents() override;
147  virtual void compileTest() override;
148  virtual QString scriptValueDebugDetails(const ScriptValue &value) override;
149  QString scriptValueDebugDetailsV8(const V8ScriptValue &value);
150  virtual QString scriptValueDebugListMembers(const ScriptValue &value) override;
151  QString scriptValueDebugListMembersV8(const V8ScriptValue &v8Value);
152  virtual void logBacktrace(const QString &title = QString("")) override;
153  virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() override;
154  virtual void startCollectingObjectStatistics() override;
155  virtual void dumpHeapObjectStatistics() override;
156  virtual void startProfiling() override;
157  virtual void stopProfilingAndSave() override;
158  void scheduleValueWrapperForDeletion(ScriptValueV8Wrapper* wrapper) {_scriptValueWrappersToDelete.enqueue(wrapper);}
159  void deleteUnusedValueWrappers();
160  virtual void perManagerLoopIterationCleanup() override;
161  virtual void disconnectSignalProxies() override;
162 
163  // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways
164  inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); }
165 
166 protected: // brought over from BaseScriptEngine
167 
168 
169  // if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it.
170  // note: this is used in cases where C++ code might call into JS API methods directly
171  bool raiseException(const V8ScriptValue& exception);
172 
173  // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways
174  static bool IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method);
175 
176 public: // public non-interface methods for other QtScript-specific classes to use
177 
179  Q_INVOKABLE void registerValue(ScriptEngineScopeGuardV8* scopeGuard, const QString& valueName, V8ScriptValue value);
180 
181  // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety
182  // V8TODO
183  //inline ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; }
184 
185 public: // not for public use, but I don't like how Qt strings this along with private friend functions
186  virtual ScriptValue create(int type, const void* ptr) override;
187  virtual QVariant convert(const ScriptValue& value, int typeId) override;
188  virtual void registerCustomType(int type, ScriptEngine::MarshalFunction marshalFunc,
189  ScriptEngine::DemarshalFunction demarshalFunc) override;
190  int computeCastPenalty(const V8ScriptValue& val, int destTypeId);
191  bool castValueToVariant(const V8ScriptValue& val, QVariant& dest, int destTypeId);
192 
193  // Converts JS objects created in V8 to variants. Iterates over all properties and converts them to variants.
194  bool convertJSArrayToVariant(v8::Local<v8::Array> array, QVariant &dest);
195  bool convertJSObjectToVariant(v8::Local<v8::Object> object, QVariant &dest);
196  V8ScriptValue castVariantToValue(const QVariant& val);
197  QString valueType(const V8ScriptValue& val);
198  v8::Isolate* getIsolate() {
199  Q_ASSERT(_v8Isolate != nullptr);
200  return _v8Isolate;}
201  v8::Local<v8::Context> getContext();
202  const v8::Local<v8::Context> getConstContext() const;
203  QString formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch);
204  // Useful for debugging
205  virtual QStringList getCurrentScriptURLs() const override;
206 
207  using ObjectWrapperMap = QMap<QObject*, QWeakPointer<ScriptObjectV8Proxy>>;
208  mutable QMutex _qobjectWrapperMapProtect;
209  ObjectWrapperMap _qobjectWrapperMap;
210  // Second map, from which wrappers are removed by script engine upon deletion
211  QMap<QObject*, QSharedPointer<ScriptObjectV8Proxy>> _qobjectWrapperMapV8;
212  // V8TODO: maybe just a single map can be used instead to increase performance?
213 
214  // Sometimes ScriptValueV8Wrapper::release() is called from inside ScriptValueV8Wrapper.
215  // Then wrapper needs to be deleted in the event loop
216  QQueue<ScriptValueV8Wrapper*> _scriptValueWrappersToDelete;
217 
218  // Used by ScriptObjectV8Proxy to create JS objects referencing C++ ones
219  v8::Local<v8::ObjectTemplate> getObjectProxyTemplate();
220  v8::Local<v8::ObjectTemplate> getMethodDataTemplate();
221  v8::Local<v8::ObjectTemplate> getFunctionDataTemplate();
222  v8::Local<v8::ObjectTemplate> getVariantDataTemplate();
223  v8::Local<v8::ObjectTemplate> getVariantProxyTemplate();
224 
225  ScriptContextV8Pointer pushContext(v8::Local<v8::Context> context);
226  void popContext();
227  void storeGlobalObjectContents();
228 #ifdef OVERTE_V8_MEMORY_DEBUG
229  void incrementScriptValueCounter() { scriptValueCount++; };
230  void decrementScriptValueCounter() { scriptValueCount--; };
231  void incrementScriptValueProxyCounter() { scriptValueProxyCount++; };
232  void decrementScriptValueProxyCounter() { scriptValueProxyCount--; };
233 #endif
234 
235 protected:
236 
237  void registerSystemTypes();
238 
239 protected:
240  static QMutex _v8InitMutex;
241  static std::once_flag _v8InitOnceFlag;
242  static v8::Platform* getV8Platform();
243 
244  void setUncaughtEngineException(const QString &message, const QString& info = QString());
245  void setUncaughtException(const v8::TryCatch &tryCatch, const QString& info = QString());
246  void setUncaughtException(std::shared_ptr<ScriptException> exception);
247 
248  friend class ScriptSignalV8Proxy;
249 
250  std::shared_ptr<ScriptException> _uncaughtException;
251 
252 
253  // V8TODO: clean up isolate when script engine is destroyed?
254  v8::Isolate* _v8Isolate;
255 
256  struct CustomMarshal {
257  ScriptEngine::MarshalFunction marshalFunc;
258  ScriptEngine::DemarshalFunction demarshalFunc;
259  };
260  using CustomMarshalMap = QHash<int, CustomMarshal>;
261  using CustomPrototypeMap = QHash<int, V8ScriptValue>;
262 
263  mutable QReadWriteLock _customTypeProtect { QReadWriteLock::Recursive };
264  CustomMarshalMap _customTypes;
265  CustomPrototypeMap _customPrototypes;
266  ScriptValue _nullValue;
267  ScriptValue _undefinedValue;
268  // Current context stack. Main context is first on the list and current one is last.
269  QList<ScriptContextV8Pointer> _contexts;
270  // V8TODO: release in destructor
271  v8::Persistent<v8::Object> _globalObjectContents;
272  bool areGlobalObjectContentsStored {false};
273 
274  // Used by ScriptObjectV8Proxy to create JS objects referencing C++ ones
275  // V8TODO: release in destructor
276  v8::Persistent<v8::ObjectTemplate> _objectProxyTemplate;
277  v8::Persistent<v8::ObjectTemplate> _methodDataTemplate;
278  v8::Persistent<v8::ObjectTemplate> _functionDataTemplate;
279  v8::Persistent<v8::ObjectTemplate> _variantDataTemplate;
280  v8::Persistent<v8::ObjectTemplate> _variantProxyTemplate;
281 
282 public:
283  volatile int _memoryCorruptionIndicator = 12345678;
284 private:
285  //V8TODO
286  //ArrayBufferClass* _arrayBufferClass;
287  // Counts how many nested evaluate calls are there at a given point
288  int _evaluatingCounter;
289 #ifdef OVERTE_V8_MEMORY_DEBUG
290  std::atomic<size_t> scriptValueCount{0};
291  std::atomic<size_t> scriptValueProxyCount{0};
292 #endif
293 
294 #ifdef OVERTE_SCRIPT_USE_AFTER_DELETE_GUARD
295  bool _wasDestroyed{false};
296 #endif
297  // Pointers to profiling classes. These are valid only when profiler is running, otherwise null
298  // Smart pointer cannot be used here since profiler has private destructor
299  v8::CpuProfiler *_profiler{nullptr};
300  v8::ProfilerId _profilerId{0};
301 
302  // Set of script signal proxy pointers. Used for disconnecting signals on cleanup.
303  // V8TODO: later it would be also worth to make sure that script proxies themselves get deleted together with script engine
304  QReadWriteLock _signalProxySetLock;
305  QSet<ScriptSignalV8Proxy*> _signalProxySet;
306 
307  friend ScriptValueV8Wrapper;
308  friend ScriptSignalV8Proxy;
309 };
310 
311 // This class is used to automatically add context to script engine's context list that is used by C++ calls
312 // An instance of it needs to be created in every V8 callback
313 
315 public:
316  ContextScopeV8(ScriptEngineV8 *engine);
317  ~ContextScopeV8();
318 private:
319  bool _isContextChangeNeeded;
320  ScriptEngineV8* _engine;
321 };
322 
323 QString getFileNameFromTryCatch(v8::TryCatch &tryCatch, v8::Isolate *isolate, v8::Local<v8::Context> &context );
324 
325 #include "V8Types.h"
326 
327 #endif // hifi_ScriptEngineV8_h
328 
[V8] Implements ScriptEngine for V8 and translates calls for QScriptEngine
Definition: ScriptEngineV8.h:314
[ScriptInterface] Provides an engine-independent interface for QScriptContext
Definition: ScriptContext.h:55
[V8] Implements ScriptContext for V8 and translates calls for V8ScriptContextInfo
Definition: ScriptContextV8Wrapper.h:33
Provides an engine-independent interface for a scripting engine.
Definition: ScriptEngine.h:93
virtual ScriptValue makeError(const ScriptValue &other, const QString &type="Error")=0
Make a ScriptValue that contains an error.
virtual void logBacktrace(const QString &title)=0
Log the current backtrace.
virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program)=0
Check a program for syntax errors.
virtual ScriptValue evaluate(const QString &program, const QString &fileName=QString())=0
Runs a script.
virtual bool raiseException(const ScriptValue &exception, const QString &reason=QString())=0
Causes an exception to be raised in the currently executing script.
virtual void stopProfilingAndSave()=0
Stops collecting profiling data and saves it to a CSV file in Logs directory.
virtual bool isEvaluating() const =0
Whether a script is currently being evaluated.
virtual std::shared_ptr< ScriptException > uncaughtException() const =0
Last uncaught exception, if any.
virtual void dumpHeapObjectStatistics()=0
Prints heap statistics to a file. Collecting needs to first be started with dumpHeapObjectStatistics(...
virtual void abortEvaluation()=0
Stops the currently running script.
virtual ScriptValue evaluateInClosure(const ScriptValue &locals, const ScriptProgramPointer &program)=0
Evaluate a script in a separate environment.
virtual ScriptContext * currentContext() const =0
Context of the currently running script.
virtual void clearExceptions()=0
Clears uncaughtException and related.
virtual bool hasUncaughtException() const =0
Whether the script has an uncaught exception.
virtual void compileTest()=0
Test the underlying scripting engine.
virtual void startCollectingObjectStatistics()=0
Start collecting object statistics that can later be reported with dumpHeapObjectStatistics().
ValueOwnership
Who owns a given object.
Definition: ScriptEngine.h:109
@ QtOwnership
Object is managed by Qt.
Definition: ScriptEngine.h:114
virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics()=0
Return memory usage statistics data.
virtual void processEvents()=0
Tells the script engine to process any events it may have queued internally. Used by V8 for async fun...
virtual void startProfiling()=0
Starts collecting profiling data.
virtual void disconnectSignalProxies()=0
Cleanup function that disconnects signals connected to script proxies to avoid use-after-delete crash...
virtual ScriptValue globalObject()
Global object which holds all the functions and variables available everywhere.
Definition: ScriptEngine.h:284
virtual std::unique_ptr< ScriptEngineScopeGuard > getScopeGuard()=0
Creates a thread safety scope guard.
Manages a single scripting engine.
Definition: ScriptManager.h:282
Definition: ScriptObjectV8Proxy.h:45
[ScriptInterface] Provides an engine-independent interface for QScriptValue
Definition: ScriptValue.h:40
[V8] Implements ScriptValue for V8 and translates calls for V8ScriptValue
Definition: ScriptValueV8Wrapper.h:32