blob: a9aa118ea455046e38e4be346842c7b8087924e2 [file] [log] [blame]
Yuto Takano39639672021-08-05 19:47:48 +01001#!/usr/bin/env python3
2#
3# Copyright The Mbed TLS Contributors
4# SPDX-License-Identifier: Apache-2.0
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
Darryl Greend5802922018-05-08 15:30:59 +010018"""
Yuto Takano39639672021-08-05 19:47:48 +010019This script confirms that the naming of all symbols and identifiers in Mbed TLS
Yuto Takano159255a2021-08-06 17:00:28 +010020are consistent with the house style and are also self-consistent. It only runs
21on Linux and macOS since it depends on nm.
22
Yuto Takano55c6c872021-08-09 15:35:19 +010023It contains two major Python classes, CodeParser and NameChecker. They both have
24a comprehensive "run-all" function (comprehensive_parse() and perform_checks())
25but the individual functions can also be used for specific needs.
26
27CodeParser makes heavy use of regular expressions to parse the code, and is
28dependent on the current code formatting. Many Python C parser libraries require
29preprocessed C code, which means no macro parsing. Compiler tools are also not
30very helpful when we want the exact location in the original source (which
31becomes impossible when e.g. comments are stripped).
32
33NameChecker performs the following checks:
Yuto Takano81528c02021-08-06 16:22:06 +010034
35- All exported and available symbols in the library object files, are explicitly
Yuto Takano159255a2021-08-06 17:00:28 +010036 declared in the header files. This uses the nm command.
Yuto Takano81528c02021-08-06 16:22:06 +010037- All macros, constants, and identifiers (function names, struct names, etc)
Yuto Takano55c6c872021-08-09 15:35:19 +010038 follow the required regex pattern.
Yuto Takano81528c02021-08-06 16:22:06 +010039- Typo checking: All words that begin with MBED exist as macros or constants.
Yuto Takanofc54dfb2021-08-07 17:18:28 +010040
Yuto Takano55c6c872021-08-09 15:35:19 +010041The script returns 0 on success, 1 on test failure, and 2 if there is a script
Yuto Takano8246eb82021-08-16 10:37:24 +010042error. It must be run from Mbed TLS root.
Darryl Greend5802922018-05-08 15:30:59 +010043"""
Yuto Takano39639672021-08-05 19:47:48 +010044
Yuto Takanofc1e9ff2021-08-23 13:54:56 +010045import abc
Yuto Takano39639672021-08-05 19:47:48 +010046import argparse
Yuto Takano977e07f2021-08-09 11:56:15 +010047import glob
Yuto Takano39639672021-08-05 19:47:48 +010048import textwrap
Darryl Greend5802922018-05-08 15:30:59 +010049import os
50import sys
51import traceback
52import re
Yuto Takanob1417b42021-08-17 10:30:20 +010053import enum
Darryl Greend5802922018-05-08 15:30:59 +010054import shutil
55import subprocess
56import logging
57
Yuto Takano81528c02021-08-06 16:22:06 +010058# Naming patterns to check against. These are defined outside the NameCheck
59# class for ease of modification.
Yuto Takanobb7dca42021-08-05 19:57:58 +010060MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
Yuto Takano81528c02021-08-06 16:22:06 +010061CONSTANTS_PATTERN = MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010062IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010063
Yuto Takanod93fa372021-08-06 23:05:55 +010064class Match(): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010065 """
66 A class representing a match, together with its found position.
67
68 Fields:
69 * filename: the file that the match was in.
70 * line: the full line containing the match.
Yuto Takano704b0f72021-08-17 10:41:23 +010071 * line_no: the line number.
72 * pos: a tuple of (start, end) positions on the line where the match is.
Yuto Takano81528c02021-08-06 16:22:06 +010073 * name: the match itself.
74 """
Yuto Takano704b0f72021-08-17 10:41:23 +010075 def __init__(self, filename, line, line_no, pos, name):
76 # pylint: disable=too-many-arguments
Yuto Takano39639672021-08-05 19:47:48 +010077 self.filename = filename
78 self.line = line
Yuto Takano704b0f72021-08-17 10:41:23 +010079 self.line_no = line_no
Yuto Takano39639672021-08-05 19:47:48 +010080 self.pos = pos
81 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010082
Yuto Takanoa4e75122021-08-06 17:23:28 +010083 def __str__(self):
Yuto Takanofb86ac72021-08-16 10:32:40 +010084 """
85 Return a formatted code listing representation of the erroneous line.
86 """
Yuto Takano704b0f72021-08-17 10:41:23 +010087 gutter = format(self.line_no, "4d")
88 underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
Yuto Takano381fda82021-08-06 23:37:20 +010089
Yuto Takanoa4e75122021-08-06 17:23:28 +010090 return (
Yuto Takanofb86ac72021-08-16 10:32:40 +010091 " {0} |\n".format(" " * len(gutter)) +
Yuto Takano381fda82021-08-06 23:37:20 +010092 " {0} | {1}".format(gutter, self.line) +
Yuto Takanofb86ac72021-08-16 10:32:40 +010093 " {0} | {1}\n".format(" " * len(gutter), underline)
Yuto Takanoa4e75122021-08-06 17:23:28 +010094 )
Yuto Takanod93fa372021-08-06 23:05:55 +010095
Yuto Takanofc1e9ff2021-08-23 13:54:56 +010096class Problem(abc.ABC): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010097 """
Yuto Takanofc1e9ff2021-08-23 13:54:56 +010098 An abstract parent class representing a form of static analysis error.
99 It extends an Abstract Base Class, which means it is not instantiable, and
100 it also mandates certain abstract methods to be implemented in subclasses.
Yuto Takano81528c02021-08-06 16:22:06 +0100101 """
Yuto Takano5473be22021-08-17 10:14:01 +0100102 # Class variable to control the quietness of all problems
103 quiet = False
Yuto Takano39639672021-08-05 19:47:48 +0100104 def __init__(self):
105 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +0100106 self.textwrapper.width = 80
Yuto Takanoa4e75122021-08-06 17:23:28 +0100107 self.textwrapper.initial_indent = " > "
Yuto Takano81528c02021-08-06 16:22:06 +0100108 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +0100109
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100110 def __str__(self):
111 """
112 Unified string representation method for all Problems.
113 """
114 if self.__class__.quiet:
115 return self.quiet_output()
116 return self.verbose_output()
117
118 @abc.abstractmethod
119 def quiet_output(self):
120 """
121 The output when --quiet is enabled.
122 """
123 pass
124
125 @abc.abstractmethod
126 def verbose_output(self):
127 """
128 The default output with explanation and code snippet if appropriate.
129 """
130 pass
131
Yuto Takanod93fa372021-08-06 23:05:55 +0100132class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100133 """
134 A problem that occurs when an exported/available symbol in the object file
135 is not explicitly declared in header files. Created with
136 NameCheck.check_symbols_declared_in_header()
137
138 Fields:
139 * symbol_name: the name of the symbol.
140 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100141 def __init__(self, symbol_name):
Yuto Takano39639672021-08-05 19:47:48 +0100142 self.symbol_name = symbol_name
143 Problem.__init__(self)
144
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100145 def quiet_output(self):
146 return "{0}".format(self.symbol_name)
Yuto Takano55614b52021-08-07 01:00:18 +0100147
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100148 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100149 return self.textwrapper.fill(
150 "'{0}' was found as an available symbol in the output of nm, "
151 "however it was not declared in any header files."
152 .format(self.symbol_name))
153
Yuto Takanod93fa372021-08-06 23:05:55 +0100154class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100155 """
156 A problem that occurs when something doesn't match the expected pattern.
157 Created with NameCheck.check_match_pattern()
158
159 Fields:
160 * pattern: the expected regex pattern
161 * match: the Match object in question
162 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100163 def __init__(self, pattern, match):
Yuto Takano39639672021-08-05 19:47:48 +0100164 self.pattern = pattern
165 self.match = match
166 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100167
Yuto Takano55614b52021-08-07 01:00:18 +0100168
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100169 def quiet_output(self):
170 return (
171 "{0}:{1}:{2}"
172 .format(self.match.filename, self.match.line_no, self.match.name)
173 )
174
175 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100176 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100177 "{0}:{1}: '{2}' does not match the required pattern '{3}'."
178 .format(
179 self.match.filename,
Yuto Takano5f831712021-08-18 18:03:24 +0100180 self.match.line_no,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100181 self.match.name,
Yuto Takanod70d4462021-08-09 12:45:51 +0100182 self.pattern
183 )
184 ) + "\n" + str(self.match)
Yuto Takano39639672021-08-05 19:47:48 +0100185
Yuto Takanod93fa372021-08-06 23:05:55 +0100186class Typo(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100187 """
188 A problem that occurs when a word using MBED doesn't appear to be defined as
189 constants nor enum values. Created with NameCheck.check_for_typos()
190
191 Fields:
192 * match: the Match object of the MBED name in question.
193 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100194 def __init__(self, match):
Yuto Takano39639672021-08-05 19:47:48 +0100195 self.match = match
196 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100197
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100198 def quiet_output(self):
199 return (
200 "{0}:{1}:{2}"
201 .format(self.match.filename, self.match.line_no, self.match.name)
202 )
Yuto Takano55614b52021-08-07 01:00:18 +0100203
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100204 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100205 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100206 "{0}:{1}: '{2}' looks like a typo. It was not found in any "
207 "macros or any enums. If this is not a typo, put "
208 "//no-check-names after it."
Yuto Takano5f831712021-08-18 18:03:24 +0100209 .format(self.match.filename, self.match.line_no, self.match.name)
Yuto Takanod70d4462021-08-09 12:45:51 +0100210 ) + "\n" + str(self.match)
Darryl Greend5802922018-05-08 15:30:59 +0100211
Yuto Takano55c6c872021-08-09 15:35:19 +0100212class CodeParser():
Yuto Takano81528c02021-08-06 16:22:06 +0100213 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100214 Class for retrieving files and parsing the code. This can be used
215 independently of the checks that NameChecker performs, for example for
216 list_internal_identifiers.py.
Yuto Takano81528c02021-08-06 16:22:06 +0100217 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100218 def __init__(self, log):
219 self.log = log
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100220 self.check_repo_path()
Yuto Takano977e07f2021-08-09 11:56:15 +0100221
Yuto Takano8e9a2192021-08-09 14:48:53 +0100222 # Memo for storing "glob expression": set(filepaths)
223 self.files = {}
224
Yuto Takano977e07f2021-08-09 11:56:15 +0100225 # Globally excluded filenames
Yuto Takano8e9a2192021-08-09 14:48:53 +0100226 self.excluded_files = ["**/bn_mul", "**/compat-2.x.h"]
Yuto Takano977e07f2021-08-09 11:56:15 +0100227
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100228 @staticmethod
229 def check_repo_path():
230 """
231 Check that the current working directory is the project root, and throw
232 an exception if not.
233 """
234 if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
235 raise Exception("This script must be run from Mbed TLS root")
236
Yuto Takano55c6c872021-08-09 15:35:19 +0100237 def comprehensive_parse(self):
Yuto Takano39639672021-08-05 19:47:48 +0100238 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100239 Comprehensive ("default") function to call each parsing function and
240 retrieve various elements of the code, together with the source location.
Darryl Greend5802922018-05-08 15:30:59 +0100241
Yuto Takano55c6c872021-08-09 15:35:19 +0100242 Returns a dict of parsed item key to the corresponding List of Matches.
Yuto Takano81528c02021-08-06 16:22:06 +0100243 """
244 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100245 self.log.debug(
Yuto Takano50953432021-08-09 14:54:36 +0100246 "The following files are excluded from the search: {}"
Yuto Takanod24e0372021-08-06 16:42:33 +0100247 .format(str(self.excluded_files))
248 )
Yuto Takano81528c02021-08-06 16:22:06 +0100249
Yuto Takano8e9a2192021-08-09 14:48:53 +0100250 all_macros = self.parse_macros([
251 "include/mbedtls/*.h",
252 "include/psa/*.h",
253 "library/*.h",
254 "tests/include/test/drivers/*.h",
Yuto Takanod70d4462021-08-09 12:45:51 +0100255 "3rdparty/everest/include/everest/everest.h",
256 "3rdparty/everest/include/everest/x25519.h"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100257 ])
258 enum_consts = self.parse_enum_consts([
259 "include/mbedtls/*.h",
260 "library/*.h",
261 "3rdparty/everest/include/everest/everest.h",
262 "3rdparty/everest/include/everest/x25519.h"
263 ])
264 identifiers = self.parse_identifiers([
265 "include/mbedtls/*.h",
266 "include/psa/*.h",
267 "library/*.h",
268 "3rdparty/everest/include/everest/everest.h",
269 "3rdparty/everest/include/everest/x25519.h"
270 ])
271 mbed_words = self.parse_mbed_words([
272 "include/mbedtls/*.h",
273 "include/psa/*.h",
274 "library/*.h",
275 "3rdparty/everest/include/everest/everest.h",
276 "3rdparty/everest/include/everest/x25519.h",
277 "library/*.c",
Yuto Takano81528c02021-08-06 16:22:06 +0100278 "3rdparty/everest/library/everest.c",
Yuto Takanod70d4462021-08-09 12:45:51 +0100279 "3rdparty/everest/library/x25519.c"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100280 ])
Yuto Takano81528c02021-08-06 16:22:06 +0100281 symbols = self.parse_symbols()
282
283 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
284 identifiers_justname = [x.name for x in identifiers]
285 actual_macros = []
286 for macro in all_macros:
287 if macro.name not in identifiers_justname:
288 actual_macros.append(macro)
289
290 self.log.debug("Found:")
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100291 # Aligns the counts on the assumption that none exceeds 4 digits
292 self.log.debug(" {:4} Total Macros".format(len(all_macros)))
293 self.log.debug(" {:4} Non-identifier Macros".format(len(actual_macros)))
294 self.log.debug(" {:4} Enum Constants".format(len(enum_consts)))
295 self.log.debug(" {:4} Identifiers".format(len(identifiers)))
296 self.log.debug(" {:4} Exported Symbols".format(len(symbols)))
Yuto Takano55c6c872021-08-09 15:35:19 +0100297 return {
Yuto Takano81528c02021-08-06 16:22:06 +0100298 "macros": actual_macros,
299 "enum_consts": enum_consts,
300 "identifiers": identifiers,
301 "symbols": symbols,
Yuto Takanod93fa372021-08-06 23:05:55 +0100302 "mbed_words": mbed_words
Yuto Takano81528c02021-08-06 16:22:06 +0100303 }
304
Yuto Takano55c6c872021-08-09 15:35:19 +0100305 def get_files(self, include_wildcards, exclude_wildcards):
306 """
307 Get all files that match any of the UNIX-style wildcards. While the
308 check_names script is designed only for use on UNIX/macOS (due to nm),
309 this function alone would work fine on Windows even with forward slashes
310 in the wildcard.
311
312 Args:
313 * include_wildcards: a List of shell-style wildcards to match filepaths.
314 * exclude_wildcards: a List of shell-style wildcards to exclude.
315
316 Returns a List of relative filepaths.
317 """
318 accumulator = set()
319
320 # exclude_wildcards may be None. Also, consider the global exclusions.
321 exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
322
Yuto Takano6adb2872021-08-16 11:38:34 +0100323 # Internal function to hit the memoisation cache or add to it the result
324 # of a glob operation. Used both for inclusion and exclusion since the
325 # only difference between them is whether they perform set union or
326 # difference on the return value of this function.
327 def hit_cache(wildcard):
328 if wildcard not in self.files:
329 self.files[wildcard] = set(glob.glob(wildcard, recursive=True))
330 return self.files[wildcard]
331
Yuto Takano55c6c872021-08-09 15:35:19 +0100332 for include_wildcard in include_wildcards:
Yuto Takano6adb2872021-08-16 11:38:34 +0100333 accumulator = accumulator.union(hit_cache(include_wildcard))
Yuto Takano55c6c872021-08-09 15:35:19 +0100334
Yuto Takano55c6c872021-08-09 15:35:19 +0100335 for exclude_wildcard in exclude_wildcards:
Yuto Takano6adb2872021-08-16 11:38:34 +0100336 accumulator = accumulator.difference(hit_cache(exclude_wildcard))
Yuto Takano55c6c872021-08-09 15:35:19 +0100337
338 return list(accumulator)
339
Yuto Takano8e9a2192021-08-09 14:48:53 +0100340 def parse_macros(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100341 """
342 Parse all macros defined by #define preprocessor directives.
343
344 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100345 * include: A List of glob expressions to look for files through.
346 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100347
348 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100349 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100350 macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
351 exclusions = (
Yuto Takano39639672021-08-05 19:47:48 +0100352 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
353 )
354
Yuto Takano50953432021-08-09 14:54:36 +0100355 files = self.get_files(include, exclude)
356 self.log.debug("Looking for macros in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100357
Yuto Takano50953432021-08-09 14:54:36 +0100358 macros = []
359 for header_file in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100360 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100361 for line_no, line in enumerate(header):
Yuto Takanod93fa372021-08-06 23:05:55 +0100362 for macro in macro_regex.finditer(line):
Yuto Takanod70d4462021-08-09 12:45:51 +0100363 if macro.group("macro").startswith(exclusions):
364 continue
365
366 macros.append(Match(
367 header_file,
368 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100369 line_no,
370 macro.span("macro"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100371 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100372
Yuto Takano39639672021-08-05 19:47:48 +0100373 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100374
Yuto Takano8e9a2192021-08-09 14:48:53 +0100375 def parse_mbed_words(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100376 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100377 Parse all words in the file that begin with MBED, in and out of macros,
378 comments, anything.
Yuto Takano39639672021-08-05 19:47:48 +0100379
380 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100381 * include: A List of glob expressions to look for files through.
382 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100383
384 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100385 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100386 # Typos of TLS are common, hence the broader check below than MBEDTLS.
Yuto Takanod93fa372021-08-06 23:05:55 +0100387 mbed_regex = re.compile(r"\bMBED.+?_[A-Z0-9_]*")
388 exclusions = re.compile(r"// *no-check-names|#error")
389
Yuto Takano50953432021-08-09 14:54:36 +0100390 files = self.get_files(include, exclude)
391 self.log.debug("Looking for MBED words in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100392
Yuto Takano50953432021-08-09 14:54:36 +0100393 mbed_words = []
394 for filename in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100395 with open(filename, "r", encoding="utf-8") as fp:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100396 for line_no, line in enumerate(fp):
Yuto Takanod93fa372021-08-06 23:05:55 +0100397 if exclusions.search(line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100398 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100399
Yuto Takanod93fa372021-08-06 23:05:55 +0100400 for name in mbed_regex.finditer(line):
401 mbed_words.append(Match(
Yuto Takano39639672021-08-05 19:47:48 +0100402 filename,
403 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100404 line_no,
405 name.span(0),
406 name.group(0)))
Yuto Takano39639672021-08-05 19:47:48 +0100407
Yuto Takanod93fa372021-08-06 23:05:55 +0100408 return mbed_words
Yuto Takano39639672021-08-05 19:47:48 +0100409
Yuto Takano8e9a2192021-08-09 14:48:53 +0100410 def parse_enum_consts(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100411 """
412 Parse all enum value constants that are declared.
413
414 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100415 * include: A List of glob expressions to look for files through.
416 * exclude: A List of glob expressions for excluding files.
Yuto Takano39639672021-08-05 19:47:48 +0100417
Yuto Takano81528c02021-08-06 16:22:06 +0100418 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100419 """
Yuto Takano50953432021-08-09 14:54:36 +0100420 files = self.get_files(include, exclude)
421 self.log.debug("Looking for enum consts in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100422
Yuto Takanob1417b42021-08-17 10:30:20 +0100423 # Emulate a finite state machine to parse enum declarations.
424 # OUTSIDE_KEYWORD = outside the enum keyword
425 # IN_BRACES = inside enum opening braces
426 # IN_BETWEEN = between enum keyword and opening braces
427 states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
Yuto Takano50953432021-08-09 14:54:36 +0100428 enum_consts = []
429 for header_file in files:
Yuto Takanob1417b42021-08-17 10:30:20 +0100430 state = states.OUTSIDE_KEYWORD
Yuto Takanoa083d152021-08-07 00:25:59 +0100431 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100432 for line_no, line in enumerate(header):
Yuto Takano13ecd992021-08-06 16:56:52 +0100433 # Match typedefs and brackets only when they are at the
434 # beginning of the line -- if they are indented, they might
435 # be sub-structures within structs, etc.
Yuto Takanob1417b42021-08-17 10:30:20 +0100436 if (state == states.OUTSIDE_KEYWORD and
Yuto Takano35906912021-08-17 11:05:43 +0100437 re.search(r"^(typedef +)?enum +{", line)):
Yuto Takanob1417b42021-08-17 10:30:20 +0100438 state = states.IN_BRACES
439 elif (state == states.OUTSIDE_KEYWORD and
440 re.search(r"^(typedef +)?enum", line)):
441 state = states.IN_BETWEEN
442 elif (state == states.IN_BETWEEN and
443 re.search(r"^{", line)):
444 state = states.IN_BRACES
445 elif (state == states.IN_BRACES and
446 re.search(r"^}", line)):
447 state = states.OUTSIDE_KEYWORD
448 elif (state == states.IN_BRACES and
449 not re.search(r"^ *#", line)):
Yuto Takano90bc0262021-08-16 11:34:10 +0100450 enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
Yuto Takanod70d4462021-08-09 12:45:51 +0100451 if not enum_const:
452 continue
453
454 enum_consts.append(Match(
455 header_file,
456 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100457 line_no,
458 enum_const.span("enum_const"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100459 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100460
Yuto Takano39639672021-08-05 19:47:48 +0100461 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100462
Yuto Takano8e9a2192021-08-09 14:48:53 +0100463 def parse_identifiers(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100464 """
Yuto Takano8246eb82021-08-16 10:37:24 +0100465 Parse all lines of a header where a function/enum/struct/union/typedef
Yuto Takanob1417b42021-08-17 10:30:20 +0100466 identifier is declared, based on some regex and heuristics. Highly
467 dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100468
Yuto Takano39639672021-08-05 19:47:48 +0100469 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100470 * include: A List of glob expressions to look for files through.
471 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100472
473 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100474 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100475 identifier_regex = re.compile(
476 # Match " something(a" or " *something(a". Functions.
477 # Assumptions:
478 # - function definition from return type to one of its arguments is
Yuto Takano55c6c872021-08-09 15:35:19 +0100479 # all on one line
Yuto Takanod93fa372021-08-06 23:05:55 +0100480 # - function definition line only contains alphanumeric, asterisk,
481 # underscore, and open bracket
482 r".* \**(\w+) *\( *\w|"
Yuto Takano55c6c872021-08-09 15:35:19 +0100483 # Match "(*something)(".
Yuto Takanod93fa372021-08-06 23:05:55 +0100484 r".*\( *\* *(\w+) *\) *\(|"
485 # Match names of named data structures.
486 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
487 # Match names of typedef instances, after closing bracket.
Yuto Takanod70d4462021-08-09 12:45:51 +0100488 r"}? *(\w+)[;[].*"
489 )
Yuto Takano35906912021-08-17 11:05:43 +0100490 # The regex below is indented for clarity.
Yuto Takanod70d4462021-08-09 12:45:51 +0100491 exclusion_lines = re.compile(
492 r"^("
Yuto Takano35906912021-08-17 11:05:43 +0100493 r"extern +\"C\"|" # pylint: disable=bad-continuation
Yuto Takano90bc0262021-08-16 11:34:10 +0100494 r"(typedef +)?(struct|union|enum)( *{)?$|"
495 r"} *;?$|"
496 r"$|"
497 r"//|"
498 r"#"
Yuto Takanod70d4462021-08-09 12:45:51 +0100499 r")"
500 )
Yuto Takanod93fa372021-08-06 23:05:55 +0100501
Yuto Takano50953432021-08-09 14:54:36 +0100502 files = self.get_files(include, exclude)
503 self.log.debug("Looking for identifiers in {} files".format(len(files)))
504
505 identifiers = []
506 for header_file in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100507 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100508 in_block_comment = False
Yuto Takano55c6c872021-08-09 15:35:19 +0100509 # The previous line variable is used for concatenating lines
Yuto Takanob1417b42021-08-17 10:30:20 +0100510 # when identifiers are formatted and spread across multiple
511 # lines.
Yuto Takanod93fa372021-08-06 23:05:55 +0100512 previous_line = ""
Darryl Greend5802922018-05-08 15:30:59 +0100513
Yuto Takano8f457cf2021-08-06 17:54:58 +0100514 for line_no, line in enumerate(header):
Yuto Takano81528c02021-08-06 16:22:06 +0100515 # Skip parsing this line if a block comment ends on it,
516 # but don't skip if it has just started -- there is a chance
517 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100518 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100519 in_block_comment = not in_block_comment
520 if re.search(r"\*/", line):
521 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100522 continue
523
Yuto Takano81528c02021-08-06 16:22:06 +0100524 if in_block_comment:
Yuto Takanod93fa372021-08-06 23:05:55 +0100525 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100526 continue
527
Yuto Takano90bc0262021-08-16 11:34:10 +0100528 if exclusion_lines.search(line):
Yuto Takanod93fa372021-08-06 23:05:55 +0100529 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100530 continue
531
Yuto Takanocfc9e4a2021-08-06 20:02:32 +0100532 # If the line contains only space-separated alphanumeric
533 # characters (or underscore, asterisk, or, open bracket),
534 # and nothing else, high chance it's a declaration that
535 # continues on the next line
Yuto Takano90bc0262021-08-16 11:34:10 +0100536 if re.search(r"^([\w\*\(]+\s+)+$", line):
Yuto Takanod93fa372021-08-06 23:05:55 +0100537 previous_line += line
Yuto Takano81528c02021-08-06 16:22:06 +0100538 continue
539
540 # If previous line seemed to start an unfinished declaration
Yuto Takanocfc9e4a2021-08-06 20:02:32 +0100541 # (as above), concat and treat them as one.
542 if previous_line:
Yuto Takano90bc0262021-08-16 11:34:10 +0100543 line = previous_line.strip() + " " + line.strip() + "\n"
Yuto Takanod93fa372021-08-06 23:05:55 +0100544 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100545
Yuto Takano8246eb82021-08-16 10:37:24 +0100546 # Skip parsing if line has a space in front = heuristic to
Yuto Takano81528c02021-08-06 16:22:06 +0100547 # skip function argument lines (highly subject to formatting
548 # changes)
549 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100550 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100551
Yuto Takanod93fa372021-08-06 23:05:55 +0100552 identifier = identifier_regex.search(line)
Yuto Takano39639672021-08-05 19:47:48 +0100553
Yuto Takanod70d4462021-08-09 12:45:51 +0100554 if not identifier:
555 continue
556
557 # Find the group that matched, and append it
558 for group in identifier.groups():
559 if not group:
560 continue
561
562 identifiers.append(Match(
563 header_file,
564 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100565 line_no,
566 identifier.span(),
Yuto Takanod70d4462021-08-09 12:45:51 +0100567 group))
Yuto Takano39639672021-08-05 19:47:48 +0100568
569 return identifiers
570
571 def parse_symbols(self):
572 """
573 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
574 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100575 Exceptions thrown here are rethrown because they would be critical
576 errors that void several tests, and thus needs to halt the program. This
577 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100578
Yuto Takano81528c02021-08-06 16:22:06 +0100579 Returns a List of unique symbols defined and used in the libraries.
580 """
581 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100582 symbols = []
583
584 # Back up the config and atomically compile with the full configratuion.
Yuto Takanod70d4462021-08-09 12:45:51 +0100585 shutil.copy(
586 "include/mbedtls/mbedtls_config.h",
587 "include/mbedtls/mbedtls_config.h.bak"
588 )
Darryl Greend5802922018-05-08 15:30:59 +0100589 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100590 # Use check=True in all subprocess calls so that failures are raised
591 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100592 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100593 ["python3", "scripts/config.py", "full"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100594 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100595 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100596 )
597 my_environment = os.environ.copy()
598 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100599 # Run make clean separately to lib to prevent unwanted behavior when
600 # make is invoked with parallelism.
Yuto Takano39639672021-08-05 19:47:48 +0100601 subprocess.run(
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100602 ["make", "clean"],
603 universal_newlines=True,
604 check=True
605 )
606 subprocess.run(
607 ["make", "lib"],
Darryl Greend5802922018-05-08 15:30:59 +0100608 env=my_environment,
Yuto Takanobcc3d992021-08-06 23:14:58 +0100609 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100610 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100611 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100612 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100613 )
Yuto Takano39639672021-08-05 19:47:48 +0100614
615 # Perform object file analysis using nm
Yuto Takanod70d4462021-08-09 12:45:51 +0100616 symbols = self.parse_symbols_from_nm([
617 "library/libmbedcrypto.a",
618 "library/libmbedtls.a",
619 "library/libmbedx509.a"
620 ])
Yuto Takano39639672021-08-05 19:47:48 +0100621
622 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100623 ["make", "clean"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100624 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100625 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100626 )
627 except subprocess.CalledProcessError as error:
Yuto Takano25eeb7b2021-08-06 21:27:59 +0100628 self.log.debug(error.output)
Yuto Takano81528c02021-08-06 16:22:06 +0100629 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100630 finally:
Yuto Takano6fececf2021-08-07 17:28:23 +0100631 # Put back the original config regardless of there being errors.
632 # Works also for keyboard interrupts.
Yuto Takanod70d4462021-08-09 12:45:51 +0100633 shutil.move(
634 "include/mbedtls/mbedtls_config.h.bak",
635 "include/mbedtls/mbedtls_config.h"
636 )
Yuto Takano39639672021-08-05 19:47:48 +0100637
638 return symbols
639
640 def parse_symbols_from_nm(self, object_files):
641 """
642 Run nm to retrieve the list of referenced symbols in each object file.
643 Does not return the position data since it is of no use.
644
Yuto Takano81528c02021-08-06 16:22:06 +0100645 Args:
Yuto Takano55c6c872021-08-09 15:35:19 +0100646 * object_files: a List of compiled object filepaths to search through.
Yuto Takano81528c02021-08-06 16:22:06 +0100647
648 Returns a List of unique symbols defined and used in any of the object
649 files.
Yuto Takano39639672021-08-05 19:47:48 +0100650 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100651 nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
652 nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100653 exclusions = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100654
655 symbols = []
656
Yuto Takano81528c02021-08-06 16:22:06 +0100657 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100658 nm_output = ""
659 for lib in object_files:
660 nm_output += subprocess.run(
661 ["nm", "-og", lib],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100662 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100663 stdout=subprocess.PIPE,
664 stderr=subprocess.STDOUT,
665 check=True
666 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100667
Yuto Takano39639672021-08-05 19:47:48 +0100668 for line in nm_output.splitlines():
Yuto Takano90bc0262021-08-16 11:34:10 +0100669 if not nm_undefined_regex.search(line):
670 symbol = nm_valid_regex.search(line)
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100671 if (symbol and not symbol.group("symbol").startswith(exclusions)):
Yuto Takanoe77f6992021-08-05 20:22:59 +0100672 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100673 else:
674 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100675
Yuto Takano39639672021-08-05 19:47:48 +0100676 return symbols
677
Yuto Takano55c6c872021-08-09 15:35:19 +0100678class NameChecker():
679 """
680 Representation of the core name checking operation performed by this script.
681 """
682 def __init__(self, parse_result, log):
683 self.parse_result = parse_result
684 self.log = log
685
Yuto Takano55614b52021-08-07 01:00:18 +0100686 def perform_checks(self, quiet=False):
Yuto Takano39639672021-08-05 19:47:48 +0100687 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100688 A comprehensive checker that performs each check in order, and outputs
689 a final verdict.
Yuto Takano81528c02021-08-06 16:22:06 +0100690
691 Args:
Yuto Takano55614b52021-08-07 01:00:18 +0100692 * quiet: whether to hide detailed problem explanation.
Yuto Takano39639672021-08-05 19:47:48 +0100693 """
Yuto Takano81528c02021-08-06 16:22:06 +0100694 self.log.info("=============")
Yuto Takano5473be22021-08-17 10:14:01 +0100695 Problem.quiet = quiet
Yuto Takano39639672021-08-05 19:47:48 +0100696 problems = 0
Yuto Takano5473be22021-08-17 10:14:01 +0100697 problems += self.check_symbols_declared_in_header()
Yuto Takano39639672021-08-05 19:47:48 +0100698
Yuto Takanod70d4462021-08-09 12:45:51 +0100699 pattern_checks = [
700 ("macros", MACRO_PATTERN),
701 ("enum_consts", CONSTANTS_PATTERN),
702 ("identifiers", IDENTIFIER_PATTERN)
703 ]
Yuto Takano39639672021-08-05 19:47:48 +0100704 for group, check_pattern in pattern_checks:
Yuto Takano5473be22021-08-17 10:14:01 +0100705 problems += self.check_match_pattern(group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100706
Yuto Takano5473be22021-08-17 10:14:01 +0100707 problems += self.check_for_typos()
Yuto Takano39639672021-08-05 19:47:48 +0100708
709 self.log.info("=============")
710 if problems > 0:
711 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano55614b52021-08-07 01:00:18 +0100712 if quiet:
713 self.log.info("Remove --quiet to see explanations.")
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100714 else:
715 self.log.info("Use --quiet for minimal output.")
Yuto Takano55c6c872021-08-09 15:35:19 +0100716 return 1
Yuto Takano39639672021-08-05 19:47:48 +0100717 else:
718 self.log.info("PASS")
Yuto Takano55c6c872021-08-09 15:35:19 +0100719 return 0
Darryl Greend5802922018-05-08 15:30:59 +0100720
Yuto Takano5473be22021-08-17 10:14:01 +0100721 def check_symbols_declared_in_header(self):
Yuto Takano39639672021-08-05 19:47:48 +0100722 """
723 Perform a check that all detected symbols in the library object files
724 are properly declared in headers.
Yuto Takano977e07f2021-08-09 11:56:15 +0100725 Assumes parse_names_in_source() was called before this.
Darryl Greend5802922018-05-08 15:30:59 +0100726
Yuto Takano81528c02021-08-06 16:22:06 +0100727 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100728 """
729 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100730
Yuto Takano39639672021-08-05 19:47:48 +0100731 for symbol in self.parse_result["symbols"]:
732 found_symbol_declared = False
733 for identifier_match in self.parse_result["identifiers"]:
734 if symbol == identifier_match.name:
735 found_symbol_declared = True
736 break
Yuto Takano81528c02021-08-06 16:22:06 +0100737
Yuto Takano39639672021-08-05 19:47:48 +0100738 if not found_symbol_declared:
Yuto Takanod70d4462021-08-09 12:45:51 +0100739 problems.append(SymbolNotInHeader(symbol))
Yuto Takano39639672021-08-05 19:47:48 +0100740
Yuto Takano5473be22021-08-17 10:14:01 +0100741 self.output_check_result("All symbols in header", problems)
Yuto Takano39639672021-08-05 19:47:48 +0100742 return len(problems)
743
Yuto Takano5473be22021-08-17 10:14:01 +0100744 def check_match_pattern(self, group_to_check, check_pattern):
Yuto Takano81528c02021-08-06 16:22:06 +0100745 """
746 Perform a check that all items of a group conform to a regex pattern.
Yuto Takano977e07f2021-08-09 11:56:15 +0100747 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100748
749 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100750 * group_to_check: string key to index into self.parse_result.
751 * check_pattern: the regex to check against.
752
753 Returns the number of problems that need fixing.
754 """
Yuto Takano39639672021-08-05 19:47:48 +0100755 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100756
Yuto Takano39639672021-08-05 19:47:48 +0100757 for item_match in self.parse_result[group_to_check]:
Yuto Takano90bc0262021-08-16 11:34:10 +0100758 if not re.search(check_pattern, item_match.name):
Yuto Takano39639672021-08-05 19:47:48 +0100759 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano90bc0262021-08-16 11:34:10 +0100760 # Double underscore should not be used for names
761 if re.search(r".*__.*", item_match.name):
Yuto Takano704b0f72021-08-17 10:41:23 +0100762 problems.append(
763 PatternMismatch("no double underscore allowed", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100764
765 self.output_check_result(
766 "Naming patterns of {}".format(group_to_check),
Yuto Takano55614b52021-08-07 01:00:18 +0100767 problems)
Yuto Takano39639672021-08-05 19:47:48 +0100768 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100769
Yuto Takano5473be22021-08-17 10:14:01 +0100770 def check_for_typos(self):
Yuto Takano81528c02021-08-06 16:22:06 +0100771 """
772 Perform a check that all words in the soure code beginning with MBED are
773 either defined as macros, or as enum constants.
Yuto Takano977e07f2021-08-09 11:56:15 +0100774 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100775
Yuto Takano81528c02021-08-06 16:22:06 +0100776 Returns the number of problems that need fixing.
777 """
Yuto Takano39639672021-08-05 19:47:48 +0100778 problems = []
Yuto Takano39639672021-08-05 19:47:48 +0100779
Yuto Takanod70d4462021-08-09 12:45:51 +0100780 # Set comprehension, equivalent to a list comprehension wrapped by set()
Yuto Takanod93fa372021-08-06 23:05:55 +0100781 all_caps_names = {
782 match.name
783 for match
784 in self.parse_result["macros"] + self.parse_result["enum_consts"]}
785 typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$")
Yuto Takano39639672021-08-05 19:47:48 +0100786
Yuto Takanod93fa372021-08-06 23:05:55 +0100787 for name_match in self.parse_result["mbed_words"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100788 found = name_match.name in all_caps_names
789
790 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
791 # PSA driver, they will not exist as macros. However, they
792 # should still be checked for typos using the equivalent
793 # BUILTINs that exist.
794 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
795 found = name_match.name.replace(
796 "MBEDTLS_PSA_ACCEL_",
797 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
798
Yuto Takanod93fa372021-08-06 23:05:55 +0100799 if not found and not typo_exclusion.search(name_match.name):
Yuto Takanod70d4462021-08-09 12:45:51 +0100800 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100801
Yuto Takano5473be22021-08-17 10:14:01 +0100802 self.output_check_result("Likely typos", problems)
Yuto Takano81528c02021-08-06 16:22:06 +0100803 return len(problems)
804
Yuto Takano5473be22021-08-17 10:14:01 +0100805 def output_check_result(self, name, problems):
Yuto Takano81528c02021-08-06 16:22:06 +0100806 """
807 Write out the PASS/FAIL status of a performed check depending on whether
808 there were problems.
Yuto Takanod70d4462021-08-09 12:45:51 +0100809
810 Args:
Yuto Takanod70d4462021-08-09 12:45:51 +0100811 * name: the name of the test
812 * problems: a List of encountered Problems
Yuto Takano81528c02021-08-06 16:22:06 +0100813 """
Yuto Takano39639672021-08-05 19:47:48 +0100814 if problems:
Yuto Takano55614b52021-08-07 01:00:18 +0100815 self.log.info("{}: FAIL\n".format(name))
816 for problem in problems:
817 self.log.warning(str(problem))
Darryl Greend5802922018-05-08 15:30:59 +0100818 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100819 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100820
Yuto Takano39639672021-08-05 19:47:48 +0100821def main():
822 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100823 Perform argument parsing, and create an instance of CodeParser and
824 NameChecker to begin the core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100825 """
Yuto Takanof005c332021-08-09 13:56:36 +0100826 parser = argparse.ArgumentParser(
Yuto Takano39639672021-08-05 19:47:48 +0100827 formatter_class=argparse.RawDescriptionHelpFormatter,
828 description=(
829 "This script confirms that the naming of all symbols and identifiers "
830 "in Mbed TLS are consistent with the house style and are also "
831 "self-consistent.\n\n"
Yuto Takanof005c332021-08-09 13:56:36 +0100832 "Expected to be run from the MbedTLS root directory.")
833 )
834 parser.add_argument(
835 "-v", "--verbose",
836 action="store_true",
837 help="show parse results"
838 )
839 parser.add_argument(
840 "-q", "--quiet",
841 action="store_true",
842 help="hide unnecessary text, explanations, and highlighs"
843 )
Darryl Greend5802922018-05-08 15:30:59 +0100844
Yuto Takanof005c332021-08-09 13:56:36 +0100845 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100846
Yuto Takano55c6c872021-08-09 15:35:19 +0100847 # Configure the global logger, which is then passed to the classes below
848 log = logging.getLogger()
849 log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
850 log.addHandler(logging.StreamHandler())
851
Darryl Greend5802922018-05-08 15:30:59 +0100852 try:
Yuto Takano55c6c872021-08-09 15:35:19 +0100853 code_parser = CodeParser(log)
854 parse_result = code_parser.comprehensive_parse()
Yuto Takanod93fa372021-08-06 23:05:55 +0100855 except Exception: # pylint: disable=broad-except
Darryl Greend5802922018-05-08 15:30:59 +0100856 traceback.print_exc()
857 sys.exit(2)
858
Yuto Takano55c6c872021-08-09 15:35:19 +0100859 name_checker = NameChecker(parse_result, log)
860 return_code = name_checker.perform_checks(quiet=args.quiet)
861
862 sys.exit(return_code)
863
Darryl Greend5802922018-05-08 15:30:59 +0100864if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100865 main()