xref: /aosp_15_r20/external/grpc-grpc/tools/distrib/check_include_guards.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1*cc02d7e2SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*cc02d7e2SAndroid Build Coastguard Worker
3*cc02d7e2SAndroid Build Coastguard Worker# Copyright 2016 gRPC authors.
4*cc02d7e2SAndroid Build Coastguard Worker#
5*cc02d7e2SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*cc02d7e2SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*cc02d7e2SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*cc02d7e2SAndroid Build Coastguard Worker#
9*cc02d7e2SAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
10*cc02d7e2SAndroid Build Coastguard Worker#
11*cc02d7e2SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*cc02d7e2SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*cc02d7e2SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*cc02d7e2SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*cc02d7e2SAndroid Build Coastguard Worker# limitations under the License.
16*cc02d7e2SAndroid Build Coastguard Worker
17*cc02d7e2SAndroid Build Coastguard Workerimport argparse
18*cc02d7e2SAndroid Build Coastguard Workerimport os
19*cc02d7e2SAndroid Build Coastguard Workerimport os.path
20*cc02d7e2SAndroid Build Coastguard Workerimport re
21*cc02d7e2SAndroid Build Coastguard Workerimport subprocess
22*cc02d7e2SAndroid Build Coastguard Workerimport sys
23*cc02d7e2SAndroid Build Coastguard Worker
24*cc02d7e2SAndroid Build Coastguard Worker
25*cc02d7e2SAndroid Build Coastguard Workerdef build_valid_guard(fpath):
26*cc02d7e2SAndroid Build Coastguard Worker    guard_components = (
27*cc02d7e2SAndroid Build Coastguard Worker        fpath.replace("++", "XX").replace(".", "_").upper().split("/")
28*cc02d7e2SAndroid Build Coastguard Worker    )
29*cc02d7e2SAndroid Build Coastguard Worker    if fpath.startswith("include/"):
30*cc02d7e2SAndroid Build Coastguard Worker        return "_".join(guard_components[1:])
31*cc02d7e2SAndroid Build Coastguard Worker    else:
32*cc02d7e2SAndroid Build Coastguard Worker        return "GRPC_" + "_".join(guard_components)
33*cc02d7e2SAndroid Build Coastguard Worker
34*cc02d7e2SAndroid Build Coastguard Worker
35*cc02d7e2SAndroid Build Coastguard Workerdef load(fpath):
36*cc02d7e2SAndroid Build Coastguard Worker    with open(fpath, "r") as f:
37*cc02d7e2SAndroid Build Coastguard Worker        return f.read()
38*cc02d7e2SAndroid Build Coastguard Worker
39*cc02d7e2SAndroid Build Coastguard Worker
40*cc02d7e2SAndroid Build Coastguard Workerdef save(fpath, contents):
41*cc02d7e2SAndroid Build Coastguard Worker    with open(fpath, "w") as f:
42*cc02d7e2SAndroid Build Coastguard Worker        f.write(contents)
43*cc02d7e2SAndroid Build Coastguard Worker
44*cc02d7e2SAndroid Build Coastguard Worker
45*cc02d7e2SAndroid Build Coastguard Workerclass GuardValidator(object):
46*cc02d7e2SAndroid Build Coastguard Worker    def __init__(self):
47*cc02d7e2SAndroid Build Coastguard Worker        self.ifndef_re = re.compile(r"#ifndef ([A-Z][A-Z_0-9]*)")
48*cc02d7e2SAndroid Build Coastguard Worker        self.define_re = re.compile(r"#define ([A-Z][A-Z_0-9]*)")
49*cc02d7e2SAndroid Build Coastguard Worker        self.endif_c_core_re = re.compile(
50*cc02d7e2SAndroid Build Coastguard Worker            r"#endif /\* (?: *\\\n *)?([A-Z][A-Z_0-9]*) (?:\\\n *)?\*/$"
51*cc02d7e2SAndroid Build Coastguard Worker        )
52*cc02d7e2SAndroid Build Coastguard Worker        self.endif_re = re.compile(r"#endif  // ([A-Z][A-Z_0-9]*)")
53*cc02d7e2SAndroid Build Coastguard Worker        self.comments_then_includes_re = re.compile(
54*cc02d7e2SAndroid Build Coastguard Worker            (
55*cc02d7e2SAndroid Build Coastguard Worker                r"^((//.*?$|/\*.*?\*/|[ \r\n\t])*)(([ \r\n\t]|#include"
56*cc02d7e2SAndroid Build Coastguard Worker                r" .*)*)(#ifndef [^\n]*\n#define [^\n]*\n)"
57*cc02d7e2SAndroid Build Coastguard Worker            ),
58*cc02d7e2SAndroid Build Coastguard Worker            re.DOTALL | re.MULTILINE,
59*cc02d7e2SAndroid Build Coastguard Worker        )
60*cc02d7e2SAndroid Build Coastguard Worker        self.failed = False
61*cc02d7e2SAndroid Build Coastguard Worker
62*cc02d7e2SAndroid Build Coastguard Worker    def _is_c_core_header(self, fpath):
63*cc02d7e2SAndroid Build Coastguard Worker        return "include" in fpath and not (
64*cc02d7e2SAndroid Build Coastguard Worker            "grpc++" in fpath
65*cc02d7e2SAndroid Build Coastguard Worker            or "grpcpp" in fpath
66*cc02d7e2SAndroid Build Coastguard Worker            or "event_engine" in fpath
67*cc02d7e2SAndroid Build Coastguard Worker            or fpath.endswith("/grpc_audit_logging.h")
68*cc02d7e2SAndroid Build Coastguard Worker            or fpath.endswith("/json.h")
69*cc02d7e2SAndroid Build Coastguard Worker        )
70*cc02d7e2SAndroid Build Coastguard Worker
71*cc02d7e2SAndroid Build Coastguard Worker    def fail(self, fpath, regexp, fcontents, match_txt, correct, fix):
72*cc02d7e2SAndroid Build Coastguard Worker        c_core_header = self._is_c_core_header(fpath)
73*cc02d7e2SAndroid Build Coastguard Worker        self.failed = True
74*cc02d7e2SAndroid Build Coastguard Worker        invalid_guards_msg_template = (
75*cc02d7e2SAndroid Build Coastguard Worker            "{0}: Missing preprocessor guards (RE {1}). "
76*cc02d7e2SAndroid Build Coastguard Worker            "Please wrap your code around the following guards:\n"
77*cc02d7e2SAndroid Build Coastguard Worker            "#ifndef {2}\n"
78*cc02d7e2SAndroid Build Coastguard Worker            "#define {2}\n"
79*cc02d7e2SAndroid Build Coastguard Worker            "...\n"
80*cc02d7e2SAndroid Build Coastguard Worker            "... epic code ...\n"
81*cc02d7e2SAndroid Build Coastguard Worker            "...\n"
82*cc02d7e2SAndroid Build Coastguard Worker            + ("#endif /* {2} */" if c_core_header else "#endif  // {2}")
83*cc02d7e2SAndroid Build Coastguard Worker        )
84*cc02d7e2SAndroid Build Coastguard Worker        if not match_txt:
85*cc02d7e2SAndroid Build Coastguard Worker            print(
86*cc02d7e2SAndroid Build Coastguard Worker                (
87*cc02d7e2SAndroid Build Coastguard Worker                    invalid_guards_msg_template.format(
88*cc02d7e2SAndroid Build Coastguard Worker                        fpath, regexp.pattern, build_valid_guard(fpath)
89*cc02d7e2SAndroid Build Coastguard Worker                    )
90*cc02d7e2SAndroid Build Coastguard Worker                )
91*cc02d7e2SAndroid Build Coastguard Worker            )
92*cc02d7e2SAndroid Build Coastguard Worker            return fcontents
93*cc02d7e2SAndroid Build Coastguard Worker
94*cc02d7e2SAndroid Build Coastguard Worker        print(
95*cc02d7e2SAndroid Build Coastguard Worker            (
96*cc02d7e2SAndroid Build Coastguard Worker                (
97*cc02d7e2SAndroid Build Coastguard Worker                    "{}: Wrong preprocessor guards (RE {}):"
98*cc02d7e2SAndroid Build Coastguard Worker                    "\n\tFound {}, expected {}"
99*cc02d7e2SAndroid Build Coastguard Worker                ).format(fpath, regexp.pattern, match_txt, correct)
100*cc02d7e2SAndroid Build Coastguard Worker            )
101*cc02d7e2SAndroid Build Coastguard Worker        )
102*cc02d7e2SAndroid Build Coastguard Worker        if fix:
103*cc02d7e2SAndroid Build Coastguard Worker            print("Fixing {}...\n".format(fpath))
104*cc02d7e2SAndroid Build Coastguard Worker            fixed_fcontents = re.sub(match_txt, correct, fcontents)
105*cc02d7e2SAndroid Build Coastguard Worker            if fixed_fcontents:
106*cc02d7e2SAndroid Build Coastguard Worker                self.failed = False
107*cc02d7e2SAndroid Build Coastguard Worker            return fixed_fcontents
108*cc02d7e2SAndroid Build Coastguard Worker        else:
109*cc02d7e2SAndroid Build Coastguard Worker            print()
110*cc02d7e2SAndroid Build Coastguard Worker        return fcontents
111*cc02d7e2SAndroid Build Coastguard Worker
112*cc02d7e2SAndroid Build Coastguard Worker    def check(self, fpath, fix):
113*cc02d7e2SAndroid Build Coastguard Worker        c_core_header = self._is_c_core_header(fpath)
114*cc02d7e2SAndroid Build Coastguard Worker        valid_guard = build_valid_guard(fpath)
115*cc02d7e2SAndroid Build Coastguard Worker
116*cc02d7e2SAndroid Build Coastguard Worker        fcontents = load(fpath)
117*cc02d7e2SAndroid Build Coastguard Worker
118*cc02d7e2SAndroid Build Coastguard Worker        match = self.ifndef_re.search(fcontents)
119*cc02d7e2SAndroid Build Coastguard Worker        if not match:
120*cc02d7e2SAndroid Build Coastguard Worker            print(("something drastically wrong with: %s" % fpath))
121*cc02d7e2SAndroid Build Coastguard Worker            return False  # failed
122*cc02d7e2SAndroid Build Coastguard Worker        if match.lastindex is None:
123*cc02d7e2SAndroid Build Coastguard Worker            # No ifndef. Request manual addition with hints
124*cc02d7e2SAndroid Build Coastguard Worker            self.fail(fpath, match.re, match.string, "", "", False)
125*cc02d7e2SAndroid Build Coastguard Worker            return False  # failed
126*cc02d7e2SAndroid Build Coastguard Worker
127*cc02d7e2SAndroid Build Coastguard Worker        # Does the guard end with a '_H'?
128*cc02d7e2SAndroid Build Coastguard Worker        running_guard = match.group(1)
129*cc02d7e2SAndroid Build Coastguard Worker        if not running_guard.endswith("_H"):
130*cc02d7e2SAndroid Build Coastguard Worker            fcontents = self.fail(
131*cc02d7e2SAndroid Build Coastguard Worker                fpath, match.re, match.string, match.group(1), valid_guard, fix
132*cc02d7e2SAndroid Build Coastguard Worker            )
133*cc02d7e2SAndroid Build Coastguard Worker            if fix:
134*cc02d7e2SAndroid Build Coastguard Worker                save(fpath, fcontents)
135*cc02d7e2SAndroid Build Coastguard Worker
136*cc02d7e2SAndroid Build Coastguard Worker        # Is it the expected one based on the file path?
137*cc02d7e2SAndroid Build Coastguard Worker        if running_guard != valid_guard:
138*cc02d7e2SAndroid Build Coastguard Worker            fcontents = self.fail(
139*cc02d7e2SAndroid Build Coastguard Worker                fpath, match.re, match.string, match.group(1), valid_guard, fix
140*cc02d7e2SAndroid Build Coastguard Worker            )
141*cc02d7e2SAndroid Build Coastguard Worker            if fix:
142*cc02d7e2SAndroid Build Coastguard Worker                save(fpath, fcontents)
143*cc02d7e2SAndroid Build Coastguard Worker
144*cc02d7e2SAndroid Build Coastguard Worker        # Is there a #define? Is it the same as the #ifndef one?
145*cc02d7e2SAndroid Build Coastguard Worker        match = self.define_re.search(fcontents)
146*cc02d7e2SAndroid Build Coastguard Worker        if match.lastindex is None:
147*cc02d7e2SAndroid Build Coastguard Worker            # No define. Request manual addition with hints
148*cc02d7e2SAndroid Build Coastguard Worker            self.fail(fpath, match.re, match.string, "", "", False)
149*cc02d7e2SAndroid Build Coastguard Worker            return False  # failed
150*cc02d7e2SAndroid Build Coastguard Worker
151*cc02d7e2SAndroid Build Coastguard Worker        # Is the #define guard the same as the #ifndef guard?
152*cc02d7e2SAndroid Build Coastguard Worker        if match.group(1) != running_guard:
153*cc02d7e2SAndroid Build Coastguard Worker            fcontents = self.fail(
154*cc02d7e2SAndroid Build Coastguard Worker                fpath, match.re, match.string, match.group(1), valid_guard, fix
155*cc02d7e2SAndroid Build Coastguard Worker            )
156*cc02d7e2SAndroid Build Coastguard Worker            if fix:
157*cc02d7e2SAndroid Build Coastguard Worker                save(fpath, fcontents)
158*cc02d7e2SAndroid Build Coastguard Worker
159*cc02d7e2SAndroid Build Coastguard Worker        # Is there a properly commented #endif?
160*cc02d7e2SAndroid Build Coastguard Worker        flines = fcontents.rstrip().splitlines()
161*cc02d7e2SAndroid Build Coastguard Worker        # Use findall and use the last result if there are multiple matches,
162*cc02d7e2SAndroid Build Coastguard Worker        # i.e. nested include guards.
163*cc02d7e2SAndroid Build Coastguard Worker        match = self.endif_c_core_re.findall("\n".join(flines[-3:]))
164*cc02d7e2SAndroid Build Coastguard Worker        if not match and not c_core_header:
165*cc02d7e2SAndroid Build Coastguard Worker            match = self.endif_re.findall("\n".join(flines[-3:]))
166*cc02d7e2SAndroid Build Coastguard Worker        if not match:
167*cc02d7e2SAndroid Build Coastguard Worker            # No endif. Check if we have the last line as just '#endif' and if so
168*cc02d7e2SAndroid Build Coastguard Worker            # replace it with a properly commented one.
169*cc02d7e2SAndroid Build Coastguard Worker            if flines[-1] == "#endif":
170*cc02d7e2SAndroid Build Coastguard Worker                flines[-1] = "#endif" + (
171*cc02d7e2SAndroid Build Coastguard Worker                    " /* {} */\n".format(valid_guard)
172*cc02d7e2SAndroid Build Coastguard Worker                    if c_core_header
173*cc02d7e2SAndroid Build Coastguard Worker                    else "  // {}\n".format(valid_guard)
174*cc02d7e2SAndroid Build Coastguard Worker                )
175*cc02d7e2SAndroid Build Coastguard Worker                if fix:
176*cc02d7e2SAndroid Build Coastguard Worker                    fcontents = "\n".join(flines)
177*cc02d7e2SAndroid Build Coastguard Worker                    save(fpath, fcontents)
178*cc02d7e2SAndroid Build Coastguard Worker            else:
179*cc02d7e2SAndroid Build Coastguard Worker                # something else is wrong, bail out
180*cc02d7e2SAndroid Build Coastguard Worker                self.fail(
181*cc02d7e2SAndroid Build Coastguard Worker                    fpath,
182*cc02d7e2SAndroid Build Coastguard Worker                    self.endif_c_core_re if c_core_header else self.endif_re,
183*cc02d7e2SAndroid Build Coastguard Worker                    flines[-1],
184*cc02d7e2SAndroid Build Coastguard Worker                    "",
185*cc02d7e2SAndroid Build Coastguard Worker                    "",
186*cc02d7e2SAndroid Build Coastguard Worker                    False,
187*cc02d7e2SAndroid Build Coastguard Worker                )
188*cc02d7e2SAndroid Build Coastguard Worker        elif match[-1] != running_guard:
189*cc02d7e2SAndroid Build Coastguard Worker            # Is the #endif guard the same as the #ifndef and #define guards?
190*cc02d7e2SAndroid Build Coastguard Worker            fcontents = self.fail(
191*cc02d7e2SAndroid Build Coastguard Worker                fpath, self.endif_re, fcontents, match[-1], valid_guard, fix
192*cc02d7e2SAndroid Build Coastguard Worker            )
193*cc02d7e2SAndroid Build Coastguard Worker            if fix:
194*cc02d7e2SAndroid Build Coastguard Worker                save(fpath, fcontents)
195*cc02d7e2SAndroid Build Coastguard Worker
196*cc02d7e2SAndroid Build Coastguard Worker        match = self.comments_then_includes_re.search(fcontents)
197*cc02d7e2SAndroid Build Coastguard Worker        assert match
198*cc02d7e2SAndroid Build Coastguard Worker        bad_includes = match.group(3)
199*cc02d7e2SAndroid Build Coastguard Worker        if bad_includes:
200*cc02d7e2SAndroid Build Coastguard Worker            print(
201*cc02d7e2SAndroid Build Coastguard Worker                "includes after initial comments but before include guards in",
202*cc02d7e2SAndroid Build Coastguard Worker                fpath,
203*cc02d7e2SAndroid Build Coastguard Worker            )
204*cc02d7e2SAndroid Build Coastguard Worker            if fix:
205*cc02d7e2SAndroid Build Coastguard Worker                fcontents = (
206*cc02d7e2SAndroid Build Coastguard Worker                    fcontents[: match.start(3)]
207*cc02d7e2SAndroid Build Coastguard Worker                    + match.group(5)
208*cc02d7e2SAndroid Build Coastguard Worker                    + match.group(3)
209*cc02d7e2SAndroid Build Coastguard Worker                    + fcontents[match.end(5) :]
210*cc02d7e2SAndroid Build Coastguard Worker                )
211*cc02d7e2SAndroid Build Coastguard Worker                save(fpath, fcontents)
212*cc02d7e2SAndroid Build Coastguard Worker
213*cc02d7e2SAndroid Build Coastguard Worker        return not self.failed  # Did the check succeed? (ie, not failed)
214*cc02d7e2SAndroid Build Coastguard Worker
215*cc02d7e2SAndroid Build Coastguard Worker
216*cc02d7e2SAndroid Build Coastguard Worker# find our home
217*cc02d7e2SAndroid Build Coastguard WorkerROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../.."))
218*cc02d7e2SAndroid Build Coastguard Workeros.chdir(ROOT)
219*cc02d7e2SAndroid Build Coastguard Worker
220*cc02d7e2SAndroid Build Coastguard Worker# parse command line
221*cc02d7e2SAndroid Build Coastguard Workerargp = argparse.ArgumentParser(description="include guard checker")
222*cc02d7e2SAndroid Build Coastguard Workerargp.add_argument("-f", "--fix", default=False, action="store_true")
223*cc02d7e2SAndroid Build Coastguard Workerargp.add_argument("--precommit", default=False, action="store_true")
224*cc02d7e2SAndroid Build Coastguard Workerargs = argp.parse_args()
225*cc02d7e2SAndroid Build Coastguard Worker
226*cc02d7e2SAndroid Build Coastguard Workergrep_filter = (
227*cc02d7e2SAndroid Build Coastguard Worker    r"grep -E '^(include|src/core|src/cpp|test/core|test/cpp|fuzztest/)/.*\.h$'"
228*cc02d7e2SAndroid Build Coastguard Worker)
229*cc02d7e2SAndroid Build Coastguard Workerif args.precommit:
230*cc02d7e2SAndroid Build Coastguard Worker    git_command = "git diff --name-only HEAD"
231*cc02d7e2SAndroid Build Coastguard Workerelse:
232*cc02d7e2SAndroid Build Coastguard Worker    git_command = "git ls-tree -r --name-only -r HEAD"
233*cc02d7e2SAndroid Build Coastguard Worker
234*cc02d7e2SAndroid Build Coastguard WorkerFILE_LIST_COMMAND = " | ".join((git_command, grep_filter))
235*cc02d7e2SAndroid Build Coastguard Worker
236*cc02d7e2SAndroid Build Coastguard Worker# scan files
237*cc02d7e2SAndroid Build Coastguard Workerok = True
238*cc02d7e2SAndroid Build Coastguard Workerfilename_list = []
239*cc02d7e2SAndroid Build Coastguard Workertry:
240*cc02d7e2SAndroid Build Coastguard Worker    filename_list = (
241*cc02d7e2SAndroid Build Coastguard Worker        subprocess.check_output(FILE_LIST_COMMAND, shell=True)
242*cc02d7e2SAndroid Build Coastguard Worker        .decode()
243*cc02d7e2SAndroid Build Coastguard Worker        .splitlines()
244*cc02d7e2SAndroid Build Coastguard Worker    )
245*cc02d7e2SAndroid Build Coastguard Worker    # Filter out non-existent files (ie, file removed or renamed)
246*cc02d7e2SAndroid Build Coastguard Worker    filename_list = (f for f in filename_list if os.path.isfile(f))
247*cc02d7e2SAndroid Build Coastguard Workerexcept subprocess.CalledProcessError:
248*cc02d7e2SAndroid Build Coastguard Worker    sys.exit(0)
249*cc02d7e2SAndroid Build Coastguard Worker
250*cc02d7e2SAndroid Build Coastguard Workervalidator = GuardValidator()
251*cc02d7e2SAndroid Build Coastguard Worker
252*cc02d7e2SAndroid Build Coastguard Workerfor filename in filename_list:
253*cc02d7e2SAndroid Build Coastguard Worker    # Skip check for upb generated code.
254*cc02d7e2SAndroid Build Coastguard Worker    if (
255*cc02d7e2SAndroid Build Coastguard Worker        filename.endswith(".upb.h")
256*cc02d7e2SAndroid Build Coastguard Worker        or filename.endswith(".upbdefs.h")
257*cc02d7e2SAndroid Build Coastguard Worker        or filename.endswith(".upbdefs.c")
258*cc02d7e2SAndroid Build Coastguard Worker        or filename.endswith(".upb_minitable.h")
259*cc02d7e2SAndroid Build Coastguard Worker        or filename.endswith(".upb_minitable.c")
260*cc02d7e2SAndroid Build Coastguard Worker    ):
261*cc02d7e2SAndroid Build Coastguard Worker        continue
262*cc02d7e2SAndroid Build Coastguard Worker    ok = ok and validator.check(filename, args.fix)
263*cc02d7e2SAndroid Build Coastguard Worker
264*cc02d7e2SAndroid Build Coastguard Workersys.exit(0 if ok else 1)
265