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