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
55static const int MAX_MODULE_ID_LENGTH { 4096 };
56static const int MAX_DEBUG_VALUE_LENGTH { 80 };
57
58static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS =
59 QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects;
60static const QScriptValue::PropertyFlags READONLY_PROP_FLAGS { QScriptValue::ReadOnly | QScriptValue::Undeletable };
61static const QScriptValue::PropertyFlags READONLY_HIDDEN_PROP_FLAGS { READONLY_PROP_FLAGS | QScriptValue::SkipInEnumeration };
62
63static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true };
64
65Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
66int qfunctionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
67
68int scriptEnginePointerMetaID = qRegisterMetaType<ScriptEngineQtScriptPointer>();
69
70bool 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
84QScriptValue 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
120ScriptValuePointer 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
139ScriptValuePointer 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
164ScriptValuePointer 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
225bool 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
242bool 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
255QScriptValue 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}
265QString Lambda::toString() const {
266 return QString("[Lambda%1]").arg(data.isValid() ? " " + data.toString() : data.toString());
267}
268
269Lambda::~Lambda() {
270#ifdef DEBUG_JS_LAMBDA_FUNCS
271 qDebug() << "~Lambda"
272 << "this" << this;
273#endif
274}
275
276Lambda::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}
285QScriptValue 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
293void 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
315static 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
383ScriptEngineQtScript::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
411bool ScriptEngineQtScript::isDebugMode() const {
412#if defined(DEBUG)
413 return true;
414#else
415 return false;
416#endif
417}
418
419ScriptEngineQtScript::~ScriptEngineQtScript() {}
420
421void 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
431void 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
440void 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
453void 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
483void 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
507void 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
526void 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
545void 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
567void 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
589void 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
622void 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
655ScriptValuePointer 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
730ScriptValuePointer 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
773Q_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
814void 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
823void 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
837ScriptValuePointer 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
842ScriptManager* ScriptEngineQtScript::manager() const {
843 return _manager;
844}
845
846ScriptValuePointer ScriptEngineQtScript::newArray(uint length) {
847 QScriptValue result = QScriptEngine::newArray(length);
848 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
849}
850
851ScriptValuePointer 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
862ScriptValuePointer ScriptEngineQtScript::newObject() {
863 QScriptValue result = QScriptEngine::newObject();
864 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
865}
866
867ScriptProgramPointer ScriptEngineQtScript::newProgram(const QString& sourceCode, const QString& fileName) {
868 QScriptProgram result(sourceCode, fileName);
869 return ScriptProgramPointer(new ScriptProgramQtWrapper(this, result));
870}
871
872ScriptValuePointer 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
880ScriptValuePointer ScriptEngineQtScript::newValue(bool value) {
881 QScriptValue result(this, value);
882 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
883}
884
885ScriptValuePointer ScriptEngineQtScript::newValue(int value) {
886 QScriptValue result(this, value);
887 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
888}
889
890ScriptValuePointer ScriptEngineQtScript::newValue(uint value) {
891 QScriptValue result(this, value);
892 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
893}
894
895ScriptValuePointer ScriptEngineQtScript::newValue(double value) {
896 QScriptValue result(this, value);
897 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
898}
899
900ScriptValuePointer ScriptEngineQtScript::newValue(const QString& value) {
901 QScriptValue result(this, value);
902 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
903}
904
905ScriptValuePointer ScriptEngineQtScript::newValue(const QLatin1String& value) {
906 QScriptValue result(this, value);
907 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
908}
909
910ScriptValuePointer ScriptEngineQtScript::newValue(const char* value) {
911 QScriptValue result(this, value);
912 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
913}
914
915ScriptValuePointer ScriptEngineQtScript::newVariant(const QVariant& value) {
916 QScriptValue result = QScriptEngine::newVariant(value);
917 return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result)));
918}
919
920ScriptValuePointer ScriptEngineQtScript::nullValue() {
921 return _nullValue;
922}
923
924void 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
931ScriptValuePointer ScriptEngineQtScript::undefinedValue() {
932 return _undefinedValue;
933}
934
935void ScriptEngineQtScript::abortEvaluation() {
936 QScriptEngine::abortEvaluation();
937}
938
939void ScriptEngineQtScript::clearExceptions() {
940 QScriptEngine::clearExceptions();
941}
942
943ScriptContext* 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
954bool ScriptEngineQtScript::hasUncaughtException() const {
955 return QScriptEngine::hasUncaughtException();
956}
957
958bool ScriptEngineQtScript::isEvaluating() const {
959 return QScriptEngine::isEvaluating();
960}
961
962ScriptValuePointer 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
980void ScriptEngineQtScript::setObjectName(const QString& name) {
981 QScriptEngine::setObjectName(name);
982}
983
984bool ScriptEngineQtScript::setProperty(const char* name, const QVariant& value) {
985 return QScriptEngine::setProperty(name, value);
986}
987
988void ScriptEngineQtScript::setProcessEventsInterval(int interval) {
989 QScriptEngine::setProcessEventsInterval(interval);
990}
991
992QThread* ScriptEngineQtScript::thread() const {
993 return QScriptEngine::thread();
994}
995
996ScriptValuePointer ScriptEngineQtScript::uncaughtException() const {
997 QScriptValue result = QScriptEngine::uncaughtException();
998 return ScriptValuePointer(new ScriptValueQtWrapper(const_cast<ScriptEngineQtScript*>(this), std::move(result)));
999}
1000
1001QStringList ScriptEngineQtScript::uncaughtExceptionBacktrace() const {
1002 return QScriptEngine::uncaughtExceptionBacktrace();
1003}
1004
1005int ScriptEngineQtScript::uncaughtExceptionLineNumber() const {
1006 return QScriptEngine::uncaughtExceptionLineNumber();
1007}
1008
1009bool 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
1015ScriptValuePointer 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
1020bool 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/*
1028void 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*/