Showing the changes to libraries/shared/src/BaseScriptEngine.cpp (1 of 8 files). Show all
Unified · Split
| @@ -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 | ||
| 24 | const QString BaseScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS { | ||
| 25 | "com.highfidelity.experimental.enableExtendedJSExceptions" | ||
| 26 | }; | ||
| 27 | |||
| 28 | const QString BaseScriptEngine::SCRIPT_EXCEPTION_FORMAT { "[%0] %1 in %2:%3" }; | 24 | const QString BaseScriptEngine::SCRIPT_EXCEPTION_FORMAT { "[%0] %1 in %2:%3" }; |
| 29 | const QString BaseScriptEngine::SCRIPT_BACKTRACE_SEP { "\n " }; | 25 | const QString BaseScriptEngine::SCRIPT_BACKTRACE_SEP { "\n " }; |
| 30 | 26 | ||
| 27 | bool 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 factory | 37 | // engine-aware JS Error copier and factory |
| 32 | QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QString& type) { | 38 | QScriptValue 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_EXCEPTIONS | 52 | #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 | #endif | 54 | #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 details | 74 | // check syntax and when there are issues returns an actual "SyntaxError" with the details |
| 66 | QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { | 75 | QScriptValue 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 exception | 100 | // this pulls from the best available information to create a detailed snapshot of the current exception |
| 89 | QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail) { | 101 | QScriptValue 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-local | 109 | // 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 | ||
| 147 | QString BaseScriptEngine::formatException(const QScriptValue& exception) { | 162 | QString 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 | ||
| 194 | bool 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 | |||
| 210 | bool 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 | |||
| 176 | QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) { | 222 | QScriptValue 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_JS | 234 | #ifdef DEBUG_JS |
| 192 | qCDebug(scriptengine) << " setting global = closure.global" << shortName; | 235 | qCDebug(shared) << " setting global = closure.global" << shortName; |
| 193 | #endif | 236 | #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_JS | 245 | #ifdef DEBUG_JS |
| 203 | qCDebug(scriptengine) << " setting this = closure.this" << shortName; | 246 | qCDebug(shared) << " setting this = closure.this" << shortName; |
| 204 | #endif | 247 | #endif |
| 205 | context->setThisObject(thiz); | 248 | context->setThisObject(thiz); |
| 206 | } | 249 | } |
| 207 | 250 | ||
| 208 | context->pushScope(closure); | 251 | context->pushScope(closure); |
| 209 | #ifdef DEBUG_JS | 252 | #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 | #endif | 254 | #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_EXCEPTIONS | 259 | #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 | #endif | 262 | #endif |
| 220 | result = err; | 263 | result = err; |
| 221 | } | 264 | } |
| 222 | } | 265 | } |
| 223 | #ifdef DEBUG_JS | 266 | #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 | #endif | 268 | #endif |
| 226 | popContext(); | 269 | popContext(); |
| 227 | 270 | ||
| 228 | if (oldGlobal.isValid()) { | 271 | if (oldGlobal.isValid()) { |
| 229 | #ifdef DEBUG_JS | 272 | #ifdef DEBUG_JS |
| 230 | qCDebug(scriptengine) << " restoring global" << shortName; | 273 | qCDebug(shared) << " restoring global" << shortName; |
| 231 | #endif | 274 | #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 | // Lambda | 281 | // Lambda |
| 239 | |||
| 240 | QScriptValue BaseScriptEngine::newLambdaFunction(std::function<QScriptValue(QScriptContext *, QScriptEngine*)> operation, const QScriptValue& data, const QScriptEngine::ValueOwnership& ownership) { | 282 | QScriptValue 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 | #endif | 304 | #endif |
| 263 | } | 305 | } |
| 264 | QScriptValue Lambda::call() { | 306 | QScriptValue 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 | ||
| 313 | QScriptValue 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 | |||
| 335 | QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result) { | ||
| 336 | return handler.property("callback").call(handler.property("scope"), QScriptValueList({ err, result })); | ||
| 337 | } | ||
| 338 | |||
| 268 | #ifdef DEBUG_JS | 339 | #ifdef DEBUG_JS |
| 269 | void BaseScriptEngine::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) { | 340 | void 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 | #endif | 360 | #endif |
| 287 | |||