History

classify ScriptEngine errors better, and send them as signal to JS

Brad Hefta-Gaub committed 9 years ago

Commit 6a7fa631 parent c65471e6 files · +108 −41Browse files

Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 6 files). Show all

Unified · Split

libraries/script-engine/src/ScriptEngine.cpp+58−40
@@ -86,6 +86,8 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ · 85 unmodified lines
86 .replace("\n", "\\n")86 .replace("\n", "\\n")
87 .replace("\r", "\\r")87 .replace("\r", "\\r")
88 .replace("'", "\\'");88 .replace("'", "\\'");
89
90 // FIXME - this approach neeeds revisiting. print() comes here, which ends up doing an evaluate?
89 engine->evaluate("Script.print('" + message + "')");91 engine->evaluate("Script.print('" + message + "')");
90 92
91 return QScriptValue();93 return QScriptValue();
@@ -130,20 +132,20 @@ QString encodeEntityIdIntoEntityUrl(const QString& url, const QString& entityID) · 38 unmodified lines
130 return url + " [EntityID:" + entityID + "]";132 return url + " [EntityID:" + entityID + "]";
131}133}
132 134
133static bool hasCorrectSyntax(const QScriptProgram& program) {135static bool hasCorrectSyntax(const QScriptProgram& program, ScriptEngine* reportingEngine) {
134 const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());136 const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());
135 if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {137 if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
136 const auto error = syntaxCheck.errorMessage();138 const auto error = syntaxCheck.errorMessage();
137 const auto line = QString::number(syntaxCheck.errorLineNumber());139 const auto line = QString::number(syntaxCheck.errorLineNumber());
138 const auto column = QString::number(syntaxCheck.errorColumnNumber());140 const auto column = QString::number(syntaxCheck.errorColumnNumber());
139 const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column);141 const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column);
140 qCWarning(scriptengine) << qPrintable(message);142 reportingEngine->scriptErrorMessage(qPrintable(message));
141 return false;143 return false;
142 }144 }
143 return true;145 return true;
144}146}
145 147
146static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) {148static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName, ScriptEngine* reportingEngine) {
147 if (engine.hasUncaughtException()) {149 if (engine.hasUncaughtException()) {
148 const auto backtrace = engine.uncaughtExceptionBacktrace();150 const auto backtrace = engine.uncaughtExceptionBacktrace();
149 const auto exception = engine.uncaughtException().toString();151 const auto exception = engine.uncaughtException().toString();
@@ -155,7 +157,7 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName · 5 unmodified lines
155 static const auto lineSeparator = "\n ";157 static const auto lineSeparator = "\n ";
156 message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));158 message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
157 }159 }
158 qCWarning(scriptengine) << qPrintable(message);160 reportingEngine->scriptErrorMessage(qPrintable(message));
159 return true;161 return true;
160 }162 }
161 return false;163 return false;
@@ -170,20 +172,20 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam · 8 unmodified lines
170 DependencyManager::get<ScriptEngines>()->addScriptEngine(this);172 DependencyManager::get<ScriptEngines>()->addScriptEngine(this);
171 173
172 connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) {174 connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) {
173 hadUncaughtExceptions(*this, _fileNameString);175 hadUncaughtExceptions(*this, _fileNameString, this);
174 });176 });
175 177
176 setProcessEventsInterval(MSECS_PER_SECOND);178 setProcessEventsInterval(MSECS_PER_SECOND);
177}179}
178 180
179ScriptEngine::~ScriptEngine() {181ScriptEngine::~ScriptEngine() {
180 qCDebug(scriptengine) << "Script Engine shutting down:" << getFilename();182 scriptInfoMessage("Script Engine shutting down:" + getFilename());
181 183
182 auto scriptEngines = DependencyManager::get<ScriptEngines>();184 auto scriptEngines = DependencyManager::get<ScriptEngines>();
183 if (scriptEngines) {185 if (scriptEngines) {
184 scriptEngines->removeScriptEngine(this);186 scriptEngines->removeScriptEngine(this);
185 } else {187 } else {
186 qCWarning(scriptengine) << "Script destroyed after ScriptEngines!";188 scriptWarningMessage("Script destroyed after ScriptEngines!");
187 }189 }
188}190}
189 191
@@ -274,7 +276,7 @@ void ScriptEngine::runDebuggable() { · 84 unmodified lines
274 }276 }
275 _lastUpdate = now;277 _lastUpdate = now;
276 // Debug and clear exceptions278 // Debug and clear exceptions
277 hadUncaughtExceptions(*this, _fileNameString);279 hadUncaughtExceptions(*this, _fileNameString, this);
278 });280 });
279 281
280 timer->start(10);282 timer->start(10);
@@ -359,7 +361,7 @@ void ScriptEngine::waitTillDoneRunning() { · 78 unmodified lines
359 QThread::yieldCurrentThread();361 QThread::yieldCurrentThread();
360 }362 }
361 363
362 qCDebug(scriptengine) << "Script Engine has stopped:" << getFilename();364 scriptInfoMessage("Script Engine has stopped:" + getFilename());
363 }365 }
364}366}
365 367
@@ -398,10 +400,25 @@ void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scrip · 32 unmodified lines
398 emit scriptLoaded(url.toString());400 emit scriptLoaded(url.toString());
399}401}
400 402
403void ScriptEngine::scriptErrorMessage(const QString& message) {
404 qCCritical(scriptengine) << message;
405 emit errorMessage(message);
406}
407
408void ScriptEngine::scriptWarningMessage(const QString& message) {
409 qCWarning(scriptengine) << message;
410 emit warningMessage(message);
411}
412
413void ScriptEngine::scriptInfoMessage(const QString& message) {
414 qCInfo(scriptengine) << message;
415 emit infoMessage(message);
416}
417
401// FIXME - switch this to the new model of ScriptCache callbacks418// FIXME - switch this to the new model of ScriptCache callbacks
402void ScriptEngine::errorInLoadingScript(const QUrl& url) {419void ScriptEngine::errorInLoadingScript(const QUrl& url) {
403 qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;420 scriptErrorMessage("ERROR Loading file:" + url.toString());
404 emit errorLoadingScript(_fileNameString); // ??421 emit errorLoadingScript(_fileNameString);
405}422}
406 423
407// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of424// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of
@@ -809,7 +826,7 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi · 401 unmodified lines
809 826
810 // Check syntax827 // Check syntax
811 const QScriptProgram program(sourceCode, fileName, lineNumber);828 const QScriptProgram program(sourceCode, fileName, lineNumber);
812 if (!hasCorrectSyntax(program)) {829 if (!hasCorrectSyntax(program, this)) {
813 return QScriptValue();830 return QScriptValue();
814 }831 }
815 832
@@ -817,7 +834,7 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi · 1 unmodified line
817 const auto result = QScriptEngine::evaluate(program);834 const auto result = QScriptEngine::evaluate(program);
818 --_evaluatesPending;835 --_evaluatesPending;
819 836
820 const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName());837 const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName(), this);
821 emit evaluationFinished(result, hadUncaughtException);838 emit evaluationFinished(result, hadUncaughtException);
822 return result;839 return result;
823}840}
@@ -934,10 +951,10 @@ void ScriptEngine::run() { · 110 unmodified lines
934 _lastUpdate = now;951 _lastUpdate = now;
935 952
936 // Debug and clear exceptions953 // Debug and clear exceptions
937 hadUncaughtExceptions(*this, _fileNameString);954 hadUncaughtExceptions(*this, _fileNameString, this);
938 }955 }
939 956
940 qCDebug(scriptengine) << "Script Engine stopping:" << getFilename();957 scriptInfoMessage("Script Engine stopping:" + getFilename());
941 958
942 stopAllTimers(); // make sure all our timers are stopped if the script is ending959 stopAllTimers(); // make sure all our timers are stopped if the script is ending
943 emit scriptEnding();960 emit scriptEnding();
@@ -1044,7 +1061,7 @@ void ScriptEngine::timerFired() { · 100 unmodified lines
1044 {1061 {
1045 auto engine = DependencyManager::get<ScriptEngines>();1062 auto engine = DependencyManager::get<ScriptEngines>();
1046 if (!engine || engine->isStopped()) {1063 if (!engine || engine->isStopped()) {
1047 qCDebug(scriptengine) << "Script.timerFired() while shutting down is ignored... parent script:" << getFilename();1064 scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename());
1048 return; // bail early1065 return; // bail early
1049 }1066 }
1050 }1067 }
@@ -1084,7 +1101,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int · 33 unmodified lines
1084 1101
1085QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {1102QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {
1086 if (DependencyManager::get<ScriptEngines>()->isStopped()) {1103 if (DependencyManager::get<ScriptEngines>()->isStopped()) {
1087 qCDebug(scriptengine) << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();1104 scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename());
1088 return NULL; // bail early1105 return NULL; // bail early
1089 }1106 }
1090 1107
@@ -1093,7 +1110,7 @@ QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) · 2 unmodified lines
1093 1110
1094QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {1111QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {
1095 if (DependencyManager::get<ScriptEngines>()->isStopped()) {1112 if (DependencyManager::get<ScriptEngines>()->isStopped()) {
1096 qCDebug(scriptengine) << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();1113 scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename());
1097 return NULL; // bail early1114 return NULL; // bail early
1098 }1115 }
1099 1116
@@ -1153,8 +1170,8 @@ void ScriptEngine::print(const QString& message) { · 53 unmodified lines
1153// all of the files have finished loading.1170// all of the files have finished loading.
1154void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {1171void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
1155 if (DependencyManager::get<ScriptEngines>()->isStopped()) {1172 if (DependencyManager::get<ScriptEngines>()->isStopped()) {
1156 qCDebug(scriptengine) << "Script.include() while shutting down is ignored..."1173 scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:"
1157 << "includeFiles:" << includeFiles << "parent script:" << getFilename();1174 + includeFiles.join(",") + "parent script:" + getFilename());
1158 return; // bail early1175 return; // bail early
1159 }1176 }
1160 QList<QUrl> urls;1177 QList<QUrl> urls;
@@ -1182,7 +1199,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 21 unmodified lines
1182 thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file)));1199 thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file)));
1183 QUrl defaultScriptsLoc = defaultScriptsLocation();1200 QUrl defaultScriptsLoc = defaultScriptsLocation();
1184 if (!defaultScriptsLoc.isParentOf(thisURL)) {1201 if (!defaultScriptsLoc.isParentOf(thisURL)) {
1185 qCDebug(scriptengine) << "ScriptEngine::include -- skipping" << file << "-- outside of standard libraries";1202 scriptWarningMessage("Script.include() -- skipping" + file + "-- outside of standard libraries");
1186 continue;1203 continue;
1187 }1204 }
1188 isStandardLibrary = true;1205 isStandardLibrary = true;
@@ -1193,8 +1210,9 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 4 unmodified lines
1193 if (!isStandardLibrary && !currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&1210 if (!isStandardLibrary && !currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&
1194 (currentSandboxURL.scheme() != "file" ||1211 (currentSandboxURL.scheme() != "file" ||
1195 !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()))) {1212 !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()))) {
1196 qCWarning(scriptengine) << "Script.include() ignoring file path"1213
1197 << thisURL << "outside of original entity script" << currentSandboxURL;1214 scriptWarningMessage("Script.include() ignoring file path" + thisURL.toString()
1215 + "outside of original entity script" + currentSandboxURL.toString());
1198 } else {1216 } else {
1199 // We could also check here for CORS, but we don't yet.1217 // We could also check here for CORS, but we don't yet.
1200 // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here.1218 // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here.
@@ -1216,7 +1234,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 15 unmodified lines
1216 for (QUrl url : urls) {1234 for (QUrl url : urls) {
1217 QString contents = data[url];1235 QString contents = data[url];
1218 if (contents.isNull()) {1236 if (contents.isNull()) {
1219 qCDebug(scriptengine) << "Error loading file: " << url << "line:" << __LINE__;1237 scriptErrorMessage("Error loading file: " + url.toString());
1220 } else {1238 } else {
1221 std::lock_guard<std::recursive_mutex> lock(_lock);1239 std::lock_guard<std::recursive_mutex> lock(_lock);
1222 if (!_includedURLs.contains(url)) {1240 if (!_includedURLs.contains(url)) {
@@ -1230,7 +1248,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 7 unmodified lines
1230 1248
1231 doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation);1249 doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation);
1232 } else {1250 } else {
1233 qCDebug(scriptengine) << "Script.include() skipping evaluation of previously included url:" << url;1251 scriptWarningMessage("Script.include() skipping evaluation of previously included url:" + url.toString());
1234 }1252 }
1235 }1253 }
1236 }1254 }
@@ -1259,8 +1277,8 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 22 unmodified lines
1259 1277
1260void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {1278void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
1261 if (DependencyManager::get<ScriptEngines>()->isStopped()) {1279 if (DependencyManager::get<ScriptEngines>()->isStopped()) {
1262 qCDebug(scriptengine) << "Script.include() while shutting down is ignored... "1280 scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:"
1263 << "includeFile:" << includeFile << "parent script:" << getFilename();1281 + includeFile + "parent script:" + getFilename());
1264 return; // bail early1282 return; // bail early
1265 }1283 }
1266 1284
@@ -1274,13 +1292,13 @@ void ScriptEngine::include(const QString& includeFile, QScriptValue callback) { · 7 unmodified lines
1274// the Application or other context will connect to in order to know to actually load the script1292// the Application or other context will connect to in order to know to actually load the script
1275void ScriptEngine::load(const QString& loadFile) {1293void ScriptEngine::load(const QString& loadFile) {
1276 if (DependencyManager::get<ScriptEngines>()->isStopped()) {1294 if (DependencyManager::get<ScriptEngines>()->isStopped()) {
1277 qCDebug(scriptengine) << "Script.load() while shutting down is ignored... "1295 scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:"
1278 << "loadFile:" << loadFile << "parent script:" << getFilename();1296 + loadFile + "parent script:" + getFilename());
1279 return; // bail early1297 return; // bail early
1280 }1298 }
1281 if (!currentEntityIdentifier.isInvalidID()) {1299 if (!currentEntityIdentifier.isInvalidID()) {
1282 qCWarning(scriptengine) << "Script.load() from entity script is ignored... "1300 scriptWarningMessage("Script.load() from entity script is ignored... loadFile:"
1283 << "loadFile:" << loadFile << "parent script:" << getFilename();1301 + loadFile + "parent script:" + getFilename());
1284 return; // bail early1302 return; // bail early
1285 }1303 }
1286 1304
@@ -1368,7 +1386,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 81 unmodified lines
1368 auto fileName = isURL ? scriptOrURL : "EmbeddedEntityScript";1386 auto fileName = isURL ? scriptOrURL : "EmbeddedEntityScript";
1369 1387
1370 QScriptProgram program(contents, fileName);1388 QScriptProgram program(contents, fileName);
1371 if (!hasCorrectSyntax(program)) {1389 if (!hasCorrectSyntax(program, this)) {
1372 if (!isFileUrl) {1390 if (!isFileUrl) {
1373 scriptCache->addScriptToBadScriptList(scriptOrURL);1391 scriptCache->addScriptToBadScriptList(scriptOrURL);
1374 }1392 }
@@ -1396,7 +1414,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 21 unmodified lines
1396 });1414 });
1397 testConstructor = sandbox.evaluate(program);1415 testConstructor = sandbox.evaluate(program);
1398 }1416 }
1399 if (hadUncaughtExceptions(sandbox, program.fileName())) {1417 if (hadUncaughtExceptions(sandbox, program.fileName(), this)) {
1400 return;1418 return;
1401 }1419 }
1402 1420
@@ -1410,10 +1428,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 7 unmodified lines
1410 if (testConstructorValue.size() > maxTestConstructorValueSize) {1428 if (testConstructorValue.size() > maxTestConstructorValueSize) {
1411 testConstructorValue = testConstructorValue.mid(0, maxTestConstructorValueSize) + "...";1429 testConstructorValue = testConstructorValue.mid(0, maxTestConstructorValueSize) + "...";
1412 }1430 }
1413 qCDebug(scriptengine) << "Error -- ScriptEngine::loadEntityScript() entity:" << entityID1431 scriptErrorMessage("Error -- ScriptEngine::loadEntityScript() entity:" + entityID.toString()
1414 << "failed to load entity script -- expected a function, got " + testConstructorType1432 + "failed to load entity script -- expected a function, got " + testConstructorType
1415 << "," << testConstructorValue1433 + "," + testConstructorValue
1416 << "," << scriptOrURL;1434 + "," + scriptOrURL);
1417 1435
1418 if (!isFileUrl) {1436 if (!isFileUrl) {
1419 scriptCache->addScriptToBadScriptList(scriptOrURL);1437 scriptCache->addScriptToBadScriptList(scriptOrURL);
@@ -1513,7 +1531,7 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { · 93 unmodified lines
1513 QString filePath = QUrl(details.scriptText).toLocalFile();1531 QString filePath = QUrl(details.scriptText).toLocalFile();
1514 auto lastModified = QFileInfo(filePath).lastModified().toMSecsSinceEpoch();1532 auto lastModified = QFileInfo(filePath).lastModified().toMSecsSinceEpoch();
1515 if (lastModified > details.lastModified) {1533 if (lastModified > details.lastModified) {
1516 qCDebug(scriptengine) << "Reloading modified script " << details.scriptText;1534 scriptInfoMessage("Reloading modified script " + details.scriptText);
1517 1535
1518 QFile file(filePath);1536 QFile file(filePath);
1519 file.open(QIODevice::ReadOnly);1537 file.open(QIODevice::ReadOnly);
@@ -1521,7 +1539,7 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { · 1 unmodified line
1521 this->unloadEntityScript(entityID);1539 this->unloadEntityScript(entityID);
1522 this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true);1540 this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true);
1523 if (!_entityScripts.contains(entityID)) {1541 if (!_entityScripts.contains(entityID)) {
1524 qWarning() << "Reload script " << details.scriptText << " failed";1542 scriptWarningMessage("Reload script " + details.scriptText + " failed");
1525 } else {1543 } else {
1526 details = _entityScripts[entityID];1544 details = _entityScripts[entityID];
1527 }1545 }
@@ -1548,7 +1566,7 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& s · 20 unmodified lines
1548#else1566#else
1549 operation();1567 operation();
1550#endif1568#endif
1551 hadUncaughtExceptions(*this, _fileNameString);1569 hadUncaughtExceptions(*this, _fileNameString, this);
1552 1570
1553 currentEntityIdentifier = oldIdentifier;1571 currentEntityIdentifier = oldIdentifier;
1554 currentSandboxURL = oldSandboxURL;1572 currentSandboxURL = oldSandboxURL;