macro_checker.py 4.1 KB

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