History

Further exception work on V8

* Get rid of maybeEmitUncaughtException * Mostly get rid of makeError * Introduce exception hierarchy, change exceptions to shared_ptr * Simplify exception throwing code

Dale Glass committed 3 years ago

Commit 67e7a731 parent c0e62c510 files · +249 −260Browse files

Showing the changes to libraries/script-engine/src/v8/ScriptEngineV8.cpp (1 of 10 files). Show all

Unified · Split

libraries/script-engine/src/v8/ScriptEngineV8.cpp+45−172
@@ -71,20 +71,17 @@ bool ScriptEngineV8::IS_THREADSAFE_INVOCATION(const QThread* thread, const QStri · 70 unmodified lines
71 return false;71 return false;
72}72}
73 73
74// engine-aware JS Error copier and factory74ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) {
75V8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QString& type) {
76 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {75 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
77 v8::Locker locker(_v8Isolate);76 return nullValue();
78 v8::Isolate::Scope isolateScope(_v8Isolate);
79 v8::HandleScope handleScope(_v8Isolate);
80 v8::Context::Scope contextScope(getContext());
81 return V8ScriptValue(this, v8::Null(_v8Isolate));
82 }77 }
83 v8::Locker locker(_v8Isolate);78 v8::Locker locker(_v8Isolate);
84 v8::Isolate::Scope isolateScope(_v8Isolate);79 v8::Isolate::Scope isolateScope(_v8Isolate);
85 v8::HandleScope handleScope(_v8Isolate);80 v8::HandleScope handleScope(_v8Isolate);
86 v8::Context::Scope contextScope(getContext());81 v8::Context::Scope contextScope(getContext());
87 return V8ScriptValue(this, v8::Null(_v8Isolate));82 return nullValue();
83}
84
88 //V8TODO85 //V8TODO
89 /*86 /*
90 auto other = _other;87 auto other = _other;
@@ -117,33 +114,8 @@ V8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QStri · 26 unmodified lines
117 err.setProperty(it.name(), it.value());114 err.setProperty(it.name(), it.value());
118 }115 }
119 return err;*/116 return err;*/
120}117//}
121 118
122ScriptValue 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// check syntax and when there are issues returns an actual "SyntaxError" with the details120// check syntax and when there are issues returns an actual "SyntaxError" with the details
149ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) {121ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) {
@@ -200,82 +172,14 @@ ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) { · 50 unmodified lines
200 return undefinedValue();172 return undefinedValue();
201}*/173}*/
202 174
203// this pulls from the best available information to create a detailed snapshot of the current exception
204ScriptValue 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
273bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) {175bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) {
274 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {176 if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
275 return false;177 return false;
276 }178 }
277 //V8TODO179 //V8TODO
278 _v8Isolate->ThrowException(makeError(exception).get());180 // _v8Isolate->ThrowException(makeError(exception).get());
181
182
279 /*if (QScriptEngine::currentContext()) {183 /*if (QScriptEngine::currentContext()) {
280 // we have an active context / JS stack frame so throw the exception per usual184 // we have an active context / JS stack frame so throw the exception per usual
281 QScriptEngine::currentContext()->throwValue(makeError(exception));185 QScriptEngine::currentContext()->throwValue(makeError(exception));
@@ -290,22 +194,6 @@ bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) { · 8 unmodified lines
290 return false;194 return false;
291}195}
292 196
293bool 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// Lambda197// Lambda
310/*ScriptValue ScriptEngineV8::newLambdaFunction(std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation,198/*ScriptValue ScriptEngineV8::newLambdaFunction(std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation,
311 const V8ScriptValue& data,199 const V8ScriptValue& data,
@@ -1019,12 +907,11 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure, · 707 unmodified lines
1019 }907 }
1020 908
1021 if (hasUncaughtException()) {909 if (hasUncaughtException()) {
1022 auto err = cloneUncaughtException(__FUNCTION__);
1023#ifdef DEBUG_JS_EXCEPTIONS910#ifdef DEBUG_JS_EXCEPTIONS
1024 qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();911 qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();
1025 err.setProperty("_result", result);912 err.setProperty("_result", result);
1026#endif913#endif
1027 result = err;914 result = nullValue();
1028 } else {915 } else {
1029 result = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, v8Result)));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 v8::ScriptOrigin scriptOrigin(getIsolate(), v8::String::NewFromUtf8(getIsolate(), fileName.toStdString().c_str()).ToLocalChecked());960 v8::ScriptOrigin scriptOrigin(getIsolate(), v8::String::NewFromUtf8(getIsolate(), fileName.toStdString().c_str()).ToLocalChecked());
1074 v8::Local<v8::Script> script;961 v8::Local<v8::Script> script;
1075 if (!v8::Script::Compile(getContext(), v8::String::NewFromUtf8(getIsolate(), sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) {962 if (!v8::Script::Compile(getContext(), v8::String::NewFromUtf8(getIsolate(), sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) {
1076 //V8TODO replace this with external function963 setUncaughtException(tryCatch, "Error while compiling script");
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");
1102 _evaluatingCounter--;964 _evaluatingCounter--;
1103 return err;965 return nullValue();
1104 }966 }
1105 //qCDebug(scriptengine) << "Script compilation succesful: " << fileName;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 return ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValue)));1007 return ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValue)));
1146}1008}
1147 1009
1010
1011void ScriptEngineV8::setUncaughtEngineException(const QString &reason, const QString& info) {
1012 auto ex = std::make_shared<ScriptEngineException>(reason, info);
1013 setUncaughtException(ex);
1014}
1015
1148void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QString& info) {1016void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QString& info) {
1149 if (!tryCatch.HasCaught()) {1017 if (!tryCatch.HasCaught()) {
1150 qCWarning(scriptengine) << "setUncaughtException called without exception";1018 qCWarning(scriptengine) << "setUncaughtException called without exception";
@@ -1152,8 +1020,8 @@ void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QS · 1 unmodified line
1152 return;1020 return;
1153 }1021 }
1154 1022
1155 ScriptException ex;1023 auto ex = std::make_shared<ScriptException>();
1156 ex.additionalInfo = info;1024 ex->additionalInfo = info;
1157 1025
1158 v8::Locker locker(_v8Isolate);1026 v8::Locker locker(_v8Isolate);
1159 v8::Isolate::Scope isolateScope(_v8Isolate);1027 v8::Isolate::Scope isolateScope(_v8Isolate);
@@ -1166,30 +1034,35 @@ void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QS · 6 unmodified lines
1166 //v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception());1034 //v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception());
1167 v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get());1035 v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get());
1168 1036
1169 ex.errorMessage = QString(*utf8Value);1037 ex->errorMessage = QString(*utf8Value);
1170 1038
1171 v8::Local<v8::Message> exceptionMessage = tryCatch.Message();1039 v8::Local<v8::Message> exceptionMessage = tryCatch.Message();
1172 if (!exceptionMessage.IsEmpty()) {1040 if (!exceptionMessage.IsEmpty()) {
1173 ex.errorLine = exceptionMessage->GetLineNumber(getContext()).FromJust();1041 ex->errorLine = exceptionMessage->GetLineNumber(getContext()).FromJust();
1174 ex.errorColumn = exceptionMessage->GetStartColumn(getContext()).FromJust();1042 ex->errorColumn = exceptionMessage->GetStartColumn(getContext()).FromJust();
1175 v8::Local<v8::Value> backtraceV8String;1043 v8::Local<v8::Value> backtraceV8String;
1176 if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) {1044 if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) {
1177 if (backtraceV8String->IsString()) {1045 if (backtraceV8String->IsString()) {
1178 if (v8::Local<v8::String>::Cast(backtraceV8String)->Length() > 0) {1046 if (v8::Local<v8::String>::Cast(backtraceV8String)->Length() > 0) {
1179 v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String);1047 v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String);
1180 QString errorBacktrace = *backtraceUtf8Value;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;1056 setUncaughtException(ex);
1189 _uncaughtException = ex;
1190 emit exception(ex);
1191}1057}
1192 1058
1059void 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
1195QString ScriptEngineV8::formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch) {1068QString ScriptEngineV8::formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch) {
@@ -1274,18 +1147,14 @@ Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& pro · 78 unmodified lines
1274 v8::Context::Scope contextScope(getContext());1147 v8::Context::Scope contextScope(getContext());
1275 ScriptProgramV8Wrapper* unwrapped = ScriptProgramV8Wrapper::unwrap(program);1148 ScriptProgramV8Wrapper* unwrapped = ScriptProgramV8Wrapper::unwrap(program);
1276 if (!unwrapped) {1149 if (!unwrapped) {
1277 errorValue = makeError(newValue("could not unwrap program"));1150 setUncaughtEngineException("Could not unwrap program", "Compile error");
1278 raiseException(errorValue);
1279 maybeEmitUncaughtException("compile");
1280 hasFailed = true;1151 hasFailed = true;
1281 }1152 }
1282 1153
1283 if(!hasFailed) {1154 if(!hasFailed) {
1284 ScriptSyntaxCheckResultPointer syntaxCheck = unwrapped->checkSyntax();1155 ScriptSyntaxCheckResultPointer syntaxCheck = unwrapped->checkSyntax();
1285 if (syntaxCheck->state() == ScriptSyntaxCheckResult::Error) {1156 if (syntaxCheck->state() == ScriptSyntaxCheckResult::Error) {
1286 errorValue = makeError(newValue(syntaxCheck->errorMessage()));1157 setUncaughtEngineException(syntaxCheck->errorMessage(), "Compile error");
1287 raiseException(errorValue);
1288 maybeEmitUncaughtException("compile");
1289 hasFailed = true;1158 hasFailed = true;
1290 }1159 }
1291 }1160 }
@@ -1307,8 +1176,7 @@ Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& pro · 15 unmodified lines
1307 Q_ASSERT(tryCatchRun.HasCaught());1176 Q_ASSERT(tryCatchRun.HasCaught());
1308 auto runError = tryCatchRun.Message();1177 auto runError = tryCatchRun.Message();
1309 errorValue = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, runError->Get())));1178 errorValue = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, runError->Get())));
1310 raiseException(errorValue);1179 raiseException(errorValue, "evaluation error");
1311 maybeEmitUncaughtException("evaluate");
1312 hasFailed = true;1180 hasFailed = true;
1313 } else {1181 } else {
1314 // V8TODO this is just to check if run will always return false for uncaught exception1182 // 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
1531void ScriptEngineV8::clearExceptions() {1399void ScriptEngineV8::clearExceptions() {
1532 _uncaughtException = ScriptException();1400 _uncaughtException.reset();
1533}1401}
1534 1402
1535ScriptContext* ScriptEngineV8::currentContext() const {1403ScriptContext* ScriptEngineV8::currentContext() const {
@@ -1551,7 +1419,7 @@ ScriptContext* ScriptEngineV8::currentContext() const { · 15 unmodified lines
1551}1419}
1552 1420
1553bool ScriptEngineV8::hasUncaughtException() const {1421bool ScriptEngineV8::hasUncaughtException() const {
1554 return !_uncaughtException.isEmpty();1422 return _uncaughtException != nullptr;
1555}1423}
1556 1424
1557bool ScriptEngineV8::isEvaluating() const {1425bool ScriptEngineV8::isEvaluating() const {
@@ -1667,11 +1535,15 @@ void ScriptEngineV8::setThread(QThread* thread) { · 109 unmodified lines
1667}*/1535}*/
1668 1536
1669 1537
1670ScriptException ScriptEngineV8::uncaughtException() const {1538std::shared_ptr<ScriptException> ScriptEngineV8::uncaughtException() const {
1671 return _uncaughtException;1539 return _uncaughtException->clone();
1540}
1541
1542bool ScriptEngineV8::raiseException(const QString& error, const QString &reason) {
1543 return raiseException(ScriptValue(), reason);
1672}1544}
1673 1545
1674bool ScriptEngineV8::raiseException(const ScriptValue& exception) {1546bool ScriptEngineV8::raiseException(const ScriptValue& exception, const QString &reason) {
1675 //V8TODO1547 //V8TODO
1676 //Q_ASSERT(false);1548 //Q_ASSERT(false);
1677// qCritical() << "Script exception occurred: " << exception.toString();1549// qCritical() << "Script exception occurred: " << exception.toString();
@@ -1681,6 +1553,7 @@ bool ScriptEngineV8::raiseException(const ScriptValue& exception) { · 3 unmodified lines
1681 // emit1553 // emit
1682 //return raiseException(qException);1554 //return raiseException(qException);
1683 1555
1556 qCCritical(scriptengine) << "Raise exception for reason" << reason << "NOT IMPLEMENTED!";
1684 return false;1557 return false;
1685}1558}
1686 1559