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