cdefs: Limit definition of fortification macros
[glibc.git] / scripts / check-obsolete-constructs.py
blobdcff21c12a9ef7f99c17f0df1629a18e167d6f50
1 #! /usr/bin/python3
2 # Copyright (C) 2019-2023 Free Software Foundation, Inc.
3 # This file is part of the GNU C Library.
5 # The GNU C Library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License, or (at your option) any later version.
10 # The GNU C Library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with the GNU C Library; if not, see
17 # <https://www.gnu.org/licenses/>.
19 """Verifies that installed headers do not use any obsolete constructs:
20 * legacy BSD typedefs superseded by <stdint.h>:
21 ushort uint ulong u_char u_short u_int u_long u_intNN_t quad_t u_quad_t
22 (sys/types.h is allowed to _define_ these types, but not to use them
23 to define anything else).
24 """
26 import argparse
27 import os
28 import re
29 import sys
31 # Make available glibc Python modules.
32 sys.path.append(os.path.dirname(os.path.realpath(__file__)))
34 import glibcpp
37 # Base and generic classes for individual checks.
40 class ConstructChecker:
41 """Scan a stream of C preprocessing tokens and possibly report
42 problems with them. The REPORTER object passed to __init__ has
43 one method, reporter.error(token, message), which should be
44 called to indicate a problem detected at the position of TOKEN.
45 If MESSAGE contains the four-character sequence '{!r}' then that
46 will be replaced with a textual representation of TOKEN.
47 """
48 def __init__(self, reporter):
49 self.reporter = reporter
51 def examine(self, tok):
52 """Called once for each token in a header file.
53 Call self.reporter.error if a problem is detected.
54 """
55 raise NotImplementedError
57 def eof(self):
58 """Called once at the end of the stream. Subclasses need only
59 override this if it might have something to do."""
60 pass
62 class NoCheck(ConstructChecker):
63 """Generic checker class which doesn't do anything. Substitute this
64 class for a real checker when a particular check should be skipped
65 for some file."""
67 def examine(self, tok):
68 pass
71 # Check for obsolete type names.
74 # The obsolete type names we're looking for:
75 OBSOLETE_TYPE_RE_ = re.compile(r"""\A
76 (__)?
77 ( quad_t
78 | u(?: short | int | long
79 | _(?: char | short | int(?:[0-9]+_t)? | long | quad_t )))
80 \Z""", re.VERBOSE)
82 class ObsoleteNotAllowed(ConstructChecker):
83 """Don't allow any use of the obsolete typedefs."""
84 def examine(self, tok):
85 if OBSOLETE_TYPE_RE_.match(tok.text):
86 self.reporter.error(tok, "use of {!r}")
88 class ObsoletePrivateDefinitionsAllowed(ConstructChecker):
89 """Allow definitions of the private versions of the
90 obsolete typedefs; that is, 'typedef [anything] __obsolete;'
91 """
92 def __init__(self, reporter):
93 super().__init__(reporter)
94 self.in_typedef = False
95 self.prev_token = None
97 def examine(self, tok):
98 # bits/types.h hides 'typedef' in a macro sometimes.
99 if (tok.kind == "IDENT"
100 and tok.text in ("typedef", "__STD_TYPE")
101 and tok.context is None):
102 self.in_typedef = True
103 elif tok.kind == "PUNCTUATOR" and tok.text == ";" and self.in_typedef:
104 self.in_typedef = False
105 if self.prev_token.kind == "IDENT":
106 m = OBSOLETE_TYPE_RE_.match(self.prev_token.text)
107 if m and m.group(1) != "__":
108 self.reporter.error(self.prev_token, "use of {!r}")
109 self.prev_token = None
110 else:
111 self._check_prev()
113 self.prev_token = tok
115 def eof(self):
116 self._check_prev()
118 def _check_prev(self):
119 if (self.prev_token is not None
120 and self.prev_token.kind == "IDENT"
121 and OBSOLETE_TYPE_RE_.match(self.prev_token.text)):
122 self.reporter.error(self.prev_token, "use of {!r}")
124 class ObsoletePublicDefinitionsAllowed(ConstructChecker):
125 """Allow definitions of the public versions of the obsolete
126 typedefs. Only specific forms of definition are allowed:
128 typedef __obsolete obsolete; // identifiers must agree
129 typedef __uintN_t u_intN_t; // N must agree
130 typedef unsigned long int ulong;
131 typedef unsigned short int ushort;
132 typedef unsigned int uint;
134 def __init__(self, reporter):
135 super().__init__(reporter)
136 self.typedef_tokens = []
138 def examine(self, tok):
139 if tok.kind in ("WHITESPACE", "BLOCK_COMMENT",
140 "LINE_COMMENT", "NL", "ESCNL"):
141 pass
143 elif (tok.kind == "IDENT" and tok.text == "typedef"
144 and tok.context is None):
145 if self.typedef_tokens:
146 self.reporter.error(tok, "typedef inside typedef")
147 self._reset()
148 self.typedef_tokens.append(tok)
150 elif tok.kind == "PUNCTUATOR" and tok.text == ";":
151 self._finish()
153 elif self.typedef_tokens:
154 self.typedef_tokens.append(tok)
156 def eof(self):
157 self._reset()
159 def _reset(self):
160 while self.typedef_tokens:
161 tok = self.typedef_tokens.pop(0)
162 if tok.kind == "IDENT" and OBSOLETE_TYPE_RE_.match(tok.text):
163 self.reporter.error(tok, "use of {!r}")
165 def _finish(self):
166 if not self.typedef_tokens: return
167 if self.typedef_tokens[-1].kind == "IDENT":
168 m = OBSOLETE_TYPE_RE_.match(self.typedef_tokens[-1].text)
169 if m:
170 if self._permissible_public_definition(m):
171 self.typedef_tokens.clear()
172 self._reset()
174 def _permissible_public_definition(self, m):
175 if m.group(1) == "__": return False
176 name = m.group(2)
177 toks = self.typedef_tokens
178 ntok = len(toks)
179 if ntok == 3 and toks[1].kind == "IDENT":
180 defn = toks[1].text
181 n = OBSOLETE_TYPE_RE_.match(defn)
182 if n and n.group(1) == "__" and n.group(2) == name:
183 return True
185 if (name[:5] == "u_int" and name[-2:] == "_t"
186 and defn[:6] == "__uint" and defn[-2:] == "_t"
187 and name[5:-2] == defn[6:-2]):
188 return True
190 return False
192 if (name == "ulong" and ntok == 5
193 and toks[1].kind == "IDENT" and toks[1].text == "unsigned"
194 and toks[2].kind == "IDENT" and toks[2].text == "long"
195 and toks[3].kind == "IDENT" and toks[3].text == "int"):
196 return True
198 if (name == "ushort" and ntok == 5
199 and toks[1].kind == "IDENT" and toks[1].text == "unsigned"
200 and toks[2].kind == "IDENT" and toks[2].text == "short"
201 and toks[3].kind == "IDENT" and toks[3].text == "int"):
202 return True
204 if (name == "uint" and ntok == 4
205 and toks[1].kind == "IDENT" and toks[1].text == "unsigned"
206 and toks[2].kind == "IDENT" and toks[2].text == "int"):
207 return True
209 return False
211 def ObsoleteTypedefChecker(reporter, fname):
212 """Factory: produce an instance of the appropriate
213 obsolete-typedef checker for FNAME."""
215 # The obsolete rpc/ and rpcsvc/ headers are allowed to use the
216 # obsolete types, because it would be more trouble than it's
217 # worth to remove them from headers that we intend to stop
218 # installing eventually anyway.
219 if (fname.startswith("rpc/")
220 or fname.startswith("rpcsvc/")
221 or "/rpc/" in fname
222 or "/rpcsvc/" in fname):
223 return NoCheck(reporter)
225 # bits/types.h is allowed to define the __-versions of the
226 # obsolete types.
227 if (fname == "bits/types.h"
228 or fname.endswith("/bits/types.h")):
229 return ObsoletePrivateDefinitionsAllowed(reporter)
231 # sys/types.h is allowed to use the __-versions of the
232 # obsolete types, but only to define the unprefixed versions.
233 if (fname == "sys/types.h"
234 or fname.endswith("/sys/types.h")):
235 return ObsoletePublicDefinitionsAllowed(reporter)
237 return ObsoleteNotAllowed(reporter)
240 # Master control
243 class HeaderChecker:
244 """Perform all of the checks on each header. This is also the
245 "reporter" object expected by tokenize_c and ConstructChecker.
247 def __init__(self):
248 self.fname = None
249 self.status = 0
251 def error(self, tok, message):
252 self.status = 1
253 if '{!r}' in message:
254 message = message.format(tok.text)
255 sys.stderr.write("{}:{}:{}: error: {}\n".format(
256 self.fname, tok.line, tok.column, message))
258 def check(self, fname):
259 self.fname = fname
260 try:
261 with open(fname, "rt", encoding="utf-8") as fp:
262 contents = fp.read()
263 except OSError as e:
264 sys.stderr.write("{}: {}\n".format(fname, e.strerror))
265 self.status = 1
266 return
268 typedef_checker = ObsoleteTypedefChecker(self, self.fname)
270 for tok in glibcpp.tokenize_c(contents, self):
271 typedef_checker.examine(tok)
273 def main():
274 ap = argparse.ArgumentParser(description=__doc__)
275 ap.add_argument("headers", metavar="header", nargs="+",
276 help="one or more headers to scan for obsolete constructs")
277 args = ap.parse_args()
279 checker = HeaderChecker()
280 for fname in args.headers:
281 # Headers whose installed name begins with "finclude/" contain
282 # Fortran, not C, and this program should completely ignore them.
283 if not (fname.startswith("finclude/") or "/finclude/" in fname):
284 checker.check(fname)
285 sys.exit(checker.status)
287 main()