blob: 0eba96740ce083cac1cf5b19354fb8afedb86947 [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
45import argparse
Yuto Takano977e07f2021-08-09 11:56:15 +010046import glob
Yuto Takano39639672021-08-05 19:47:48 +010047import textwrap
Darryl Greend5802922018-05-08 15:30:59 +010048import os
49import sys
50import traceback
51import re
Yuto Takanob1417b42021-08-17 10:30:20 +010052import enum
Darryl Greend5802922018-05-08 15:30:59 +010053import shutil
54import subprocess
55import logging
56
Yuto Takano81528c02021-08-06 16:22:06 +010057# Naming patterns to check against. These are defined outside the NameCheck
58# class for ease of modification.
Yuto Takanobb7dca42021-08-05 19:57:58 +010059MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
Yuto Takano81528c02021-08-06 16:22:06 +010060CONSTANTS_PATTERN = MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010061IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010062
Yuto Takanod93fa372021-08-06 23:05:55 +010063class Match(): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010064 """
65 A class representing a match, together with its found position.
66
67 Fields:
68 * filename: the file that the match was in.
69 * line: the full line containing the match.
Yuto Takano704b0f72021-08-17 10:41:23 +010070 * line_no: the line number.
71 * pos: a tuple of (start, end) positions on the line where the match is.
Yuto Takano81528c02021-08-06 16:22:06 +010072 * name: the match itself.
73 """
Yuto Takano704b0f72021-08-17 10:41:23 +010074 def __init__(self, filename, line, line_no, pos, name):
75 # pylint: disable=too-many-arguments
Yuto Takano39639672021-08-05 19:47:48 +010076 self.filename = filename
77 self.line = line
Yuto Takano704b0f72021-08-17 10:41:23 +010078 self.line_no = line_no
Yuto Takano39639672021-08-05 19:47:48 +010079 self.pos = pos
80 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010081
Yuto Takanoa4e75122021-08-06 17:23:28 +010082 def __str__(self):
Yuto Takanofb86ac72021-08-16 10:32:40 +010083 """
84 Return a formatted code listing representation of the erroneous line.
85 """
Yuto Takano704b0f72021-08-17 10:41:23 +010086 gutter = format(self.line_no, "4d")
87 underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
Yuto Takano381fda82021-08-06 23:37:20 +010088
Yuto Takanoa4e75122021-08-06 17:23:28 +010089 return (
Yuto Takanofb86ac72021-08-16 10:32:40 +010090 " {0} |\n".format(" " * len(gutter)) +
Yuto Takano381fda82021-08-06 23:37:20 +010091 " {0} | {1}".format(gutter, self.line) +
Yuto Takanofb86ac72021-08-16 10:32:40 +010092 " {0} | {1}\n".format(" " * len(gutter), underline)
Yuto Takanoa4e75122021-08-06 17:23:28 +010093 )
Yuto Takanod93fa372021-08-06 23:05:55 +010094
95class Problem(): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010096 """
97 A parent class representing a form of static analysis error.
Yuto Takano81528c02021-08-06 16:22:06 +010098 """
Yuto Takano5473be22021-08-17 10:14:01 +010099 # Class variable to control the quietness of all problems
100 quiet = False
Yuto Takano39639672021-08-05 19:47:48 +0100101 def __init__(self):
102 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +0100103 self.textwrapper.width = 80
Yuto Takanoa4e75122021-08-06 17:23:28 +0100104 self.textwrapper.initial_indent = " > "
Yuto Takano81528c02021-08-06 16:22:06 +0100105 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +0100106
Yuto Takanod93fa372021-08-06 23:05:55 +0100107class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100108 """
109 A problem that occurs when an exported/available symbol in the object file
110 is not explicitly declared in header files. Created with
111 NameCheck.check_symbols_declared_in_header()
112
113 Fields:
114 * symbol_name: the name of the symbol.
115 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100116 def __init__(self, symbol_name):
Yuto Takano39639672021-08-05 19:47:48 +0100117 self.symbol_name = symbol_name
118 Problem.__init__(self)
119
120 def __str__(self):
Yuto Takano55614b52021-08-07 01:00:18 +0100121 if self.quiet:
122 return "{0}".format(self.symbol_name)
123
Yuto Takano39639672021-08-05 19:47:48 +0100124 return self.textwrapper.fill(
125 "'{0}' was found as an available symbol in the output of nm, "
126 "however it was not declared in any header files."
127 .format(self.symbol_name))
128
Yuto Takanod93fa372021-08-06 23:05:55 +0100129class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100130 """
131 A problem that occurs when something doesn't match the expected pattern.
132 Created with NameCheck.check_match_pattern()
133
134 Fields:
135 * pattern: the expected regex pattern
136 * match: the Match object in question
137 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100138 def __init__(self, pattern, match):
Yuto Takano39639672021-08-05 19:47:48 +0100139 self.pattern = pattern
140 self.match = match
141 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100142
Yuto Takano39639672021-08-05 19:47:48 +0100143 def __str__(self):
Yuto Takano55614b52021-08-07 01:00:18 +0100144 if self.quiet:
Yuto Takanod70d4462021-08-09 12:45:51 +0100145 return (
Yuto Takano206b0222021-08-10 11:30:43 +0100146 "{0}:{1}:{2}"
Yuto Takano5f831712021-08-18 18:03:24 +0100147 .format(self.match.filename, self.match.line_no, self.match.name)
Yuto Takanod70d4462021-08-09 12:45:51 +0100148 )
Yuto Takano55614b52021-08-07 01:00:18 +0100149
Yuto Takano39639672021-08-05 19:47:48 +0100150 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100151 "{0}:{1}: '{2}' does not match the required pattern '{3}'."
152 .format(
153 self.match.filename,
Yuto Takano5f831712021-08-18 18:03:24 +0100154 self.match.line_no,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100155 self.match.name,
Yuto Takanod70d4462021-08-09 12:45:51 +0100156 self.pattern
157 )
158 ) + "\n" + str(self.match)
Yuto Takano39639672021-08-05 19:47:48 +0100159
Yuto Takanod93fa372021-08-06 23:05:55 +0100160class Typo(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100161 """
162 A problem that occurs when a word using MBED doesn't appear to be defined as
163 constants nor enum values. Created with NameCheck.check_for_typos()
164
165 Fields:
166 * match: the Match object of the MBED name in question.
167 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100168 def __init__(self, match):
Yuto Takano39639672021-08-05 19:47:48 +0100169 self.match = match
170 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100171
Yuto Takano39639672021-08-05 19:47:48 +0100172 def __str__(self):
Yuto Takano55614b52021-08-07 01:00:18 +0100173 if self.quiet:
Yuto Takanod70d4462021-08-09 12:45:51 +0100174 return (
175 "{0}:{1}:{2}"
Yuto Takano5f831712021-08-18 18:03:24 +0100176 .format(self.match.filename, self.match.line_no, self.match.name)
Yuto Takanod70d4462021-08-09 12:45:51 +0100177 )
Yuto Takano55614b52021-08-07 01:00:18 +0100178
Yuto Takano39639672021-08-05 19:47:48 +0100179 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100180 "{0}:{1}: '{2}' looks like a typo. It was not found in any "
181 "macros or any enums. If this is not a typo, put "
182 "//no-check-names after it."
Yuto Takano5f831712021-08-18 18:03:24 +0100183 .format(self.match.filename, self.match.line_no, self.match.name)
Yuto Takanod70d4462021-08-09 12:45:51 +0100184 ) + "\n" + str(self.match)
Darryl Greend5802922018-05-08 15:30:59 +0100185
Yuto Takano55c6c872021-08-09 15:35:19 +0100186class CodeParser():
Yuto Takano81528c02021-08-06 16:22:06 +0100187 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100188 Class for retrieving files and parsing the code. This can be used
189 independently of the checks that NameChecker performs, for example for
190 list_internal_identifiers.py.
Yuto Takano81528c02021-08-06 16:22:06 +0100191 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100192 def __init__(self, log):
193 self.log = log
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100194 self.check_repo_path()
Yuto Takano977e07f2021-08-09 11:56:15 +0100195
Yuto Takano8e9a2192021-08-09 14:48:53 +0100196 # Memo for storing "glob expression": set(filepaths)
197 self.files = {}
198
Yuto Takano977e07f2021-08-09 11:56:15 +0100199 # Globally excluded filenames
Yuto Takano8e9a2192021-08-09 14:48:53 +0100200 self.excluded_files = ["**/bn_mul", "**/compat-2.x.h"]
Yuto Takano977e07f2021-08-09 11:56:15 +0100201
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100202 @staticmethod
203 def check_repo_path():
204 """
205 Check that the current working directory is the project root, and throw
206 an exception if not.
207 """
208 if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
209 raise Exception("This script must be run from Mbed TLS root")
210
Yuto Takano55c6c872021-08-09 15:35:19 +0100211 def comprehensive_parse(self):
Yuto Takano39639672021-08-05 19:47:48 +0100212 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100213 Comprehensive ("default") function to call each parsing function and
214 retrieve various elements of the code, together with the source location.
Darryl Greend5802922018-05-08 15:30:59 +0100215
Yuto Takano55c6c872021-08-09 15:35:19 +0100216 Returns a dict of parsed item key to the corresponding List of Matches.
Yuto Takano81528c02021-08-06 16:22:06 +0100217 """
218 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100219 self.log.debug(
Yuto Takano50953432021-08-09 14:54:36 +0100220 "The following files are excluded from the search: {}"
Yuto Takanod24e0372021-08-06 16:42:33 +0100221 .format(str(self.excluded_files))
222 )
Yuto Takano81528c02021-08-06 16:22:06 +0100223
Yuto Takano8e9a2192021-08-09 14:48:53 +0100224 all_macros = self.parse_macros([
225 "include/mbedtls/*.h",
226 "include/psa/*.h",
227 "library/*.h",
228 "tests/include/test/drivers/*.h",
Yuto Takanod70d4462021-08-09 12:45:51 +0100229 "3rdparty/everest/include/everest/everest.h",
230 "3rdparty/everest/include/everest/x25519.h"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100231 ])
232 enum_consts = self.parse_enum_consts([
233 "include/mbedtls/*.h",
234 "library/*.h",
235 "3rdparty/everest/include/everest/everest.h",
236 "3rdparty/everest/include/everest/x25519.h"
237 ])
238 identifiers = self.parse_identifiers([
239 "include/mbedtls/*.h",
240 "include/psa/*.h",
241 "library/*.h",
242 "3rdparty/everest/include/everest/everest.h",
243 "3rdparty/everest/include/everest/x25519.h"
244 ])
245 mbed_words = self.parse_mbed_words([
246 "include/mbedtls/*.h",
247 "include/psa/*.h",
248 "library/*.h",
249 "3rdparty/everest/include/everest/everest.h",
250 "3rdparty/everest/include/everest/x25519.h",
251 "library/*.c",
Yuto Takano81528c02021-08-06 16:22:06 +0100252 "3rdparty/everest/library/everest.c",
Yuto Takanod70d4462021-08-09 12:45:51 +0100253 "3rdparty/everest/library/x25519.c"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100254 ])
Yuto Takano81528c02021-08-06 16:22:06 +0100255 symbols = self.parse_symbols()
256
257 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
258 identifiers_justname = [x.name for x in identifiers]
259 actual_macros = []
260 for macro in all_macros:
261 if macro.name not in identifiers_justname:
262 actual_macros.append(macro)
263
264 self.log.debug("Found:")
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100265 # Aligns the counts on the assumption that none exceeds 4 digits
266 self.log.debug(" {:4} Total Macros".format(len(all_macros)))
267 self.log.debug(" {:4} Non-identifier Macros".format(len(actual_macros)))
268 self.log.debug(" {:4} Enum Constants".format(len(enum_consts)))
269 self.log.debug(" {:4} Identifiers".format(len(identifiers)))
270 self.log.debug(" {:4} Exported Symbols".format(len(symbols)))
Yuto Takano55c6c872021-08-09 15:35:19 +0100271 return {
Yuto Takano81528c02021-08-06 16:22:06 +0100272 "macros": actual_macros,
273 "enum_consts": enum_consts,
274 "identifiers": identifiers,
275 "symbols": symbols,
Yuto Takanod93fa372021-08-06 23:05:55 +0100276 "mbed_words": mbed_words
Yuto Takano81528c02021-08-06 16:22:06 +0100277 }
278
Yuto Takano55c6c872021-08-09 15:35:19 +0100279 def get_files(self, include_wildcards, exclude_wildcards):
280 """
281 Get all files that match any of the UNIX-style wildcards. While the
282 check_names script is designed only for use on UNIX/macOS (due to nm),
283 this function alone would work fine on Windows even with forward slashes
284 in the wildcard.
285
286 Args:
287 * include_wildcards: a List of shell-style wildcards to match filepaths.
288 * exclude_wildcards: a List of shell-style wildcards to exclude.
289
290 Returns a List of relative filepaths.
291 """
292 accumulator = set()
293
294 # exclude_wildcards may be None. Also, consider the global exclusions.
295 exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
296
Yuto Takano6adb2872021-08-16 11:38:34 +0100297 # Internal function to hit the memoisation cache or add to it the result
298 # of a glob operation. Used both for inclusion and exclusion since the
299 # only difference between them is whether they perform set union or
300 # difference on the return value of this function.
301 def hit_cache(wildcard):
302 if wildcard not in self.files:
303 self.files[wildcard] = set(glob.glob(wildcard, recursive=True))
304 return self.files[wildcard]
305
Yuto Takano55c6c872021-08-09 15:35:19 +0100306 for include_wildcard in include_wildcards:
Yuto Takano6adb2872021-08-16 11:38:34 +0100307 accumulator = accumulator.union(hit_cache(include_wildcard))
Yuto Takano55c6c872021-08-09 15:35:19 +0100308
Yuto Takano55c6c872021-08-09 15:35:19 +0100309 for exclude_wildcard in exclude_wildcards:
Yuto Takano6adb2872021-08-16 11:38:34 +0100310 accumulator = accumulator.difference(hit_cache(exclude_wildcard))
Yuto Takano55c6c872021-08-09 15:35:19 +0100311
312 return list(accumulator)
313
Yuto Takano8e9a2192021-08-09 14:48:53 +0100314 def parse_macros(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100315 """
316 Parse all macros defined by #define preprocessor directives.
317
318 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100319 * include: A List of glob expressions to look for files through.
320 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100321
322 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100323 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100324 macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
325 exclusions = (
Yuto Takano39639672021-08-05 19:47:48 +0100326 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
327 )
328
Yuto Takano50953432021-08-09 14:54:36 +0100329 files = self.get_files(include, exclude)
330 self.log.debug("Looking for macros in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100331
Yuto Takano50953432021-08-09 14:54:36 +0100332 macros = []
333 for header_file in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100334 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100335 for line_no, line in enumerate(header):
Yuto Takanod93fa372021-08-06 23:05:55 +0100336 for macro in macro_regex.finditer(line):
Yuto Takanod70d4462021-08-09 12:45:51 +0100337 if macro.group("macro").startswith(exclusions):
338 continue
339
340 macros.append(Match(
341 header_file,
342 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100343 line_no,
344 macro.span("macro"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100345 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100346
Yuto Takano39639672021-08-05 19:47:48 +0100347 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100348
Yuto Takano8e9a2192021-08-09 14:48:53 +0100349 def parse_mbed_words(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100350 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100351 Parse all words in the file that begin with MBED, in and out of macros,
352 comments, anything.
Yuto Takano39639672021-08-05 19:47:48 +0100353
354 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100355 * include: A List of glob expressions to look for files through.
356 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100357
358 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100359 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100360 # Typos of TLS are common, hence the broader check below than MBEDTLS.
Yuto Takanod93fa372021-08-06 23:05:55 +0100361 mbed_regex = re.compile(r"\bMBED.+?_[A-Z0-9_]*")
362 exclusions = re.compile(r"// *no-check-names|#error")
363
Yuto Takano50953432021-08-09 14:54:36 +0100364 files = self.get_files(include, exclude)
365 self.log.debug("Looking for MBED words in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100366
Yuto Takano50953432021-08-09 14:54:36 +0100367 mbed_words = []
368 for filename in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100369 with open(filename, "r", encoding="utf-8") as fp:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100370 for line_no, line in enumerate(fp):
Yuto Takanod93fa372021-08-06 23:05:55 +0100371 if exclusions.search(line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100372 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100373
Yuto Takanod93fa372021-08-06 23:05:55 +0100374 for name in mbed_regex.finditer(line):
375 mbed_words.append(Match(
Yuto Takano39639672021-08-05 19:47:48 +0100376 filename,
377 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100378 line_no,
379 name.span(0),
380 name.group(0)))
Yuto Takano39639672021-08-05 19:47:48 +0100381
Yuto Takanod93fa372021-08-06 23:05:55 +0100382 return mbed_words
Yuto Takano39639672021-08-05 19:47:48 +0100383
Yuto Takano8e9a2192021-08-09 14:48:53 +0100384 def parse_enum_consts(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100385 """
386 Parse all enum value constants that are declared.
387
388 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100389 * include: A List of glob expressions to look for files through.
390 * exclude: A List of glob expressions for excluding files.
Yuto Takano39639672021-08-05 19:47:48 +0100391
Yuto Takano81528c02021-08-06 16:22:06 +0100392 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100393 """
Yuto Takano50953432021-08-09 14:54:36 +0100394 files = self.get_files(include, exclude)
395 self.log.debug("Looking for enum consts in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100396
Yuto Takanob1417b42021-08-17 10:30:20 +0100397 # Emulate a finite state machine to parse enum declarations.
398 # OUTSIDE_KEYWORD = outside the enum keyword
399 # IN_BRACES = inside enum opening braces
400 # IN_BETWEEN = between enum keyword and opening braces
401 states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
Yuto Takano50953432021-08-09 14:54:36 +0100402 enum_consts = []
403 for header_file in files:
Yuto Takanob1417b42021-08-17 10:30:20 +0100404 state = states.OUTSIDE_KEYWORD
Yuto Takanoa083d152021-08-07 00:25:59 +0100405 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100406 for line_no, line in enumerate(header):
Yuto Takano13ecd992021-08-06 16:56:52 +0100407 # Match typedefs and brackets only when they are at the
408 # beginning of the line -- if they are indented, they might
409 # be sub-structures within structs, etc.
Yuto Takanob1417b42021-08-17 10:30:20 +0100410 if (state == states.OUTSIDE_KEYWORD and
Yuto Takano35906912021-08-17 11:05:43 +0100411 re.search(r"^(typedef +)?enum +{", line)):
Yuto Takanob1417b42021-08-17 10:30:20 +0100412 state = states.IN_BRACES
413 elif (state == states.OUTSIDE_KEYWORD and
414 re.search(r"^(typedef +)?enum", line)):
415 state = states.IN_BETWEEN
416 elif (state == states.IN_BETWEEN and
417 re.search(r"^{", line)):
418 state = states.IN_BRACES
419 elif (state == states.IN_BRACES and
420 re.search(r"^}", line)):
421 state = states.OUTSIDE_KEYWORD
422 elif (state == states.IN_BRACES and
423 not re.search(r"^ *#", line)):
Yuto Takano90bc0262021-08-16 11:34:10 +0100424 enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
Yuto Takanod70d4462021-08-09 12:45:51 +0100425 if not enum_const:
426 continue
427
428 enum_consts.append(Match(
429 header_file,
430 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100431 line_no,
432 enum_const.span("enum_const"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100433 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100434
Yuto Takano39639672021-08-05 19:47:48 +0100435 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100436
Yuto Takano8e9a2192021-08-09 14:48:53 +0100437 def parse_identifiers(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100438 """
Yuto Takano8246eb82021-08-16 10:37:24 +0100439 Parse all lines of a header where a function/enum/struct/union/typedef
Yuto Takanob1417b42021-08-17 10:30:20 +0100440 identifier is declared, based on some regex and heuristics. Highly
441 dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100442
Yuto Takano39639672021-08-05 19:47:48 +0100443 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100444 * include: A List of glob expressions to look for files through.
445 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100446
447 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100448 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100449 identifier_regex = re.compile(
450 # Match " something(a" or " *something(a". Functions.
451 # Assumptions:
452 # - function definition from return type to one of its arguments is
Yuto Takano55c6c872021-08-09 15:35:19 +0100453 # all on one line
Yuto Takanod93fa372021-08-06 23:05:55 +0100454 # - function definition line only contains alphanumeric, asterisk,
455 # underscore, and open bracket
456 r".* \**(\w+) *\( *\w|"
Yuto Takano55c6c872021-08-09 15:35:19 +0100457 # Match "(*something)(".
Yuto Takanod93fa372021-08-06 23:05:55 +0100458 r".*\( *\* *(\w+) *\) *\(|"
459 # Match names of named data structures.
460 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
461 # Match names of typedef instances, after closing bracket.
Yuto Takanod70d4462021-08-09 12:45:51 +0100462 r"}? *(\w+)[;[].*"
463 )
Yuto Takano35906912021-08-17 11:05:43 +0100464 # The regex below is indented for clarity.
Yuto Takanod70d4462021-08-09 12:45:51 +0100465 exclusion_lines = re.compile(
466 r"^("
Yuto Takano35906912021-08-17 11:05:43 +0100467 r"extern +\"C\"|" # pylint: disable=bad-continuation
Yuto Takano90bc0262021-08-16 11:34:10 +0100468 r"(typedef +)?(struct|union|enum)( *{)?$|"
469 r"} *;?$|"
470 r"$|"
471 r"//|"
472 r"#"
Yuto Takanod70d4462021-08-09 12:45:51 +0100473 r")"
474 )
Yuto Takanod93fa372021-08-06 23:05:55 +0100475
Yuto Takano50953432021-08-09 14:54:36 +0100476 files = self.get_files(include, exclude)
477 self.log.debug("Looking for identifiers in {} files".format(len(files)))
478
479 identifiers = []
480 for header_file in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100481 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100482 in_block_comment = False
Yuto Takano55c6c872021-08-09 15:35:19 +0100483 # The previous line variable is used for concatenating lines
Yuto Takanob1417b42021-08-17 10:30:20 +0100484 # when identifiers are formatted and spread across multiple
485 # lines.
Yuto Takanod93fa372021-08-06 23:05:55 +0100486 previous_line = ""
Darryl Greend5802922018-05-08 15:30:59 +0100487
Yuto Takano8f457cf2021-08-06 17:54:58 +0100488 for line_no, line in enumerate(header):
Yuto Takano81528c02021-08-06 16:22:06 +0100489 # Skip parsing this line if a block comment ends on it,
490 # but don't skip if it has just started -- there is a chance
491 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100492 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100493 in_block_comment = not in_block_comment
494 if re.search(r"\*/", line):
495 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100496 continue
497
Yuto Takano81528c02021-08-06 16:22:06 +0100498 if in_block_comment:
Yuto Takanod93fa372021-08-06 23:05:55 +0100499 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100500 continue
501
Yuto Takano90bc0262021-08-16 11:34:10 +0100502 if exclusion_lines.search(line):
Yuto Takanod93fa372021-08-06 23:05:55 +0100503 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100504 continue
505
Yuto Takanocfc9e4a2021-08-06 20:02:32 +0100506 # If the line contains only space-separated alphanumeric
507 # characters (or underscore, asterisk, or, open bracket),
508 # and nothing else, high chance it's a declaration that
509 # continues on the next line
Yuto Takano90bc0262021-08-16 11:34:10 +0100510 if re.search(r"^([\w\*\(]+\s+)+$", line):
Yuto Takanod93fa372021-08-06 23:05:55 +0100511 previous_line += line
Yuto Takano81528c02021-08-06 16:22:06 +0100512 continue
513
514 # If previous line seemed to start an unfinished declaration
Yuto Takanocfc9e4a2021-08-06 20:02:32 +0100515 # (as above), concat and treat them as one.
516 if previous_line:
Yuto Takano90bc0262021-08-16 11:34:10 +0100517 line = previous_line.strip() + " " + line.strip() + "\n"
Yuto Takanod93fa372021-08-06 23:05:55 +0100518 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100519
Yuto Takano8246eb82021-08-16 10:37:24 +0100520 # Skip parsing if line has a space in front = heuristic to
Yuto Takano81528c02021-08-06 16:22:06 +0100521 # skip function argument lines (highly subject to formatting
522 # changes)
523 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100524 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100525
Yuto Takanod93fa372021-08-06 23:05:55 +0100526 identifier = identifier_regex.search(line)
Yuto Takano39639672021-08-05 19:47:48 +0100527
Yuto Takanod70d4462021-08-09 12:45:51 +0100528 if not identifier:
529 continue
530
531 # Find the group that matched, and append it
532 for group in identifier.groups():
533 if not group:
534 continue
535
536 identifiers.append(Match(
537 header_file,
538 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100539 line_no,
540 identifier.span(),
Yuto Takanod70d4462021-08-09 12:45:51 +0100541 group))
Yuto Takano39639672021-08-05 19:47:48 +0100542
543 return identifiers
544
545 def parse_symbols(self):
546 """
547 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
548 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100549 Exceptions thrown here are rethrown because they would be critical
550 errors that void several tests, and thus needs to halt the program. This
551 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100552
Yuto Takano81528c02021-08-06 16:22:06 +0100553 Returns a List of unique symbols defined and used in the libraries.
554 """
555 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100556 symbols = []
557
558 # Back up the config and atomically compile with the full configratuion.
Yuto Takanod70d4462021-08-09 12:45:51 +0100559 shutil.copy(
560 "include/mbedtls/mbedtls_config.h",
561 "include/mbedtls/mbedtls_config.h.bak"
562 )
Darryl Greend5802922018-05-08 15:30:59 +0100563 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100564 # Use check=True in all subprocess calls so that failures are raised
565 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100566 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100567 ["python3", "scripts/config.py", "full"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100568 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100569 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100570 )
571 my_environment = os.environ.copy()
572 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100573 # Run make clean separately to lib to prevent unwanted behavior when
574 # make is invoked with parallelism.
Yuto Takano39639672021-08-05 19:47:48 +0100575 subprocess.run(
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100576 ["make", "clean"],
577 universal_newlines=True,
578 check=True
579 )
580 subprocess.run(
581 ["make", "lib"],
Darryl Greend5802922018-05-08 15:30:59 +0100582 env=my_environment,
Yuto Takanobcc3d992021-08-06 23:14:58 +0100583 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100584 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100585 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100586 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100587 )
Yuto Takano39639672021-08-05 19:47:48 +0100588
589 # Perform object file analysis using nm
Yuto Takanod70d4462021-08-09 12:45:51 +0100590 symbols = self.parse_symbols_from_nm([
591 "library/libmbedcrypto.a",
592 "library/libmbedtls.a",
593 "library/libmbedx509.a"
594 ])
Yuto Takano39639672021-08-05 19:47:48 +0100595
596 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100597 ["make", "clean"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100598 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100599 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100600 )
601 except subprocess.CalledProcessError as error:
Yuto Takano25eeb7b2021-08-06 21:27:59 +0100602 self.log.debug(error.output)
Yuto Takano81528c02021-08-06 16:22:06 +0100603 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100604 finally:
Yuto Takano6fececf2021-08-07 17:28:23 +0100605 # Put back the original config regardless of there being errors.
606 # Works also for keyboard interrupts.
Yuto Takanod70d4462021-08-09 12:45:51 +0100607 shutil.move(
608 "include/mbedtls/mbedtls_config.h.bak",
609 "include/mbedtls/mbedtls_config.h"
610 )
Yuto Takano39639672021-08-05 19:47:48 +0100611
612 return symbols
613
614 def parse_symbols_from_nm(self, object_files):
615 """
616 Run nm to retrieve the list of referenced symbols in each object file.
617 Does not return the position data since it is of no use.
618
Yuto Takano81528c02021-08-06 16:22:06 +0100619 Args:
Yuto Takano55c6c872021-08-09 15:35:19 +0100620 * object_files: a List of compiled object filepaths to search through.
Yuto Takano81528c02021-08-06 16:22:06 +0100621
622 Returns a List of unique symbols defined and used in any of the object
623 files.
Yuto Takano39639672021-08-05 19:47:48 +0100624 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100625 nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
626 nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100627 exclusions = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100628
629 symbols = []
630
Yuto Takano81528c02021-08-06 16:22:06 +0100631 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100632 nm_output = ""
633 for lib in object_files:
634 nm_output += subprocess.run(
635 ["nm", "-og", lib],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100636 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100637 stdout=subprocess.PIPE,
638 stderr=subprocess.STDOUT,
639 check=True
640 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100641
Yuto Takano39639672021-08-05 19:47:48 +0100642 for line in nm_output.splitlines():
Yuto Takano90bc0262021-08-16 11:34:10 +0100643 if not nm_undefined_regex.search(line):
644 symbol = nm_valid_regex.search(line)
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100645 if (symbol and not symbol.group("symbol").startswith(exclusions)):
Yuto Takanoe77f6992021-08-05 20:22:59 +0100646 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100647 else:
648 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100649
Yuto Takano39639672021-08-05 19:47:48 +0100650 return symbols
651
Yuto Takano55c6c872021-08-09 15:35:19 +0100652class NameChecker():
653 """
654 Representation of the core name checking operation performed by this script.
655 """
656 def __init__(self, parse_result, log):
657 self.parse_result = parse_result
658 self.log = log
659
Yuto Takano55614b52021-08-07 01:00:18 +0100660 def perform_checks(self, quiet=False):
Yuto Takano39639672021-08-05 19:47:48 +0100661 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100662 A comprehensive checker that performs each check in order, and outputs
663 a final verdict.
Yuto Takano81528c02021-08-06 16:22:06 +0100664
665 Args:
Yuto Takano55614b52021-08-07 01:00:18 +0100666 * quiet: whether to hide detailed problem explanation.
Yuto Takano39639672021-08-05 19:47:48 +0100667 """
Yuto Takano81528c02021-08-06 16:22:06 +0100668 self.log.info("=============")
Yuto Takano5473be22021-08-17 10:14:01 +0100669 Problem.quiet = quiet
Yuto Takano39639672021-08-05 19:47:48 +0100670 problems = 0
Yuto Takano5473be22021-08-17 10:14:01 +0100671 problems += self.check_symbols_declared_in_header()
Yuto Takano39639672021-08-05 19:47:48 +0100672
Yuto Takanod70d4462021-08-09 12:45:51 +0100673 pattern_checks = [
674 ("macros", MACRO_PATTERN),
675 ("enum_consts", CONSTANTS_PATTERN),
676 ("identifiers", IDENTIFIER_PATTERN)
677 ]
Yuto Takano39639672021-08-05 19:47:48 +0100678 for group, check_pattern in pattern_checks:
Yuto Takano5473be22021-08-17 10:14:01 +0100679 problems += self.check_match_pattern(group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100680
Yuto Takano5473be22021-08-17 10:14:01 +0100681 problems += self.check_for_typos()
Yuto Takano39639672021-08-05 19:47:48 +0100682
683 self.log.info("=============")
684 if problems > 0:
685 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano55614b52021-08-07 01:00:18 +0100686 if quiet:
687 self.log.info("Remove --quiet to see explanations.")
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100688 else:
689 self.log.info("Use --quiet for minimal output.")
Yuto Takano55c6c872021-08-09 15:35:19 +0100690 return 1
Yuto Takano39639672021-08-05 19:47:48 +0100691 else:
692 self.log.info("PASS")
Yuto Takano55c6c872021-08-09 15:35:19 +0100693 return 0
Darryl Greend5802922018-05-08 15:30:59 +0100694
Yuto Takano5473be22021-08-17 10:14:01 +0100695 def check_symbols_declared_in_header(self):
Yuto Takano39639672021-08-05 19:47:48 +0100696 """
697 Perform a check that all detected symbols in the library object files
698 are properly declared in headers.
Yuto Takano977e07f2021-08-09 11:56:15 +0100699 Assumes parse_names_in_source() was called before this.
Darryl Greend5802922018-05-08 15:30:59 +0100700
Yuto Takano81528c02021-08-06 16:22:06 +0100701 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100702 """
703 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100704
Yuto Takano39639672021-08-05 19:47:48 +0100705 for symbol in self.parse_result["symbols"]:
706 found_symbol_declared = False
707 for identifier_match in self.parse_result["identifiers"]:
708 if symbol == identifier_match.name:
709 found_symbol_declared = True
710 break
Yuto Takano81528c02021-08-06 16:22:06 +0100711
Yuto Takano39639672021-08-05 19:47:48 +0100712 if not found_symbol_declared:
Yuto Takanod70d4462021-08-09 12:45:51 +0100713 problems.append(SymbolNotInHeader(symbol))
Yuto Takano39639672021-08-05 19:47:48 +0100714
Yuto Takano5473be22021-08-17 10:14:01 +0100715 self.output_check_result("All symbols in header", problems)
Yuto Takano39639672021-08-05 19:47:48 +0100716 return len(problems)
717
Yuto Takano5473be22021-08-17 10:14:01 +0100718 def check_match_pattern(self, group_to_check, check_pattern):
Yuto Takano81528c02021-08-06 16:22:06 +0100719 """
720 Perform a check that all items of a group conform to a regex pattern.
Yuto Takano977e07f2021-08-09 11:56:15 +0100721 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100722
723 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100724 * group_to_check: string key to index into self.parse_result.
725 * check_pattern: the regex to check against.
726
727 Returns the number of problems that need fixing.
728 """
Yuto Takano39639672021-08-05 19:47:48 +0100729 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100730
Yuto Takano39639672021-08-05 19:47:48 +0100731 for item_match in self.parse_result[group_to_check]:
Yuto Takano90bc0262021-08-16 11:34:10 +0100732 if not re.search(check_pattern, item_match.name):
Yuto Takano39639672021-08-05 19:47:48 +0100733 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano90bc0262021-08-16 11:34:10 +0100734 # Double underscore should not be used for names
735 if re.search(r".*__.*", item_match.name):
Yuto Takano704b0f72021-08-17 10:41:23 +0100736 problems.append(
737 PatternMismatch("no double underscore allowed", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100738
739 self.output_check_result(
740 "Naming patterns of {}".format(group_to_check),
Yuto Takano55614b52021-08-07 01:00:18 +0100741 problems)
Yuto Takano39639672021-08-05 19:47:48 +0100742 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100743
Yuto Takano5473be22021-08-17 10:14:01 +0100744 def check_for_typos(self):
Yuto Takano81528c02021-08-06 16:22:06 +0100745 """
746 Perform a check that all words in the soure code beginning with MBED are
747 either defined as macros, or as enum constants.
Yuto Takano977e07f2021-08-09 11:56:15 +0100748 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100749
Yuto Takano81528c02021-08-06 16:22:06 +0100750 Returns the number of problems that need fixing.
751 """
Yuto Takano39639672021-08-05 19:47:48 +0100752 problems = []
Yuto Takano39639672021-08-05 19:47:48 +0100753
Yuto Takanod70d4462021-08-09 12:45:51 +0100754 # Set comprehension, equivalent to a list comprehension wrapped by set()
Yuto Takanod93fa372021-08-06 23:05:55 +0100755 all_caps_names = {
756 match.name
757 for match
758 in self.parse_result["macros"] + self.parse_result["enum_consts"]}
759 typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$")
Yuto Takano39639672021-08-05 19:47:48 +0100760
Yuto Takanod93fa372021-08-06 23:05:55 +0100761 for name_match in self.parse_result["mbed_words"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100762 found = name_match.name in all_caps_names
763
764 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
765 # PSA driver, they will not exist as macros. However, they
766 # should still be checked for typos using the equivalent
767 # BUILTINs that exist.
768 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
769 found = name_match.name.replace(
770 "MBEDTLS_PSA_ACCEL_",
771 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
772
Yuto Takanod93fa372021-08-06 23:05:55 +0100773 if not found and not typo_exclusion.search(name_match.name):
Yuto Takanod70d4462021-08-09 12:45:51 +0100774 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100775
Yuto Takano5473be22021-08-17 10:14:01 +0100776 self.output_check_result("Likely typos", problems)
Yuto Takano81528c02021-08-06 16:22:06 +0100777 return len(problems)
778
Yuto Takano5473be22021-08-17 10:14:01 +0100779 def output_check_result(self, name, problems):
Yuto Takano81528c02021-08-06 16:22:06 +0100780 """
781 Write out the PASS/FAIL status of a performed check depending on whether
782 there were problems.
Yuto Takanod70d4462021-08-09 12:45:51 +0100783
784 Args:
Yuto Takanod70d4462021-08-09 12:45:51 +0100785 * name: the name of the test
786 * problems: a List of encountered Problems
Yuto Takano81528c02021-08-06 16:22:06 +0100787 """
Yuto Takano39639672021-08-05 19:47:48 +0100788 if problems:
Yuto Takano55614b52021-08-07 01:00:18 +0100789 self.log.info("{}: FAIL\n".format(name))
790 for problem in problems:
791 self.log.warning(str(problem))
Darryl Greend5802922018-05-08 15:30:59 +0100792 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100793 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100794
Yuto Takano39639672021-08-05 19:47:48 +0100795def main():
796 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100797 Perform argument parsing, and create an instance of CodeParser and
798 NameChecker to begin the core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100799 """
Yuto Takanof005c332021-08-09 13:56:36 +0100800 parser = argparse.ArgumentParser(
Yuto Takano39639672021-08-05 19:47:48 +0100801 formatter_class=argparse.RawDescriptionHelpFormatter,
802 description=(
803 "This script confirms that the naming of all symbols and identifiers "
804 "in Mbed TLS are consistent with the house style and are also "
805 "self-consistent.\n\n"
Yuto Takanof005c332021-08-09 13:56:36 +0100806 "Expected to be run from the MbedTLS root directory.")
807 )
808 parser.add_argument(
809 "-v", "--verbose",
810 action="store_true",
811 help="show parse results"
812 )
813 parser.add_argument(
814 "-q", "--quiet",
815 action="store_true",
816 help="hide unnecessary text, explanations, and highlighs"
817 )
Darryl Greend5802922018-05-08 15:30:59 +0100818
Yuto Takanof005c332021-08-09 13:56:36 +0100819 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100820
Yuto Takano55c6c872021-08-09 15:35:19 +0100821 # Configure the global logger, which is then passed to the classes below
822 log = logging.getLogger()
823 log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
824 log.addHandler(logging.StreamHandler())
825
Darryl Greend5802922018-05-08 15:30:59 +0100826 try:
Yuto Takano55c6c872021-08-09 15:35:19 +0100827 code_parser = CodeParser(log)
828 parse_result = code_parser.comprehensive_parse()
Yuto Takanod93fa372021-08-06 23:05:55 +0100829 except Exception: # pylint: disable=broad-except
Darryl Greend5802922018-05-08 15:30:59 +0100830 traceback.print_exc()
831 sys.exit(2)
832
Yuto Takano55c6c872021-08-09 15:35:19 +0100833 name_checker = NameChecker(parse_result, log)
834 return_code = name_checker.perform_checks(quiet=args.quiet)
835
836 sys.exit(return_code)
837
Darryl Greend5802922018-05-08 15:30:59 +0100838if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100839 main()