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