History

* Update per 21114-part2 changes. * Add explicit thread safety guards. * Add Entities.queryPropertyMetdata for unit testing Entity script module support. * Cleanup / commenting pass.

humbletim committed 9 years ago

Commit 40ba8181 parent 9b096518 files · +439 −158Browse files

Showing the changes to libraries/shared/src/BaseScriptEngine.cpp (1 of 8 files). Show all

Unified · Split

libraries/shared/src/BaseScriptEngine.cpp+101−28
@@ -10,6 +10,7 @@ · 9 unmodified lines
10//10//
11 11
12#include "BaseScriptEngine.h"12#include "BaseScriptEngine.h"
13#include "SharedLogging.h"
13 14
14#include <QtCore/QString>15#include <QtCore/QString>
15#include <QtCore/QThread>16#include <QtCore/QThread>
@@ -18,18 +19,26 @@ · 2 unmodified lines
18#include <QtScript/QScriptValueIterator>19#include <QtScript/QScriptValueIterator>
19#include <QtScript/QScriptContextInfo>20#include <QtScript/QScriptContextInfo>
20 21
21#include "ScriptEngineLogging.h"
22#include "Profile.h"22#include "Profile.h"
23 23
24const QString BaseScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS {
25 "com.highfidelity.experimental.enableExtendedJSExceptions"
26};
27
28const QString BaseScriptEngine::SCRIPT_EXCEPTION_FORMAT { "[%0] %1 in %2:%3" };24const QString BaseScriptEngine::SCRIPT_EXCEPTION_FORMAT { "[%0] %1 in %2:%3" };
29const QString BaseScriptEngine::SCRIPT_BACKTRACE_SEP { "\n " };25const QString BaseScriptEngine::SCRIPT_BACKTRACE_SEP { "\n " };
30 26
27bool BaseScriptEngine::IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method) {
28 if (QThread::currentThread() == thread) {
29 return true;
30 }
31 qCCritical(shared) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3")
32 .arg(method).arg(thread ? thread->objectName() : "(!thread)").arg(QThread::currentThread()->objectName());
33 qCDebug(shared) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)";
34 return false;
35}
36
31// engine-aware JS Error copier and factory37// engine-aware JS Error copier and factory
32QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QString& type) {38QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QString& type) {
39 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
40 return unboundNullValue();
41 }
33 auto other = _other;42 auto other = _other;
34 if (other.isString()) {43 if (other.isString()) {
35 other = newObject();44 other = newObject();
@@ -41,7 +50,7 @@ QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QStri · 5 unmodified lines
41 }50 }
42 if (!proto.isFunction()) {51 if (!proto.isFunction()) {
43#ifdef DEBUG_JS_EXCEPTIONS52#ifdef DEBUG_JS_EXCEPTIONS
44 qCDebug(scriptengine) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead";53 qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead";
45#endif54#endif
46 proto = globalObject().property("Error");55 proto = globalObject().property("Error");
47 }56 }
@@ -64,6 +73,9 @@ QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QStri · 16 unmodified lines
64 73
65// check syntax and when there are issues returns an actual "SyntaxError" with the details74// check syntax and when there are issues returns an actual "SyntaxError" with the details
66QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) {75QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) {
76 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
77 return unboundNullValue();
78 }
67 const auto syntaxCheck = checkSyntax(sourceCode);79 const auto syntaxCheck = checkSyntax(sourceCode);
68 if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {80 if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
69 auto err = globalObject().property("SyntaxError")81 auto err = globalObject().property("SyntaxError")
@@ -82,13 +94,16 @@ QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QStri · 12 unmodified lines
82 }94 }
83 return err;95 return err;
84 }96 }
85 return undefinedValue();97 return QScriptValue();
86}98}
87 99
88// this pulls from the best available information to create a detailed snapshot of the current exception100// this pulls from the best available information to create a detailed snapshot of the current exception
89QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail) {101QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail) {
102 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
103 return unboundNullValue();
104 }
90 if (!hasUncaughtException()) {105 if (!hasUncaughtException()) {
91 return QScriptValue();106 return unboundNullValue();
92 }107 }
93 auto exception = uncaughtException();108 auto exception = uncaughtException();
94 // ensure the error object is engine-local109 // ensure the error object is engine-local
@@ -144,7 +159,10 @@ QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail · 49 unmodified lines
144 return err;159 return err;
145}160}
146 161
147QString BaseScriptEngine::formatException(const QScriptValue& exception) {162QString BaseScriptEngine::formatException(const QScriptValue& exception, bool includeExtendedDetails) {
163 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
164 return QString();
165 }
148 QString note { "UncaughtException" };166 QString note { "UncaughtException" };
149 QString result;167 QString result;
150 168
@@ -156,8 +174,8 @@ QString BaseScriptEngine::formatException(const QScriptValue& exception) { · 5 unmodified lines
156 const auto lineNumber = exception.property("lineNumber").toString();174 const auto lineNumber = exception.property("lineNumber").toString();
157 const auto stacktrace = exception.property("stack").toString();175 const auto stacktrace = exception.property("stack").toString();
158 176
159 if (_enableExtendedJSExceptions.get()) {177 if (includeExtendedDetails) {
160 // This setting toggles display of the hints now being added during the loading process.178 // Display additional exception / troubleshooting hints that can be added via the custom Error .detail property
161 // Example difference:179 // Example difference:
162 // [UncaughtExceptions] Error: Can't find variable: foobar in atp:/myentity.js\n...180 // [UncaughtExceptions] Error: Can't find variable: foobar in atp:/myentity.js\n...
163 // [UncaughtException (construct {1eb5d3fa-23b1-411c-af83-163af7220e3f})] Error: Can't find variable: foobar in atp:/myentity.js\n...181 // [UncaughtException (construct {1eb5d3fa-23b1-411c-af83-163af7220e3f})] Error: Can't find variable: foobar in atp:/myentity.js\n...
@@ -173,14 +191,39 @@ QString BaseScriptEngine::formatException(const QScriptValue& exception) { · 9 unmodified lines
173 return result;191 return result;
174}192}
175 193
194bool BaseScriptEngine::raiseException(const QScriptValue& exception) {
195 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
196 return false;
197 }
198 if (currentContext()) {
199 // we have an active context / JS stack frame so throw the exception per usual
200 currentContext()->throwValue(makeError(exception));
201 return true;
202 } else {
203 // we are within a pure C++ stack frame (ie: being called directly by other C++ code)
204 // in this case no context information is available so just emit the exception for reporting
205 emit unhandledException(makeError(exception));
206 }
207 return false;
208}
209
210bool BaseScriptEngine::maybeEmitUncaughtException(const QString& debugHint) {
211 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
212 return false;
213 }
214 if (!isEvaluating() && hasUncaughtException()) {
215 emit unhandledException(cloneUncaughtException(debugHint));
216 clearExceptions();
217 return true;
218 }
219 return false;
220}
221
176QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) {222QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) {
177 PROFILE_RANGE(script, "evaluateInClosure");223 PROFILE_RANGE(script, "evaluateInClosure");
178 if (QThread::currentThread() != thread()) {224 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
179 qCCritical(scriptengine) << "*** CRITICAL *** ScriptEngine::evaluateInClosure() is meant to be called from engine thread only.";225 return unboundNullValue();
180 // note: a recursive mutex might be needed around below code if this method ever becomes Q_INVOKABLE
181 return QScriptValue();
182 }226 }
183
184 const auto fileName = program.fileName();227 const auto fileName = program.fileName();
185 const auto shortName = QUrl(fileName).fileName();228 const auto shortName = QUrl(fileName).fileName();
186 229
@@ -189,7 +232,7 @@ QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, co · 2 unmodified lines
189 auto global = closure.property("global");232 auto global = closure.property("global");
190 if (global.isObject()) {233 if (global.isObject()) {
191#ifdef DEBUG_JS234#ifdef DEBUG_JS
192 qCDebug(scriptengine) << " setting global = closure.global" << shortName;235 qCDebug(shared) << " setting global = closure.global" << shortName;
193#endif236#endif
194 oldGlobal = globalObject();237 oldGlobal = globalObject();
195 setGlobalObject(global);238 setGlobalObject(global);
@@ -200,34 +243,34 @@ QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, co · 4 unmodified lines
200 auto thiz = closure.property("this");243 auto thiz = closure.property("this");
201 if (thiz.isObject()) {244 if (thiz.isObject()) {
202#ifdef DEBUG_JS245#ifdef DEBUG_JS
203 qCDebug(scriptengine) << " setting this = closure.this" << shortName;246 qCDebug(shared) << " setting this = closure.this" << shortName;
204#endif247#endif
205 context->setThisObject(thiz);248 context->setThisObject(thiz);
206 }249 }
207 250
208 context->pushScope(closure);251 context->pushScope(closure);
209#ifdef DEBUG_JS252#ifdef DEBUG_JS
210 qCDebug(scriptengine) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName);253 qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
211#endif254#endif
212 {255 {
213 result = BaseScriptEngine::evaluate(program);256 result = BaseScriptEngine::evaluate(program);
214 if (hasUncaughtException()) {257 if (hasUncaughtException()) {
215 auto err = cloneUncaughtException(__FUNCTION__);258 auto err = cloneUncaughtException(__FUNCTION__);
216#ifdef DEBUG_JS_EXCEPTIONS259#ifdef DEBUG_JS_EXCEPTIONS
217 qCWarning(scriptengine) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();260 qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();
218 err.setProperty("_result", result);261 err.setProperty("_result", result);
219#endif262#endif
220 result = err;263 result = err;
221 }264 }
222 }265 }
223#ifdef DEBUG_JS266#ifdef DEBUG_JS
224 qCDebug(scriptengine) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName);267 qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
225#endif268#endif
226 popContext();269 popContext();
227 270
228 if (oldGlobal.isValid()) {271 if (oldGlobal.isValid()) {
229#ifdef DEBUG_JS272#ifdef DEBUG_JS
230 qCDebug(scriptengine) << " restoring global" << shortName;273 qCDebug(shared) << " restoring global" << shortName;
231#endif274#endif
232 setGlobalObject(oldGlobal);275 setGlobalObject(oldGlobal);
233 }276 }
@@ -236,7 +279,6 @@ QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, co · 2 unmodified lines
236}279}
237 280
238// Lambda281// Lambda
239
240QScriptValue BaseScriptEngine::newLambdaFunction(std::function<QScriptValue(QScriptContext *, QScriptEngine*)> operation, const QScriptValue& data, const QScriptEngine::ValueOwnership& ownership) {282QScriptValue BaseScriptEngine::newLambdaFunction(std::function<QScriptValue(QScriptContext *, QScriptEngine*)> operation, const QScriptValue& data, const QScriptEngine::ValueOwnership& ownership) {
241 auto lambda = new Lambda(this, operation, data);283 auto lambda = new Lambda(this, operation, data);
242 auto object = newQObject(lambda, ownership);284 auto object = newQObject(lambda, ownership);
@@ -262,26 +304,57 @@ Lambda::Lambda(QScriptEngine *engine, std::function<QScriptValue(QScriptContext · 19 unmodified lines
262#endif304#endif
263}305}
264QScriptValue Lambda::call() {306QScriptValue Lambda::call() {
307 if (!BaseScriptEngine::IS_THREADSAFE_INVOCATION(engine->thread(), __FUNCTION__)) {
308 return BaseScriptEngine::unboundNullValue();
309 }
265 return operation(engine->currentContext(), engine);310 return operation(engine->currentContext(), engine);
266}311}
267 312
313QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue methodOrName) {
314 auto engine = scopeOrCallback.engine();
315 if (!engine) {
316 return scopeOrCallback;
317 }
318 auto scope = QScriptValue();
319 auto callback = scopeOrCallback;
320 if (scopeOrCallback.isObject()) {
321 if (methodOrName.isString()) {
322 scope = scopeOrCallback;
323 callback = scope.property(methodOrName.toString());
324 } else if (methodOrName.isFunction()) {
325 scope = scopeOrCallback;
326 callback = methodOrName;
327 }
328 }
329 auto handler = engine->newObject();
330 handler.setProperty("scope", scope);
331 handler.setProperty("callback", callback);
332 return handler;
333}
334
335QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result) {
336 return handler.property("callback").call(handler.property("scope"), QScriptValueList({ err, result }));
337}
338
268#ifdef DEBUG_JS339#ifdef DEBUG_JS
269void BaseScriptEngine::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) {340void BaseScriptEngine::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) {
341 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
342 return;
343 }
270 if (!header.isEmpty()) {344 if (!header.isEmpty()) {
271 qCDebug(scriptengine) << header;345 qCDebug(shared) << header;
272 }346 }
273 if (!object.isObject()) {347 if (!object.isObject()) {
274 qCDebug(scriptengine) << "(!isObject)" << object.toVariant().toString() << object.toString();348 qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString();
275 return;349 return;
276 }350 }
277 QScriptValueIterator it(object);351 QScriptValueIterator it(object);
278 while (it.hasNext()) {352 while (it.hasNext()) {
279 it.next();353 it.next();
280 qCDebug(scriptengine) << it.name() << ":" << it.value().toString();354 qCDebug(shared) << it.name() << ":" << it.value().toString();
281 }355 }
282 if (!footer.isEmpty()) {356 if (!footer.isEmpty()) {
283 qCDebug(scriptengine) << footer;357 qCDebug(shared) << footer;
284 }358 }
285}359}
286#endif360#endif
287