diff --git a/src/3rdparty/squirrel/include/squirrel.h b/src/3rdparty/squirrel/include/squirrel.h index 5623760fac..799c263f6b 100644 --- a/src/3rdparty/squirrel/include/squirrel.h +++ b/src/3rdparty/squirrel/include/squirrel.h @@ -286,6 +286,7 @@ SQUIRREL_API void sq_setprintfunc(HSQUIRRELVM v, SQPRINTFUNCTION printfunc); SQUIRREL_API SQPRINTFUNCTION sq_getprintfunc(HSQUIRRELVM v); SQUIRREL_API SQRESULT sq_suspendvm(HSQUIRRELVM v); SQUIRREL_API bool sq_resumecatch(HSQUIRRELVM v, int suspend = -1); +SQUIRREL_API bool sq_resumeerror(HSQUIRRELVM v); SQUIRREL_API SQRESULT sq_wakeupvm(HSQUIRRELVM v,SQBool resumedret,SQBool retval,SQBool raiseerror); SQUIRREL_API SQInteger sq_getvmstate(HSQUIRRELVM v); diff --git a/src/3rdparty/squirrel/squirrel/sqapi.cpp b/src/3rdparty/squirrel/squirrel/sqapi.cpp index 314de4dc83..4247e25236 100644 --- a/src/3rdparty/squirrel/squirrel/sqapi.cpp +++ b/src/3rdparty/squirrel/squirrel/sqapi.cpp @@ -1010,6 +1010,14 @@ bool sq_resumecatch(HSQUIRRELVM v, int suspend) return v->Execute(_null_, v->_top, -1, -1, ret, SQTrue, SQVM::ET_RESUME_OPENTTD); } +bool sq_resumeerror(HSQUIRRELVM v) +{ + SQObjectPtr ret; + v->_can_suspend = true; + v->_ops_till_suspend = 1; + return v->Execute(_null_, v->_top, -1, -1, ret, SQTrue, SQVM::ET_RESUME_THROW_VM); +} + void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook) { if(sq_gettop(v) >= 1){ diff --git a/src/3rdparty/squirrel/squirrel/sqclass.h b/src/3rdparty/squirrel/squirrel/sqclass.h index 06f2b51e9d..895c053c24 100644 --- a/src/3rdparty/squirrel/squirrel/sqclass.h +++ b/src/3rdparty/squirrel/squirrel/sqclass.h @@ -128,7 +128,17 @@ public: } void Release() { _uiRef++; - if (_hook) { _hook(_userpointer,0);} + try { + if (_hook) { _hook(_userpointer,0);} + } catch (...) { + _uiRef--; + if (_uiRef == 0) { + SQInteger size = _memsize; + this->~SQInstance(); + SQ_FREE(this, size); + } + throw; + } _uiRef--; if(_uiRef > 0) return; SQInteger size = _memsize; diff --git a/src/3rdparty/squirrel/squirrel/sqvm.cpp b/src/3rdparty/squirrel/squirrel/sqvm.cpp index 4ad1b9374d..ed9d81f42a 100644 --- a/src/3rdparty/squirrel/squirrel/sqvm.cpp +++ b/src/3rdparty/squirrel/squirrel/sqvm.cpp @@ -684,10 +684,12 @@ bool SQVM::Execute(SQObjectPtr &closure, SQInteger target, SQInteger nargs, SQIn break; case ET_RESUME_GENERATOR: _generator(closure)->Resume(this, target); ci->_root = SQTrue; traps += ci->_etraps; break; case ET_RESUME_VM: + case ET_RESUME_THROW_VM: traps = _suspended_traps; ci->_root = _suspended_root; ci->_vargs = _suspend_varargs; _suspended = SQFalse; + if(et == ET_RESUME_THROW_VM) { SQ_THROW(); } break; case ET_RESUME_OPENTTD: traps = _suspended_traps; diff --git a/src/3rdparty/squirrel/squirrel/sqvm.h b/src/3rdparty/squirrel/squirrel/sqvm.h index 18c8681e75..437fadfd57 100644 --- a/src/3rdparty/squirrel/squirrel/sqvm.h +++ b/src/3rdparty/squirrel/squirrel/sqvm.h @@ -53,7 +53,7 @@ struct SQVM : public CHAINABLE_OBJ typedef sqvector CallInfoVec; public: - enum ExecutionType { ET_CALL, ET_RESUME_GENERATOR, ET_RESUME_VM, ET_RESUME_OPENTTD }; + enum ExecutionType { ET_CALL, ET_RESUME_GENERATOR, ET_RESUME_VM, ET_RESUME_THROW_VM, ET_RESUME_OPENTTD }; SQVM(SQSharedState *ss); ~SQVM(); bool Init(SQVM *friendvm, SQInteger stacksize); diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index 79f0ab5a7b..e31c8772c5 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -269,7 +269,7 @@ void AIInstance::Died() void AIInstance::GameLoop() { - if (this->is_dead) return; + if (this->IsDead()) return; if (this->engine->HasScriptCrashed()) { /* The script crashed during saving, kill it here. */ this->Died(); @@ -322,6 +322,11 @@ void AIInstance::GameLoop() } catch (AI_VMSuspend e) { this->suspend = e.GetSuspendTime(); this->callback = e.GetSuspendCallback(); + } catch (AI_FatalError e) { + this->is_dead = true; + this->engine->ThrowError(e.GetErrorMessage()); + this->engine->ResumeError(); + this->Died(); } this->is_started = true; @@ -338,12 +343,17 @@ void AIInstance::GameLoop() } catch (AI_VMSuspend e) { this->suspend = e.GetSuspendTime(); this->callback = e.GetSuspendCallback(); + } catch (AI_FatalError e) { + this->is_dead = true; + this->engine->ThrowError(e.GetErrorMessage()); + this->engine->ResumeError(); + this->Died(); } } void AIInstance::CollectGarbage() { - if (this->is_started && !this->is_dead) this->engine->CollectGarbage(); + if (this->is_started && !this->IsDead()) this->engine->CollectGarbage(); } /* static */ void AIInstance::DoCommandReturn(AIInstance *instance) @@ -562,10 +572,25 @@ void AIInstance::Save() /* We don't want to be interrupted during the save function. */ bool backup_allow = AIObject::GetAllowDoCommand(); AIObject::SetAllowDoCommand(false); - if (!this->engine->CallMethod(*this->instance, "Save", &savedata)) { - /* The script crashed in the Save function. We can't kill - * it here, but do so in the next AI tick. */ + try { + if (!this->engine->CallMethod(*this->instance, "Save", &savedata)) { + /* The script crashed in the Save function. We can't kill + * it here, but do so in the next AI tick. */ + SaveEmpty(); + this->engine->CrashOccurred(); + return; + } + } catch (AI_FatalError e) { + /* If we don't mark the AI as dead here cleaning up the squirrel + * stack could throw AI_FatalError again. */ + this->is_dead = true; + this->engine->ThrowError(e.GetErrorMessage()); + this->engine->ResumeError(); SaveEmpty(); + /* We can't kill the AI here, so mark it as crashed (not dead) and + * kill it in the next AI tick. */ + this->is_dead = false; + this->engine->CrashOccurred(); return; } AIObject::SetAllowDoCommand(backup_allow); diff --git a/src/ai/ai_instance.hpp b/src/ai/ai_instance.hpp index 25d72f8a2e..f99b3c335a 100644 --- a/src/ai/ai_instance.hpp +++ b/src/ai/ai_instance.hpp @@ -18,7 +18,7 @@ public: AI_VMSuspend(int time, AISuspendCallbackProc *callback) : time(time), callback(callback) - {} + {} int GetSuspendTime() { return time; } AISuspendCallbackProc *GetSuspendCallback() { return callback; } @@ -28,6 +28,21 @@ private: AISuspendCallbackProc *callback; }; +/** + * A throw-class that is given when the AI made a fatal error. + */ +class AI_FatalError { +public: + AI_FatalError(const char *msg) : + msg(msg) + {} + + const char *GetErrorMessage() { return msg; } + +private: + const char *msg; +}; + class AIInstance { public: friend class AIObject; @@ -80,6 +95,11 @@ public: */ class AIController *GetController() { return controller; } + /** + * Return the "this AI died" value + */ + inline bool IsDead() { return this->is_dead; } + /** * Call the AI Save function and save all data in the savegame. */ diff --git a/src/ai/api/ai_controller.cpp b/src/ai/api/ai_controller.cpp index f9ef2ab166..7e7e5d0191 100644 --- a/src/ai/api/ai_controller.cpp +++ b/src/ai/api/ai_controller.cpp @@ -24,8 +24,7 @@ /* static */ void AIController::Sleep(int ticks) { if (!AIObject::GetAllowDoCommand()) { - AILog::Error("You are not allowed to call Sleep in your constructor, Save(), Load(), and any valuator.\n"); - return; + throw AI_FatalError("You are not allowed to call Sleep in your constructor, Save(), Load(), and any valuator."); } if (ticks <= 0) { diff --git a/src/ai/api/ai_execmode.cpp b/src/ai/api/ai_execmode.cpp index 941718bee1..fd3b5f62f2 100644 --- a/src/ai/api/ai_execmode.cpp +++ b/src/ai/api/ai_execmode.cpp @@ -4,6 +4,9 @@ #include "ai_execmode.hpp" #include "../../command_type.h" +#include "../../company_base.h" +#include "../../company_func.h" +#include "../ai_instance.hpp" bool AIExecMode::ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs) { @@ -21,6 +24,12 @@ AIExecMode::AIExecMode() AIExecMode::~AIExecMode() { - assert(this->GetDoCommandModeInstance() == this); + if (this->GetDoCommandModeInstance() != this) { + AIInstance *instance = GetCompany(_current_company)->ai_instance; + /* Ignore this error if the AI already died. */ + if (!instance->IsDead()) { + throw AI_FatalError("AIExecMode object was removed while it was not the latest AI*Mode object created."); + } + } this->SetDoCommandMode(this->last_mode, this->last_instance); } diff --git a/src/ai/api/ai_object.cpp b/src/ai/api/ai_object.cpp index 912f724157..cdf57090cb 100644 --- a/src/ai/api/ai_object.cpp +++ b/src/ai/api/ai_object.cpp @@ -190,8 +190,7 @@ int AIObject::GetCallbackVariable(int index) bool AIObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback) { if (AIObject::GetAllowDoCommand() == false) { - AILog::Error("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator.\n"); - return false; + throw AI_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator."); } CommandCost res; diff --git a/src/ai/api/ai_testmode.cpp b/src/ai/api/ai_testmode.cpp index 484333b422..c3cdb77f46 100644 --- a/src/ai/api/ai_testmode.cpp +++ b/src/ai/api/ai_testmode.cpp @@ -4,6 +4,9 @@ #include "ai_testmode.hpp" #include "../../command_type.h" +#include "../../company_base.h" +#include "../../company_func.h" +#include "../ai_instance.hpp" bool AITestMode::ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs) { @@ -21,6 +24,12 @@ AITestMode::AITestMode() AITestMode::~AITestMode() { - assert(this->GetDoCommandModeInstance() == this); + if (this->GetDoCommandModeInstance() != this) { + AIInstance *instance = GetCompany(_current_company)->ai_instance; + /* Ignore this error if the AI already died. */ + if (!instance->IsDead()) { + throw AI_FatalError("AITestmode object was removed while it was not the latest AI*Mode object created."); + } + } this->SetDoCommandMode(this->last_mode, this->last_instance); } diff --git a/src/script/squirrel.cpp b/src/script/squirrel.cpp index 31dcf6aea0..1dd407589a 100644 --- a/src/script/squirrel.cpp +++ b/src/script/squirrel.cpp @@ -187,6 +187,12 @@ bool Squirrel::Resume(int suspend) return this->vm->_suspended != 0; } +void Squirrel::ResumeError() +{ + assert(!this->crashed); + sq_resumeerror(this->vm); +} + void Squirrel::CollectGarbage() { sq_collectgarbage(this->vm); diff --git a/src/script/squirrel.hpp b/src/script/squirrel.hpp index 612c6ce2ef..97dd7988d6 100644 --- a/src/script/squirrel.hpp +++ b/src/script/squirrel.hpp @@ -106,6 +106,11 @@ public: */ bool Resume(int suspend = -1); + /** + * Resume the VM with an error so it prints a stack trace. + */ + void ResumeError(); + /** * Tell the VM to do a garbage collection run. */