1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0
# Author: Julian Sun <sunjunchao2870@gmail.com>
""" Find macro definitions with unused parameters. """
import argparse
import os
import re
parser = argparse.ArgumentParser()
parser.add_argument("path", type=str, help="The file or dir path that needs check")
parser.add_argument("-v", "--verbose", action="store_true",
help="Check conditional macros, but may lead to more false positives")
args = parser.parse_args()
macro_pattern = r"#define\s+(\w+)\(([^)]*)\)"
# below vars were used to reduce false positives
fp_patterns = [r"\s*do\s*\{\s*\}\s*while\s*\(\s*0\s*\)",
r"\(?0\)?", r"\(?1\)?"]
correct_macros = []
cond_compile_mark = "#if"
cond_compile_end = "#endif"
def check_macro(macro_line, report):
match = re.match(macro_pattern, macro_line)
if match:
macro_def = re.sub(macro_pattern, '', macro_line)
identifier = match.group(1)
content = match.group(2)
arguments = [item.strip() for item in content.split(',') if item.strip()]
macro_def = macro_def.strip()
if not macro_def:
return
# used to reduce false positives, like #define endfor_nexthops(rt) }
if len(macro_def) == 1:
return
for fp_pattern in fp_patterns:
if (re.match(fp_pattern, macro_def)):
return
for arg in arguments:
# used to reduce false positives
if "..." in arg:
return
for arg in arguments:
if not arg in macro_def and report == False:
return
# if there is a correct macro with the same name, do not report it.
if not arg in macro_def and identifier not in correct_macros:
print(f"Argument {arg} is not used in function-line macro {identifier}")
return
correct_macros.append(identifier)
# remove comment and whitespace
def macro_strip(macro):
comment_pattern1 = r"\/\/*"
comment_pattern2 = r"\/\**\*\/"
macro = macro.strip()
macro = re.sub(comment_pattern1, '', macro)
macro = re.sub(comment_pattern2, '', macro)
return macro
def file_check_macro(file_path, report):
# number of conditional compiling
cond_compile = 0
# only check .c and .h file
if not file_path.endswith(".c") and not file_path.endswith(".h"):
return
with open(file_path, "r") as f:
while True:
line = f.readline()
if not line:
break
line = line.strip()
if line.startswith(cond_compile_mark):
cond_compile += 1
continue
if line.startswith(cond_compile_end):
cond_compile -= 1
continue
macro = re.match(macro_pattern, line)
if macro:
macro = macro_strip(macro.string)
while macro[-1] == '\\':
macro = macro[0:-1]
macro = macro.strip()
macro += f.readline()
macro = macro_strip(macro)
if not args.verbose:
if file_path.endswith(".c") and cond_compile != 0:
continue
# 1 is for #ifdef xxx at the beginning of the header file
if file_path.endswith(".h") and cond_compile != 1:
continue
check_macro(macro, report)
def get_correct_macros(path):
file_check_macro(path, False)
def dir_check_macro(dir_path):
for dentry in os.listdir(dir_path):
path = os.path.join(dir_path, dentry)
if os.path.isdir(path):
dir_check_macro(path)
elif os.path.isfile(path):
get_correct_macros(path)
file_check_macro(path, True)
def main():
if os.path.isfile(args.path):
get_correct_macros(args.path)
file_check_macro(args.path, True)
elif os.path.isdir(args.path):
dir_check_macro(args.path)
else:
print(f"{args.path} doesn't exit or is neither a file nor a dir")
if __name__ == "__main__":
main()
|