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