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
This commit is contained in:
Dale Glass 2023-03-05 00:43:04 +01:00 committed by ksuprynowicz
parent c0e62c5cc2
commit 67e7a7375a
10 changed files with 249 additions and 260 deletions

View file

@ -132,7 +132,7 @@ void EntityScriptingInterface::releaseEntityPacketSenderMessages(bool wait) {
void EntityScriptingInterface::attachDefaultEventHandlers(ScriptManager* manager) { void EntityScriptingInterface::attachDefaultEventHandlers(ScriptManager* manager) {
// Connect up ALL the handlers to the global entities object's signals. // Connect up ALL the handlers to the global entities object's signals.
// (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
// Bug? These handlers are deleted when entityID is deleted, which is nice. // Bug? These handlers are deleted when entityID is deleted, which is nice.
// But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads? // But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads?
// E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow, // E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow,
@ -194,7 +194,7 @@ void EntityScriptingInterface::attachDefaultEventHandlers(ScriptManager* manager
}; };
/*@jsdoc /*@jsdoc
* <p>The name of an entity event. When the entity event occurs, any function that has been registered for that event * <p>The name of an entity event. When the entity event occurs, any function that has been registered for that event
* via {@link Script.addEventHandler} is called with parameters per the entity event.</p> * via {@link Script.addEventHandler} is called with parameters per the entity event.</p>
* <table> * <table>
* <thead> * <thead>
@ -782,7 +782,7 @@ QUuid EntityScriptingInterface::cloneEntity(const QUuid& entityIDToClone) {
} else if (cloneAvatarEntity) { } else if (cloneAvatarEntity) {
return addEntityInternal(properties, entity::HostType::AVATAR); return addEntityInternal(properties, entity::HostType::AVATAR);
} else { } else {
// setLastEdited timestamp to 0 to ensure this entity gets updated with the properties // setLastEdited timestamp to 0 to ensure this entity gets updated with the properties
// from the server-created entity, don't change this unless you know what you are doing // from the server-created entity, don't change this unless you know what you are doing
properties.setLastEdited(0); properties.setLastEdited(0);
bool success = addLocalEntityCopy(properties, newEntityID, true); bool success = addLocalEntityCopy(properties, newEntityID, true);
@ -1255,7 +1255,7 @@ void EntityScriptingInterface::setNonPersistentEntitiesScriptEngine(std::shared_
void EntityScriptingInterface::callEntityMethod(const QUuid& id, const QString& method, const QStringList& params) { void EntityScriptingInterface::callEntityMethod(const QUuid& id, const QString& method, const QStringList& params) {
PROFILE_RANGE(script_entities, __FUNCTION__); PROFILE_RANGE(script_entities, __FUNCTION__);
auto entity = getEntityTree()->findEntityByEntityItemID(id); auto entity = getEntityTree()->findEntityByEntityItemID(id);
if (entity) { if (entity) {
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock); std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
@ -1605,7 +1605,7 @@ bool EntityScriptingInterface::queryPropertyMetadata(const QUuid& entityID,
#endif #endif
if (!handler.property("callback").isFunction()) { if (!handler.property("callback").isFunction()) {
qDebug() << "!handler.callback.isFunction" << manager; qDebug() << "!handler.callback.isFunction" << manager;
engine->raiseException(engine->makeError(engine->newValue("callback is not a function"), "TypeError")); engine->raiseException("callback is not a function", "TypeError");
return false; return false;
} }
@ -1628,8 +1628,7 @@ bool EntityScriptingInterface::queryPropertyMetadata(const QUuid& entityID,
} else if (name == "serverScripts") { } else if (name == "serverScripts") {
return request.serverScripts(entityID, handler); return request.serverScripts(entityID, handler);
} else { } else {
engine->raiseException(engine->makeError(engine->newValue("metadata for property " + name + " is not yet queryable"))); engine->raiseException("metadata for property " + name + " is not yet queryable");
engine->maybeEmitUncaughtException(__FUNCTION__);
return false; return false;
} }
} }
@ -1644,8 +1643,7 @@ bool EntityScriptingInterface::getServerScriptStatus(const QUuid& entityID, Scri
Q_ASSERT(QThread::currentThread() == engine->thread()); Q_ASSERT(QThread::currentThread() == engine->thread());
Q_ASSERT(QThread::currentThread() == engine->manager()->thread()); Q_ASSERT(QThread::currentThread() == engine->manager()->thread());
if (!manager) { if (!manager) {
engine->raiseException(engine->makeError(engine->newValue("This script does not belong to a ScriptManager"))); engine->raiseException("This script does not belong to a ScriptManager");
engine->maybeEmitUncaughtException(__FUNCTION__);
return false; return false;
} }
@ -2015,17 +2013,17 @@ EntityItemPointer EntityScriptingInterface::checkForTreeEntityAndTypeMatch(const
if (!_entityTree) { if (!_entityTree) {
return EntityItemPointer(); return EntityItemPointer();
} }
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) { if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::checkForTreeEntityAndTypeMatch - no entity with ID" << entityID; qCDebug(entities) << "EntityScriptingInterface::checkForTreeEntityAndTypeMatch - no entity with ID" << entityID;
return entity; return entity;
} }
if (entityType != EntityTypes::Unknown && entity->getType() != entityType) { if (entityType != EntityTypes::Unknown && entity->getType() != entityType) {
return EntityItemPointer(); return EntityItemPointer();
} }
return entity; return entity;
} }

View file

@ -179,14 +179,6 @@ public:
*/ */
virtual void clearExceptions() = 0; virtual void clearExceptions() = 0;
/**
* @brief Creates a clone of the current exception
*
* @param detail Additional text to add to the report
* @return ScriptValue Result
*/
virtual ScriptValue cloneUncaughtException(const QString& detail = QString()) = 0;
/** /**
* @brief Context of the currently running script * @brief Context of the currently running script
* *
@ -274,15 +266,6 @@ public:
*/ */
virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) = 0; virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) = 0;
/**
* @brief Creates a ScriptValue that contains an error
*
* @param other
* @param type
* @return ScriptValue
*/
virtual ScriptValue makeError(const ScriptValue& other = ScriptValue(), const QString& type = "Error") = 0;
/** /**
* @brief Pointer to the ScriptManager that controls this scripting engine * @brief Pointer to the ScriptManager that controls this scripting engine
* *
@ -290,19 +273,6 @@ public:
*/ */
ScriptManager* manager() const { return _manager; } ScriptManager* manager() const { return _manager; }
/**
* @brief Emit the current uncaught exception if there is one
*
* If there's an uncaught exception, emit it, and clear the exception status.
*
* This fails if there's no uncaught exception, there's no ScriptManager,
* or the engine is evaluating.
*
* @param debugHint A debugging hint to be added to the error message
* @return true There was an uncaught exception, and it was emitted
* @return false There was no uncaught exception
*/
virtual bool maybeEmitUncaughtException(const QString& debugHint = QString()) = 0;
virtual ScriptValue newArray(uint length = 0) = 0; virtual ScriptValue newArray(uint length = 0) = 0;
virtual ScriptValue newArrayBuffer(const QByteArray& message) = 0; virtual ScriptValue newArrayBuffer(const QByteArray& message) = 0;
virtual ScriptValue newFunction(FunctionSignature fun, int length = 0) { virtual ScriptValue newFunction(FunctionSignature fun, int length = 0) {
@ -323,14 +293,39 @@ public:
virtual ScriptValue nullValue() = 0; virtual ScriptValue nullValue() = 0;
/**
* @brief Make a ScriptValue that contains an error
*
* This is used to throw an error inside the running script
*
* @param other
* @param type
* @return ScriptValue ScriptValue containing error
*/
virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") = 0;
/** /**
* @brief Causes an exception to be raised in the currently executing script * @brief Causes an exception to be raised in the currently executing script
* *
* @param exception Exception to be thrown in the script * @param exception Exception to be thrown in the script
* @param reason Explanatory text about why the exception happened, for logging
* @return true Exception was successfully thrown * @return true Exception was successfully thrown
* @return false Exception couldn't be thrown because no script is running * @return false Exception couldn't be thrown because no script is running
*/ */
virtual bool raiseException(const ScriptValue& exception) = 0; virtual bool raiseException(const ScriptValue& exception, const QString &reason = QString()) = 0;
/**
* @brief Causes an exception to be raised in the currently executing script
*
* @param error Exception to be thrown in the script
* @param reason Explanatory text about why the exception happened, for logging
* @return true Exception was successfully thrown
* @return false Exception couldn't be thrown because no script is running
*/
virtual bool raiseException(const QString& error, const QString &reason = QString()) = 0;
virtual void registerEnum(const QString& enumName, QMetaEnum newEnum) = 0; virtual void registerEnum(const QString& enumName, QMetaEnum newEnum) = 0;
virtual void registerFunction(const QString& name, FunctionSignature fun, int numArguments = -1) = 0; virtual void registerFunction(const QString& name, FunctionSignature fun, int numArguments = -1) = 0;
virtual void registerFunction(const QString& parent, const QString& name, FunctionSignature fun, int numArguments = -1) = 0; virtual void registerFunction(const QString& parent, const QString& name, FunctionSignature fun, int numArguments = -1) = 0;
@ -346,11 +341,14 @@ public:
virtual ScriptValue undefinedValue() = 0; virtual ScriptValue undefinedValue() = 0;
/** /**
* @brief Last uncaught exception, if any * @brief Last uncaught exception, if any.
* *
* @return ScriptValue Uncaught exception from the script * The returned shared pointer is newly allocated by the function,
* and modifying it has no effect on the internal state of the ScriptEngine.
*
* @return std::shared_ptr<ScriptValue> Uncaught exception from the script
*/ */
virtual ScriptException uncaughtException() const = 0; virtual std::shared_ptr<ScriptException> uncaughtException() const = 0;
virtual void updateMemoryCost(const qint64& deltaSize) = 0; virtual void updateMemoryCost(const qint64& deltaSize) = 0;
virtual void requestCollectGarbage() = 0; virtual void requestCollectGarbage() = 0;
@ -403,7 +401,7 @@ signals:
* *
* @param exception Exception that was thrown * @param exception Exception that was thrown
*/ */
void exception(const ScriptException &exception); void exception(std::shared_ptr<ScriptException> exception);
protected: protected:
~ScriptEngine() {} // prevent explicit deletion of base class ~ScriptEngine() {} // prevent explicit deletion of base class

View file

@ -12,12 +12,15 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include "ScriptValue.h"
#pragma once #pragma once
/** /**
* @brief Scripting exception * @brief Scripting exception
* *
* Emitted from the scripting engine when an exception happens inside it. * Emitted from the scripting engine when an exception happens inside it.
* This is the base class.
* *
*/ */
class ScriptException { class ScriptException {
@ -63,6 +66,89 @@ class ScriptException {
QStringList backtrace; QStringList backtrace;
bool isEmpty() const { return errorMessage.isEmpty(); } bool isEmpty() const { return errorMessage.isEmpty(); }
/**
* @brief Clones this object.
*
* This is used in the scripting engine to ensure that while it can return different
* exception objects depending on what happened, the returned object is a copy that
* doesn't allow the caller to accidentally break the ScriptEngine's internal state.
*
* @return std::shared_ptr<ScriptException>
*/
virtual std::shared_ptr<ScriptException> clone() const {
return std::make_shared<ScriptException>(*this);
}
};
/**
* @brief Exception that ocurred inside the scripting engine on the c++ side
*
* This is something that went wrong inside the ScriptEngine or ScriptManager
* infrastructure.
*
*/
class ScriptEngineException : public ScriptException {
public:
ScriptEngineException(QString message = "", QString info = "", int line = 0, int column = 0, QStringList backtraceList = QStringList()) :
ScriptException(message, info, line, column, backtraceList) {
}
/**
* @brief Clones this object.
*
* This is used in the scripting engine to ensure that while it can return different
* exception objects depending on what happened, the returned object is a copy that
* doesn't allow the caller to accidentally break the ScriptEngine's internal state.
*
* @return std::shared_ptr<ScriptException>
*/
virtual std::shared_ptr<ScriptException> clone() const override {
return std::make_shared<ScriptEngineException>(*this);
}
};
/**
* @brief Exception that ocurred inside the running script
*
* This is something that went wrong inside the running script.
*
*/
class ScriptRuntimeException : public ScriptException {
public:
ScriptRuntimeException(QString message = "", QString info = "", int line = 0, int column = 0, QStringList backtraceList = QStringList()) :
ScriptException(message, info, line, column, backtraceList) {
}
/**
* @brief The actual value that was thrown by the script.
*
* The content is completely arbitrary.
*
*/
ScriptValue thrownValue;
/**
* @brief Clones this object.
*
* This is used in the scripting engine to ensure that while it can return different
* exception objects depending on what happened, the returned object is a copy that
* doesn't allow the caller to accidentally break the ScriptEngine's internal state.
*
* @return std::shared_ptr<ScriptException>
*/
virtual std::shared_ptr<ScriptException> clone() const override {
return std::make_shared<ScriptRuntimeException>(*this);
}
}; };
inline QDebug operator<<(QDebug debug, const ScriptException& e) { inline QDebug operator<<(QDebug debug, const ScriptException& e) {
@ -76,5 +162,20 @@ inline QDebug operator<<(QDebug debug, const ScriptException& e) {
debug << e.backtrace; debug << e.backtrace;
} }
return debug;
}
// Is this a bad practice?
inline QDebug operator<<(QDebug debug, std::shared_ptr<ScriptException> e) {
debug << "Exception:"
<< e->errorMessage
<< (e->additionalInfo.isEmpty() ? QString("") : "[" + e->additionalInfo + "]")
<< " at line " << e->errorLine << ", column " << e->errorColumn;
if (e->backtrace.length()) {
debug << "Backtrace:";
debug << e->backtrace;
}
return debug; return debug;
} }

View file

@ -282,6 +282,11 @@ ScriptManager::ScriptManager(Context context, const QString& scriptContents, con
processLevelMaxRetries = 1; processLevelMaxRetries = 1;
} }
#if 0
// V8TODO: Is this actually needed? Exceptions will already be logged on the
// script engine side.
// this is where all unhandled exceptions end up getting logged // this is where all unhandled exceptions end up getting logged
connect(this, &ScriptManager::unhandledException, this, [this](const ScriptValue& err) { connect(this, &ScriptManager::unhandledException, this, [this](const ScriptValue& err) {
qCCritical(scriptengine) << "Caught exception"; qCCritical(scriptengine) << "Caught exception";
@ -296,6 +301,7 @@ ScriptManager::ScriptManager(Context context, const QString& scriptContents, con
logException(output); logException(output);
}); });
#endif
if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) { if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) {
QObject::connect(this, &ScriptManager::update, this, [this]() { QObject::connect(this, &ScriptManager::update, this, [this]() {
@ -891,13 +897,11 @@ void ScriptManager::run() {
{ {
PROFILE_RANGE(script, _fileNameString); PROFILE_RANGE(script, _fileNameString);
_returnValue = _engine->evaluate(_scriptContents, _fileNameString); _returnValue = _engine->evaluate(_scriptContents, _fileNameString);
_engine->maybeEmitUncaughtException(__FUNCTION__);
if (_engine->hasUncaughtException()) { if (_engine->hasUncaughtException()) {
qCWarning(scriptengine) << "Engine has uncaught exception, stopping"; qCWarning(scriptengine) << "Engine has uncaught exception, stopping";
stop(); stop();
emit unhandledException(_engine->cloneUncaughtException(__FUNCTION__));
_engine->clearExceptions(); _engine->clearExceptions();
} }
} }
@ -1023,7 +1027,7 @@ void ScriptManager::run() {
if (!_engine->isEvaluating() && _engine->hasUncaughtException()) { if (!_engine->isEvaluating() && _engine->hasUncaughtException()) {
qCWarning(scriptengine) << __FUNCTION__ << "---------- UNCAUGHT EXCEPTION --------"; qCWarning(scriptengine) << __FUNCTION__ << "---------- UNCAUGHT EXCEPTION --------";
qCWarning(scriptengine) << "runInThread" << _engine->uncaughtException(); qCWarning(scriptengine) << "runInThread" << _engine->uncaughtException();
emit unhandledException(_engine->cloneUncaughtException(__FUNCTION__)); emit unhandledException(_engine->uncaughtException());
_engine->clearExceptions(); _engine->clearExceptions();
} }
} }
@ -1258,9 +1262,8 @@ QString ScriptManager::_requireResolve(const QString& moduleId, const QString& r
} }
auto message = QString("Cannot find module '%1' (%2)").arg(displayId); auto message = QString("Cannot find module '%1' (%2)").arg(displayId);
auto throwResolveError = [&](const ScriptValue& error) -> QString { auto throwResolveError = [&](const QString& error, const QString &details = QString()) -> QString {
_engine->raiseException(error); _engine->raiseException(error, "require.resolve: " + details);
_engine->maybeEmitUncaughtException("require.resolve");
return QString(); return QString();
}; };
@ -1269,7 +1272,7 @@ QString ScriptManager::_requireResolve(const QString& moduleId, const QString& r
if (idLength < 1 || idLength > MAX_MODULE_ID_LENGTH) { if (idLength < 1 || idLength > MAX_MODULE_ID_LENGTH) {
auto details = QString("rejecting invalid module id size (%1 chars [1,%2])") auto details = QString("rejecting invalid module id size (%1 chars [1,%2])")
.arg(idLength).arg(MAX_MODULE_ID_LENGTH); .arg(idLength).arg(MAX_MODULE_ID_LENGTH);
return throwResolveError(_engine->makeError(_engine->newValue(message.arg(details)), "RangeError")); return throwResolveError(details, "RangeError");
} }
// this regex matches: absolute, dotted or path-like URLs // this regex matches: absolute, dotted or path-like URLs
@ -1296,15 +1299,15 @@ QString ScriptManager::_requireResolve(const QString& moduleId, const QString& r
if (QFileInfo(unanchoredUrl.toLocalFile()).isFile()) { if (QFileInfo(unanchoredUrl.toLocalFile()).isFile()) {
auto msg = QString("relative module ids must be anchored; use './%1' instead") auto msg = QString("relative module ids must be anchored; use './%1' instead")
.arg(moduleId); .arg(moduleId);
return throwResolveError(_engine->makeError(_engine->newValue(message.arg(msg)))); return throwResolveError(message.arg(msg));
} }
} }
return throwResolveError(_engine->makeError(_engine->newValue(message.arg("system module not found")))); return throwResolveError(message.arg("system module not found"));
} }
} }
if (url.isRelative()) { if (url.isRelative()) {
return throwResolveError(_engine->makeError(_engine->newValue(message.arg("could not resolve module id")))); return throwResolveError(message.arg("could not resolve module id"));
} }
// if it looks like a local file, verify that it's an allowed path and really a file // if it looks like a local file, verify that it's an allowed path and really a file
@ -1317,22 +1320,21 @@ QString ScriptManager::_requireResolve(const QString& moduleId, const QString& r
bool disallowOutsideFiles = !PathUtils::defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile(); bool disallowOutsideFiles = !PathUtils::defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile();
if (disallowOutsideFiles && !PathUtils::isDescendantOf(canonical, currentSandboxURL)) { if (disallowOutsideFiles && !PathUtils::isDescendantOf(canonical, currentSandboxURL)) {
return throwResolveError(_engine->makeError(_engine->newValue(message.arg( return throwResolveError(message.arg(
QString("path '%1' outside of origin script '%2' '%3'") QString("path '%1' outside of origin script '%2' '%3'")
.arg(PathUtils::stripFilename(url)) .arg(PathUtils::stripFilename(url))
.arg(PathUtils::stripFilename(currentSandboxURL)) .arg(PathUtils::stripFilename(currentSandboxURL))
.arg(canonical.toString()) .arg(canonical.toString())
)))); ));
} }
if (!file.exists()) { if (!file.exists()) {
return throwResolveError(_engine->makeError(_engine->newValue(message.arg("path does not exist: " + url.toLocalFile())))); return throwResolveError(message.arg("path does not exist: " + url.toLocalFile()));
} }
if (!file.isFile()) { if (!file.isFile()) {
return throwResolveError(_engine->makeError(_engine->newValue(message.arg("path is not a file: " + url.toLocalFile())))); return throwResolveError(message.arg("path is not a file: " + url.toLocalFile()));
} }
} }
_engine->maybeEmitUncaughtException(__FUNCTION__);
return url.toString(); return url.toString();
} }
@ -1488,7 +1490,7 @@ ScriptValue ScriptManager::instantiateModule(const ScriptValue& module, const QS
//_engine->scriptValueDebugDetails(module); //_engine->scriptValueDebugDetails(module);
result = _engine->evaluateInClosure(closure, _engine->newProgram( sourceCode, modulePath )); result = _engine->evaluateInClosure(closure, _engine->newProgram( sourceCode, modulePath ));
} }
_engine->maybeEmitUncaughtException(__FUNCTION__);
return result; return result;
} }
@ -1510,9 +1512,8 @@ ScriptValue ScriptManager::require(const QString& moduleId) {
#ifdef DEBUG_JS_MODULES #ifdef DEBUG_JS_MODULES
qCWarning(scriptengine_module) << "throwing module error:" << error.toString() << modulePath << error.property("stack").toString(); qCWarning(scriptengine_module) << "throwing module error:" << error.toString() << modulePath << error.property("stack").toString();
#endif #endif
_engine->raiseException(error); _engine->raiseException(error, "module error");
} }
_engine->maybeEmitUncaughtException("module");
return _engine->nullValue(); return _engine->nullValue();
}; };
@ -1520,7 +1521,6 @@ ScriptValue ScriptManager::require(const QString& moduleId) {
QString modulePath = _requireResolve(moduleId); QString modulePath = _requireResolve(moduleId);
if (modulePath.isNull() || _engine->hasUncaughtException()) { if (modulePath.isNull() || _engine->hasUncaughtException()) {
// the resolver already threw an exception -- bail early // the resolver already threw an exception -- bail early
_engine->maybeEmitUncaughtException(__FUNCTION__);
return _engine->nullValue(); return _engine->nullValue();
} }
@ -1546,7 +1546,6 @@ ScriptValue ScriptManager::require(const QString& moduleId) {
qCDebug(scriptengine_module) << QString("require - using cached module for '%1' (loaded: %2)") qCDebug(scriptengine_module) << QString("require - using cached module for '%1' (loaded: %2)")
.arg(moduleId).arg(module.property("loaded").toString()); .arg(moduleId).arg(module.property("loaded").toString());
registerModuleWithParent(module, parent); registerModuleWithParent(module, parent);
_engine->maybeEmitUncaughtException("cached module");
return exports; return exports;
} }
@ -1594,7 +1593,6 @@ ScriptValue ScriptManager::require(const QString& moduleId) {
//qCDebug(scriptengine_module) << "//ScriptManager::require(" << moduleId << ")"; //qCDebug(scriptengine_module) << "//ScriptManager::require(" << moduleId << ")";
_engine->maybeEmitUncaughtException(__FUNCTION__);
//qCDebug(scriptengine_module) << "Exports: " << _engine->scriptValueDebugDetails(module.property("exports")); //qCDebug(scriptengine_module) << "Exports: " << _engine->scriptValueDebugDetails(module.property("exports"));
return module.property("exports"); return module.property("exports");
} }
@ -1670,7 +1668,9 @@ void ScriptManager::include(const QStringList& includeFiles, const ScriptValue&
doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation); doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation);
if(_engine->hasUncaughtException()) { if(_engine->hasUncaughtException()) {
emit unhandledException(_engine->cloneUncaughtException("evaluateInclude")); auto ex = _engine->uncaughtException();
ex->additionalInfo += "; evaluateInClosure";
emit unhandledException(ex);
_engine->clearExceptions(); _engine->clearExceptions();
} }
} else { } else {
@ -1995,11 +1995,14 @@ void ScriptManager::entityScriptContentAvailable(const EntityItemID& entityID, c
//syntaxError.setProperty("detail", entityID.toString()); //syntaxError.setProperty("detail", entityID.toString());
//V8TODO //V8TODO
//emit unhandledException(syntaxError); //emit unhandledException(syntaxError);
return; return;
} }
if (!program) { if (!program) {
setError("Bad program (isNull)", EntityScriptStatus::ERROR_RUNNING_SCRIPT); setError("Bad program (isNull)", EntityScriptStatus::ERROR_RUNNING_SCRIPT);
emit unhandledException(_engine->makeError(_engine->newValue("program.isNull"))); std::shared_ptr<ScriptException> ex = std::make_shared<ScriptEngineException>("Program is Null", "Bad program in entityScriptContentAvailable");
emit unhandledException(ex);
return; // done processing script return; // done processing script
} }
@ -2163,7 +2166,10 @@ void ScriptManager::entityScriptContentAvailable(const EntityItemID& entityID, c
entityScriptObject = entityScriptConstructor.construct(); entityScriptObject = entityScriptConstructor.construct();
if (_engine->hasUncaughtException()) { if (_engine->hasUncaughtException()) {
entityScriptObject = _engine->cloneUncaughtException("(construct " + entityID.toString() + ")"); // V8TODO: Why were we copying the uncaught exception here? Does anything
// actually make use of that?
// entityScriptObject = _engine->cloneUncaughtException("(construct " + entityID.toString() + ")");
entityScriptObject = _engine->nullValue();
_engine->clearExceptions(); _engine->clearExceptions();
} }
}; };
@ -2171,9 +2177,10 @@ void ScriptManager::entityScriptContentAvailable(const EntityItemID& entityID, c
doWithEnvironment(entityID, sandboxURL, initialization); doWithEnvironment(entityID, sandboxURL, initialization);
if (entityScriptObject.isError()) { if (entityScriptObject.isError()) {
auto exception = entityScriptObject; // auto exception = entityScriptObject;
setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT); // setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT);
emit unhandledException(exception); // emit unhandledException(exception);
// V8TODO: Is this needed? Wouldn't the ScriptManager have already emitted the exception?
return; return;
} }
@ -2340,7 +2347,6 @@ void ScriptManager::doWithEnvironment(const EntityItemID& entityID, const QUrl&
#else #else
operation(); operation();
#endif #endif
_engine->maybeEmitUncaughtException(!entityID.isNull() ? entityID.toString() : __FUNCTION__);
currentEntityIdentifier = oldIdentifier; currentEntityIdentifier = oldIdentifier;
currentSandboxURL = oldSandboxURL; currentSandboxURL = oldSandboxURL;
} }

View file

@ -46,6 +46,7 @@
#include "Quat.h" #include "Quat.h"
#include "ScriptUUID.h" #include "ScriptUUID.h"
#include "ScriptValue.h" #include "ScriptValue.h"
#include "ScriptException.h"
#include "Vec3.h" #include "Vec3.h"
static const QString NO_SCRIPT(""); static const QString NO_SCRIPT("");
@ -1326,7 +1327,7 @@ signals:
* *
* @param exception * @param exception
*/ */
void unhandledException(const ScriptValue& exception); void unhandledException(std::shared_ptr<ScriptException> exception);
///@} ///@}

View file

@ -30,5 +30,12 @@
connect(_manager, &ScriptManager::doneRunning, this, &ScriptManagerScriptingInterface::doneRunning); connect(_manager, &ScriptManager::doneRunning, this, &ScriptManagerScriptingInterface::doneRunning);
connect(_manager, &ScriptManager::entityScriptDetailsUpdated, this, &ScriptManagerScriptingInterface::entityScriptDetailsUpdated); connect(_manager, &ScriptManager::entityScriptDetailsUpdated, this, &ScriptManagerScriptingInterface::entityScriptDetailsUpdated);
connect(_manager, &ScriptManager::entityScriptPreloadFinished, this, &ScriptManagerScriptingInterface::entityScriptPreloadFinished); connect(_manager, &ScriptManager::entityScriptPreloadFinished, this, &ScriptManagerScriptingInterface::entityScriptPreloadFinished);
connect(_manager, &ScriptManager::unhandledException, this, &ScriptManagerScriptingInterface::unhandledException); connect(_manager, &ScriptManager::unhandledException, this, &ScriptManagerScriptingInterface::scriptManagerException);
}
void ScriptManagerScriptingInterface::scriptManagerException(std::shared_ptr<ScriptException> exception) {
// V8TODO: What should we actually handle here?
// emit unhandledException(exception.thrownValue);
} }

View file

@ -698,7 +698,11 @@ protected:
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status) Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status)
{ _manager->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success, status); } { _manager->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success, status); }
private slots:
void scriptManagerException(std::shared_ptr<ScriptException> exception);
private: private:
ScriptManager *_manager; ScriptManager *_manager;
}; };

View file

@ -71,20 +71,17 @@ bool ScriptEngineV8::IS_THREADSAFE_INVOCATION(const QThread* thread, const QStri
return false; return false;
} }
// engine-aware JS Error copier and factory ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) {
V8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QString& type) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
v8::Locker locker(_v8Isolate); return nullValue();
v8::Isolate::Scope isolateScope(_v8Isolate);
v8::HandleScope handleScope(_v8Isolate);
v8::Context::Scope contextScope(getContext());
return V8ScriptValue(this, v8::Null(_v8Isolate));
} }
v8::Locker locker(_v8Isolate); v8::Locker locker(_v8Isolate);
v8::Isolate::Scope isolateScope(_v8Isolate); v8::Isolate::Scope isolateScope(_v8Isolate);
v8::HandleScope handleScope(_v8Isolate); v8::HandleScope handleScope(_v8Isolate);
v8::Context::Scope contextScope(getContext()); v8::Context::Scope contextScope(getContext());
return V8ScriptValue(this, v8::Null(_v8Isolate)); return nullValue();
}
//V8TODO //V8TODO
/* /*
auto other = _other; auto other = _other;
@ -117,33 +114,8 @@ V8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QStri
err.setProperty(it.name(), it.value()); err.setProperty(it.name(), it.value());
} }
return err;*/ return err;*/
} //}
ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return nullValue();
}
v8::Locker locker(_v8Isolate);
v8::Isolate::Scope isolateScope(_v8Isolate);
v8::HandleScope handleScope(_v8Isolate);
v8::Context::Scope contextScope(getContext());
return nullValue();
//V8TODO
//what does makeError actually do?
/*ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(_other);
V8ScriptValue other;
if (_other.isString()) {
other = QScriptEngine::newObject();
other.setProperty("message", _other.toString());
} else if (unwrapped) {
other = unwrapped->toV8Value();
} else {
other = QScriptEngine::newVariant(_other.toVariant());
}
V8ScriptValue result = makeError(other, type);
return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result)));*/
}
// check syntax and when there are issues returns an actual "SyntaxError" with the details // check syntax and when there are issues returns an actual "SyntaxError" with the details
ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) { ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) {
@ -200,82 +172,14 @@ ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) {
return undefinedValue(); return undefinedValue();
}*/ }*/
// this pulls from the best available information to create a detailed snapshot of the current exception
ScriptValue ScriptEngineV8::cloneUncaughtException(const QString& extraDetail) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return nullValue();
}
if (!hasUncaughtException()) {
return nullValue();
}
v8::Locker locker(_v8Isolate);
v8::Isolate::Scope isolateScope(_v8Isolate);
v8::HandleScope handleScope(_v8Isolate);
v8::Context::Scope contextScope(getContext());
return nullValue();
//V8TODO
/*
auto exception = uncaughtException();
// ensure the error object is engine-local
auto err = makeError(exception);
// not sure why Qt does't offer uncaughtExceptionFileName -- but the line number
// on its own is often useless/wrong if arbitrarily married to a filename.
// when the error object already has this info, it seems to be the most reliable
auto fileName = exception.property("fileName").toString();
auto lineNumber = exception.property("lineNumber").toInt32();
// the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace
auto backtrace = uncaughtExceptionBacktrace();
if (backtrace.isEmpty()) {
// fallback to the error object
backtrace = exception.property("stack").toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP);
}
// the ad hoc "detail" property can be used now to embed additional clues
auto detail = exception.property("detail").toString();
if (detail.isEmpty()) {
detail = extraDetail;
} else if (!extraDetail.isEmpty()) {
detail += "(" + extraDetail + ")";
}
if (lineNumber <= 0) {
lineNumber = uncaughtExceptionLineNumber();
}
if (fileName.isEmpty()) {
// climb the stack frames looking for something useful to display
for (auto c = QScriptEngine::currentContext(); c && fileName.isEmpty(); c = c->parentContext()) {
V8ScriptContextInfo info{ c };
if (!info.fileName().isEmpty()) {
// take fileName:lineNumber as a pair
fileName = info.fileName();
lineNumber = info.lineNumber();
if (backtrace.isEmpty()) {
backtrace = c->backtrace();
}
break;
}
}
}
err.setProperty("fileName", fileNlintame);
err.setProperty("lineNumber", lineNumber);
err.setProperty("detail", detail);
err.setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP));
#ifdef DEBUG_JS_EXCEPTIONS
err.setProperty("_fileName", exception.property("fileName").toString());
err.setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP));
err.setProperty("_lineNumber", uncaughtExceptionLineNumber());
#endif
return err;
*/
}
bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) { bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return false; return false;
} }
//V8TODO //V8TODO
_v8Isolate->ThrowException(makeError(exception).get()); // _v8Isolate->ThrowException(makeError(exception).get());
/*if (QScriptEngine::currentContext()) { /*if (QScriptEngine::currentContext()) {
// we have an active context / JS stack frame so throw the exception per usual // we have an active context / JS stack frame so throw the exception per usual
QScriptEngine::currentContext()->throwValue(makeError(exception)); QScriptEngine::currentContext()->throwValue(makeError(exception));
@ -290,22 +194,6 @@ bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) {
return false; return false;
} }
bool ScriptEngineV8::maybeEmitUncaughtException(const QString& debugHint) {
/*
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return false;
}
if (!isEvaluating() && hasUncaughtException()) {
emit exception(cloneUncaughtException(debugHint));
clearExceptions();
return true;
}
*/
return false;
}
// Lambda // Lambda
/*ScriptValue ScriptEngineV8::newLambdaFunction(std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation, /*ScriptValue ScriptEngineV8::newLambdaFunction(std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation,
const V8ScriptValue& data, const V8ScriptValue& data,
@ -1019,12 +907,11 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure,
} }
if (hasUncaughtException()) { if (hasUncaughtException()) {
auto err = cloneUncaughtException(__FUNCTION__);
#ifdef DEBUG_JS_EXCEPTIONS #ifdef DEBUG_JS_EXCEPTIONS
qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString(); qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();
err.setProperty("_result", result); err.setProperty("_result", result);
#endif #endif
result = err; result = nullValue();
} else { } else {
result = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, v8Result))); result = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, v8Result)));
} }
@ -1073,34 +960,9 @@ ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& f
v8::ScriptOrigin scriptOrigin(getIsolate(), v8::String::NewFromUtf8(getIsolate(), fileName.toStdString().c_str()).ToLocalChecked()); v8::ScriptOrigin scriptOrigin(getIsolate(), v8::String::NewFromUtf8(getIsolate(), fileName.toStdString().c_str()).ToLocalChecked());
v8::Local<v8::Script> script; v8::Local<v8::Script> script;
if (!v8::Script::Compile(getContext(), v8::String::NewFromUtf8(getIsolate(), sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { if (!v8::Script::Compile(getContext(), v8::String::NewFromUtf8(getIsolate(), sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) {
//V8TODO replace this with external function setUncaughtException(tryCatch, "Error while compiling script");
int errorColumnNumber = 0;
int errorLineNumber = 0;
QString errorMessage = "";
QString errorBacktrace = "";
//v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception());
v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get());
errorMessage = QString(*utf8Value);
v8::Local<v8::Message> exceptionMessage = tryCatch.Message();
if (!exceptionMessage.IsEmpty()) {
errorLineNumber = exceptionMessage->GetLineNumber(getContext()).FromJust();
errorColumnNumber = exceptionMessage->GetStartColumn(getContext()).FromJust();
v8::Local<v8::Value> backtraceV8String;
if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) {
if (backtraceV8String->IsString()) {
if (v8::Local<v8::String>::Cast(backtraceV8String)->Length() > 0) {
v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String);
errorBacktrace = *backtraceUtf8Value;
}
}
}
qCDebug(scriptengine) << "Compiling script \"" << fileName << "\" failed on line " << errorLineNumber << " column " << errorColumnNumber << " with message: \"" << errorMessage <<"\" backtrace: " << errorBacktrace;
}
auto err = makeError(newValue(errorMessage));
raiseException(err);
maybeEmitUncaughtException("compile");
_evaluatingCounter--; _evaluatingCounter--;
return err; return nullValue();
} }
//qCDebug(scriptengine) << "Script compilation succesful: " << fileName; //qCDebug(scriptengine) << "Script compilation succesful: " << fileName;
@ -1145,6 +1007,12 @@ ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& f
return ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValue))); return ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValue)));
} }
void ScriptEngineV8::setUncaughtEngineException(const QString &reason, const QString& info) {
auto ex = std::make_shared<ScriptEngineException>(reason, info);
setUncaughtException(ex);
}
void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QString& info) { void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QString& info) {
if (!tryCatch.HasCaught()) { if (!tryCatch.HasCaught()) {
qCWarning(scriptengine) << "setUncaughtException called without exception"; qCWarning(scriptengine) << "setUncaughtException called without exception";
@ -1152,8 +1020,8 @@ void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QS
return; return;
} }
ScriptException ex; auto ex = std::make_shared<ScriptException>();
ex.additionalInfo = info; ex->additionalInfo = info;
v8::Locker locker(_v8Isolate); v8::Locker locker(_v8Isolate);
v8::Isolate::Scope isolateScope(_v8Isolate); v8::Isolate::Scope isolateScope(_v8Isolate);
@ -1166,30 +1034,35 @@ void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QS
//v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception()); //v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception());
v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get()); v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get());
ex.errorMessage = QString(*utf8Value); ex->errorMessage = QString(*utf8Value);
v8::Local<v8::Message> exceptionMessage = tryCatch.Message(); v8::Local<v8::Message> exceptionMessage = tryCatch.Message();
if (!exceptionMessage.IsEmpty()) { if (!exceptionMessage.IsEmpty()) {
ex.errorLine = exceptionMessage->GetLineNumber(getContext()).FromJust(); ex->errorLine = exceptionMessage->GetLineNumber(getContext()).FromJust();
ex.errorColumn = exceptionMessage->GetStartColumn(getContext()).FromJust(); ex->errorColumn = exceptionMessage->GetStartColumn(getContext()).FromJust();
v8::Local<v8::Value> backtraceV8String; v8::Local<v8::Value> backtraceV8String;
if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) { if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) {
if (backtraceV8String->IsString()) { if (backtraceV8String->IsString()) {
if (v8::Local<v8::String>::Cast(backtraceV8String)->Length() > 0) { if (v8::Local<v8::String>::Cast(backtraceV8String)->Length() > 0) {
v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String); v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String);
QString errorBacktrace = *backtraceUtf8Value; QString errorBacktrace = *backtraceUtf8Value;
ex.backtrace = errorBacktrace.split("\n"); ex->backtrace = errorBacktrace.split("\n");
} }
} }
} }
} }
qCDebug(scriptengine) << "Emitting exception:" << ex; setUncaughtException(ex);
_uncaughtException = ex;
emit exception(ex);
} }
void ScriptEngineV8::setUncaughtException(std::shared_ptr<ScriptException> uncaughtException) {
qCDebug(scriptengine) << "Emitting exception:" << uncaughtException;
_uncaughtException = uncaughtException;
auto copy = uncaughtException->clone();
emit exception(copy);
}
QString ScriptEngineV8::formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch) { QString ScriptEngineV8::formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch) {
@ -1274,18 +1147,14 @@ Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& pro
v8::Context::Scope contextScope(getContext()); v8::Context::Scope contextScope(getContext());
ScriptProgramV8Wrapper* unwrapped = ScriptProgramV8Wrapper::unwrap(program); ScriptProgramV8Wrapper* unwrapped = ScriptProgramV8Wrapper::unwrap(program);
if (!unwrapped) { if (!unwrapped) {
errorValue = makeError(newValue("could not unwrap program")); setUncaughtEngineException("Could not unwrap program", "Compile error");
raiseException(errorValue);
maybeEmitUncaughtException("compile");
hasFailed = true; hasFailed = true;
} }
if(!hasFailed) { if(!hasFailed) {
ScriptSyntaxCheckResultPointer syntaxCheck = unwrapped->checkSyntax(); ScriptSyntaxCheckResultPointer syntaxCheck = unwrapped->checkSyntax();
if (syntaxCheck->state() == ScriptSyntaxCheckResult::Error) { if (syntaxCheck->state() == ScriptSyntaxCheckResult::Error) {
errorValue = makeError(newValue(syntaxCheck->errorMessage())); setUncaughtEngineException(syntaxCheck->errorMessage(), "Compile error");
raiseException(errorValue);
maybeEmitUncaughtException("compile");
hasFailed = true; hasFailed = true;
} }
} }
@ -1307,8 +1176,7 @@ Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& pro
Q_ASSERT(tryCatchRun.HasCaught()); Q_ASSERT(tryCatchRun.HasCaught());
auto runError = tryCatchRun.Message(); auto runError = tryCatchRun.Message();
errorValue = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, runError->Get()))); errorValue = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, runError->Get())));
raiseException(errorValue); raiseException(errorValue, "evaluation error");
maybeEmitUncaughtException("evaluate");
hasFailed = true; hasFailed = true;
} else { } else {
// V8TODO this is just to check if run will always return false for uncaught exception // V8TODO this is just to check if run will always return false for uncaught exception
@ -1529,7 +1397,7 @@ void ScriptEngineV8::abortEvaluation() {
} }
void ScriptEngineV8::clearExceptions() { void ScriptEngineV8::clearExceptions() {
_uncaughtException = ScriptException(); _uncaughtException.reset();
} }
ScriptContext* ScriptEngineV8::currentContext() const { ScriptContext* ScriptEngineV8::currentContext() const {
@ -1551,7 +1419,7 @@ ScriptContext* ScriptEngineV8::currentContext() const {
} }
bool ScriptEngineV8::hasUncaughtException() const { bool ScriptEngineV8::hasUncaughtException() const {
return !_uncaughtException.isEmpty(); return _uncaughtException != nullptr;
} }
bool ScriptEngineV8::isEvaluating() const { bool ScriptEngineV8::isEvaluating() const {
@ -1667,11 +1535,15 @@ void ScriptEngineV8::setThread(QThread* thread) {
}*/ }*/
ScriptException ScriptEngineV8::uncaughtException() const { std::shared_ptr<ScriptException> ScriptEngineV8::uncaughtException() const {
return _uncaughtException; return _uncaughtException->clone();
} }
bool ScriptEngineV8::raiseException(const ScriptValue& exception) { bool ScriptEngineV8::raiseException(const QString& error, const QString &reason) {
return raiseException(ScriptValue(), reason);
}
bool ScriptEngineV8::raiseException(const ScriptValue& exception, const QString &reason) {
//V8TODO //V8TODO
//Q_ASSERT(false); //Q_ASSERT(false);
// qCritical() << "Script exception occurred: " << exception.toString(); // qCritical() << "Script exception occurred: " << exception.toString();
@ -1681,6 +1553,7 @@ bool ScriptEngineV8::raiseException(const ScriptValue& exception) {
// emit // emit
//return raiseException(qException); //return raiseException(qException);
qCCritical(scriptengine) << "Raise exception for reason" << reason << "NOT IMPLEMENTED!";
return false; return false;
} }

View file

@ -71,7 +71,6 @@ public: // construction
public: // ScriptEngine implementation public: // ScriptEngine implementation
virtual void abortEvaluation() override; virtual void abortEvaluation() override;
virtual void clearExceptions() override; virtual void clearExceptions() override;
virtual ScriptValue cloneUncaughtException(const QString& detail = QString()) override;
virtual ScriptContext* currentContext() const override; virtual ScriptContext* currentContext() const override;
Q_INVOKABLE virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) override; Q_INVOKABLE virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) override;
Q_INVOKABLE virtual ScriptValue evaluate(const ScriptProgramPointer& program) override; Q_INVOKABLE virtual ScriptValue evaluate(const ScriptProgramPointer& program) override;
@ -81,11 +80,6 @@ public: // ScriptEngine implementation
virtual bool isEvaluating() const override; virtual bool isEvaluating() const override;
//virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) override; //virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) override;
virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) override; virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) override;
virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") override;
// if there is a pending exception and we are at the top level (non-recursive) stack frame, this emits and resets it
virtual bool maybeEmitUncaughtException(const QString& debugHint = QString()) override;
virtual ScriptValue newArray(uint length = 0) override; virtual ScriptValue newArray(uint length = 0) override;
virtual ScriptValue newArrayBuffer(const QByteArray& message) override; virtual ScriptValue newArrayBuffer(const QByteArray& message) override;
@ -106,7 +100,11 @@ public: // ScriptEngine implementation
virtual ScriptValue newValue(const char* value) override; virtual ScriptValue newValue(const char* value) override;
virtual ScriptValue newVariant(const QVariant& value) override; virtual ScriptValue newVariant(const QVariant& value) override;
virtual ScriptValue nullValue() override; virtual ScriptValue nullValue() override;
virtual bool raiseException(const ScriptValue& exception) override;
virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") override;
virtual bool raiseException(const QString& exception, const QString &reason = QString()) override;
virtual bool raiseException(const ScriptValue& exception, const QString &reason = QString()) override;
Q_INVOKABLE virtual void registerEnum(const QString& enumName, QMetaEnum newEnum) override; Q_INVOKABLE virtual void registerEnum(const QString& enumName, QMetaEnum newEnum) override;
Q_INVOKABLE virtual void registerFunction(const QString& name, Q_INVOKABLE virtual void registerFunction(const QString& name,
ScriptEngine::FunctionSignature fun, ScriptEngine::FunctionSignature fun,
@ -128,7 +126,7 @@ public: // ScriptEngine implementation
virtual void setThread(QThread* thread) override; virtual void setThread(QThread* thread) override;
//Q_INVOKABLE virtual void enterIsolateOnThisThread() override; //Q_INVOKABLE virtual void enterIsolateOnThisThread() override;
virtual ScriptValue undefinedValue() override; virtual ScriptValue undefinedValue() override;
virtual ScriptException uncaughtException() const override; virtual std::shared_ptr<ScriptException> uncaughtException() const override;
virtual void updateMemoryCost(const qint64& deltaSize) override; virtual void updateMemoryCost(const qint64& deltaSize) override;
virtual void requestCollectGarbage() override { while(!_v8Isolate->IdleNotificationDeadline(getV8Platform()->MonotonicallyIncreasingTime() + GARBAGE_COLLECTION_TIME_LIMIT_S)) {}; } virtual void requestCollectGarbage() override { while(!_v8Isolate->IdleNotificationDeadline(getV8Platform()->MonotonicallyIncreasingTime() + GARBAGE_COLLECTION_TIME_LIMIT_S)) {}; }
virtual void compileTest() override; virtual void compileTest() override;
@ -142,7 +140,7 @@ public: // ScriptEngine implementation
inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); } inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); }
protected: // brought over from BaseScriptEngine protected: // brought over from BaseScriptEngine
V8ScriptValue makeError(const V8ScriptValue& other, const QString& type = "Error");
// if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it. // if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it.
// note: this is used in cases where C++ code might call into JS API methods directly // note: this is used in cases where C++ code might call into JS API methods directly
@ -223,16 +221,18 @@ signals:
* *
* @param exception Exception that was thrown * @param exception Exception that was thrown
*/ */
void exception(const ScriptException &exception); void exception(std::shared_ptr<ScriptException> exception);
protected: protected:
static QMutex _v8InitMutex; static QMutex _v8InitMutex;
static std::once_flag _v8InitOnceFlag; static std::once_flag _v8InitOnceFlag;
static v8::Platform* getV8Platform(); static v8::Platform* getV8Platform();
void setUncaughtEngineException(const QString &message, const QString& info = QString());
void setUncaughtException(const v8::TryCatch &tryCatch, const QString& info = QString()); void setUncaughtException(const v8::TryCatch &tryCatch, const QString& info = QString());
void setUncaughtException(std::shared_ptr<ScriptException> exception);
ScriptException _uncaughtException; std::shared_ptr<ScriptException> _uncaughtException;
// V8TODO: clean up isolate when script engine is destroyed? // V8TODO: clean up isolate when script engine is destroyed?

View file

@ -0,0 +1 @@
foo();