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