check-obsolete-constructs.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. #! /usr/bin/python3
  2. # Copyright (C) 2019-2026 Free Software Foundation, Inc.
  3. # This file is part of the GNU C Library.
  4. #
  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.
  9. #
  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.
  14. #
  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/>.
  18. """Verifies that installed headers do not use any obsolete constructs:
  19. * legacy BSD typedefs superseded by <stdint.h>:
  20. ushort uint ulong u_char u_short u_int u_long u_intNN_t quad_t u_quad_t
  21. (sys/types.h is allowed to _define_ these types, but not to use them
  22. to define anything else).
  23. """
  24. import argparse
  25. import os
  26. import re
  27. import sys
  28. # Make available glibc Python modules.
  29. sys.path.append(os.path.dirname(os.path.realpath(__file__)))
  30. import glibcpp
  31. #
  32. # Base and generic classes for individual checks.
  33. #
  34. class ConstructChecker:
  35. """Scan a stream of C preprocessing tokens and possibly report
  36. problems with them. The REPORTER object passed to __init__ has
  37. one method, reporter.error(token, message), which should be
  38. called to indicate a problem detected at the position of TOKEN.
  39. If MESSAGE contains the four-character sequence '{!r}' then that
  40. will be replaced with a textual representation of TOKEN.
  41. """
  42. def __init__(self, reporter):
  43. self.reporter = reporter
  44. def examine(self, tok):
  45. """Called once for each token in a header file.
  46. Call self.reporter.error if a problem is detected.
  47. """
  48. raise NotImplementedError
  49. def eof(self):
  50. """Called once at the end of the stream. Subclasses need only
  51. override this if it might have something to do."""
  52. pass
  53. class NoCheck(ConstructChecker):
  54. """Generic checker class which doesn't do anything. Substitute this
  55. class for a real checker when a particular check should be skipped
  56. for some file."""
  57. def examine(self, tok):
  58. pass
  59. #
  60. # Check for obsolete type names.
  61. #
  62. # The obsolete type names we're looking for:
  63. OBSOLETE_TYPE_RE_ = re.compile(r"""\A
  64. (__)?
  65. ( quad_t
  66. | u(?: short | int | long
  67. | _(?: char | short | int(?:[0-9]+_t)? | long | quad_t )))
  68. \Z""", re.VERBOSE)
  69. class ObsoleteNotAllowed(ConstructChecker):
  70. """Don't allow any use of the obsolete typedefs."""
  71. def examine(self, tok):
  72. if OBSOLETE_TYPE_RE_.match(tok.text):
  73. self.reporter.error(tok, "use of {!r}")
  74. class ObsoletePrivateDefinitionsAllowed(ConstructChecker):
  75. """Allow definitions of the private versions of the
  76. obsolete typedefs; that is, 'typedef [anything] __obsolete;'
  77. """
  78. def __init__(self, reporter):
  79. super().__init__(reporter)
  80. self.in_typedef = False
  81. self.prev_token = None
  82. def examine(self, tok):
  83. # bits/types.h hides 'typedef' in a macro sometimes.
  84. if (tok.kind == "IDENT"
  85. and tok.text in ("typedef", "__STD_TYPE")
  86. and tok.context is None):
  87. self.in_typedef = True
  88. elif tok.kind == "PUNCTUATOR" and tok.text == ";" and self.in_typedef:
  89. self.in_typedef = False
  90. if self.prev_token.kind == "IDENT":
  91. m = OBSOLETE_TYPE_RE_.match(self.prev_token.text)
  92. if m and m.group(1) != "__":
  93. self.reporter.error(self.prev_token, "use of {!r}")
  94. self.prev_token = None
  95. else:
  96. self._check_prev()
  97. self.prev_token = tok
  98. def eof(self):
  99. self._check_prev()
  100. def _check_prev(self):
  101. if (self.prev_token is not None
  102. and self.prev_token.kind == "IDENT"
  103. and OBSOLETE_TYPE_RE_.match(self.prev_token.text)):
  104. self.reporter.error(self.prev_token, "use of {!r}")
  105. class ObsoletePublicDefinitionsAllowed(ConstructChecker):
  106. """Allow definitions of the public versions of the obsolete
  107. typedefs. Only specific forms of definition are allowed:
  108. typedef __obsolete obsolete; // identifiers must agree
  109. typedef __uintN_t u_intN_t; // N must agree
  110. typedef unsigned long int ulong;
  111. typedef unsigned short int ushort;
  112. typedef unsigned int uint;
  113. """
  114. def __init__(self, reporter):
  115. super().__init__(reporter)
  116. self.typedef_tokens = []
  117. def examine(self, tok):
  118. if tok.kind in ("WHITESPACE", "BLOCK_COMMENT",
  119. "LINE_COMMENT", "NL", "ESCNL"):
  120. pass
  121. elif (tok.kind == "IDENT" and tok.text == "typedef"
  122. and tok.context is None):
  123. if self.typedef_tokens:
  124. self.reporter.error(tok, "typedef inside typedef")
  125. self._reset()
  126. self.typedef_tokens.append(tok)
  127. elif tok.kind == "PUNCTUATOR" and tok.text == ";":
  128. self._finish()
  129. elif self.typedef_tokens:
  130. self.typedef_tokens.append(tok)
  131. def eof(self):
  132. self._reset()
  133. def _reset(self):
  134. while self.typedef_tokens:
  135. tok = self.typedef_tokens.pop(0)
  136. if tok.kind == "IDENT" and OBSOLETE_TYPE_RE_.match(tok.text):
  137. self.reporter.error(tok, "use of {!r}")
  138. def _finish(self):
  139. if not self.typedef_tokens: return
  140. if self.typedef_tokens[-1].kind == "IDENT":
  141. m = OBSOLETE_TYPE_RE_.match(self.typedef_tokens[-1].text)
  142. if m:
  143. if self._permissible_public_definition(m):
  144. self.typedef_tokens.clear()
  145. self._reset()
  146. def _permissible_public_definition(self, m):
  147. if m.group(1) == "__": return False
  148. name = m.group(2)
  149. toks = self.typedef_tokens
  150. ntok = len(toks)
  151. if ntok == 3 and toks[1].kind == "IDENT":
  152. defn = toks[1].text
  153. n = OBSOLETE_TYPE_RE_.match(defn)
  154. if n and n.group(1) == "__" and n.group(2) == name:
  155. return True
  156. if (name[:5] == "u_int" and name[-2:] == "_t"
  157. and defn[:6] == "__uint" and defn[-2:] == "_t"
  158. and name[5:-2] == defn[6:-2]):
  159. return True
  160. return False
  161. if (name == "ulong" and ntok == 5
  162. and toks[1].kind == "IDENT" and toks[1].text == "unsigned"
  163. and toks[2].kind == "IDENT" and toks[2].text == "long"
  164. and toks[3].kind == "IDENT" and toks[3].text == "int"):
  165. return True
  166. if (name == "ushort" and ntok == 5
  167. and toks[1].kind == "IDENT" and toks[1].text == "unsigned"
  168. and toks[2].kind == "IDENT" and toks[2].text == "short"
  169. and toks[3].kind == "IDENT" and toks[3].text == "int"):
  170. return True
  171. if (name == "uint" and ntok == 4
  172. and toks[1].kind == "IDENT" and toks[1].text == "unsigned"
  173. and toks[2].kind == "IDENT" and toks[2].text == "int"):
  174. return True
  175. return False
  176. def ObsoleteTypedefChecker(reporter, fname):
  177. """Factory: produce an instance of the appropriate
  178. obsolete-typedef checker for FNAME."""
  179. # The obsolete rpc/ and rpcsvc/ headers are allowed to use the
  180. # obsolete types, because it would be more trouble than it's
  181. # worth to remove them from headers that we intend to stop
  182. # installing eventually anyway.
  183. if (fname.startswith("rpc/")
  184. or fname.startswith("rpcsvc/")
  185. or "/rpc/" in fname
  186. or "/rpcsvc/" in fname):
  187. return NoCheck(reporter)
  188. # bits/types.h is allowed to define the __-versions of the
  189. # obsolete types.
  190. if (fname == "bits/types.h"
  191. or fname.endswith("/bits/types.h")):
  192. return ObsoletePrivateDefinitionsAllowed(reporter)
  193. # sys/types.h is allowed to use the __-versions of the
  194. # obsolete types, but only to define the unprefixed versions.
  195. if (fname == "sys/types.h"
  196. or fname.endswith("/sys/types.h")):
  197. return ObsoletePublicDefinitionsAllowed(reporter)
  198. return ObsoleteNotAllowed(reporter)
  199. #
  200. # Master control
  201. #
  202. class HeaderChecker:
  203. """Perform all of the checks on each header. This is also the
  204. "reporter" object expected by tokenize_c and ConstructChecker.
  205. """
  206. def __init__(self):
  207. self.fname = None
  208. self.status = 0
  209. def error(self, tok, message):
  210. self.status = 1
  211. if '{!r}' in message:
  212. message = message.format(tok.text)
  213. sys.stderr.write("{}:{}:{}: error: {}\n".format(
  214. self.fname, tok.line, tok.column, message))
  215. def check(self, fname):
  216. self.fname = fname
  217. try:
  218. with open(fname, "rt", encoding="utf-8") as fp:
  219. contents = fp.read()
  220. except OSError as e:
  221. sys.stderr.write("{}: {}\n".format(fname, e.strerror))
  222. self.status = 1
  223. return
  224. typedef_checker = ObsoleteTypedefChecker(self, self.fname)
  225. for tok in glibcpp.tokenize_c(contents, self):
  226. typedef_checker.examine(tok)
  227. def main():
  228. ap = argparse.ArgumentParser(description=__doc__)
  229. ap.add_argument("headers", metavar="header", nargs="+",
  230. help="one or more headers to scan for obsolete constructs")
  231. args = ap.parse_args()
  232. checker = HeaderChecker()
  233. for fname in args.headers:
  234. # Headers whose installed name begins with "finclude/" contain
  235. # Fortran, not C, and this program should completely ignore them.
  236. if not (fname.startswith("finclude/") or "/finclude/" in fname):
  237. checker.check(fname)
  238. sys.exit(checker.status)
  239. main()