diff options
-rw-r--r-- | src/ai/ai_info.cpp | 27 | ||||
-rw-r--r-- | src/ai/ai_info.hpp | 2 | ||||
-rw-r--r-- | src/ai/ai_instance.cpp | 31 | ||||
-rw-r--r-- | src/ai/ai_instance.hpp | 2 | ||||
-rw-r--r-- | src/ai/ai_scanner.cpp | 5 | ||||
-rw-r--r-- | src/script/squirrel.cpp | 49 | ||||
-rw-r--r-- | src/script/squirrel.hpp | 22 |
7 files changed, 108 insertions, 30 deletions
diff --git a/src/ai/ai_info.cpp b/src/ai/ai_info.cpp index de1a71bc9..d55c6e57c 100644 --- a/src/ai/ai_info.cpp +++ b/src/ai/ai_info.cpp @@ -45,9 +45,9 @@ AILibrary::~AILibrary() free((void *)this->category); } -void AIFileInfo::GetSettings() const +bool AIFileInfo::GetSettings() { - this->engine->CallMethod(*this->SQ_instance, "GetSettings", NULL, -1); + return this->engine->CallMethod(*this->SQ_instance, "GetSettings", NULL, -1); } bool AIFileInfo::CheckMethod(const char *name) const @@ -90,13 +90,13 @@ bool AIFileInfo::CheckMethod(const char *name) const info->main_script = strdup(info->base->GetMainScript()); /* Cache the data the info file gives us. */ - info->author = info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAuthor"); - info->name = info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetName"); - info->short_name = info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetShortName"); - info->description = info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDescription"); - info->date = info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDate"); - info->version = info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion"); - info->instance_name = info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance"); + if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAuthor", &info->author)) return SQ_ERROR; + if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetName", &info->name)) return SQ_ERROR; + if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetShortName", &info->short_name)) return SQ_ERROR; + if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDescription", &info->description)) return SQ_ERROR; + if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDate", &info->date)) return SQ_ERROR; + if (!info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion", &info->version)) return SQ_ERROR; + if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance", &info->instance_name)) return SQ_ERROR; return 0; } @@ -118,10 +118,10 @@ bool AIFileInfo::CheckMethod(const char *name) const /* Check if we have settings */ if (info->engine->MethodExists(*info->SQ_instance, "GetSettings")) { - info->GetSettings(); + if (!info->GetSettings()) return SQ_ERROR; } if (info->engine->MethodExists(*info->SQ_instance, "MinVersionToLoad")) { - info->min_loadable_version = info->engine->CallIntegerMethod(*info->SQ_instance, "MinVersionToLoad"); + if (!info->engine->CallIntegerMethod(*info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version)) return SQ_ERROR; } else { info->min_loadable_version = info->GetVersion(); } @@ -367,7 +367,10 @@ int AIInfo::GetSettingDefaultValue(const char *name) const } /* Cache the category */ - library->category = library->engine->CallStringMethodStrdup(*library->SQ_instance, "GetCategory"); + if (!library->engine->CallStringMethodStrdup(*library->SQ_instance, "GetCategory", &library->category)) { + delete library; + return SQ_ERROR; + } /* Register the Library to the base system */ library->base->RegisterLibrary(library); diff --git a/src/ai/ai_info.hpp b/src/ai/ai_info.hpp index b43474500..ef8505e2d 100644 --- a/src/ai/ai_info.hpp +++ b/src/ai/ai_info.hpp @@ -72,7 +72,7 @@ public: /** * Get the settings of the AI. */ - void GetSettings() const; + bool GetSettings(); /** * Get the date of the AI. diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index 496ad6c34..12165e9dd 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -249,6 +249,11 @@ void AIInstance::Died() void AIInstance::GameLoop() { if (this->is_dead) return; + if (this->engine->HasScriptCrashed()) { + /* The script crashed during saving, kill it here. */ + this->Died(); + return; + } this->controller->ticks++; if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1. @@ -275,12 +280,12 @@ void AIInstance::GameLoop() AIObject::SetAllowDoCommand(false); /* Run the constructor if it exists. Don't allow any DoCommands in it. */ if (this->engine->MethodExists(*this->instance, "constructor")) { - this->engine->CallMethod(*this->instance, "constructor"); + if (!this->engine->CallMethod(*this->instance, "constructor")) { this->Died(); return; } } - this->CallLoad(); + if (!this->CallLoad()) { this->Died(); return; } AIObject::SetAllowDoCommand(true); /* Start the AI by calling Start() */ - if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.ai.ai_max_opcode_till_suspend)) this->Died(); + if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.ai.ai_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died(); } catch (AI_VMSuspend e) { this->suspend = e.GetSuspendTime(); this->callback = e.GetSuspendCallback(); @@ -500,8 +505,8 @@ enum { void AIInstance::Save() { - /* Don't save data if the AI didn't start yet. */ - if (this->engine == NULL) { + /* Don't save data if the AI didn't start yet or if it crashed. */ + if (this->engine == NULL || this->engine->HasScriptCrashed()) { SaveEmpty(); return; } @@ -526,7 +531,12 @@ void AIInstance::Save() /* We don't want to be interrupted during the save function. */ bool backup_allow = AIObject::GetAllowDoCommand(); AIObject::SetAllowDoCommand(false); - this->engine->CallMethod(*this->instance, "Save", &savedata); + 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(); + return; + } AIObject::SetAllowDoCommand(backup_allow); if (!sq_istable(savedata)) { @@ -637,21 +647,21 @@ void AIInstance::Load(int version) sq_pushbool(vm, true); } -void AIInstance::CallLoad() +bool AIInstance::CallLoad() { HSQUIRRELVM vm = this->engine->GetVM(); /* Is there save data that we should load? */ SQBool res; sq_getbool(vm, -1, &res); sq_poptop(vm); - if (!res) return; + if (!res) return true; if (!this->engine->MethodExists(*this->instance, "Load")) { AILog::Warning("Loading failed: there was data for the AI to load, but the AI does not have a Load() function."); /* Pop the savegame data and version. */ sq_pop(vm, 2); - return; + return true; } /* Go to the instance-root */ @@ -668,8 +678,9 @@ void AIInstance::CallLoad() /* Call the AI load function. sq_call removes the arguments (but not the * function pointer) from the stack. */ - sq_call(vm, 3, SQFalse, SQFalse); + if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse))) return false; /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */ sq_pop(vm, 4); + return true; } diff --git a/src/ai/ai_instance.hpp b/src/ai/ai_instance.hpp index 9c1aaca59..bf501dc23 100644 --- a/src/ai/ai_instance.hpp +++ b/src/ai/ai_instance.hpp @@ -100,7 +100,7 @@ public: * Call the AI Load function if it exists and data was loaded * from a savegame. */ - void CallLoad(); + bool CallLoad(); /** * Load and discard data from a savegame. diff --git a/src/ai/ai_scanner.cpp b/src/ai/ai_scanner.cpp index db5acd90c..7e4e878d4 100644 --- a/src/ai/ai_scanner.cpp +++ b/src/ai/ai_scanner.cpp @@ -88,6 +88,8 @@ void AIScanner::ScanDir(const char *dirname, bool library_scan, bool library_rec if (!FioCheckFileExists(info_script, AI_DIR) || !FioCheckFileExists(main_script, AI_DIR)) continue; DEBUG(ai, 6, "Loading AI at location '%s'", main_script); + /* We don't care if one of the other scripst failed to load. */ + this->engine->ResetCrashed(); this->engine->LoadScript(info_script); } else { char library_script[MAX_PATH]; @@ -100,6 +102,8 @@ void AIScanner::ScanDir(const char *dirname, bool library_scan, bool library_rec if (!FioCheckFileExists(library_script, AI_LIBRARY_DIR) || !FioCheckFileExists(main_script, AI_LIBRARY_DIR)) continue; DEBUG(ai, 6, "Loading AI Library at location '%s'", main_script); + /* We don't care if one of the other scripst failed to load. */ + this->engine->ResetCrashed(); this->engine->LoadScript(library_script); } } @@ -157,6 +161,7 @@ AIScanner::AIScanner() : this->ScanAIDir(); /* Create the dummy AI */ + this->engine->ResetCrashed(); extern void AI_CreateAIInfoDummy(HSQUIRRELVM vm); AI_CreateAIInfoDummy(this->engine->GetVM()); } diff --git a/src/script/squirrel.cpp b/src/script/squirrel.cpp index 602cc593d..0718f7945 100644 --- a/src/script/squirrel.cpp +++ b/src/script/squirrel.cpp @@ -24,7 +24,9 @@ void Squirrel::CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *so #endif /* Check if we have a custom print function */ - SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func; + Squirrel *engine = (Squirrel *)sq_getforeignptr(vm); + engine->crashed = true; + SQPrintFunc *func = engine->print_func; if (func == NULL) { scfprintf(stderr, _SC("%s"), buf); } else { @@ -59,7 +61,9 @@ void Squirrel::RunError(HSQUIRRELVM vm, const SQChar *error) /* Check if we have a custom print function */ SQChar buf[1024]; scsnprintf(buf, lengthof(buf), _SC("Your script made an error: %s\n"), error); - SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func; + Squirrel *engine = (Squirrel *)sq_getforeignptr(vm); + engine->crashed = true; + SQPrintFunc *func = engine->print_func; if (func == NULL) { scfprintf(stderr, _SC("%s"), buf); } else { @@ -156,6 +160,7 @@ void Squirrel::AddClassEnd() bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name) { + assert(!this->crashed); int top = sq_gettop(this->vm); /* Go to the instance-root */ sq_pushobject(this->vm, instance); @@ -171,6 +176,7 @@ bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name) bool Squirrel::Resume(int suspend) { + assert(!this->crashed); sq_resumecatch(this->vm, suspend); return this->vm->_suspended != 0; } @@ -182,6 +188,7 @@ void Squirrel::CollectGarbage() bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend) { + assert(!this->crashed); /* Store the stack-location for the return value. We need to * restore this after saving or the stack will be corrupted * if we're in the middle of a DoCommand. */ @@ -199,7 +206,7 @@ bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT } /* Call the method */ sq_pushobject(this->vm, instance); - sq_call(this->vm, 1, ret == NULL ? SQFalse : SQTrue, SQTrue, suspend); + if (SQ_FAILED(sq_call(this->vm, 1, ret == NULL ? SQFalse : SQTrue, SQTrue, suspend))) return false; if (ret != NULL) sq_getstackobj(vm, -1, ret); /* Reset the top, but don't do so for the AI main function, as we need * a correct stack when resuming. */ @@ -207,7 +214,25 @@ bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT /* Restore the return-value location. */ this->vm->_suspended_target = last_target; - return this->vm->_suspended != 0; + return true; +} + +bool Squirrel::CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend) +{ + HSQOBJECT ret; + if (!this->CallMethod(instance, method_name, &ret, suspend)) return false; + if (ret._type != OT_STRING) return false; + *res = strdup(ObjectToString(&ret)); + return true; +} + +bool Squirrel::CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend) +{ + HSQOBJECT ret; + if (!this->CallMethod(instance, method_name, &ret, suspend)) return false; + if (ret._type != OT_INTEGER) return false; + *res = ObjectToInteger(&ret); + return true; } /* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const char *class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook) @@ -258,6 +283,7 @@ Squirrel::Squirrel() this->vm = sq_open(1024); this->print_func = NULL; this->global_pointer = NULL; + this->crashed = false; /* Handle compile-errors ourself, so we can display it nicely */ sq_setcompilererrorhandler(this->vm, &Squirrel::CompileError); @@ -459,3 +485,18 @@ void Squirrel::InsertResult(int result) { vm->DecreaseOps(ops); } + +bool Squirrel::IsSuspended() +{ + return this->vm->_suspended != 0; +} + +bool Squirrel::HasScriptCrashed() +{ + return this->crashed; +} + +void Squirrel::ResetCrashed() +{ + this->crashed = false; +} diff --git a/src/script/squirrel.hpp b/src/script/squirrel.hpp index b5b352bd0..5ce8675ed 100644 --- a/src/script/squirrel.hpp +++ b/src/script/squirrel.hpp @@ -12,6 +12,7 @@ private: HSQUIRRELVM vm; ///< The VirtualMachine instnace for squirrel void *global_pointer; ///< Can be set by who ever initializes Squirrel SQPrintFunc *print_func; ///< Points to either NULL, or a custom print handler + bool crashed; ///< True if the squirrel script made an error. /** * The internal RunError handler. It looks up the real error and calls RunError with it. @@ -111,11 +112,12 @@ public: /** * Call a method of an instance, in various flavors. + * @return False if the script crashed or returned a wrong type. */ bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend = -1); bool CallMethod(HSQOBJECT instance, const char *method_name, int suspend = -1) { return this->CallMethod(instance, method_name, NULL, suspend); } - const char *CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, int suspend = -1) { HSQOBJECT ret; this->CallMethod(instance, method_name, &ret, suspend); return strdup(ObjectToString(&ret)); } - int CallIntegerMethod(HSQOBJECT instance, const char *method_name, int suspend = -1) { HSQOBJECT ret; this->CallMethod(instance, method_name, &ret, suspend); return ObjectToInteger(&ret); } + bool CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend = -1); + bool CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend = -1); /** * Check if a method exists in an instance. @@ -191,6 +193,22 @@ public: * Tell the VM to remove \c amount ops from the number of ops till suspend. */ static void DecreaseOps(HSQUIRRELVM vm, int amount); + + /** + * Did the squirrel code suspend or return normally. + * @return True if the function suspended. + */ + bool IsSuspended(); + + /** + * Find out if the squirrel script made an error before. + */ + bool HasScriptCrashed(); + + /** + * Reset the crashed status. + */ + void ResetCrashed(); }; #endif /* SQUIRREL_HPP */ |