lint-format-strings.py
1 #!/usr/bin/env python3 2 # 3 # Copyright (c) 2018-2022 The Bitcoin Core developers 4 # Distributed under the MIT software license, see the accompanying 5 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 6 # 7 8 """ 9 Lint format strings: This program checks that the number of arguments passed 10 to a variadic format string function matches the number of format specifiers 11 in the format string. 12 """ 13 14 import subprocess 15 import re 16 import sys 17 18 FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS = [ 19 'FatalErrorf,0', 20 'fprintf,1', 21 'tfm::format,1', # Assuming tfm::::format(std::ostream&, ... 22 'LogConnectFailure,1', 23 'LogError,0', 24 'LogWarning,0', 25 'LogInfo,0', 26 'LogDebug,1', 27 'LogTrace,1', 28 'LogPrint,1', 29 'LogPrintf,0', 30 'LogPrintfCategory,1', 31 'LogPrintLevel,2', 32 'printf,0', 33 'snprintf,2', 34 'sprintf,1', 35 'strprintf,0', 36 'vfprintf,1', 37 'vprintf,1', 38 'vsnprintf,1', 39 'vsprintf,1', 40 'WalletLogPrintf,0', 41 ] 42 RUN_LINT_FILE = 'test/lint/run-lint-format-strings.py' 43 44 def check_doctest(): 45 command = [ 46 sys.executable, 47 '-m', 48 'doctest', 49 RUN_LINT_FILE, 50 ] 51 try: 52 subprocess.run(command, check = True) 53 except subprocess.CalledProcessError: 54 sys.exit(1) 55 56 def get_matching_files(function_name): 57 command = [ 58 'git', 59 'grep', 60 '--full-name', 61 '-l', 62 function_name, 63 '--', 64 '*.c', 65 '*.cpp', 66 '*.h', 67 ] 68 try: 69 return subprocess.check_output(command, stderr = subprocess.STDOUT).decode('utf-8').splitlines() 70 except subprocess.CalledProcessError as e: 71 if e.returncode > 1: # return code is 1 when match is empty 72 print(e.output.decode('utf-8'), end='') 73 sys.exit(1) 74 return [] 75 76 def main(): 77 exit_code = 0 78 check_doctest() 79 for s in FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS: 80 function_name, skip_arguments = s.split(',') 81 matching_files = get_matching_files(function_name) 82 83 matching_files_filtered = [] 84 for matching_file in matching_files: 85 if not re.search('^src/(leveldb|secp256k1|minisketch|tinyformat|test/fuzz/strprintf.cpp)|contrib/devtools/bitcoin-tidy/example_logprintf.cpp', matching_file): 86 matching_files_filtered.append(matching_file) 87 matching_files_filtered.sort() 88 89 run_lint_args = [ 90 RUN_LINT_FILE, 91 '--skip-arguments', 92 skip_arguments, 93 function_name, 94 ] 95 run_lint_args.extend(matching_files_filtered) 96 97 try: 98 subprocess.run(run_lint_args, check = True) 99 except subprocess.CalledProcessError: 100 exit_code = 1 101 102 sys.exit(exit_code) 103 104 if __name__ == '__main__': 105 main()