131 lines · 4.1 KB · Python
RawDownload
#!/usr/bin/python3# SPDX-License-Identifier: GPL-2.0# Author: Julian Sun <sunjunchao2870@gmail.com> """ Find macro definitions with unused parameters. """ import argparseimport osimport 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 positivesfp_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 whitespacedef 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()