From ce8cde3b8d74befb15b4b3f6d9975b75f076a578 Mon Sep 17 00:00:00 2001 From: glx22 Date: Sun, 5 Mar 2023 17:00:57 +0100 Subject: [PATCH] Fix: [Script] Detect circular references in ScriptText --- src/script/api/script_text.cpp | 16 ++++++++++++---- src/script/api/script_text.hpp | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/script/api/script_text.cpp b/src/script/api/script_text.cpp index 03c00fdae5..79166ee394 100644 --- a/src/script/api/script_text.cpp +++ b/src/script/api/script_text.cpp @@ -159,19 +159,25 @@ SQInteger ScriptText::_set(HSQUIRRELVM vm) const std::string ScriptText::GetEncodedText() { static char buf[1024]; + static StringIDList seen_ids; int param_count = 0; - this->_GetEncodedText(buf, lastof(buf), param_count); + seen_ids.clear(); + this->_GetEncodedText(buf, lastof(buf), param_count, seen_ids); if (param_count > SCRIPT_TEXT_MAX_PARAMETERS) throw Script_FatalError(fmt::format("{}: Too many parameters", GetGameStringName(this->string))); return buf; } -char *ScriptText::_GetEncodedText(char *p, char *lastofp, int ¶m_count) +char *ScriptText::_GetEncodedText(char *p, char *lastofp, int ¶m_count, StringIDList &seen_ids) { + const std::string &name = GetGameStringName(this->string); + + if (std::find(seen_ids.begin(), seen_ids.end(), this->string) != seen_ids.end()) throw Script_FatalError(fmt::format("{}: Circular reference detected", name)); + seen_ids.push_back(this->string); + p += Utf8Encode(p, SCC_ENCODED); p += seprintf(p, lastofp, "%X", this->string); const StringParams ¶ms = GetGameStringParams(this->string); - const std::string &name = GetGameStringName(this->string); int cur_idx = 0; for (const StringParam &cur_param : params) { @@ -187,7 +193,7 @@ char *ScriptText::_GetEncodedText(char *p, char *lastofp, int ¶m_count) if (!std::holds_alternative(this->param[cur_idx])) throw Script_FatalError(fmt::format("{}: Parameter {} expects a substring", name, cur_idx)); int count = 1; // 1 because the string id is included in consumed parameters p += seprintf(p, lastofp, ":"); - p = std::get(this->param[cur_idx++])->_GetEncodedText(p, lastofp, count); + p = std::get(this->param[cur_idx++])->_GetEncodedText(p, lastofp, count, seen_ids); if (count != cur_param.consumes) throw Script_FatalError(fmt::format("{}: Parameter {} substring consumes {}, but expected {} to be consumed", name, cur_idx, count - 1, cur_param.consumes - 1)); break; } @@ -203,6 +209,8 @@ char *ScriptText::_GetEncodedText(char *p, char *lastofp, int ¶m_count) param_count += cur_param.consumes; } + seen_ids.pop_back(); + return p; } diff --git a/src/script/api/script_text.hpp b/src/script/api/script_text.hpp index e64dda6b6c..597f2bdd74 100644 --- a/src/script/api/script_text.hpp +++ b/src/script/api/script_text.hpp @@ -129,6 +129,7 @@ public: private: using ScriptTextRef = ScriptObjectRef; + using StringIDList = std::vector; StringID string; std::variant param[SCRIPT_TEXT_MAX_PARAMETERS]; @@ -140,9 +141,10 @@ private: * @param p The current position in the buffer. * @param lastofp The last position valid in the buffer. * @param param_count The number of parameters that are in the string. + * @param seen_ids The list of seen StringID. * @return The new current position in the buffer. */ - char *_GetEncodedText(char *p, char *lastofp, int ¶m_count); + char *_GetEncodedText(char *p, char *lastofp, int ¶m_count, StringIDList &seen_ids); /** * Set a parameter, where the value is the first item on the stack.