* Get rid of maybeEmitUncaughtException * Mostly get rid of makeError * Introduce exception hierarchy, change exceptions to shared_ptr * Simplify exception throwing code
Showing the changes to libraries/script-engine/src/v8/ScriptEngineV8.cpp (1 of 10 files). Show all
Unified · Split
| @@ -71,20 +71,17 @@ bool ScriptEngineV8::IS_THREADSAFE_INVOCATION(const QThread* thread, const QStri · 70 unmodified lines | ||
| 71 | 71 | return false; |
| 72 | 72 | } |
| 73 | 73 | |
| 74 | −// engine-aware JS Error copier and factory | |
| 75 | −V8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QString& type) { | |
| 74 | +ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) { | |
| 76 | 75 | if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { |
| 77 | − v8::Locker locker(_v8Isolate); | |
| 78 | − v8::Isolate::Scope isolateScope(_v8Isolate); | |
| 79 | − v8::HandleScope handleScope(_v8Isolate); | |
| 80 | − v8::Context::Scope contextScope(getContext()); | |
| 81 | − return V8ScriptValue(this, v8::Null(_v8Isolate)); | |
| 76 | + return nullValue(); | |
| 82 | 77 | } |
| 83 | 78 | v8::Locker locker(_v8Isolate); |
| 84 | 79 | v8::Isolate::Scope isolateScope(_v8Isolate); |
| 85 | 80 | v8::HandleScope handleScope(_v8Isolate); |
| 86 | 81 | v8::Context::Scope contextScope(getContext()); |
| 87 | − return V8ScriptValue(this, v8::Null(_v8Isolate)); | |
| 82 | + return nullValue(); | |
| 83 | +} | |
| 84 | + | |
| 88 | 85 | //V8TODO |
| 89 | 86 | /* |
| 90 | 87 | auto other = _other; |
| @@ -117,33 +114,8 @@ V8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QStri · 26 unmodified lines | ||
| 117 | 114 | err.setProperty(it.name(), it.value()); |
| 118 | 115 | } |
| 119 | 116 | return err;*/ |
| 120 | −} | |
| 117 | +//} | |
| 121 | 118 | |
| 122 | −ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) { | |
| 123 | − if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { | |
| 124 | − return nullValue(); | |
| 125 | − } | |
| 126 | − v8::Locker locker(_v8Isolate); | |
| 127 | − v8::Isolate::Scope isolateScope(_v8Isolate); | |
| 128 | − v8::HandleScope handleScope(_v8Isolate); | |
| 129 | − v8::Context::Scope contextScope(getContext()); | |
| 130 | − return nullValue(); | |
| 131 | − | |
| 132 | − //V8TODO | |
| 133 | − //what does makeError actually do? | |
| 134 | − /*ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(_other); | |
| 135 | − V8ScriptValue other; | |
| 136 | − if (_other.isString()) { | |
| 137 | − other = QScriptEngine::newObject(); | |
| 138 | − other.setProperty("message", _other.toString()); | |
| 139 | − } else if (unwrapped) { | |
| 140 | − other = unwrapped->toV8Value(); | |
| 141 | − } else { | |
| 142 | − other = QScriptEngine::newVariant(_other.toVariant()); | |
| 143 | − } | |
| 144 | − V8ScriptValue result = makeError(other, type); | |
| 145 | − return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result)));*/ | |
| 146 | −} | |
| 147 | 119 | |
| 148 | 120 | // check syntax and when there are issues returns an actual "SyntaxError" with the details |
| 149 | 121 | ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) { |
| @@ -200,82 +172,14 @@ ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) { · 50 unmodified lines | ||
| 200 | 172 | return undefinedValue(); |
| 201 | 173 | }*/ |
| 202 | 174 | |
| 203 | −// this pulls from the best available information to create a detailed snapshot of the current exception | |
| 204 | −ScriptValue ScriptEngineV8::cloneUncaughtException(const QString& extraDetail) { | |
| 205 | − if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { | |
| 206 | − return nullValue(); | |
| 207 | − } | |
| 208 | − if (!hasUncaughtException()) { | |
| 209 | − return nullValue(); | |
| 210 | − } | |
| 211 | − v8::Locker locker(_v8Isolate); | |
| 212 | − v8::Isolate::Scope isolateScope(_v8Isolate); | |
| 213 | − v8::HandleScope handleScope(_v8Isolate); | |
| 214 | − v8::Context::Scope contextScope(getContext()); | |
| 215 | − return nullValue(); | |
| 216 | − //V8TODO | |
| 217 | − /* | |
| 218 | − auto exception = uncaughtException(); | |
| 219 | − // ensure the error object is engine-local | |
| 220 | − auto err = makeError(exception); | |
| 221 | − | |
| 222 | − // not sure why Qt does't offer uncaughtExceptionFileName -- but the line number | |
| 223 | − // on its own is often useless/wrong if arbitrarily married to a filename. | |
| 224 | − // when the error object already has this info, it seems to be the most reliable | |
| 225 | − auto fileName = exception.property("fileName").toString(); | |
| 226 | − auto lineNumber = exception.property("lineNumber").toInt32(); | |
| 227 | − | |
| 228 | − // the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace | |
| 229 | − auto backtrace = uncaughtExceptionBacktrace(); | |
| 230 | − if (backtrace.isEmpty()) { | |
| 231 | − // fallback to the error object | |
| 232 | − backtrace = exception.property("stack").toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP); | |
| 233 | − } | |
| 234 | − // the ad hoc "detail" property can be used now to embed additional clues | |
| 235 | − auto detail = exception.property("detail").toString(); | |
| 236 | − if (detail.isEmpty()) { | |
| 237 | − detail = extraDetail; | |
| 238 | − } else if (!extraDetail.isEmpty()) { | |
| 239 | − detail += "(" + extraDetail + ")"; | |
| 240 | − } | |
| 241 | − if (lineNumber <= 0) { | |
| 242 | − lineNumber = uncaughtExceptionLineNumber(); | |
| 243 | − } | |
| 244 | − if (fileName.isEmpty()) { | |
| 245 | − // climb the stack frames looking for something useful to display | |
| 246 | − for (auto c = QScriptEngine::currentContext(); c && fileName.isEmpty(); c = c->parentContext()) { | |
| 247 | − V8ScriptContextInfo info{ c }; | |
| 248 | − if (!info.fileName().isEmpty()) { | |
| 249 | − // take fileName:lineNumber as a pair | |
| 250 | − fileName = info.fileName(); | |
| 251 | − lineNumber = info.lineNumber(); | |
| 252 | − if (backtrace.isEmpty()) { | |
| 253 | − backtrace = c->backtrace(); | |
| 254 | − } | |
| 255 | − break; | |
| 256 | − } | |
| 257 | − } | |
| 258 | − } | |
| 259 | − err.setProperty("fileName", fileNlintame); | |
| 260 | − err.setProperty("lineNumber", lineNumber); | |
| 261 | − err.setProperty("detail", detail); | |
| 262 | − err.setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP)); | |
| 263 | − | |
| 264 | −#ifdef DEBUG_JS_EXCEPTIONS | |
| 265 | − err.setProperty("_fileName", exception.property("fileName").toString()); | |
| 266 | − err.setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP)); | |
| 267 | − err.setProperty("_lineNumber", uncaughtExceptionLineNumber()); | |
| 268 | −#endif | |
| 269 | − return err; | |
| 270 | − */ | |
| 271 | −} | |
| 272 | − | |
| 273 | 175 | bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) { |
| 274 | 176 | if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { |
| 275 | 177 | return false; |
| 276 | 178 | } |
| 277 | 179 | //V8TODO |
| 278 | − _v8Isolate->ThrowException(makeError(exception).get()); | |
| 180 | + // _v8Isolate->ThrowException(makeError(exception).get()); | |
| 181 | + | |
| 182 | + | |
| 279 | 183 | /*if (QScriptEngine::currentContext()) { |
| 280 | 184 | // we have an active context / JS stack frame so throw the exception per usual |
| 281 | 185 | QScriptEngine::currentContext()->throwValue(makeError(exception)); |
| @@ -290,22 +194,6 @@ bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) { · 8 unmodified lines | ||
| 290 | 194 | return false; |
| 291 | 195 | } |
| 292 | 196 | |
| 293 | −bool ScriptEngineV8::maybeEmitUncaughtException(const QString& debugHint) { | |
| 294 | − | |
| 295 | − /* | |
| 296 | − if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { | |
| 297 | − return false; | |
| 298 | − } | |
| 299 | − if (!isEvaluating() && hasUncaughtException()) { | |
| 300 | − emit exception(cloneUncaughtException(debugHint)); | |
| 301 | − clearExceptions(); | |
| 302 | − return true; | |
| 303 | − } | |
| 304 | − | |
| 305 | − */ | |
| 306 | − return false; | |
| 307 | −} | |
| 308 | − | |
| 309 | 197 | // Lambda |
| 310 | 198 | /*ScriptValue ScriptEngineV8::newLambdaFunction(std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation, |
| 311 | 199 | const V8ScriptValue& data, |
| @@ -1019,12 +907,11 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure, · 707 unmodified lines | ||
| 1019 | 907 | } |
| 1020 | 908 | |
| 1021 | 909 | if (hasUncaughtException()) { |
| 1022 | − auto err = cloneUncaughtException(__FUNCTION__); | |
| 1023 | 910 | #ifdef DEBUG_JS_EXCEPTIONS |
| 1024 | 911 | qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString(); |
| 1025 | 912 | err.setProperty("_result", result); |
| 1026 | 913 | #endif |
| 1027 | − result = err; | |
| 914 | + result = nullValue(); | |
| 1028 | 915 | } else { |
| 1029 | 916 | result = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, v8Result))); |
| 1030 | 917 | } |
| @@ -1073,34 +960,9 @@ ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& f · 42 unmodified lines | ||
| 1073 | 960 | v8::ScriptOrigin scriptOrigin(getIsolate(), v8::String::NewFromUtf8(getIsolate(), fileName.toStdString().c_str()).ToLocalChecked()); |
| 1074 | 961 | v8::Local<v8::Script> script; |
| 1075 | 962 | if (!v8::Script::Compile(getContext(), v8::String::NewFromUtf8(getIsolate(), sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { |
| 1076 | − //V8TODO replace this with external function | |
| 1077 | − int errorColumnNumber = 0; | |
| 1078 | − int errorLineNumber = 0; | |
| 1079 | − QString errorMessage = ""; | |
| 1080 | − QString errorBacktrace = ""; | |
| 1081 | − //v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception()); | |
| 1082 | − v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get()); | |
| 1083 | − errorMessage = QString(*utf8Value); | |
| 1084 | − v8::Local<v8::Message> exceptionMessage = tryCatch.Message(); | |
| 1085 | − if (!exceptionMessage.IsEmpty()) { | |
| 1086 | − errorLineNumber = exceptionMessage->GetLineNumber(getContext()).FromJust(); | |
| 1087 | − errorColumnNumber = exceptionMessage->GetStartColumn(getContext()).FromJust(); | |
| 1088 | − v8::Local<v8::Value> backtraceV8String; | |
| 1089 | − if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) { | |
| 1090 | − if (backtraceV8String->IsString()) { | |
| 1091 | − if (v8::Local<v8::String>::Cast(backtraceV8String)->Length() > 0) { | |
| 1092 | − v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String); | |
| 1093 | − errorBacktrace = *backtraceUtf8Value; | |
| 1094 | − } | |
| 1095 | − } | |
| 1096 | − } | |
| 1097 | − qCDebug(scriptengine) << "Compiling script \"" << fileName << "\" failed on line " << errorLineNumber << " column " << errorColumnNumber << " with message: \"" << errorMessage <<"\" backtrace: " << errorBacktrace; | |
| 1098 | − } | |
| 1099 | − auto err = makeError(newValue(errorMessage)); | |
| 1100 | − raiseException(err); | |
| 1101 | − maybeEmitUncaughtException("compile"); | |
| 963 | + setUncaughtException(tryCatch, "Error while compiling script"); | |
| 1102 | 964 | _evaluatingCounter--; |
| 1103 | − return err; | |
| 965 | + return nullValue(); | |
| 1104 | 966 | } |
| 1105 | 967 | //qCDebug(scriptengine) << "Script compilation succesful: " << fileName; |
| 1106 | 968 | |
| @@ -1145,6 +1007,12 @@ ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& f · 38 unmodified lines | ||
| 1145 | 1007 | return ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValue))); |
| 1146 | 1008 | } |
| 1147 | 1009 | |
| 1010 | + | |
| 1011 | +void ScriptEngineV8::setUncaughtEngineException(const QString &reason, const QString& info) { | |
| 1012 | + auto ex = std::make_shared<ScriptEngineException>(reason, info); | |
| 1013 | + setUncaughtException(ex); | |
| 1014 | +} | |
| 1015 | + | |
| 1148 | 1016 | void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QString& info) { |
| 1149 | 1017 | if (!tryCatch.HasCaught()) { |
| 1150 | 1018 | qCWarning(scriptengine) << "setUncaughtException called without exception"; |
| @@ -1152,8 +1020,8 @@ void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QS · 1 unmodified line | ||
| 1152 | 1020 | return; |
| 1153 | 1021 | } |
| 1154 | 1022 | |
| 1155 | − ScriptException ex; | |
| 1156 | − ex.additionalInfo = info; | |
| 1023 | + auto ex = std::make_shared<ScriptException>(); | |
| 1024 | + ex->additionalInfo = info; | |
| 1157 | 1025 | |
| 1158 | 1026 | v8::Locker locker(_v8Isolate); |
| 1159 | 1027 | v8::Isolate::Scope isolateScope(_v8Isolate); |
| @@ -1166,30 +1034,35 @@ void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QS · 6 unmodified lines | ||
| 1166 | 1034 | //v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception()); |
| 1167 | 1035 | v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get()); |
| 1168 | 1036 | |
| 1169 | − ex.errorMessage = QString(*utf8Value); | |
| 1037 | + ex->errorMessage = QString(*utf8Value); | |
| 1170 | 1038 | |
| 1171 | 1039 | v8::Local<v8::Message> exceptionMessage = tryCatch.Message(); |
| 1172 | 1040 | if (!exceptionMessage.IsEmpty()) { |
| 1173 | − ex.errorLine = exceptionMessage->GetLineNumber(getContext()).FromJust(); | |
| 1174 | − ex.errorColumn = exceptionMessage->GetStartColumn(getContext()).FromJust(); | |
| 1041 | + ex->errorLine = exceptionMessage->GetLineNumber(getContext()).FromJust(); | |
| 1042 | + ex->errorColumn = exceptionMessage->GetStartColumn(getContext()).FromJust(); | |
| 1175 | 1043 | v8::Local<v8::Value> backtraceV8String; |
| 1176 | 1044 | if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) { |
| 1177 | 1045 | if (backtraceV8String->IsString()) { |
| 1178 | 1046 | if (v8::Local<v8::String>::Cast(backtraceV8String)->Length() > 0) { |
| 1179 | 1047 | v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String); |
| 1180 | 1048 | QString errorBacktrace = *backtraceUtf8Value; |
| 1181 | − ex.backtrace = errorBacktrace.split("\n"); | |
| 1049 | + ex->backtrace = errorBacktrace.split("\n"); | |
| 1182 | 1050 | |
| 1183 | 1051 | } |
| 1184 | 1052 | } |
| 1185 | 1053 | } |
| 1186 | 1054 | } |
| 1187 | 1055 | |
| 1188 | − qCDebug(scriptengine) << "Emitting exception:" << ex; | |
| 1189 | − _uncaughtException = ex; | |
| 1190 | − emit exception(ex); | |
| 1056 | + setUncaughtException(ex); | |
| 1191 | 1057 | } |
| 1192 | 1058 | |
| 1059 | +void ScriptEngineV8::setUncaughtException(std::shared_ptr<ScriptException> uncaughtException) { | |
| 1060 | + qCDebug(scriptengine) << "Emitting exception:" << uncaughtException; | |
| 1061 | + _uncaughtException = uncaughtException; | |
| 1062 | + | |
| 1063 | + auto copy = uncaughtException->clone(); | |
| 1064 | + emit exception(copy); | |
| 1065 | +} | |
| 1193 | 1066 | |
| 1194 | 1067 | |
| 1195 | 1068 | QString ScriptEngineV8::formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch) { |
| @@ -1274,18 +1147,14 @@ Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& pro · 78 unmodified lines | ||
| 1274 | 1147 | v8::Context::Scope contextScope(getContext()); |
| 1275 | 1148 | ScriptProgramV8Wrapper* unwrapped = ScriptProgramV8Wrapper::unwrap(program); |
| 1276 | 1149 | if (!unwrapped) { |
| 1277 | − errorValue = makeError(newValue("could not unwrap program")); | |
| 1278 | − raiseException(errorValue); | |
| 1279 | − maybeEmitUncaughtException("compile"); | |
| 1150 | + setUncaughtEngineException("Could not unwrap program", "Compile error"); | |
| 1280 | 1151 | hasFailed = true; |
| 1281 | 1152 | } |
| 1282 | 1153 | |
| 1283 | 1154 | if(!hasFailed) { |
| 1284 | 1155 | ScriptSyntaxCheckResultPointer syntaxCheck = unwrapped->checkSyntax(); |
| 1285 | 1156 | if (syntaxCheck->state() == ScriptSyntaxCheckResult::Error) { |
| 1286 | − errorValue = makeError(newValue(syntaxCheck->errorMessage())); | |
| 1287 | − raiseException(errorValue); | |
| 1288 | − maybeEmitUncaughtException("compile"); | |
| 1157 | + setUncaughtEngineException(syntaxCheck->errorMessage(), "Compile error"); | |
| 1289 | 1158 | hasFailed = true; |
| 1290 | 1159 | } |
| 1291 | 1160 | } |
| @@ -1307,8 +1176,7 @@ Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& pro · 15 unmodified lines | ||
| 1307 | 1176 | Q_ASSERT(tryCatchRun.HasCaught()); |
| 1308 | 1177 | auto runError = tryCatchRun.Message(); |
| 1309 | 1178 | errorValue = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, runError->Get()))); |
| 1310 | − raiseException(errorValue); | |
| 1311 | − maybeEmitUncaughtException("evaluate"); | |
| 1179 | + raiseException(errorValue, "evaluation error"); | |
| 1312 | 1180 | hasFailed = true; |
| 1313 | 1181 | } else { |
| 1314 | 1182 | // V8TODO this is just to check if run will always return false for uncaught exception |
| @@ -1529,7 +1397,7 @@ void ScriptEngineV8::abortEvaluation() { · 214 unmodified lines | ||
| 1529 | 1397 | } |
| 1530 | 1398 | |
| 1531 | 1399 | void ScriptEngineV8::clearExceptions() { |
| 1532 | − _uncaughtException = ScriptException(); | |
| 1400 | + _uncaughtException.reset(); | |
| 1533 | 1401 | } |
| 1534 | 1402 | |
| 1535 | 1403 | ScriptContext* ScriptEngineV8::currentContext() const { |
| @@ -1551,7 +1419,7 @@ ScriptContext* ScriptEngineV8::currentContext() const { · 15 unmodified lines | ||
| 1551 | 1419 | } |
| 1552 | 1420 | |
| 1553 | 1421 | bool ScriptEngineV8::hasUncaughtException() const { |
| 1554 | − return !_uncaughtException.isEmpty(); | |
| 1422 | + return _uncaughtException != nullptr; | |
| 1555 | 1423 | } |
| 1556 | 1424 | |
| 1557 | 1425 | bool ScriptEngineV8::isEvaluating() const { |
| @@ -1667,11 +1535,15 @@ void ScriptEngineV8::setThread(QThread* thread) { · 109 unmodified lines | ||
| 1667 | 1535 | }*/ |
| 1668 | 1536 | |
| 1669 | 1537 | |
| 1670 | −ScriptException ScriptEngineV8::uncaughtException() const { | |
| 1671 | − return _uncaughtException; | |
| 1538 | +std::shared_ptr<ScriptException> ScriptEngineV8::uncaughtException() const { | |
| 1539 | + return _uncaughtException->clone(); | |
| 1540 | +} | |
| 1541 | + | |
| 1542 | +bool ScriptEngineV8::raiseException(const QString& error, const QString &reason) { | |
| 1543 | + return raiseException(ScriptValue(), reason); | |
| 1672 | 1544 | } |
| 1673 | 1545 | |
| 1674 | −bool ScriptEngineV8::raiseException(const ScriptValue& exception) { | |
| 1546 | +bool ScriptEngineV8::raiseException(const ScriptValue& exception, const QString &reason) { | |
| 1675 | 1547 | //V8TODO |
| 1676 | 1548 | //Q_ASSERT(false); |
| 1677 | 1549 | // qCritical() << "Script exception occurred: " << exception.toString(); |
| @@ -1681,6 +1553,7 @@ bool ScriptEngineV8::raiseException(const ScriptValue& exception) { · 3 unmodified lines | ||
| 1681 | 1553 | // emit |
| 1682 | 1554 | //return raiseException(qException); |
| 1683 | 1555 | |
| 1556 | + qCCritical(scriptengine) << "Raise exception for reason" << reason << "NOT IMPLEMENTED!"; | |
| 1684 | 1557 | return false; |
| 1685 | 1558 | } |
| 1686 | 1559 | |