Home | History | Annotate | Line # | Download | only in gdb
      1 #!/usr/bin/env python3
      2 
      3 # Copyright (C) 2024 Free Software Foundation, Inc.
      4 #
      5 # This file is part of GDB.
      6 #
      7 # This program is free software; you can redistribute it and/or modify
      8 # it under the terms of the GNU General Public License as published by
      9 # the Free Software Foundation; either version 3 of the License, or
     10 # (at your option) any later version.
     11 #
     12 # This program is distributed in the hope that it will be useful,
     13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 # GNU General Public License for more details.
     16 #
     17 # You should have received a copy of the GNU General Public License
     18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     19 
     20 # This is intended to be run from pre-commit.  You can also run it by
     21 # hand by passing repository-relative filenames to it, like:
     22 #   ./gdb/check-include-guards.py [--update] gdb/*.h
     23 # When --update is used, rewrite the files in place as needed.
     24 
     25 
     26 import re
     27 import sys
     28 from typing import List
     29 
     30 DEF = re.compile("^#ifndef ([A-Za-z0-9_]+)\n")
     31 OLDDEF = re.compile("^#if !defined *\\(([A-Za-z0-9_]+)\\)\n")
     32 
     33 # Some headers -- in particular, ones that aren't maintained by gdb --
     34 # should be excluded from the checks.
     35 EXCLUDED = frozenset(["gdbsupport/unordered_dense.h"])
     36 
     37 
     38 # See if
     39 write_files = False
     40 args = sys.argv[1:]
     41 if len(args) > 0 and args[0] == "--update":
     42     write_files = True
     43     args = args[1:]
     44 
     45 
     46 def failure(filename: str, ndx: int, text: str):
     47     print(filename + ":" + str(ndx + 1) + ": " + text, file=sys.stderr)
     48     sys.exit(1)
     49 
     50 
     51 def skip_comments_and_blanks(ndx: int, contents: List[str]):
     52     while ndx < len(contents) and contents[ndx].startswith("/*"):
     53         while ndx < len(contents):
     54             ndx += 1
     55             if contents[ndx - 1].endswith("*/\n"):
     56                 break
     57         # Skip blank lines.
     58         while ndx < len(contents):
     59             if contents[ndx].strip() != "":
     60                 break
     61             ndx += 1
     62     return ndx
     63 
     64 
     65 def write_header(filename: str, contents: List[str]):
     66     with open(filename, "w") as f:
     67         f.writelines(contents)
     68 
     69 
     70 def check_header(filename: str):
     71     if filename in EXCLUDED:
     72         return
     73 
     74     # Turn x/y-z.h into X_Y_Z_H.
     75     assert filename.endswith(".h")
     76     expected = filename.replace("-", "_")
     77     expected = expected.replace(".", "_")
     78     expected = expected.replace("/", "_")
     79     expected = expected.upper()
     80     with open(filename) as f:
     81         contents = list(f)
     82     if len(contents) == 0:
     83         # Empty file -- pathological but we can just ignore rather
     84         # than crashing.
     85         return
     86     if "THIS FILE IS GENERATED" in contents[0]:
     87         # Ignore.
     88         return
     89     if not contents[0].startswith("/*"):
     90         failure(filename, 0, "header should start with comment")
     91     i = skip_comments_and_blanks(0, contents)
     92     if i == len(contents):
     93         failure(filename, i, "unterminated intro comment or missing body")
     94     m = DEF.match(contents[i])
     95     force_rewrite = False
     96     if not m:
     97         m = OLDDEF.match(contents[i])
     98         if not m:
     99             failure(filename, i, "no header guard")
    100         force_rewrite = True
    101     symbol = m.group(1)
    102     updated = False
    103     if symbol != expected:
    104         force_rewrite = True
    105     if force_rewrite:
    106         contents[i] = "#ifndef " + expected + "\n"
    107         updated = True
    108     i += 1
    109     if i == len(contents):
    110         failure(filename, i, "premature EOF")
    111     if not contents[i].startswith("#define "):
    112         failure(filename, i, "no define of header guard")
    113     if contents[i] != "#define " + expected + "\n":
    114         contents[i] = "#define " + expected + "\n"
    115         updated = True
    116     i = len(contents) - 1
    117     if not contents[i].startswith("#endif"):
    118         failure(filename, i, "no trailing endif")
    119     if contents[i] != "#endif /* " + expected + " */\n":
    120         contents[i] = "#endif /* " + expected + " */\n"
    121         updated = True
    122     if updated and write_files:
    123         write_header(filename, contents)
    124 
    125 
    126 for filename in args:
    127     check_header(filename)
    128