72 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			72 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""
 | 
						|
Script to scan the OpenTTD's script API for functions that miss checks for the
 | 
						|
function being called from the right mode (deity or company mode).
 | 
						|
 | 
						|
When a function calls either ScriptObject::Command or ScriptObject::GetCompany
 | 
						|
then the function is considered dangerous. When one of the mode enforcement
 | 
						|
macros from script_error.hpp, i.e. EnforceDeityMode, EnforceCompanyModeValid or
 | 
						|
EnforceDeityOrCompanyModeValid, are called in the function, then we consider
 | 
						|
that the function has mode enforcement.
 | 
						|
 | 
						|
Any dangerous function for which no enforcement is found are emitted as errors.
 | 
						|
"""
 | 
						|
 | 
						|
import glob
 | 
						|
import re
 | 
						|
import sys
 | 
						|
 | 
						|
 | 
						|
def check_mode_enforcement(path):
 | 
						|
    errors = []
 | 
						|
    with open(path, "r") as reader:
 | 
						|
        mode_enforcement_found = False
 | 
						|
        dangerous_function = False
 | 
						|
        for line in reader:
 | 
						|
            # Line does not start with a tab and have <word>::<word>. That looks like the begin of a function, so reset the state.
 | 
						|
            if re.match(r"^[^\t].*\w::\w", line):
 | 
						|
                mode_enforcement_found = False
 | 
						|
                dangerous_function = False
 | 
						|
                currentFunction = line
 | 
						|
                continue
 | 
						|
 | 
						|
            if re.match(
 | 
						|
                r"\t(EnforceDeityMode|EnforceCompanyModeValid|EnforceCompanyModeValid_Void|EnforceDeityOrCompanyModeValid|EnforceDeityOrCompanyModeValid_Void)\(",
 | 
						|
                line,
 | 
						|
            ):
 | 
						|
                # Mode enforcement macro found
 | 
						|
                mode_enforcement_found = True
 | 
						|
                continue
 | 
						|
 | 
						|
            if re.match(r".*(ScriptObject::Command|ScriptObject::GetCompany).*", line):
 | 
						|
                # Dangerous function found
 | 
						|
                dangerous_function = True
 | 
						|
                continue
 | 
						|
 | 
						|
            # Line with only a closing bracket. That looks like the end of a function, so check for the dangerous function without mode enforcement
 | 
						|
            if re.match(r"^}$", line) and dangerous_function and not mode_enforcement_found:
 | 
						|
                function_name = currentFunction.rstrip("\n").replace("/* static */ ", "")
 | 
						|
                errors.append(f"{path}: {function_name}")
 | 
						|
 | 
						|
    return errors
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    errors = []
 | 
						|
    for path in sorted(glob.glob("src/script/api/*.cpp")):
 | 
						|
        # Skip a number of files that yield only false positives
 | 
						|
        if path.endswith(("script_object.cpp", "script_companymode.cpp", "script_controller.cpp", "script_game.cpp")):
 | 
						|
            continue
 | 
						|
 | 
						|
        errors.extend(check_mode_enforcement(path))
 | 
						|
 | 
						|
    if errors:
 | 
						|
        print("Mode enforcement was expected in the following files/functions:")
 | 
						|
        print("\n".join(errors))
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    print("OK")
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |