@@ -500,6 +500,114 @@ bool CResourceChecker::CheckLuaDeobfuscateRequirements(const string& strFileCont
500
500
return IsLuaObfuscatedScript (strFileContents.c_str (), strFileContents.length ());
501
501
}
502
502
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
+
503
611
// /////////////////////////////////////////////////////////////
504
612
//
505
613
// CResourceChecker::CheckLuaSourceForIssues
@@ -513,6 +621,7 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
513
621
{
514
622
CHashMap<SString, long > doneWarningMap;
515
623
long lLineNumber = 1 ;
624
+
516
625
// Check if this is a UTF-8 script
517
626
bool bUTF8 = IsUTF8BOM (strLuaSource.c_str (), strLuaSource.length ());
518
627
@@ -542,8 +651,8 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
542
651
if (checkerMode == ECheckerMode::WARNINGS)
543
652
{
544
653
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" );
547
656
}
548
657
}
549
658
}
@@ -557,12 +666,12 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
557
666
if (lNameOffset == -1 )
558
667
break ;
559
668
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;
562
671
563
672
string strIdentifierName (strLuaSource.c_str () + lNameOffset, lNameLength);
564
673
565
- // In-place upgrade ...
674
+ // Handle upgrades ...
566
675
if (checkerMode == ECheckerMode::UPGRADE)
567
676
{
568
677
assert (!bCompiledScript);
@@ -585,12 +694,18 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
585
694
// Log warnings...
586
695
if (checkerMode == ECheckerMode::WARNINGS)
587
696
{
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 ())
590
699
{
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
+ }
594
709
CheckVersionRequirements (strIdentifierName, bClientScript);
595
710
}
596
711
}
@@ -720,7 +835,6 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
720
835
string strHow;
721
836
CMtaVersion strVersion;
722
837
ECheckerWhatType what = GetLuaFunctionNameUpgradeInfo (strFunctionName, bClientScript, strHow, strVersion);
723
-
724
838
if (what == ECheckerWhat::NONE)
725
839
return ;
726
840
@@ -740,7 +854,6 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
740
854
strTemp.Format (" %s %s because <min_mta_version> %s setting in meta.xml is below %s" , strFunctionName.c_str (), strHow.c_str (),
741
855
bClientScript ? " Client" : " Server" , strVersion.c_str ());
742
856
}
743
-
744
857
CLogger::LogPrint (SString (" WARNING: %s/%s(Line %lu) [%s] %s\n " , strResourceName.c_str (), strFileName.c_str (), ulLineNumber,
745
858
bClientScript ? " Client" : " Server" , *strTemp));
746
859
}
@@ -755,21 +868,33 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
755
868
ECheckerWhatType CResourceChecker::GetLuaFunctionNameUpgradeInfo (const string& strFunctionName, bool bClientScript, string& strOutHow,
756
869
CMtaVersion& strOutVersion)
757
870
{
871
+ // Early exit if this is likely a variable assignment
872
+ if (strFunctionName.find (' =' ) != std::string::npos)
873
+ return ECheckerWhat::NONE;
874
+
758
875
static CHashMap<SString, SDeprecatedItem*> clientUpgradeInfoMap;
759
876
static CHashMap<SString, SDeprecatedItem*> serverUpgradeInfoMap;
760
-
761
877
if (clientUpgradeInfoMap.size () == 0 )
762
878
{
763
879
// Make maps to speed things up
764
880
for (uint i = 0 ; i < NUMELMS (clientDeprecatedList); i++)
765
881
clientUpgradeInfoMap[clientDeprecatedList[i].strOldName ] = &clientDeprecatedList[i];
766
-
767
882
for (uint i = 0 ; i < NUMELMS (serverDeprecatedList); i++)
768
883
serverUpgradeInfoMap[serverDeprecatedList[i].strOldName ] = &serverDeprecatedList[i];
769
884
}
770
885
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);
773
898
if (!pItem)
774
899
return ECheckerWhat::NONE; // Nothing found
775
900
0 commit comments