History

working to clean up the QtScript implementation and move towards completion of the proxy interface

Heather Anderson committed 5 years ago

Commit 2dd9d781 parent 1e018db38 files · +2240 −923Browse files

Showing the changes to libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp (1 of 38 files). Show all

Unified · Split

libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp+1037−0
@@ -0,0 +1,1037 @@
1+//
2+// ScriptEngineQtScript.cpp
3+// libraries/script-engine-qtscript/src
4+//
5+// Created by Brad Hefta-Gaub on 12/14/13.
6+// Copyright 2013 High Fidelity, Inc.
7+//
8+// Distributed under the Apache License, Version 2.0.
9+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10+//
11+
12+#include "ScriptEngineQtScript.h"
13+
14+#include <chrono>
15+#include <thread>
16+
17+#include <QtCore/QCoreApplication>
18+#include <QtCore/QEventLoop>
19+#include <QtCore/QFileInfo>
20+#include <QtCore/QTimer>
21+#include <QtCore/QThread>
22+#include <QtCore/QRegularExpression>
23+
24+#include <QtCore/QFuture>
25+#include <QtConcurrent/QtConcurrentRun>
26+
27+#include <QtWidgets/QMainWindow>
28+#include <QtWidgets/QApplication>
29+#include <QtWidgets/QMenuBar>
30+#include <QtWidgets/QMenu>
31+
32+#include <QtNetwork/QNetworkRequest>
33+#include <QtNetwork/QNetworkReply>
34+
35+#include <QtScript/QScriptContextInfo>
36+#include <QtScript/QScriptValue>
37+#include <QtScript/QScriptValueIterator>
38+
39+#include <QtScriptTools/QScriptEngineDebugger>
40+
41+#include <shared/LocalFileAccessGate.h>
42+#include <shared/QtHelpers.h>
43+#include <shared/AbstractLoggerInterface.h>
44+
45+#include <Profile.h>
46+
47+#include "../ScriptEngineLogging.h"
48+#include "../ScriptProgram.h"
49+#include "../ScriptValue.h"
50+
51+#include "ScriptContextQtWrapper.h"
52+#include "ScriptProgramQtWrapper.h"
53+#include "ScriptValueQtWrapper.h"
54+
55+static const int MAX_MODULE_ID_LENGTH { 4096 };
56+static const int MAX_DEBUG_VALUE_LENGTH { 80 };
57+
58+static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS =
59+ QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects;
60+static const QScriptValue::PropertyFlags READONLY_PROP_FLAGS { QScriptValue::ReadOnly | QScriptValue::Undeletable };
61+static const QScriptValue::PropertyFlags READONLY_HIDDEN_PROP_FLAGS { READONLY_PROP_FLAGS | QScriptValue::SkipInEnumeration };
62+
63+static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true };
64+
65+Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
66+int qfunctionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
67+
68+int scriptEnginePointerMetaID = qRegisterMetaType<ScriptEngineQtScriptPointer>();
69+
70+bool ScriptEngineQtScript::IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method) {
71+ if (QThread::currentThread() == thread) {
72+ return true;
73+ }
74+ qCCritical(scriptengine) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3")
75+ .arg(method)
76+ .arg(thread ? thread->objectName() : "(!thread)")
77+ .arg(QThread::currentThread()->objectName());
78+ qCDebug(scriptengine) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)";
79+ Q_ASSERT(false);
80+ return false;
81+}
82+
83+// engine-aware JS Error copier and factory
84+QScriptValue ScriptEngineQtScript::makeError(const QScriptValue& _other, const QString& type) {
85+ if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
86+ return QScriptEngine::nullValue();
87+ }
88+ auto other = _other;
89+ if (other.isString()) {
90+ other = QScriptEngine::newObject();
91+ other.setProperty("message", _other.toString());
92+ }
93+ auto proto = QScriptEngine::globalObject().property(type);
94+ if (!proto.isFunction()) {
95+ proto = QScriptEngine::globalObject().property(other.prototype().property("constructor").property("name").toString());
96+ }
97+ if (!proto.isFunction()) {
98+#ifdef DEBUG_JS_EXCEPTIONS
99+ qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead";
100+#endif
101+ proto = QScriptEngine::globalObject().property("Error");
102+ }
103+ if (other.engine() != this) {
104+ // JS Objects are parented to a specific script engine instance
105+ // -- this effectively ~clones it locally by routing through a QVariant and back
106+ other = QScriptEngine::toScriptValue(other.toVariant());
107+ }
108+ // ~ var err = new Error(other.message)
109+ auto err = proto.construct(QScriptValueList({ other.property("message") }));
110+
111+ // transfer over any existing properties
112+ QScriptValueIterator it(other);
113+ while (it.hasNext()) {
114+ it.next();
115+ err.setProperty(it.name(), it.value());
116+ }
117+ return err;
118+}
119+
120+ScriptValuePointer ScriptEngineQtScript::makeError(const ScriptValuePointer& _other, const QString& type) {
121+ if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
122+ return nullValue();
123+ }
124+ ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(_other);
125+ QScriptValue other;
126+ if (_other->isString()) {
127+ other = QScriptEngine::newObject();
128+ other.setProperty("message", _other->toString());
129+ } else if (unwrapped) {
130+ other = unwrapped->toQtValue();
131+ } else {
132+ other = QScriptEngine::newVariant(_other->toVariant());
133+ }
134+ QScriptValue result = makeError(other, type);
135+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
136+}
137+
138+// check syntax and when there are issues returns an actual "SyntaxError" with the details
139+ScriptValuePointer ScriptEngineQtScript::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) {
140+ if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
141+ return nullValue();
142+ }
143+ const auto syntaxCheck = checkSyntax(sourceCode);
144+ if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
145+ auto err = QScriptEngine::globalObject().property("SyntaxError").construct(QScriptValueList({ syntaxCheck.errorMessage() }));
146+ err.setProperty("fileName", fileName);
147+ err.setProperty("lineNumber", syntaxCheck.errorLineNumber());
148+ err.setProperty("expressionBeginOffset", syntaxCheck.errorColumnNumber());
149+ err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP));
150+ {
151+ const auto error = syntaxCheck.errorMessage();
152+ const auto line = QString::number(syntaxCheck.errorLineNumber());
153+ const auto column = QString::number(syntaxCheck.errorColumnNumber());
154+ // for compatibility with legacy reporting
155+ const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column);
156+ err.setProperty("formatted", message);
157+ }
158+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(err)));
159+ }
160+ return undefinedValue();
161+}
162+
163+// this pulls from the best available information to create a detailed snapshot of the current exception
164+ScriptValuePointer ScriptEngineQtScript::cloneUncaughtException(const QString& extraDetail) {
165+ if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
166+ return nullValue();
167+ }
168+ if (!hasUncaughtException()) {
169+ return nullValue();
170+ }
171+ auto exception = uncaughtException();
172+ // ensure the error object is engine-local
173+ auto err = makeError(exception);
174+
175+ // not sure why Qt does't offer uncaughtExceptionFileName -- but the line number
176+ // on its own is often useless/wrong if arbitrarily married to a filename.
177+ // when the error object already has this info, it seems to be the most reliable
178+ auto fileName = exception->property("fileName")->toString();
179+ auto lineNumber = exception->property("lineNumber")->toInt32();
180+
181+ // the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace
182+ auto backtrace = uncaughtExceptionBacktrace();
183+ if (backtrace.isEmpty()) {
184+ // fallback to the error object
185+ backtrace = exception->property("stack")->toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP);
186+ }
187+ // the ad hoc "detail" property can be used now to embed additional clues
188+ auto detail = exception->property("detail")->toString();
189+ if (detail.isEmpty()) {
190+ detail = extraDetail;
191+ } else if (!extraDetail.isEmpty()) {
192+ detail += "(" + extraDetail + ")";
193+ }
194+ if (lineNumber <= 0) {
195+ lineNumber = uncaughtExceptionLineNumber();
196+ }
197+ if (fileName.isEmpty()) {
198+ // climb the stack frames looking for something useful to display
199+ for (auto c = QScriptEngine::currentContext(); c && fileName.isEmpty(); c = c->parentContext()) {
200+ QScriptContextInfo info{ c };
201+ if (!info.fileName().isEmpty()) {
202+ // take fileName:lineNumber as a pair
203+ fileName = info.fileName();
204+ lineNumber = info.lineNumber();
205+ if (backtrace.isEmpty()) {
206+ backtrace = c->backtrace();
207+ }
208+ break;
209+ }
210+ }
211+ }
212+ err->setProperty("fileName", fileName);
213+ err->setProperty("lineNumber", lineNumber);
214+ err->setProperty("detail", detail);
215+ err->setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP));
216+
217+#ifdef DEBUG_JS_EXCEPTIONS
218+ err->setProperty("_fileName", exception.property("fileName").toString());
219+ err->setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP));
220+ err->setProperty("_lineNumber", uncaughtExceptionLineNumber());
221+#endif
222+ return err;
223+}
224+
225+bool ScriptEngineQtScript::raiseException(const QScriptValue& exception) {
226+ if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
227+ return false;
228+ }
229+ if (QScriptEngine::currentContext()) {
230+ // we have an active context / JS stack frame so throw the exception per usual
231+ QScriptEngine::currentContext()->throwValue(makeError(exception));
232+ return true;
233+ } else if(_manager) {
234+ // we are within a pure C++ stack frame (ie: being called directly by other C++ code)
235+ // in this case no context information is available so just emit the exception for reporting
236+ QScriptValue thrown = makeError(exception);
237+ emit _manager->unhandledException(ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(thrown))));
238+ }
239+ return false;
240+}
241+
242+bool ScriptEngineQtScript::maybeEmitUncaughtException(const QString& debugHint) {
243+ if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
244+ return false;
245+ }
246+ if (!isEvaluating() && hasUncaughtException() && _manager) {
247+ emit _manager->unhandledException(cloneUncaughtException(debugHint));
248+ clearExceptions();
249+ return true;
250+ }
251+ return false;
252+}
253+
254+// Lambda
255+QScriptValue ScriptEngineQtScript::newLambdaFunction(std::function<QScriptValue(QScriptContext*, ScriptEngineQtScript*)> operation,
256+ const QScriptValue& data,
257+ const QScriptEngine::ValueOwnership& ownership) {
258+ auto lambda = new Lambda(this, operation, data);
259+ auto object = QScriptEngine::newQObject(lambda, ownership);
260+ auto call = object.property("call");
261+ call.setPrototype(object); // context->callee().prototype() === Lambda QObject
262+ call.setData(data); // context->callee().data() will === data param
263+ return call;
264+}
265+QString Lambda::toString() const {
266+ return QString("[Lambda%1]").arg(data.isValid() ? " " + data.toString() : data.toString());
267+}
268+
269+Lambda::~Lambda() {
270+#ifdef DEBUG_JS_LAMBDA_FUNCS
271+ qDebug() << "~Lambda"
272+ << "this" << this;
273+#endif
274+}
275+
276+Lambda::Lambda(ScriptEngineQtScript* engine,
277+ std::function<QScriptValue(QScriptContext*, ScriptEngineQtScript*)> operation,
278+ QScriptValue data) :
279+ engine(engine),
280+ operation(operation), data(data) {
281+#ifdef DEBUG_JS_LAMBDA_FUNCS
282+ qDebug() << "Lambda" << data.toString();
283+#endif
284+}
285+QScriptValue Lambda::call() {
286+ if (!engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) {
287+ return static_cast<QScriptEngine*>(engine)->nullValue();
288+ }
289+ return operation(static_cast<QScriptEngine*>(engine)->currentContext(), engine);
290+}
291+
292+#ifdef DEBUG_JS
293+void ScriptEngineQtScript::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) {
294+ if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
295+ return;
296+ }
297+ if (!header.isEmpty()) {
298+ qCDebug(shared) << header;
299+ }
300+ if (!object.isObject()) {
301+ qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString();
302+ return;
303+ }
304+ QScriptValueIterator it(object);
305+ while (it.hasNext()) {
306+ it.next();
307+ qCDebug(shared) << it.name() << ":" << it.value().toString();
308+ }
309+ if (!footer.isEmpty()) {
310+ qCDebug(shared) << footer;
311+ }
312+}
313+#endif
314+
315+static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
316+ // assemble the message by concatenating our arguments
317+ QString message = "";
318+ for (int i = 0; i < context->argumentCount(); i++) {
319+ if (i > 0) {
320+ message += " ";
321+ }
322+ message += context->argument(i).toString();
323+ }
324+
325+ // was this generated by a script engine? If we don't recognize it then send the message and exit
326+ ScriptEngineQtScript* scriptEngine = qobject_cast<ScriptEngineQtScript*>(engine);
327+ if (!scriptEngine) {
328+ qCDebug(scriptengine_script, "%s", qUtf8Printable(message));
329+ return QScriptValue();
330+ }
331+
332+ QString filename;
333+ auto scriptManager = scriptEngine->manager();
334+ if (scriptManager) {
335+ filename = scriptManager->getFilename();
336+ }
337+
338+ // This message was sent by one of our script engines, let's try to see if we can find the source.
339+ // Note that the first entry in the backtrace should be "print" and is somewhat useless to us
340+ AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get();
341+ if (loggerInterface && loggerInterface->showSourceDebugging()) {
342+ QScriptContext* userContext = context;
343+ while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) {
344+ userContext = userContext->parentContext();
345+ }
346+ QString location;
347+ if (userContext) {
348+ QScriptContextInfo contextInfo(userContext);
349+ QString fileName = contextInfo.fileName();
350+ int lineNumber = contextInfo.lineNumber();
351+ QString functionName = contextInfo.functionName();
352+
353+ location = functionName;
354+ if (!fileName.isEmpty()) {
355+ if (location.isEmpty()) {
356+ location = fileName;
357+ } else {
358+ location = QString("%1 at %2").arg(location).arg(fileName);
359+ }
360+ }
361+ if (lineNumber != -1) {
362+ location = QString("%1:%2").arg(location).arg(lineNumber);
363+ }
364+ }
365+ if (location.isEmpty()) {
366+ location = filename;
367+ }
368+
369+ // give the script engine a chance to notify the system about this message
370+ scriptEngine->print(message);
371+
372+ // send the message to debug log
373+ qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(location), qUtf8Printable(message));
374+ } else {
375+ scriptEngine->print(message);
376+ // prefix the script engine name to help disambiguate messages in the main debug log
377+ qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(filename), qUtf8Printable(message));
378+ }
379+
380+ return QScriptValue();
381+}
382+
383+ScriptEngineQtScript::ScriptEngineQtScript(ScriptManager* scriptManager) :
384+ QScriptEngine(),
385+ _manager(scriptManager),
386+ _arrayBufferClass(new ArrayBufferClass(this))
387+{
388+ if (_manager) {
389+ connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) {
390+ if (hasUncaughtException()) {
391+ // the engine's uncaughtException() seems to produce much better stack traces here
392+ emit _manager->unhandledException(cloneUncaughtException("signalHandlerException"));
393+ clearExceptions();
394+ } else {
395+ // ... but may not always be available -- so if needed we fallback to the passed exception
396+ QScriptValue thrown = makeError(exception);
397+ emit _manager->unhandledException(ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(thrown))));
398+ }
399+ }, Qt::DirectConnection);
400+ }
401+
402+ QScriptValue null = QScriptEngine::nullValue();
403+ _nullValue = ScriptValuePointer(new ScriptValueQtWrapper(const_cast<ScriptEngineQtScript*>(this), std::move(null)));
404+
405+ QScriptValue undefined = QScriptEngine::undefinedValue();
406+ _undefinedValue = ScriptValuePointer(new ScriptValueQtWrapper(const_cast<ScriptEngineQtScript*>(this), std::move(undefined)));
407+
408+ QScriptEngine::setProcessEventsInterval(MSECS_PER_SECOND);
409+}
410+
411+bool ScriptEngineQtScript::isDebugMode() const {
412+#if defined(DEBUG)
413+ return true;
414+#else
415+ return false;
416+#endif
417+}
418+
419+ScriptEngineQtScript::~ScriptEngineQtScript() {}
420+
421+void ScriptEngineQtScript::disconnectNonEssentialSignals() {
422+ disconnect();
423+ QThread* workerThread;
424+ // Ensure the thread should be running, and does exist
425+ if (_isRunning && _isThreaded && (workerThread = QScriptEngine::thread())) {
426+ connect(this, &QObject::destroyed, workerThread, &QThread::quit);
427+ connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
428+ }
429+}
430+
431+void ScriptEngineQtScript::executeOnScriptThread(std::function<void()> function, const Qt::ConnectionType& type ) {
432+ if (QThread::currentThread() != QScriptEngine::thread()) {
433+ QMetaObject::invokeMethod(this, "executeOnScriptThread", type, Q_ARG(std::function<void()>, function));
434+ return;
435+ }
436+
437+ function();
438+}
439+
440+void ScriptEngineQtScript::registerEnum(const QString& enumName, QMetaEnum newEnum) {
441+ if (!newEnum.isValid()) {
442+ qCCritical(scriptengine) << "registerEnum called on invalid enum with name " << enumName;
443+ return;
444+ }
445+
446+ for (int i = 0; i < newEnum.keyCount(); i++) {
447+ const char* keyName = newEnum.key(i);
448+ QString fullName = enumName + "." + keyName;
449+ registerValue(fullName, newEnum.keyToValue(keyName));
450+ }
451+}
452+
453+void ScriptEngineQtScript::registerValue(const QString& valueName, QScriptValue value) {
454+ if (QThread::currentThread() != QScriptEngine::thread()) {
455+#ifdef THREAD_DEBUGGING
456+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
457+#endif
458+ QMetaObject::invokeMethod(this, "registerValue",
459+ Q_ARG(const QString&, valueName),
460+ Q_ARG(QScriptValue, value));
461+ return;
462+ }
463+
464+ QStringList pathToValue = valueName.split(".");
465+ int partsToGo = pathToValue.length();
466+ QScriptValue partObject = globalObject();
467+
468+ for (const auto& pathPart : pathToValue) {
469+ partsToGo--;
470+ if (!partObject.property(pathPart).isValid()) {
471+ if (partsToGo > 0) {
472+ //QObject *object = new QObject;
473+ QScriptValue partValue = newArray(); //newQObject(object, QScriptEngine::ScriptOwnership);
474+ partObject.setProperty(pathPart, partValue);
475+ } else {
476+ partObject.setProperty(pathPart, value);
477+ }
478+ }
479+ partObject = partObject.property(pathPart);
480+ }
481+}
482+
483+void ScriptEngineQtScript::registerGlobalObject(const QString& name, QObject* object) {
484+ if (QThread::currentThread() != QScriptEngine::thread()) {
485+#ifdef THREAD_DEBUGGING
486+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
487+#endif
488+ QMetaObject::invokeMethod(this, "registerGlobalObject",
489+ Q_ARG(const QString&, name),
490+ Q_ARG(QObject*, object));
491+ return;
492+ }
493+#ifdef THREAD_DEBUGGING
494+ qCDebug(scriptengine) << "ScriptEngineQtScript::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name;
495+#endif
496+
497+ if (!QScriptEngine::globalObject().property(name).isValid()) {
498+ if (object) {
499+ QScriptValue value = QScriptEngine::newQObject(object, QScriptEngine::QtOwnership, DEFAULT_QOBJECT_WRAP_OPTIONS);
500+ QScriptEngine::globalObject().setProperty(name, value);
501+ } else {
502+ QScriptEngine::globalObject().setProperty(name, QScriptValue());
503+ }
504+ }
505+}
506+
507+void ScriptEngineQtScript::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
508+ if (QThread::currentThread() != QScriptEngine::thread()) {
509+#ifdef THREAD_DEBUGGING
510+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
511+#endif
512+ QMetaObject::invokeMethod(this, "registerFunction",
513+ Q_ARG(const QString&, name),
514+ Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
515+ Q_ARG(int, numArguments));
516+ return;
517+ }
518+#ifdef THREAD_DEBUGGING
519+ qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name;
520+#endif
521+
522+ QScriptValue scriptFun = QScriptEngine::newFunction(functionSignature, numArguments);
523+ QScriptEngine::globalObject().setProperty(name, scriptFun);
524+}
525+
526+void ScriptEngineQtScript::registerFunction(const QString& name, ScriptEngine::FunctionSignature functionSignature, int numArguments) {
527+ if (QThread::currentThread() != QScriptEngine::thread()) {
528+#ifdef THREAD_DEBUGGING
529+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
530+#endif
531+ QMetaObject::invokeMethod(this, "registerFunction",
532+ Q_ARG(const QString&, name),
533+ Q_ARG(ScriptEngine::FunctionSignature, functionSignature),
534+ Q_ARG(int, numArguments));
535+ return;
536+ }
537+#ifdef THREAD_DEBUGGING
538+ qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name;
539+#endif
540+
541+ auto scriptFun = newFunction(functionSignature, numArguments);
542+ globalObject()->setProperty(name, scriptFun);
543+}
544+
545+void ScriptEngineQtScript::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
546+ if (QThread::currentThread() != QScriptEngine::thread()) {
547+#ifdef THREAD_DEBUGGING
548+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name;
549+#endif
550+ QMetaObject::invokeMethod(this, "registerFunction",
551+ Q_ARG(const QString&, name),
552+ Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
553+ Q_ARG(int, numArguments));
554+ return;
555+ }
556+#ifdef THREAD_DEBUGGING
557+ qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name;
558+#endif
559+
560+ QScriptValue object = QScriptEngine::globalObject().property(parent);
561+ if (object.isValid()) {
562+ QScriptValue scriptFun = QScriptEngine::newFunction(functionSignature, numArguments);
563+ object.setProperty(name, scriptFun);
564+ }
565+}
566+
567+void ScriptEngineQtScript::registerFunction(const QString& parent, const QString& name, ScriptEngine::FunctionSignature functionSignature, int numArguments) {
568+ if (QThread::currentThread() != QScriptEngine::thread()) {
569+#ifdef THREAD_DEBUGGING
570+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name;
571+#endif
572+ QMetaObject::invokeMethod(this, "registerFunction",
573+ Q_ARG(const QString&, name),
574+ Q_ARG(ScriptEngine::FunctionSignature, functionSignature),
575+ Q_ARG(int, numArguments));
576+ return;
577+ }
578+#ifdef THREAD_DEBUGGING
579+ qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name;
580+#endif
581+
582+ auto object = globalObject()->property(parent);
583+ if (object->isValid()) {
584+ auto scriptFun = newFunction(functionSignature, numArguments);
585+ object->setProperty(name, scriptFun);
586+ }
587+}
588+
589+void ScriptEngineQtScript::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
590+ QScriptEngine::FunctionSignature setter, const QString& parent) {
591+ if (QThread::currentThread() != QScriptEngine::thread()) {
592+#ifdef THREAD_DEBUGGING
593+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
594+ " name:" << name << "parent:" << parent;
595+#endif
596+ QMetaObject::invokeMethod(this, "registerGetterSetter",
597+ Q_ARG(const QString&, name),
598+ Q_ARG(QScriptEngine::FunctionSignature, getter),
599+ Q_ARG(QScriptEngine::FunctionSignature, setter),
600+ Q_ARG(const QString&, parent));
601+ return;
602+ }
603+#ifdef THREAD_DEBUGGING
604+ qCDebug(scriptengine) << "ScriptEngineQtScript::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent;
605+#endif
606+
607+ QScriptValue setterFunction = QScriptEngine::newFunction(setter, 1);
608+ QScriptValue getterFunction = QScriptEngine::newFunction(getter);
609+
610+ if (!parent.isNull() && !parent.isEmpty()) {
611+ QScriptValue object = QScriptEngine::globalObject().property(parent);
612+ if (object.isValid()) {
613+ object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
614+ object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
615+ }
616+ } else {
617+ QScriptEngine::globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);
618+ QScriptEngine::globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);
619+ }
620+}
621+
622+void ScriptEngineQtScript::registerGetterSetter(const QString& name, ScriptEngine::FunctionSignature getter,
623+ ScriptEngine::FunctionSignature setter, const QString& parent) {
624+ if (QThread::currentThread() != QScriptEngine::thread()) {
625+#ifdef THREAD_DEBUGGING
626+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
627+ " name:" << name << "parent:" << parent;
628+#endif
629+ QMetaObject::invokeMethod(this, "registerGetterSetter",
630+ Q_ARG(const QString&, name),
631+ Q_ARG(ScriptEngine::FunctionSignature, getter),
632+ Q_ARG(ScriptEngine::FunctionSignature, setter),
633+ Q_ARG(const QString&, parent));
634+ return;
635+ }
636+#ifdef THREAD_DEBUGGING
637+ qCDebug(scriptengine) << "ScriptEngineQtScript::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent;
638+#endif
639+
640+ auto setterFunction = newFunction(setter, 1);
641+ auto getterFunction = newFunction(getter);
642+
643+ if (!parent.isNull() && !parent.isEmpty()) {
644+ auto object = globalObject()->property(parent);
645+ if (object->isValid()) {
646+ object->setProperty(name, setterFunction, ScriptValue::PropertySetter);
647+ object->setProperty(name, getterFunction, ScriptValue::PropertyGetter);
648+ }
649+ } else {
650+ globalObject()->setProperty(name, setterFunction, ScriptValue::PropertySetter);
651+ globalObject()->setProperty(name, getterFunction, ScriptValue::PropertyGetter);
652+ }
653+}
654+
655+ScriptValuePointer ScriptEngineQtScript::evaluateInClosure(const ScriptValuePointer& _closure,
656+ const ScriptProgramPointer& _program) {
657+ PROFILE_RANGE(script, "evaluateInClosure");
658+ if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
659+ return nullValue();
660+ }
661+ ScriptProgramQtWrapper* unwrappedProgram = ScriptProgramQtWrapper::unwrap(_program);
662+ if (unwrappedProgram == nullptr) {
663+ return nullValue();
664+ }
665+ const QScriptProgram& program = unwrappedProgram->toQtValue();
666+
667+ const auto fileName = program.fileName();
668+ const auto shortName = QUrl(fileName).fileName();
669+
670+ ScriptValueQtWrapper* unwrappedClosure = ScriptValueQtWrapper::unwrap(_closure);
671+ if (unwrappedClosure == nullptr) {
672+ return nullValue();
673+ }
674+ const QScriptValue& closure = unwrappedClosure->toQtValue();
675+
676+ QScriptValue oldGlobal;
677+ auto global = closure.property("global");
678+ if (global.isObject()) {
679+#ifdef DEBUG_JS
680+ qCDebug(shared) << " setting global = closure.global" << shortName;
681+#endif
682+ oldGlobal = QScriptEngine::globalObject();
683+ setGlobalObject(global);
684+ }
685+
686+ auto context = pushContext();
687+
688+ auto thiz = closure.property("this");
689+ if (thiz.isObject()) {
690+#ifdef DEBUG_JS
691+ qCDebug(shared) << " setting this = closure.this" << shortName;
692+#endif
693+ context->setThisObject(thiz);
694+ }
695+
696+ context->pushScope(closure);
697+#ifdef DEBUG_JS
698+ qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
699+#endif
700+ ScriptValuePointer result;
701+ {
702+ auto qResult = QScriptEngine::evaluate(program);
703+
704+ if (hasUncaughtException()) {
705+ auto err = cloneUncaughtException(__FUNCTION__);
706+#ifdef DEBUG_JS_EXCEPTIONS
707+ qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();
708+ err.setProperty("_result", result);
709+#endif
710+ result = err;
711+ } else {
712+ result = ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(qResult)));
713+ }
714+ }
715+#ifdef DEBUG_JS
716+ qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
717+#endif
718+ popContext();
719+
720+ if (oldGlobal.isValid()) {
721+#ifdef DEBUG_JS
722+ qCDebug(shared) << " restoring global" << shortName;
723+#endif
724+ setGlobalObject(oldGlobal);
725+ }
726+
727+ return result;
728+}
729+
730+ScriptValuePointer ScriptEngineQtScript::evaluate(const QString& sourceCode, const QString& fileName) {
731+ if (_manager && _manager->isStopped()) {
732+ return undefinedValue(); // bail early
733+ }
734+
735+ if (QThread::currentThread() != QScriptEngine::thread()) {
736+ ScriptValuePointer result;
737+#ifdef THREAD_DEBUGGING
738+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
739+ "sourceCode:" << sourceCode << " fileName:" << fileName;
740+#endif
741+ BLOCKING_INVOKE_METHOD(this, "evaluate",
742+ Q_RETURN_ARG(ScriptValuePointer, result),
743+ Q_ARG(const QString&, sourceCode),
744+ Q_ARG(const QString&, fileName));
745+ return result;
746+ }
747+
748+ // Check syntax
749+ auto syntaxError = lintScript(sourceCode, fileName);
750+ if (syntaxError->isError()) {
751+ if (!isEvaluating()) {
752+ syntaxError->setProperty("detail", "evaluate");
753+ }
754+ raiseException(syntaxError);
755+ maybeEmitUncaughtException("lint");
756+ return syntaxError;
757+ }
758+ QScriptProgram program { sourceCode, fileName, 1 };
759+ if (program.isNull()) {
760+ // can this happen?
761+ auto err = makeError(newValue("could not create QScriptProgram for " + fileName));
762+ raiseException(err);
763+ maybeEmitUncaughtException("compile");
764+ return err;
765+ }
766+
767+ QScriptValue result = QScriptEngine::evaluate(program);
768+ maybeEmitUncaughtException("evaluate");
769+
770+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
771+}
772+
773+Q_INVOKABLE ScriptValuePointer ScriptEngineQtScript::evaluate(const ScriptProgramPointer& program) {
774+ if (_manager && _manager->isStopped()) {
775+ return undefinedValue(); // bail early
776+ }
777+
778+ if (QThread::currentThread() != QScriptEngine::thread()) {
779+ ScriptValuePointer result;
780+#ifdef THREAD_DEBUGGING
781+ qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
782+ "sourceCode:" << sourceCode << " fileName:" << fileName;
783+#endif
784+ BLOCKING_INVOKE_METHOD(this, "evaluate",
785+ Q_RETURN_ARG(ScriptValuePointer, result),
786+ Q_ARG(const ScriptProgramPointer&, program));
787+ return result;
788+ }
789+
790+ ScriptProgramQtWrapper* unwrapped = ScriptProgramQtWrapper::unwrap(program);
791+ if (!unwrapped) {
792+ auto err = makeError(newValue("could not unwrap program"));
793+ raiseException(err);
794+ maybeEmitUncaughtException("compile");
795+ return err;
796+ }
797+
798+ const QScriptProgram& qProgram = unwrapped->toQtValue();
799+ if (qProgram.isNull()) {
800+ // can this happen?
801+ auto err = makeError(newValue("requested program is empty"));
802+ raiseException(err);
803+ maybeEmitUncaughtException("compile");
804+ return err;
805+ }
806+
807+ QScriptValue result = QScriptEngine::evaluate(qProgram);
808+ maybeEmitUncaughtException("evaluate");
809+
810+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
811+}
812+
813+
814+void ScriptEngineQtScript::updateMemoryCost(const qint64& deltaSize) {
815+ if (deltaSize > 0) {
816+ // We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only.
817+#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
818+ reportAdditionalMemoryCost(deltaSize);
819+#endif
820+ }
821+}
822+
823+void ScriptEngineQtScript::print(const QString& message) {
824+ QString filename;
825+ auto scriptManager = manager();
826+ if (scriptManager) {
827+ filename = scriptManager->getFilename();
828+ }
829+
830+ emit printedMessage(message, filename);
831+}
832+
833+
834+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
835+// ScriptEngine implementation
836+
837+ScriptValuePointer ScriptEngineQtScript::globalObject() const {
838+ QScriptValue global = QScriptEngine::globalObject(); // can't cache the value as it may change
839+ return ScriptValuePointer(new ScriptValueQtWrapper(const_cast < ScriptEngineQtScript*>(this), std::move(global)));
840+}
841+
842+ScriptManager* ScriptEngineQtScript::manager() const {
843+ return _manager;
844+}
845+
846+ScriptValuePointer ScriptEngineQtScript::newArray(uint length) {
847+ QScriptValue result = QScriptEngine::newArray(length);
848+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
849+}
850+
851+ScriptValuePointer ScriptEngineQtScript::newArrayBuffer(const QByteArray& message) {
852+ QScriptValue data = QScriptEngine::newVariant(QVariant::fromValue(message));
853+ QScriptValue ctor = QScriptEngine::globalObject().property("ArrayBuffer");
854+ auto array = qscriptvalue_cast<ArrayBufferClass*>(ctor.data());
855+ if (!array) {
856+ return undefinedValue();
857+ }
858+ QScriptValue result = QScriptEngine::newObject(array, data);
859+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
860+}
861+
862+ScriptValuePointer ScriptEngineQtScript::newObject() {
863+ QScriptValue result = QScriptEngine::newObject();
864+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
865+}
866+
867+ScriptProgramPointer ScriptEngineQtScript::newProgram(const QString& sourceCode, const QString& fileName) {
868+ QScriptProgram result(sourceCode, fileName);
869+ return ScriptProgramPointer(new ScriptProgramQtWrapper(this, result));
870+}
871+
872+ScriptValuePointer ScriptEngineQtScript::newQObject(QObject* obj,
873+ ScriptEngine::ValueOwnership ownership,
874+ const ScriptEngine::QObjectWrapOptions& options) {
875+ QScriptValue result = QScriptEngine::newQObject(obj, static_cast<QScriptEngine::ValueOwnership>(ownership),
876+ (QScriptEngine::QObjectWrapOptions)(int)options);
877+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
878+}
879+
880+ScriptValuePointer ScriptEngineQtScript::newValue(bool value) {
881+ QScriptValue result(this, value);
882+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
883+}
884+
885+ScriptValuePointer ScriptEngineQtScript::newValue(int value) {
886+ QScriptValue result(this, value);
887+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
888+}
889+
890+ScriptValuePointer ScriptEngineQtScript::newValue(uint value) {
891+ QScriptValue result(this, value);
892+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
893+}
894+
895+ScriptValuePointer ScriptEngineQtScript::newValue(double value) {
896+ QScriptValue result(this, value);
897+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
898+}
899+
900+ScriptValuePointer ScriptEngineQtScript::newValue(const QString& value) {
901+ QScriptValue result(this, value);
902+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
903+}
904+
905+ScriptValuePointer ScriptEngineQtScript::newValue(const QLatin1String& value) {
906+ QScriptValue result(this, value);
907+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
908+}
909+
910+ScriptValuePointer ScriptEngineQtScript::newValue(const char* value) {
911+ QScriptValue result(this, value);
912+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
913+}
914+
915+ScriptValuePointer ScriptEngineQtScript::newVariant(const QVariant& value) {
916+ QScriptValue result = QScriptEngine::newVariant(value);
917+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
918+}
919+
920+ScriptValuePointer ScriptEngineQtScript::nullValue() {
921+ return _nullValue;
922+}
923+
924+void ScriptEngineQtScript::setDefaultPrototype(int metaTypeId, const ScriptValuePointer& prototype){
925+ ScriptValueQtWrapper* unwrappedPrototype = ScriptValueQtWrapper::unwrap(prototype);
926+ if (unwrappedPrototype) {
927+ QScriptEngine::setDefaultPrototype(metaTypeId, unwrappedPrototype->toQtValue());
928+ }
929+}
930+
931+ScriptValuePointer ScriptEngineQtScript::undefinedValue() {
932+ return _undefinedValue;
933+}
934+
935+void ScriptEngineQtScript::abortEvaluation() {
936+ QScriptEngine::abortEvaluation();
937+}
938+
939+void ScriptEngineQtScript::clearExceptions() {
940+ QScriptEngine::clearExceptions();
941+}
942+
943+ScriptContext* ScriptEngineQtScript::currentContext() const {
944+ QScriptContext* localCtx = QScriptEngine::currentContext();
945+ if (!localCtx) {
946+ return nullptr;
947+ }
948+ if (!_currContext || _currContext->toQtValue() != localCtx) {
949+ _currContext = ScriptContextQtPointer(new ScriptContextQtWrapper(const_cast<ScriptEngineQtScript*>(this), localCtx));
950+ }
951+ return _currContext.get();
952+}
953+
954+bool ScriptEngineQtScript::hasUncaughtException() const {
955+ return QScriptEngine::hasUncaughtException();
956+}
957+
958+bool ScriptEngineQtScript::isEvaluating() const {
959+ return QScriptEngine::isEvaluating();
960+}
961+
962+ScriptValuePointer ScriptEngineQtScript::newFunction(ScriptEngine::FunctionSignature fun, int length) {
963+ auto innerFunc = [](QScriptContext* _context, QScriptEngine* _engine) -> QScriptValue {
964+ auto callee = _context->callee();
965+ QVariant funAddr = callee.property("_func").toVariant();
966+ ScriptEngine::FunctionSignature fun = reinterpret_cast<ScriptEngine::FunctionSignature>(funAddr.toULongLong());
967+ ScriptEngineQtScript* engine = static_cast<ScriptEngineQtScript*>(_engine);
968+ ScriptContextQtWrapper context(engine, _context);
969+ ScriptValuePointer result = fun(&context, engine);
970+ ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(result);
971+ return unwrapped ? unwrapped->toQtValue() : QScriptValue();
972+ };
973+
974+ QScriptValue result = QScriptEngine::newFunction(innerFunc, length);
975+ auto funAddr = QScriptEngine::newVariant(QVariant(reinterpret_cast<qulonglong>(fun)));
976+ result.setProperty("_func", funAddr, QScriptValue::PropertyFlags(QScriptValue::ReadOnly + QScriptValue::Undeletable + QScriptValue::SkipInEnumeration));
977+ return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
978+}
979+
980+void ScriptEngineQtScript::setObjectName(const QString& name) {
981+ QScriptEngine::setObjectName(name);
982+}
983+
984+bool ScriptEngineQtScript::setProperty(const char* name, const QVariant& value) {
985+ return QScriptEngine::setProperty(name, value);
986+}
987+
988+void ScriptEngineQtScript::setProcessEventsInterval(int interval) {
989+ QScriptEngine::setProcessEventsInterval(interval);
990+}
991+
992+QThread* ScriptEngineQtScript::thread() const {
993+ return QScriptEngine::thread();
994+}
995+
996+ScriptValuePointer ScriptEngineQtScript::uncaughtException() const {
997+ QScriptValue result = QScriptEngine::uncaughtException();
998+ return ScriptValuePointer(new ScriptValueQtWrapper(const_cast<ScriptEngineQtScript*>(this), std::move(result)));
999+}
1000+
1001+QStringList ScriptEngineQtScript::uncaughtExceptionBacktrace() const {
1002+ return QScriptEngine::uncaughtExceptionBacktrace();
1003+}
1004+
1005+int ScriptEngineQtScript::uncaughtExceptionLineNumber() const {
1006+ return QScriptEngine::uncaughtExceptionLineNumber();
1007+}
1008+
1009+bool ScriptEngineQtScript::raiseException(const ScriptValuePointer& exception) {
1010+ ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(exception);
1011+ QScriptValue qException = unwrapped ? unwrapped->toQtValue() : QScriptEngine::newVariant(exception->toVariant());
1012+ return raiseException(qException);
1013+}
1014+
1015+ScriptValuePointer ScriptEngineQtScript::create(int type, const void* ptr) {
1016+ QScriptValue result = qScriptValueFromValue_helper(this, type, ptr);
1017+ return ScriptValuePointer(new ScriptValueQtWrapper(const_cast<ScriptEngineQtScript*>(this), std::move(result)));
1018+}
1019+
1020+bool ScriptEngineQtScript::convert(const ScriptValuePointer& value, int type, void* ptr) {
1021+ ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(value);
1022+ if (unwrapped == nullptr) {
1023+ return false;
1024+ }
1025+ return qscriptvalue_cast_helper(unwrapped->toQtValue(), type, ptr);
1026+}
1027+/*
1028+void ScriptEngineQtScript::registerCustomType(int type,
1029+ ScriptEngine::MarshalFunction mf,
1030+ ScriptEngine::DemarshalFunction df,
1031+ const ScriptValuePointer& prototype) {
1032+ ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(prototype);
1033+ if (unwrapped != nullptr) {
1034+ qScriptRegisterMetaType_helper(this, type, mf, df, unwrapped->toQtValue());
1035+ }
1036+}
1037+*/