|  | #!/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() |