From 968cab6794b472b714325e11b97e63a3568eaa2f Mon Sep 17 00:00:00 2001 From: YelehaUwU <162886331+YelehaUwU@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:37:27 +0100 Subject: [PATCH 1/7] Update CResourceChecker.h --- Server/mods/deathmatch/logic/CResourceChecker.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/mods/deathmatch/logic/CResourceChecker.h b/Server/mods/deathmatch/logic/CResourceChecker.h index ea9e7a1be9..05eed4cc4d 100644 --- a/Server/mods/deathmatch/logic/CResourceChecker.h +++ b/Server/mods/deathmatch/logic/CResourceChecker.h @@ -68,7 +68,7 @@ class CResourceChecker long FindLuaIdentifier(const char* szLuaSource, long* plOutLength, long* plLineNumber); bool UpgradeLuaFunctionName(const std::string& strFunctionName, bool bClientScript, std::string& strOutUpgraded); void IssueLuaFunctionNameWarnings(const std::string& strFunctionName, const std::string& strFileName, const std::string& strResourceName, - bool bClientScript, unsigned long ulLineNumber); + bool bClientScript, unsigned long ulLineNumber, const std::string& strContext); ECheckerWhatType GetLuaFunctionNameUpgradeInfo(const std::string& strFunctionName, bool bClientScript, std::string& strOutHow, CMtaVersion& strOutVersion); int ReplaceFilesInZIP(const std::string& strOrigZip, const std::string& strTempZip, const std::vector& pathInArchiveList, const std::vector& upgradedFullPathList); From ddb63e12b510d84f412bfcbbb82cc1831e1ae6a7 Mon Sep 17 00:00:00 2001 From: YelehaUwU <162886331+YelehaUwU@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:37:54 +0100 Subject: [PATCH 2/7] Update CResourceChecker.cpp --- .../deathmatch/logic/CResourceChecker.cpp | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/Server/mods/deathmatch/logic/CResourceChecker.cpp b/Server/mods/deathmatch/logic/CResourceChecker.cpp index 9998b089ca..f17136b639 100644 --- a/Server/mods/deathmatch/logic/CResourceChecker.cpp +++ b/Server/mods/deathmatch/logic/CResourceChecker.cpp @@ -513,6 +513,7 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string { CHashMap doneWarningMap; long lLineNumber = 1; + // Check if this is a UTF-8 script bool bUTF8 = IsUTF8BOM(strLuaSource.c_str(), strLuaSource.length()); @@ -542,8 +543,8 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string if (checkerMode == ECheckerMode::WARNINGS) { m_ulDeprecatedWarningCount++; - CLogger::LogPrintf("WARNING: %s/%s [%s] is encoded in ANSI instead of UTF-8. Please convert your file to UTF-8.\n", strResourceName.c_str(), - strFileName.c_str(), bClientScript ? "Client" : "Server"); + CLogger::LogPrintf("WARNING: %s/%s [%s] is encoded in ANSI instead of UTF-8. Please convert your file to UTF-8.\n", + strResourceName.c_str(), strFileName.c_str(), bClientScript ? "Client" : "Server"); } } } @@ -560,6 +561,11 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string lNameOffset += lPos; // Make offset absolute from the start of the file lPos = lNameOffset + lNameLength; // Adjust so the next pass starts from just after this identifier + // Get some context around the identifier (up to 50 chars before and after) + long contextStart = max(0L, lNameOffset - 50); + long contextEnd = min((long)strLuaSource.length(), lNameOffset + lNameLength + 50); + string strContext = strLuaSource.substr(contextStart, contextEnd - contextStart); + string strIdentifierName(strLuaSource.c_str() + lNameOffset, lNameLength); // In-place upgrade... @@ -586,11 +592,23 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string if (checkerMode == ECheckerMode::WARNINGS) { // Only do the identifier once per file - if (doneWarningMap.find(strIdentifierName) == doneWarningMap.end()) + string strContextKey = strIdentifierName + ":" + std::to_string(lLineNumber); + if (doneWarningMap.find(strContextKey) == doneWarningMap.end()) { - doneWarningMap[strIdentifierName] = 1; - if (!bCompiledScript) // Don't issue deprecated function warnings if the script is compiled, because we can't upgrade it - IssueLuaFunctionNameWarnings(strIdentifierName, strFileName, strResourceName, bClientScript, lLineNumber); + doneWarningMap[strContextKey] = 1; + if (!bCompiledScript) // Don't issue deprecated function warnings if the script is compiled + { + // Skip variable declarations and references + if (strContext.find("local " + strIdentifierName) == string::npos && + strContext.find("=" + strIdentifierName) == string::npos) + { + // Only check for function calls + if (strContext.find(strIdentifierName + "(") != string::npos) + { + IssueLuaFunctionNameWarnings(strIdentifierName, strFileName, strResourceName, bClientScript, lLineNumber, strContext); + } + } + } CheckVersionRequirements(strIdentifierName, bClientScript); } } @@ -715,12 +733,20 @@ bool CResourceChecker::UpgradeLuaFunctionName(const string& strFunctionName, boo // /////////////////////////////////////////////////////////////// void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionName, const string& strFileName, const string& strResourceName, bool bClientScript, - unsigned long ulLineNumber) + unsigned long ulLineNumber, const string& strContext) { + // Skip if this is clearly a variable declaration or usage + if (strContext.find("local " + strFunctionName) != string::npos || + strContext.find("=" + strFunctionName) != string::npos) + return; + + // Only proceed if it looks like a function call + if (strContext.find(strFunctionName + "(") == string::npos) + return; + string strHow; CMtaVersion strVersion; ECheckerWhatType what = GetLuaFunctionNameUpgradeInfo(strFunctionName, bClientScript, strHow, strVersion); - if (what == ECheckerWhat::NONE) return; @@ -740,7 +766,6 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam strTemp.Format("%s %s because %s setting in meta.xml is below %s", strFunctionName.c_str(), strHow.c_str(), bClientScript ? "Client" : "Server", strVersion.c_str()); } - CLogger::LogPrint(SString("WARNING: %s/%s(Line %lu) [%s] %s\n", strResourceName.c_str(), strFileName.c_str(), ulLineNumber, bClientScript ? "Client" : "Server", *strTemp)); } @@ -755,21 +780,33 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam ECheckerWhatType CResourceChecker::GetLuaFunctionNameUpgradeInfo(const string& strFunctionName, bool bClientScript, string& strOutHow, CMtaVersion& strOutVersion) { + // Early exit if this is likely a variable assignment + if (strFunctionName.find('=') != string::npos) + return ECheckerWhat::NONE; + static CHashMap clientUpgradeInfoMap; static CHashMap serverUpgradeInfoMap; - if (clientUpgradeInfoMap.size() == 0) { // Make maps to speed things up for (uint i = 0; i < NUMELMS(clientDeprecatedList); i++) clientUpgradeInfoMap[clientDeprecatedList[i].strOldName] = &clientDeprecatedList[i]; - for (uint i = 0; i < NUMELMS(serverDeprecatedList); i++) serverUpgradeInfoMap[serverDeprecatedList[i].strOldName] = &serverDeprecatedList[i]; } - // Query the correct map - SDeprecatedItem* pItem = MapFindRef(bClientScript ? clientUpgradeInfoMap : serverUpgradeInfoMap, strFunctionName); + // Extract just the function name if it's being called + string strCleanFunctionName = strFunctionName; + size_t parenPos = strCleanFunctionName.find('('); + if (parenPos != string::npos) + strCleanFunctionName = strCleanFunctionName.substr(0, parenPos); + + // Trim any whitespace + strCleanFunctionName.erase(0, strCleanFunctionName.find_first_not_of(" \t\n\r")); + strCleanFunctionName.erase(strCleanFunctionName.find_last_not_of(" \t\n\r") + 1); + + // Query the correct map with the cleaned function name + SDeprecatedItem* pItem = MapFindRef(bClientScript ? clientUpgradeInfoMap : serverUpgradeInfoMap, strCleanFunctionName); if (!pItem) return ECheckerWhat::NONE; // Nothing found From 9455b26b7b11d5082abac895cf0a610148821030 Mon Sep 17 00:00:00 2001 From: YelehaUwU <162886331+YelehaUwU@users.noreply.github.com> Date: Mon, 6 Jan 2025 07:20:31 +0100 Subject: [PATCH 3/7] Implement a new approch to scanning for deprecated functions --- .../deathmatch/logic/CResourceChecker.cpp | 144 ++++++++++++++---- 1 file changed, 116 insertions(+), 28 deletions(-) diff --git a/Server/mods/deathmatch/logic/CResourceChecker.cpp b/Server/mods/deathmatch/logic/CResourceChecker.cpp index f17136b639..3a59be20d2 100644 --- a/Server/mods/deathmatch/logic/CResourceChecker.cpp +++ b/Server/mods/deathmatch/logic/CResourceChecker.cpp @@ -500,6 +500,114 @@ bool CResourceChecker::CheckLuaDeobfuscateRequirements(const string& strFileCont return IsLuaObfuscatedScript(strFileContents.c_str(), strFileContents.length()); } +// Helper struct to store token information +struct LuaToken { + enum Type { + IDENTIFIER, + OPERATOR, + BRACKET, + PARENTHESIS, + OTHER + }; + + Type type; + string value; + long position; + long line; +}; + +// Helper class to track parsing state +class LuaParseState { +public: + bool isInComment = false; + bool isInString = false; + char stringDelimiter = 0; + int bracketDepth = 0; + int parenthesisDepth = 0; +}; + +class CLuaSyntaxChecker { +public: + static bool IsFunctionCall(const string& source, long identifierPos, long identifierLength, long& outLine) { + LuaParseState state; + vector tokens; + + // First, tokenize everything after the identifier + long pos = identifierPos + identifierLength; + while (pos < (long)source.length()) { + // Skip whitespace + while (pos < (long)source.length() && isspace(source[pos])) { + if (source[pos] == '\n') outLine++; + pos++; + } + + if (pos >= (long)source.length()) break; + + char c = source[pos]; + + // Handle comments + if (!state.isInString && c == '-' && pos + 1 < (long)source.length() && source[pos + 1] == '-') { + // Skip until end of line + while (pos < (long)source.length() && source[pos] != '\n') pos++; + continue; + } + + // Handle strings + if (!state.isInString && (c == '"' || c == '\'')) { + state.isInString = true; + state.stringDelimiter = c; + pos++; + continue; + } + if (state.isInString && c == state.stringDelimiter) { + state.isInString = false; + pos++; + continue; + } + if (state.isInString) { + pos++; + continue; + } + + // Track brackets and parentheses + if (c == '(') { + tokens.push_back({LuaToken::PARENTHESIS, "(", pos, outLine}); + state.parenthesisDepth++; + pos++; + break; // We found an opening parenthesis, no need to look further + } + else if (c == '{' || c == '[') { + tokens.push_back({LuaToken::BRACKET, string(1, c), pos, outLine}); + state.bracketDepth++; + pos++; + } + else if (c == '}' || c == ']') { + tokens.push_back({LuaToken::BRACKET, string(1, c), pos, outLine}); + state.bracketDepth--; + pos++; + } + else if (isalnum(c) || c == '_') { + // Skip identifiers + while (pos < (long)source.length() && (isalnum(source[pos]) || source[pos] == '_')) pos++; + } + else { + // Handle operators and other characters + tokens.push_back({LuaToken::OPERATOR, string(1, c), pos, outLine}); + pos++; + } + + // If we find anything other than whitespace or comments before a parenthesis, + // then this isn't a function call + if (!tokens.empty() && tokens.back().type != LuaToken::PARENTHESIS) { + return false; + } + } + + // Check if we found an opening parenthesis + return !tokens.empty() && tokens.back().type == LuaToken::PARENTHESIS; + } +}; + /////////////////////////////////////////////////////////////// // // CResourceChecker::CheckLuaSourceForIssues @@ -558,17 +666,12 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string if (lNameOffset == -1) break; - lNameOffset += lPos; // Make offset absolute from the start of the file - lPos = lNameOffset + lNameLength; // Adjust so the next pass starts from just after this identifier - - // Get some context around the identifier (up to 50 chars before and after) - long contextStart = max(0L, lNameOffset - 50); - long contextEnd = min((long)strLuaSource.length(), lNameOffset + lNameLength + 50); - string strContext = strLuaSource.substr(contextStart, contextEnd - contextStart); + lNameOffset += lPos; + lPos = lNameOffset + lNameLength; string strIdentifierName(strLuaSource.c_str() + lNameOffset, lNameLength); - // In-place upgrade... + // Handle upgrades... if (checkerMode == ECheckerMode::UPGRADE) { assert(!bCompiledScript); @@ -591,22 +694,16 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string // Log warnings... if (checkerMode == ECheckerMode::WARNINGS) { - // Only do the identifier once per file string strContextKey = strIdentifierName + ":" + std::to_string(lLineNumber); if (doneWarningMap.find(strContextKey) == doneWarningMap.end()) { doneWarningMap[strContextKey] = 1; - if (!bCompiledScript) // Don't issue deprecated function warnings if the script is compiled + if (!bCompiledScript) { - // Skip variable declarations and references - if (strContext.find("local " + strIdentifierName) == string::npos && - strContext.find("=" + strIdentifierName) == string::npos) + long currentLine = lLineNumber; + if (CLuaSyntaxChecker::IsFunctionCall(strLuaSource, lNameOffset, lNameLength, currentLine)) { - // Only check for function calls - if (strContext.find(strIdentifierName + "(") != string::npos) - { - IssueLuaFunctionNameWarnings(strIdentifierName, strFileName, strResourceName, bClientScript, lLineNumber, strContext); - } + IssueLuaFunctionNameWarnings(strIdentifierName, strFileName, strResourceName, bClientScript, lLineNumber); } } CheckVersionRequirements(strIdentifierName, bClientScript); @@ -733,17 +830,8 @@ bool CResourceChecker::UpgradeLuaFunctionName(const string& strFunctionName, boo // /////////////////////////////////////////////////////////////// void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionName, const string& strFileName, const string& strResourceName, bool bClientScript, - unsigned long ulLineNumber, const string& strContext) + unsigned long ulLineNumber) { - // Skip if this is clearly a variable declaration or usage - if (strContext.find("local " + strFunctionName) != string::npos || - strContext.find("=" + strFunctionName) != string::npos) - return; - - // Only proceed if it looks like a function call - if (strContext.find(strFunctionName + "(") == string::npos) - return; - string strHow; CMtaVersion strVersion; ECheckerWhatType what = GetLuaFunctionNameUpgradeInfo(strFunctionName, bClientScript, strHow, strVersion); From 68cfe921abb9ae1b5417bcecaa0e4768ca338913 Mon Sep 17 00:00:00 2001 From: YelehaUwU <162886331+YelehaUwU@users.noreply.github.com> Date: Mon, 6 Jan 2025 07:20:58 +0100 Subject: [PATCH 4/7] Changes implemented before are no longer needed --- Server/mods/deathmatch/logic/CResourceChecker.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/mods/deathmatch/logic/CResourceChecker.h b/Server/mods/deathmatch/logic/CResourceChecker.h index 05eed4cc4d..ea9e7a1be9 100644 --- a/Server/mods/deathmatch/logic/CResourceChecker.h +++ b/Server/mods/deathmatch/logic/CResourceChecker.h @@ -68,7 +68,7 @@ class CResourceChecker long FindLuaIdentifier(const char* szLuaSource, long* plOutLength, long* plLineNumber); bool UpgradeLuaFunctionName(const std::string& strFunctionName, bool bClientScript, std::string& strOutUpgraded); void IssueLuaFunctionNameWarnings(const std::string& strFunctionName, const std::string& strFileName, const std::string& strResourceName, - bool bClientScript, unsigned long ulLineNumber, const std::string& strContext); + bool bClientScript, unsigned long ulLineNumber); ECheckerWhatType GetLuaFunctionNameUpgradeInfo(const std::string& strFunctionName, bool bClientScript, std::string& strOutHow, CMtaVersion& strOutVersion); int ReplaceFilesInZIP(const std::string& strOrigZip, const std::string& strTempZip, const std::vector& pathInArchiveList, const std::vector& upgradedFullPathList); From 2d55ed8f1f452bedd070141ae81d17311cd582d9 Mon Sep 17 00:00:00 2001 From: YelehaUwU <162886331+YelehaUwU@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:19:57 +0100 Subject: [PATCH 5/7] Explicit std namespace --- Server/mods/deathmatch/logic/CResourceChecker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Server/mods/deathmatch/logic/CResourceChecker.cpp b/Server/mods/deathmatch/logic/CResourceChecker.cpp index 3a59be20d2..2d8465fb05 100644 --- a/Server/mods/deathmatch/logic/CResourceChecker.cpp +++ b/Server/mods/deathmatch/logic/CResourceChecker.cpp @@ -511,7 +511,7 @@ struct LuaToken { }; Type type; - string value; + std::string value; long position; long line; }; @@ -528,9 +528,9 @@ class LuaParseState { class CLuaSyntaxChecker { public: - static bool IsFunctionCall(const string& source, long identifierPos, long identifierLength, long& outLine) { + static bool IsFunctionCall(const std::string& source, long identifierPos, long identifierLength, long& outLine) { LuaParseState state; - vector tokens; + std::vector tokens; // First, tokenize everything after the identifier long pos = identifierPos + identifierLength; From b1ebf661050f74abd5d9b9a47221266a9690e309 Mon Sep 17 00:00:00 2001 From: YelehaUwU <162886331+YelehaUwU@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:43:05 +0100 Subject: [PATCH 6/7] Explicit std namespace, part 2 --- Server/mods/deathmatch/logic/CResourceChecker.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Server/mods/deathmatch/logic/CResourceChecker.cpp b/Server/mods/deathmatch/logic/CResourceChecker.cpp index 2d8465fb05..a6563694d6 100644 --- a/Server/mods/deathmatch/logic/CResourceChecker.cpp +++ b/Server/mods/deathmatch/logic/CResourceChecker.cpp @@ -511,7 +511,7 @@ struct LuaToken { }; Type type; - std::string value; + string value; long position; long line; }; @@ -528,9 +528,9 @@ class LuaParseState { class CLuaSyntaxChecker { public: - static bool IsFunctionCall(const std::string& source, long identifierPos, long identifierLength, long& outLine) { + static bool IsFunctionCall(const string& source, long identifierPos, long identifierLength, long& outLine) { LuaParseState state; - std::vector tokens; + vector tokens; // First, tokenize everything after the identifier long pos = identifierPos + identifierLength; @@ -694,7 +694,7 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string // Log warnings... if (checkerMode == ECheckerMode::WARNINGS) { - string strContextKey = strIdentifierName + ":" + std::to_string(lLineNumber); + std::string strContextKey = strIdentifierName + ":" + std::to_string(lLineNumber); if (doneWarningMap.find(strContextKey) == doneWarningMap.end()) { doneWarningMap[strContextKey] = 1; @@ -869,7 +869,7 @@ ECheckerWhatType CResourceChecker::GetLuaFunctionNameUpgradeInfo(const string& s CMtaVersion& strOutVersion) { // Early exit if this is likely a variable assignment - if (strFunctionName.find('=') != string::npos) + if (strFunctionName.find('=') != std::string::npos) return ECheckerWhat::NONE; static CHashMap clientUpgradeInfoMap; @@ -884,9 +884,9 @@ ECheckerWhatType CResourceChecker::GetLuaFunctionNameUpgradeInfo(const string& s } // Extract just the function name if it's being called - string strCleanFunctionName = strFunctionName; + std::string strCleanFunctionName = strFunctionName; size_t parenPos = strCleanFunctionName.find('('); - if (parenPos != string::npos) + if (parenPos != std::string::npos) strCleanFunctionName = strCleanFunctionName.substr(0, parenPos); // Trim any whitespace From f83d21a84487fec67fd4af981dc7f490f41594a0 Mon Sep 17 00:00:00 2001 From: YelehaUwU <162886331+YelehaUwU@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:45:49 +0100 Subject: [PATCH 7/7] Explicit std namespace, part 3 --- Server/mods/deathmatch/logic/CResourceChecker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Server/mods/deathmatch/logic/CResourceChecker.cpp b/Server/mods/deathmatch/logic/CResourceChecker.cpp index a6563694d6..1d0dd8b46a 100644 --- a/Server/mods/deathmatch/logic/CResourceChecker.cpp +++ b/Server/mods/deathmatch/logic/CResourceChecker.cpp @@ -511,7 +511,7 @@ struct LuaToken { }; Type type; - string value; + std::string value; long position; long line; }; @@ -528,9 +528,9 @@ class LuaParseState { class CLuaSyntaxChecker { public: - static bool IsFunctionCall(const string& source, long identifierPos, long identifierLength, long& outLine) { + static bool IsFunctionCall(const std::string& source, long identifierPos, long identifierLength, long& outLine) { LuaParseState state; - vector tokens; + std::vector tokens; // First, tokenize everything after the identifier long pos = identifierPos + identifierLength;