Skip to content

Commit f23e395

Browse files
authored
Fix wrong function deprecated warnings with variables (Issue #1435) (#3927)
1 parent e347659 commit f23e395

File tree

1 file changed

+141
-16
lines changed

1 file changed

+141
-16
lines changed

Server/mods/deathmatch/logic/CResourceChecker.cpp

Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,114 @@ bool CResourceChecker::CheckLuaDeobfuscateRequirements(const string& strFileCont
500500
return IsLuaObfuscatedScript(strFileContents.c_str(), strFileContents.length());
501501
}
502502

503+
// Helper struct to store token information
504+
struct LuaToken {
505+
enum Type {
506+
IDENTIFIER,
507+
OPERATOR,
508+
BRACKET,
509+
PARENTHESIS,
510+
OTHER
511+
};
512+
513+
Type type;
514+
std::string value;
515+
long position;
516+
long line;
517+
};
518+
519+
// Helper class to track parsing state
520+
class LuaParseState {
521+
public:
522+
bool isInComment = false;
523+
bool isInString = false;
524+
char stringDelimiter = 0;
525+
int bracketDepth = 0;
526+
int parenthesisDepth = 0;
527+
};
528+
529+
class CLuaSyntaxChecker {
530+
public:
531+
static bool IsFunctionCall(const std::string& source, long identifierPos, long identifierLength, long& outLine) {
532+
LuaParseState state;
533+
std::vector<LuaToken> tokens;
534+
535+
// First, tokenize everything after the identifier
536+
long pos = identifierPos + identifierLength;
537+
while (pos < (long)source.length()) {
538+
// Skip whitespace
539+
while (pos < (long)source.length() && isspace(source[pos])) {
540+
if (source[pos] == '\n') outLine++;
541+
pos++;
542+
}
543+
544+
if (pos >= (long)source.length()) break;
545+
546+
char c = source[pos];
547+
548+
// Handle comments
549+
if (!state.isInString && c == '-' && pos + 1 < (long)source.length() && source[pos + 1] == '-') {
550+
// Skip until end of line
551+
while (pos < (long)source.length() && source[pos] != '\n') pos++;
552+
continue;
553+
}
554+
555+
// Handle strings
556+
if (!state.isInString && (c == '"' || c == '\'')) {
557+
state.isInString = true;
558+
state.stringDelimiter = c;
559+
pos++;
560+
continue;
561+
}
562+
if (state.isInString && c == state.stringDelimiter) {
563+
state.isInString = false;
564+
pos++;
565+
continue;
566+
}
567+
if (state.isInString) {
568+
pos++;
569+
continue;
570+
}
571+
572+
// Track brackets and parentheses
573+
if (c == '(') {
574+
tokens.push_back({LuaToken::PARENTHESIS, "(", pos, outLine});
575+
state.parenthesisDepth++;
576+
pos++;
577+
break; // We found an opening parenthesis, no need to look further
578+
}
579+
else if (c == '{' || c == '[') {
580+
tokens.push_back({LuaToken::BRACKET, string(1, c), pos, outLine});
581+
state.bracketDepth++;
582+
pos++;
583+
}
584+
else if (c == '}' || c == ']') {
585+
tokens.push_back({LuaToken::BRACKET, string(1, c), pos, outLine});
586+
state.bracketDepth--;
587+
pos++;
588+
}
589+
else if (isalnum(c) || c == '_') {
590+
// Skip identifiers
591+
while (pos < (long)source.length() && (isalnum(source[pos]) || source[pos] == '_')) pos++;
592+
}
593+
else {
594+
// Handle operators and other characters
595+
tokens.push_back({LuaToken::OPERATOR, string(1, c), pos, outLine});
596+
pos++;
597+
}
598+
599+
// If we find anything other than whitespace or comments before a parenthesis,
600+
// then this isn't a function call
601+
if (!tokens.empty() && tokens.back().type != LuaToken::PARENTHESIS) {
602+
return false;
603+
}
604+
}
605+
606+
// Check if we found an opening parenthesis
607+
return !tokens.empty() && tokens.back().type == LuaToken::PARENTHESIS;
608+
}
609+
};
610+
503611
///////////////////////////////////////////////////////////////
504612
//
505613
// CResourceChecker::CheckLuaSourceForIssues
@@ -513,6 +621,7 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
513621
{
514622
CHashMap<SString, long> doneWarningMap;
515623
long lLineNumber = 1;
624+
516625
// Check if this is a UTF-8 script
517626
bool bUTF8 = IsUTF8BOM(strLuaSource.c_str(), strLuaSource.length());
518627

@@ -542,8 +651,8 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
542651
if (checkerMode == ECheckerMode::WARNINGS)
543652
{
544653
m_ulDeprecatedWarningCount++;
545-
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(),
546-
strFileName.c_str(), bClientScript ? "Client" : "Server");
654+
CLogger::LogPrintf("WARNING: %s/%s [%s] is encoded in ANSI instead of UTF-8. Please convert your file to UTF-8.\n",
655+
strResourceName.c_str(), strFileName.c_str(), bClientScript ? "Client" : "Server");
547656
}
548657
}
549658
}
@@ -557,12 +666,12 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
557666
if (lNameOffset == -1)
558667
break;
559668

560-
lNameOffset += lPos; // Make offset absolute from the start of the file
561-
lPos = lNameOffset + lNameLength; // Adjust so the next pass starts from just after this identifier
669+
lNameOffset += lPos;
670+
lPos = lNameOffset + lNameLength;
562671

563672
string strIdentifierName(strLuaSource.c_str() + lNameOffset, lNameLength);
564673

565-
// In-place upgrade...
674+
// Handle upgrades...
566675
if (checkerMode == ECheckerMode::UPGRADE)
567676
{
568677
assert(!bCompiledScript);
@@ -585,12 +694,18 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
585694
// Log warnings...
586695
if (checkerMode == ECheckerMode::WARNINGS)
587696
{
588-
// Only do the identifier once per file
589-
if (doneWarningMap.find(strIdentifierName) == doneWarningMap.end())
697+
std::string strContextKey = strIdentifierName + ":" + std::to_string(lLineNumber);
698+
if (doneWarningMap.find(strContextKey) == doneWarningMap.end())
590699
{
591-
doneWarningMap[strIdentifierName] = 1;
592-
if (!bCompiledScript) // Don't issue deprecated function warnings if the script is compiled, because we can't upgrade it
593-
IssueLuaFunctionNameWarnings(strIdentifierName, strFileName, strResourceName, bClientScript, lLineNumber);
700+
doneWarningMap[strContextKey] = 1;
701+
if (!bCompiledScript)
702+
{
703+
long currentLine = lLineNumber;
704+
if (CLuaSyntaxChecker::IsFunctionCall(strLuaSource, lNameOffset, lNameLength, currentLine))
705+
{
706+
IssueLuaFunctionNameWarnings(strIdentifierName, strFileName, strResourceName, bClientScript, lLineNumber);
707+
}
708+
}
594709
CheckVersionRequirements(strIdentifierName, bClientScript);
595710
}
596711
}
@@ -720,7 +835,6 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
720835
string strHow;
721836
CMtaVersion strVersion;
722837
ECheckerWhatType what = GetLuaFunctionNameUpgradeInfo(strFunctionName, bClientScript, strHow, strVersion);
723-
724838
if (what == ECheckerWhat::NONE)
725839
return;
726840

@@ -740,7 +854,6 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
740854
strTemp.Format("%s %s because <min_mta_version> %s setting in meta.xml is below %s", strFunctionName.c_str(), strHow.c_str(),
741855
bClientScript ? "Client" : "Server", strVersion.c_str());
742856
}
743-
744857
CLogger::LogPrint(SString("WARNING: %s/%s(Line %lu) [%s] %s\n", strResourceName.c_str(), strFileName.c_str(), ulLineNumber,
745858
bClientScript ? "Client" : "Server", *strTemp));
746859
}
@@ -755,21 +868,33 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
755868
ECheckerWhatType CResourceChecker::GetLuaFunctionNameUpgradeInfo(const string& strFunctionName, bool bClientScript, string& strOutHow,
756869
CMtaVersion& strOutVersion)
757870
{
871+
// Early exit if this is likely a variable assignment
872+
if (strFunctionName.find('=') != std::string::npos)
873+
return ECheckerWhat::NONE;
874+
758875
static CHashMap<SString, SDeprecatedItem*> clientUpgradeInfoMap;
759876
static CHashMap<SString, SDeprecatedItem*> serverUpgradeInfoMap;
760-
761877
if (clientUpgradeInfoMap.size() == 0)
762878
{
763879
// Make maps to speed things up
764880
for (uint i = 0; i < NUMELMS(clientDeprecatedList); i++)
765881
clientUpgradeInfoMap[clientDeprecatedList[i].strOldName] = &clientDeprecatedList[i];
766-
767882
for (uint i = 0; i < NUMELMS(serverDeprecatedList); i++)
768883
serverUpgradeInfoMap[serverDeprecatedList[i].strOldName] = &serverDeprecatedList[i];
769884
}
770885

771-
// Query the correct map
772-
SDeprecatedItem* pItem = MapFindRef(bClientScript ? clientUpgradeInfoMap : serverUpgradeInfoMap, strFunctionName);
886+
// Extract just the function name if it's being called
887+
std::string strCleanFunctionName = strFunctionName;
888+
size_t parenPos = strCleanFunctionName.find('(');
889+
if (parenPos != std::string::npos)
890+
strCleanFunctionName = strCleanFunctionName.substr(0, parenPos);
891+
892+
// Trim any whitespace
893+
strCleanFunctionName.erase(0, strCleanFunctionName.find_first_not_of(" \t\n\r"));
894+
strCleanFunctionName.erase(strCleanFunctionName.find_last_not_of(" \t\n\r") + 1);
895+
896+
// Query the correct map with the cleaned function name
897+
SDeprecatedItem* pItem = MapFindRef(bClientScript ? clientUpgradeInfoMap : serverUpgradeInfoMap, strCleanFunctionName);
773898
if (!pItem)
774899
return ECheckerWhat::NONE; // Nothing found
775900

0 commit comments

Comments
 (0)