blob: 1d87f26b03f53d48b5ed16778fd4c5fcea070f65 [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
Gilles Peskine89458d12021-09-27 19:20:17 +020047import fnmatch
Yuto Takano977e07f2021-08-09 11:56:15 +010048import glob
Yuto Takano39639672021-08-05 19:47:48 +010049import textwrap
Darryl Greend5802922018-05-08 15:30:59 +010050import os
51import sys
52import traceback
53import re
Yuto Takanob1417b42021-08-17 10:30:20 +010054import enum
Darryl Greend5802922018-05-08 15:30:59 +010055import shutil
56import subprocess
57import logging
58
Yuto Takano81528c02021-08-06 16:22:06 +010059# Naming patterns to check against. These are defined outside the NameCheck
60# class for ease of modification.
Janos Follath99387192022-08-10 11:11:34 +010061PUBLIC_MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
62INTERNAL_MACRO_PATTERN = r"^[0-9A-Za-z_]*[0-9A-Z]$"
63CONSTANTS_PATTERN = PUBLIC_MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010064IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010065
Yuto Takanod93fa372021-08-06 23:05:55 +010066class Match(): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010067 """
68 A class representing a match, together with its found position.
69
70 Fields:
71 * filename: the file that the match was in.
72 * line: the full line containing the match.
Yuto Takano704b0f72021-08-17 10:41:23 +010073 * line_no: the line number.
74 * pos: a tuple of (start, end) positions on the line where the match is.
Yuto Takano81528c02021-08-06 16:22:06 +010075 * name: the match itself.
76 """
Yuto Takano704b0f72021-08-17 10:41:23 +010077 def __init__(self, filename, line, line_no, pos, name):
78 # pylint: disable=too-many-arguments
Yuto Takano39639672021-08-05 19:47:48 +010079 self.filename = filename
80 self.line = line
Yuto Takano704b0f72021-08-17 10:41:23 +010081 self.line_no = line_no
Yuto Takano39639672021-08-05 19:47:48 +010082 self.pos = pos
83 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010084
Yuto Takanoa4e75122021-08-06 17:23:28 +010085 def __str__(self):
Yuto Takanofb86ac72021-08-16 10:32:40 +010086 """
87 Return a formatted code listing representation of the erroneous line.
88 """
Yuto Takano704b0f72021-08-17 10:41:23 +010089 gutter = format(self.line_no, "4d")
90 underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
Yuto Takano381fda82021-08-06 23:37:20 +010091
Yuto Takanoa4e75122021-08-06 17:23:28 +010092 return (
Yuto Takanofb86ac72021-08-16 10:32:40 +010093 " {0} |\n".format(" " * len(gutter)) +
Yuto Takano381fda82021-08-06 23:37:20 +010094 " {0} | {1}".format(gutter, self.line) +
Yuto Takanofb86ac72021-08-16 10:32:40 +010095 " {0} | {1}\n".format(" " * len(gutter), underline)
Yuto Takanoa4e75122021-08-06 17:23:28 +010096 )
Yuto Takanod93fa372021-08-06 23:05:55 +010097
Yuto Takanofc1e9ff2021-08-23 13:54:56 +010098class Problem(abc.ABC): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010099 """
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100100 An abstract parent class representing a form of static analysis error.
101 It extends an Abstract Base Class, which means it is not instantiable, and
102 it also mandates certain abstract methods to be implemented in subclasses.
Yuto Takano81528c02021-08-06 16:22:06 +0100103 """
Yuto Takano5473be22021-08-17 10:14:01 +0100104 # Class variable to control the quietness of all problems
105 quiet = False
Yuto Takano39639672021-08-05 19:47:48 +0100106 def __init__(self):
107 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +0100108 self.textwrapper.width = 80
Yuto Takanoa4e75122021-08-06 17:23:28 +0100109 self.textwrapper.initial_indent = " > "
Yuto Takano81528c02021-08-06 16:22:06 +0100110 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +0100111
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100112 def __str__(self):
113 """
114 Unified string representation method for all Problems.
115 """
116 if self.__class__.quiet:
117 return self.quiet_output()
118 return self.verbose_output()
119
120 @abc.abstractmethod
121 def quiet_output(self):
122 """
123 The output when --quiet is enabled.
124 """
125 pass
126
127 @abc.abstractmethod
128 def verbose_output(self):
129 """
130 The default output with explanation and code snippet if appropriate.
131 """
132 pass
133
Yuto Takanod93fa372021-08-06 23:05:55 +0100134class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100135 """
136 A problem that occurs when an exported/available symbol in the object file
137 is not explicitly declared in header files. Created with
138 NameCheck.check_symbols_declared_in_header()
139
140 Fields:
141 * symbol_name: the name of the symbol.
142 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100143 def __init__(self, symbol_name):
Yuto Takano39639672021-08-05 19:47:48 +0100144 self.symbol_name = symbol_name
145 Problem.__init__(self)
146
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100147 def quiet_output(self):
148 return "{0}".format(self.symbol_name)
Yuto Takano55614b52021-08-07 01:00:18 +0100149
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100150 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100151 return self.textwrapper.fill(
152 "'{0}' was found as an available symbol in the output of nm, "
153 "however it was not declared in any header files."
154 .format(self.symbol_name))
155
Yuto Takanod93fa372021-08-06 23:05:55 +0100156class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100157 """
158 A problem that occurs when something doesn't match the expected pattern.
159 Created with NameCheck.check_match_pattern()
160
161 Fields:
162 * pattern: the expected regex pattern
163 * match: the Match object in question
164 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100165 def __init__(self, pattern, match):
Yuto Takano39639672021-08-05 19:47:48 +0100166 self.pattern = pattern
167 self.match = match
168 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100169
Yuto Takano55614b52021-08-07 01:00:18 +0100170
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100171 def quiet_output(self):
172 return (
173 "{0}:{1}:{2}"
174 .format(self.match.filename, self.match.line_no, self.match.name)
175 )
176
177 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100178 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100179 "{0}:{1}: '{2}' does not match the required pattern '{3}'."
180 .format(
181 self.match.filename,
Yuto Takano5f831712021-08-18 18:03:24 +0100182 self.match.line_no,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100183 self.match.name,
Yuto Takanod70d4462021-08-09 12:45:51 +0100184 self.pattern
185 )
186 ) + "\n" + str(self.match)
Yuto Takano39639672021-08-05 19:47:48 +0100187
Yuto Takanod93fa372021-08-06 23:05:55 +0100188class Typo(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100189 """
190 A problem that occurs when a word using MBED doesn't appear to be defined as
191 constants nor enum values. Created with NameCheck.check_for_typos()
192
193 Fields:
194 * match: the Match object of the MBED name in question.
195 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100196 def __init__(self, match):
Yuto Takano39639672021-08-05 19:47:48 +0100197 self.match = match
198 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100199
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100200 def quiet_output(self):
201 return (
202 "{0}:{1}:{2}"
203 .format(self.match.filename, self.match.line_no, self.match.name)
204 )
Yuto Takano55614b52021-08-07 01:00:18 +0100205
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100206 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100207 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100208 "{0}:{1}: '{2}' looks like a typo. It was not found in any "
209 "macros or any enums. If this is not a typo, put "
210 "//no-check-names after it."
Yuto Takano5f831712021-08-18 18:03:24 +0100211 .format(self.match.filename, self.match.line_no, self.match.name)
Yuto Takanod70d4462021-08-09 12:45:51 +0100212 ) + "\n" + str(self.match)
Darryl Greend5802922018-05-08 15:30:59 +0100213
Yuto Takano55c6c872021-08-09 15:35:19 +0100214class CodeParser():
Yuto Takano81528c02021-08-06 16:22:06 +0100215 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100216 Class for retrieving files and parsing the code. This can be used
217 independently of the checks that NameChecker performs, for example for
218 list_internal_identifiers.py.
Yuto Takano81528c02021-08-06 16:22:06 +0100219 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100220 def __init__(self, log):
221 self.log = log
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100222 self.check_repo_path()
Yuto Takano977e07f2021-08-09 11:56:15 +0100223
Yuto Takano8e9a2192021-08-09 14:48:53 +0100224 # Memo for storing "glob expression": set(filepaths)
225 self.files = {}
226
Gilles Peskine89458d12021-09-27 19:20:17 +0200227 # Globally excluded filenames.
228 # Note that "*" can match directory separators in exclude lists.
229 self.excluded_files = ["*/bn_mul", "*/compat-2.x.h"]
Yuto Takano977e07f2021-08-09 11:56:15 +0100230
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100231 @staticmethod
232 def check_repo_path():
233 """
234 Check that the current working directory is the project root, and throw
235 an exception if not.
236 """
237 if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
238 raise Exception("This script must be run from Mbed TLS root")
239
Yuto Takano55c6c872021-08-09 15:35:19 +0100240 def comprehensive_parse(self):
Yuto Takano39639672021-08-05 19:47:48 +0100241 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100242 Comprehensive ("default") function to call each parsing function and
243 retrieve various elements of the code, together with the source location.
Darryl Greend5802922018-05-08 15:30:59 +0100244
Yuto Takano55c6c872021-08-09 15:35:19 +0100245 Returns a dict of parsed item key to the corresponding List of Matches.
Yuto Takano81528c02021-08-06 16:22:06 +0100246 """
247 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100248 self.log.debug(
Yuto Takano50953432021-08-09 14:54:36 +0100249 "The following files are excluded from the search: {}"
Yuto Takanod24e0372021-08-06 16:42:33 +0100250 .format(str(self.excluded_files))
251 )
Yuto Takano81528c02021-08-06 16:22:06 +0100252
Janos Follath99387192022-08-10 11:11:34 +0100253 all_macros = {"public": [], "internal": []}
254 all_macros["public"] = self.parse_macros([
Yuto Takano8e9a2192021-08-09 14:48:53 +0100255 "include/mbedtls/*.h",
256 "include/psa/*.h",
Yuto Takanod70d4462021-08-09 12:45:51 +0100257 "3rdparty/everest/include/everest/everest.h",
258 "3rdparty/everest/include/everest/x25519.h"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100259 ])
Janos Follath99387192022-08-10 11:11:34 +0100260 all_macros["internal"] = self.parse_macros([
261 "library/*.h",
262 "tests/include/test/drivers/*.h",
263 ])
Yuto Takano8e9a2192021-08-09 14:48:53 +0100264 enum_consts = self.parse_enum_consts([
265 "include/mbedtls/*.h",
266 "library/*.h",
267 "3rdparty/everest/include/everest/everest.h",
268 "3rdparty/everest/include/everest/x25519.h"
269 ])
270 identifiers = self.parse_identifiers([
271 "include/mbedtls/*.h",
272 "include/psa/*.h",
273 "library/*.h",
274 "3rdparty/everest/include/everest/everest.h",
275 "3rdparty/everest/include/everest/x25519.h"
276 ])
277 mbed_words = self.parse_mbed_words([
278 "include/mbedtls/*.h",
279 "include/psa/*.h",
280 "library/*.h",
281 "3rdparty/everest/include/everest/everest.h",
282 "3rdparty/everest/include/everest/x25519.h",
283 "library/*.c",
Yuto Takano81528c02021-08-06 16:22:06 +0100284 "3rdparty/everest/library/everest.c",
Yuto Takanod70d4462021-08-09 12:45:51 +0100285 "3rdparty/everest/library/x25519.c"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100286 ])
Yuto Takano81528c02021-08-06 16:22:06 +0100287 symbols = self.parse_symbols()
288
289 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
290 identifiers_justname = [x.name for x in identifiers]
Janos Follath99387192022-08-10 11:11:34 +0100291 actual_macros = {"public": [], "internal": []}
292 for scope in actual_macros:
293 for macro in all_macros[scope]:
294 if macro.name not in identifiers_justname:
295 actual_macros[scope].append(macro)
Yuto Takano81528c02021-08-06 16:22:06 +0100296
297 self.log.debug("Found:")
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100298 # Aligns the counts on the assumption that none exceeds 4 digits
Janos Follath99387192022-08-10 11:11:34 +0100299 for scope in actual_macros:
300 self.log.debug(" {:4} Total {} Macros"
301 .format(len(all_macros[scope]), scope))
302 self.log.debug(" {:4} {} Non-identifier Macros"
303 .format(len(actual_macros[scope]), scope))
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100304 self.log.debug(" {:4} Enum Constants".format(len(enum_consts)))
305 self.log.debug(" {:4} Identifiers".format(len(identifiers)))
306 self.log.debug(" {:4} Exported Symbols".format(len(symbols)))
Yuto Takano55c6c872021-08-09 15:35:19 +0100307 return {
Janos Follath99387192022-08-10 11:11:34 +0100308 "public_macros": actual_macros["public"],
309 "internal_macros": actual_macros["internal"],
Yuto Takano81528c02021-08-06 16:22:06 +0100310 "enum_consts": enum_consts,
311 "identifiers": identifiers,
312 "symbols": symbols,
Yuto Takanod93fa372021-08-06 23:05:55 +0100313 "mbed_words": mbed_words
Yuto Takano81528c02021-08-06 16:22:06 +0100314 }
315
Gilles Peskine89458d12021-09-27 19:20:17 +0200316 def is_file_excluded(self, path, exclude_wildcards):
Gilles Peskine8a832242021-09-28 10:12:49 +0200317 """Whether the given file path is excluded."""
Gilles Peskine89458d12021-09-27 19:20:17 +0200318 # exclude_wildcards may be None. Also, consider the global exclusions.
319 exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
320 for pattern in exclude_wildcards:
321 if fnmatch.fnmatch(path, pattern):
322 return True
323 return False
324
Yuto Takano55c6c872021-08-09 15:35:19 +0100325 def get_files(self, include_wildcards, exclude_wildcards):
326 """
327 Get all files that match any of the UNIX-style wildcards. While the
328 check_names script is designed only for use on UNIX/macOS (due to nm),
329 this function alone would work fine on Windows even with forward slashes
330 in the wildcard.
331
332 Args:
333 * include_wildcards: a List of shell-style wildcards to match filepaths.
334 * exclude_wildcards: a List of shell-style wildcards to exclude.
335
336 Returns a List of relative filepaths.
337 """
338 accumulator = set()
339
Yuto Takano55c6c872021-08-09 15:35:19 +0100340 for include_wildcard in include_wildcards:
Gilles Peskine89458d12021-09-27 19:20:17 +0200341 accumulator = accumulator.union(glob.iglob(include_wildcard))
Yuto Takano55c6c872021-08-09 15:35:19 +0100342
Gilles Peskine89458d12021-09-27 19:20:17 +0200343 return list(path for path in accumulator
344 if not self.is_file_excluded(path, exclude_wildcards))
Yuto Takano55c6c872021-08-09 15:35:19 +0100345
Yuto Takano8e9a2192021-08-09 14:48:53 +0100346 def parse_macros(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100347 """
348 Parse all macros defined by #define preprocessor directives.
349
350 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100351 * include: A List of glob expressions to look for files through.
352 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100353
354 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100355 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100356 macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
357 exclusions = (
Yuto Takano39639672021-08-05 19:47:48 +0100358 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
359 )
360
Yuto Takano50953432021-08-09 14:54:36 +0100361 files = self.get_files(include, exclude)
362 self.log.debug("Looking for macros in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100363
Yuto Takano50953432021-08-09 14:54:36 +0100364 macros = []
365 for header_file in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100366 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100367 for line_no, line in enumerate(header):
Yuto Takanod93fa372021-08-06 23:05:55 +0100368 for macro in macro_regex.finditer(line):
Yuto Takanod70d4462021-08-09 12:45:51 +0100369 if macro.group("macro").startswith(exclusions):
370 continue
371
372 macros.append(Match(
373 header_file,
374 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100375 line_no,
376 macro.span("macro"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100377 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100378
Yuto Takano39639672021-08-05 19:47:48 +0100379 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100380
Yuto Takano8e9a2192021-08-09 14:48:53 +0100381 def parse_mbed_words(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100382 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100383 Parse all words in the file that begin with MBED, in and out of macros,
384 comments, anything.
Yuto Takano39639672021-08-05 19:47:48 +0100385
386 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100387 * include: A List of glob expressions to look for files through.
388 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100389
390 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100391 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100392 # Typos of TLS are common, hence the broader check below than MBEDTLS.
Yuto Takanod93fa372021-08-06 23:05:55 +0100393 mbed_regex = re.compile(r"\bMBED.+?_[A-Z0-9_]*")
394 exclusions = re.compile(r"// *no-check-names|#error")
395
Yuto Takano50953432021-08-09 14:54:36 +0100396 files = self.get_files(include, exclude)
397 self.log.debug("Looking for MBED words in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100398
Yuto Takano50953432021-08-09 14:54:36 +0100399 mbed_words = []
400 for filename in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100401 with open(filename, "r", encoding="utf-8") as fp:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100402 for line_no, line in enumerate(fp):
Yuto Takanod93fa372021-08-06 23:05:55 +0100403 if exclusions.search(line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100404 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100405
Yuto Takanod93fa372021-08-06 23:05:55 +0100406 for name in mbed_regex.finditer(line):
407 mbed_words.append(Match(
Yuto Takano39639672021-08-05 19:47:48 +0100408 filename,
409 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100410 line_no,
411 name.span(0),
412 name.group(0)))
Yuto Takano39639672021-08-05 19:47:48 +0100413
Yuto Takanod93fa372021-08-06 23:05:55 +0100414 return mbed_words
Yuto Takano39639672021-08-05 19:47:48 +0100415
Yuto Takano8e9a2192021-08-09 14:48:53 +0100416 def parse_enum_consts(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100417 """
418 Parse all enum value constants that are declared.
419
420 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100421 * include: A List of glob expressions to look for files through.
422 * exclude: A List of glob expressions for excluding files.
Yuto Takano39639672021-08-05 19:47:48 +0100423
Yuto Takano81528c02021-08-06 16:22:06 +0100424 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100425 """
Yuto Takano50953432021-08-09 14:54:36 +0100426 files = self.get_files(include, exclude)
427 self.log.debug("Looking for enum consts in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100428
Yuto Takanob1417b42021-08-17 10:30:20 +0100429 # Emulate a finite state machine to parse enum declarations.
430 # OUTSIDE_KEYWORD = outside the enum keyword
431 # IN_BRACES = inside enum opening braces
432 # IN_BETWEEN = between enum keyword and opening braces
433 states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
Yuto Takano50953432021-08-09 14:54:36 +0100434 enum_consts = []
435 for header_file in files:
Yuto Takanob1417b42021-08-17 10:30:20 +0100436 state = states.OUTSIDE_KEYWORD
Yuto Takanoa083d152021-08-07 00:25:59 +0100437 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100438 for line_no, line in enumerate(header):
Yuto Takano13ecd992021-08-06 16:56:52 +0100439 # Match typedefs and brackets only when they are at the
440 # beginning of the line -- if they are indented, they might
441 # be sub-structures within structs, etc.
Yuto Takanob1417b42021-08-17 10:30:20 +0100442 if (state == states.OUTSIDE_KEYWORD and
Yuto Takano35906912021-08-17 11:05:43 +0100443 re.search(r"^(typedef +)?enum +{", line)):
Yuto Takanob1417b42021-08-17 10:30:20 +0100444 state = states.IN_BRACES
445 elif (state == states.OUTSIDE_KEYWORD and
446 re.search(r"^(typedef +)?enum", line)):
447 state = states.IN_BETWEEN
448 elif (state == states.IN_BETWEEN and
449 re.search(r"^{", line)):
450 state = states.IN_BRACES
451 elif (state == states.IN_BRACES and
452 re.search(r"^}", line)):
453 state = states.OUTSIDE_KEYWORD
454 elif (state == states.IN_BRACES and
455 not re.search(r"^ *#", line)):
Yuto Takano90bc0262021-08-16 11:34:10 +0100456 enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
Yuto Takanod70d4462021-08-09 12:45:51 +0100457 if not enum_const:
458 continue
459
460 enum_consts.append(Match(
461 header_file,
462 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100463 line_no,
464 enum_const.span("enum_const"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100465 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100466
Yuto Takano39639672021-08-05 19:47:48 +0100467 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100468
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100469 IGNORED_CHUNK_REGEX = re.compile('|'.join([
470 r'/\*.*?\*/', # block comment entirely on one line
471 r'//.*', # line comment
472 r'(?P<string>")(?:[^\\\"]|\\.)*"', # string literal
473 ]))
474
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100475 def strip_comments_and_literals(self, line, in_block_comment):
476 """Strip comments and string literals from line.
477
478 Continuation lines are not supported.
479
480 If in_block_comment is true, assume that the line starts inside a
481 block comment.
482
483 Return updated values of (line, in_block_comment) where:
484 * Comments in line have been replaced by a space (or nothing at the
485 start or end of the line).
486 * String contents have been removed.
487 * in_block_comment indicates whether the line ends inside a block
488 comment that continues on the next line.
489 """
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100490
491 # Terminate current multiline comment?
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100492 if in_block_comment:
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100493 m = re.search(r"\*/", line)
494 if m:
495 in_block_comment = False
496 line = line[m.end(0):]
497 else:
498 return '', True
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100499
500 # Remove full comments and string literals.
501 # Do it all together to handle cases like "/*" correctly.
502 # Note that continuation lines are not supported.
503 line = re.sub(self.IGNORED_CHUNK_REGEX,
504 lambda s: '""' if s.group('string') else ' ',
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100505 line)
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100506
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100507 # Start an unfinished comment?
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100508 # (If `/*` was part of a complete comment, it's already been removed.)
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100509 m = re.search(r"/\*", line)
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100510 if m:
511 in_block_comment = True
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100512 line = line[:m.start(0)]
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100513
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100514 return line, in_block_comment
515
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100516 IDENTIFIER_REGEX = re.compile('|'.join([
Gilles Peskine152de232021-11-16 20:56:47 +0100517 # Match " something(a" or " *something(a". Functions.
518 # Assumptions:
519 # - function definition from return type to one of its arguments is
520 # all on one line
521 # - function definition line only contains alphanumeric, asterisk,
522 # underscore, and open bracket
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100523 r".* \**(\w+) *\( *\w",
Gilles Peskine152de232021-11-16 20:56:47 +0100524 # Match "(*something)(".
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100525 r".*\( *\* *(\w+) *\) *\(",
Gilles Peskine152de232021-11-16 20:56:47 +0100526 # Match names of named data structures.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100527 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$",
Gilles Peskine152de232021-11-16 20:56:47 +0100528 # Match names of typedef instances, after closing bracket.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100529 r"}? *(\w+)[;[].*",
530 ]))
Gilles Peskine152de232021-11-16 20:56:47 +0100531 # The regex below is indented for clarity.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100532 EXCLUSION_LINES = re.compile("|".join([
533 r"extern +\"C\"",
534 r"(typedef +)?(struct|union|enum)( *{)?$",
535 r"} *;?$",
536 r"$",
537 r"//",
538 r"#",
539 ]))
Gilles Peskine152de232021-11-16 20:56:47 +0100540
541 def parse_identifiers_in_file(self, header_file, identifiers):
542 """
543 Parse all lines of a header where a function/enum/struct/union/typedef
544 identifier is declared, based on some regex and heuristics. Highly
545 dependent on formatting style.
546
547 Append found matches to the list ``identifiers``.
548 """
549
550 with open(header_file, "r", encoding="utf-8") as header:
551 in_block_comment = False
552 # The previous line variable is used for concatenating lines
553 # when identifiers are formatted and spread across multiple
554 # lines.
555 previous_line = ""
556
557 for line_no, line in enumerate(header):
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100558 line, in_block_comment = \
559 self.strip_comments_and_literals(line, in_block_comment)
Gilles Peskine152de232021-11-16 20:56:47 +0100560
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100561 if self.EXCLUSION_LINES.match(line):
Gilles Peskine152de232021-11-16 20:56:47 +0100562 previous_line = ""
563 continue
564
565 # If the line contains only space-separated alphanumeric
Gilles Peskinebc1e8f62021-11-17 20:39:56 +0100566 # characters (or underscore, asterisk, or open parenthesis),
Gilles Peskine152de232021-11-16 20:56:47 +0100567 # and nothing else, high chance it's a declaration that
568 # continues on the next line
569 if re.search(r"^([\w\*\(]+\s+)+$", line):
570 previous_line += line
571 continue
572
573 # If previous line seemed to start an unfinished declaration
574 # (as above), concat and treat them as one.
575 if previous_line:
576 line = previous_line.strip() + " " + line.strip() + "\n"
577 previous_line = ""
578
579 # Skip parsing if line has a space in front = heuristic to
580 # skip function argument lines (highly subject to formatting
581 # changes)
582 if line[0] == " ":
583 continue
584
585 identifier = self.IDENTIFIER_REGEX.search(line)
586
587 if not identifier:
588 continue
589
590 # Find the group that matched, and append it
591 for group in identifier.groups():
592 if not group:
593 continue
594
595 identifiers.append(Match(
596 header_file,
597 line,
598 line_no,
599 identifier.span(),
600 group))
601
Yuto Takano8e9a2192021-08-09 14:48:53 +0100602 def parse_identifiers(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100603 """
Yuto Takano8246eb82021-08-16 10:37:24 +0100604 Parse all lines of a header where a function/enum/struct/union/typedef
Yuto Takanob1417b42021-08-17 10:30:20 +0100605 identifier is declared, based on some regex and heuristics. Highly
606 dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100607
Yuto Takano39639672021-08-05 19:47:48 +0100608 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100609 * include: A List of glob expressions to look for files through.
610 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100611
612 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100613 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100614
Yuto Takano50953432021-08-09 14:54:36 +0100615 files = self.get_files(include, exclude)
616 self.log.debug("Looking for identifiers in {} files".format(len(files)))
617
618 identifiers = []
619 for header_file in files:
Gilles Peskine152de232021-11-16 20:56:47 +0100620 self.parse_identifiers_in_file(header_file, identifiers)
Yuto Takano39639672021-08-05 19:47:48 +0100621
622 return identifiers
623
624 def parse_symbols(self):
625 """
626 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
627 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100628 Exceptions thrown here are rethrown because they would be critical
629 errors that void several tests, and thus needs to halt the program. This
630 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100631
Yuto Takano81528c02021-08-06 16:22:06 +0100632 Returns a List of unique symbols defined and used in the libraries.
633 """
634 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100635 symbols = []
636
637 # Back up the config and atomically compile with the full configratuion.
Yuto Takanod70d4462021-08-09 12:45:51 +0100638 shutil.copy(
639 "include/mbedtls/mbedtls_config.h",
640 "include/mbedtls/mbedtls_config.h.bak"
641 )
Darryl Greend5802922018-05-08 15:30:59 +0100642 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100643 # Use check=True in all subprocess calls so that failures are raised
644 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100645 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100646 ["python3", "scripts/config.py", "full"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100647 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100648 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100649 )
650 my_environment = os.environ.copy()
651 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100652 # Run make clean separately to lib to prevent unwanted behavior when
653 # make is invoked with parallelism.
Yuto Takano39639672021-08-05 19:47:48 +0100654 subprocess.run(
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100655 ["make", "clean"],
656 universal_newlines=True,
657 check=True
658 )
659 subprocess.run(
660 ["make", "lib"],
Darryl Greend5802922018-05-08 15:30:59 +0100661 env=my_environment,
Yuto Takanobcc3d992021-08-06 23:14:58 +0100662 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100663 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100664 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100665 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100666 )
Yuto Takano39639672021-08-05 19:47:48 +0100667
668 # Perform object file analysis using nm
Yuto Takanod70d4462021-08-09 12:45:51 +0100669 symbols = self.parse_symbols_from_nm([
670 "library/libmbedcrypto.a",
671 "library/libmbedtls.a",
672 "library/libmbedx509.a"
673 ])
Yuto Takano39639672021-08-05 19:47:48 +0100674
675 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100676 ["make", "clean"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100677 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100678 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100679 )
680 except subprocess.CalledProcessError as error:
Yuto Takano25eeb7b2021-08-06 21:27:59 +0100681 self.log.debug(error.output)
Yuto Takano81528c02021-08-06 16:22:06 +0100682 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100683 finally:
Yuto Takano6fececf2021-08-07 17:28:23 +0100684 # Put back the original config regardless of there being errors.
685 # Works also for keyboard interrupts.
Yuto Takanod70d4462021-08-09 12:45:51 +0100686 shutil.move(
687 "include/mbedtls/mbedtls_config.h.bak",
688 "include/mbedtls/mbedtls_config.h"
689 )
Yuto Takano39639672021-08-05 19:47:48 +0100690
691 return symbols
692
693 def parse_symbols_from_nm(self, object_files):
694 """
695 Run nm to retrieve the list of referenced symbols in each object file.
696 Does not return the position data since it is of no use.
697
Yuto Takano81528c02021-08-06 16:22:06 +0100698 Args:
Yuto Takano55c6c872021-08-09 15:35:19 +0100699 * object_files: a List of compiled object filepaths to search through.
Yuto Takano81528c02021-08-06 16:22:06 +0100700
701 Returns a List of unique symbols defined and used in any of the object
702 files.
Yuto Takano39639672021-08-05 19:47:48 +0100703 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100704 nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
705 nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100706 exclusions = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100707
708 symbols = []
709
Yuto Takano81528c02021-08-06 16:22:06 +0100710 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100711 nm_output = ""
712 for lib in object_files:
713 nm_output += subprocess.run(
714 ["nm", "-og", lib],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100715 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100716 stdout=subprocess.PIPE,
717 stderr=subprocess.STDOUT,
718 check=True
719 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100720
Yuto Takano39639672021-08-05 19:47:48 +0100721 for line in nm_output.splitlines():
Yuto Takano90bc0262021-08-16 11:34:10 +0100722 if not nm_undefined_regex.search(line):
723 symbol = nm_valid_regex.search(line)
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100724 if (symbol and not symbol.group("symbol").startswith(exclusions)):
Yuto Takanoe77f6992021-08-05 20:22:59 +0100725 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100726 else:
727 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100728
Yuto Takano39639672021-08-05 19:47:48 +0100729 return symbols
730
Yuto Takano55c6c872021-08-09 15:35:19 +0100731class NameChecker():
732 """
733 Representation of the core name checking operation performed by this script.
734 """
735 def __init__(self, parse_result, log):
736 self.parse_result = parse_result
737 self.log = log
738
Yuto Takano55614b52021-08-07 01:00:18 +0100739 def perform_checks(self, quiet=False):
Yuto Takano39639672021-08-05 19:47:48 +0100740 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100741 A comprehensive checker that performs each check in order, and outputs
742 a final verdict.
Yuto Takano81528c02021-08-06 16:22:06 +0100743
744 Args:
Yuto Takano55614b52021-08-07 01:00:18 +0100745 * quiet: whether to hide detailed problem explanation.
Yuto Takano39639672021-08-05 19:47:48 +0100746 """
Yuto Takano81528c02021-08-06 16:22:06 +0100747 self.log.info("=============")
Yuto Takano5473be22021-08-17 10:14:01 +0100748 Problem.quiet = quiet
Yuto Takano39639672021-08-05 19:47:48 +0100749 problems = 0
Yuto Takano5473be22021-08-17 10:14:01 +0100750 problems += self.check_symbols_declared_in_header()
Yuto Takano39639672021-08-05 19:47:48 +0100751
Yuto Takanod70d4462021-08-09 12:45:51 +0100752 pattern_checks = [
Janos Follath99387192022-08-10 11:11:34 +0100753 ("public_macros", PUBLIC_MACRO_PATTERN),
754 ("internal_macros", INTERNAL_MACRO_PATTERN),
Yuto Takanod70d4462021-08-09 12:45:51 +0100755 ("enum_consts", CONSTANTS_PATTERN),
756 ("identifiers", IDENTIFIER_PATTERN)
757 ]
Yuto Takano39639672021-08-05 19:47:48 +0100758 for group, check_pattern in pattern_checks:
Yuto Takano5473be22021-08-17 10:14:01 +0100759 problems += self.check_match_pattern(group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100760
Yuto Takano5473be22021-08-17 10:14:01 +0100761 problems += self.check_for_typos()
Yuto Takano39639672021-08-05 19:47:48 +0100762
763 self.log.info("=============")
764 if problems > 0:
765 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano55614b52021-08-07 01:00:18 +0100766 if quiet:
767 self.log.info("Remove --quiet to see explanations.")
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100768 else:
769 self.log.info("Use --quiet for minimal output.")
Yuto Takano55c6c872021-08-09 15:35:19 +0100770 return 1
Yuto Takano39639672021-08-05 19:47:48 +0100771 else:
772 self.log.info("PASS")
Yuto Takano55c6c872021-08-09 15:35:19 +0100773 return 0
Darryl Greend5802922018-05-08 15:30:59 +0100774
Yuto Takano5473be22021-08-17 10:14:01 +0100775 def check_symbols_declared_in_header(self):
Yuto Takano39639672021-08-05 19:47:48 +0100776 """
777 Perform a check that all detected symbols in the library object files
778 are properly declared in headers.
Yuto Takano977e07f2021-08-09 11:56:15 +0100779 Assumes parse_names_in_source() was called before this.
Darryl Greend5802922018-05-08 15:30:59 +0100780
Yuto Takano81528c02021-08-06 16:22:06 +0100781 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100782 """
783 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100784
Yuto Takano39639672021-08-05 19:47:48 +0100785 for symbol in self.parse_result["symbols"]:
786 found_symbol_declared = False
787 for identifier_match in self.parse_result["identifiers"]:
788 if symbol == identifier_match.name:
789 found_symbol_declared = True
790 break
Yuto Takano81528c02021-08-06 16:22:06 +0100791
Yuto Takano39639672021-08-05 19:47:48 +0100792 if not found_symbol_declared:
Yuto Takanod70d4462021-08-09 12:45:51 +0100793 problems.append(SymbolNotInHeader(symbol))
Yuto Takano39639672021-08-05 19:47:48 +0100794
Yuto Takano5473be22021-08-17 10:14:01 +0100795 self.output_check_result("All symbols in header", problems)
Yuto Takano39639672021-08-05 19:47:48 +0100796 return len(problems)
797
Yuto Takano5473be22021-08-17 10:14:01 +0100798 def check_match_pattern(self, group_to_check, check_pattern):
Yuto Takano81528c02021-08-06 16:22:06 +0100799 """
800 Perform a check that all items of a group conform to a regex pattern.
Yuto Takano977e07f2021-08-09 11:56:15 +0100801 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100802
803 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100804 * group_to_check: string key to index into self.parse_result.
805 * check_pattern: the regex to check against.
806
807 Returns the number of problems that need fixing.
808 """
Yuto Takano39639672021-08-05 19:47:48 +0100809 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100810
Yuto Takano39639672021-08-05 19:47:48 +0100811 for item_match in self.parse_result[group_to_check]:
Yuto Takano90bc0262021-08-16 11:34:10 +0100812 if not re.search(check_pattern, item_match.name):
Yuto Takano39639672021-08-05 19:47:48 +0100813 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano90bc0262021-08-16 11:34:10 +0100814 # Double underscore should not be used for names
815 if re.search(r".*__.*", item_match.name):
Yuto Takano704b0f72021-08-17 10:41:23 +0100816 problems.append(
817 PatternMismatch("no double underscore allowed", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100818
819 self.output_check_result(
820 "Naming patterns of {}".format(group_to_check),
Yuto Takano55614b52021-08-07 01:00:18 +0100821 problems)
Yuto Takano39639672021-08-05 19:47:48 +0100822 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100823
Yuto Takano5473be22021-08-17 10:14:01 +0100824 def check_for_typos(self):
Yuto Takano81528c02021-08-06 16:22:06 +0100825 """
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800826 Perform a check that all words in the source code beginning with MBED are
Yuto Takano81528c02021-08-06 16:22:06 +0100827 either defined as macros, or as enum constants.
Yuto Takano977e07f2021-08-09 11:56:15 +0100828 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100829
Yuto Takano81528c02021-08-06 16:22:06 +0100830 Returns the number of problems that need fixing.
831 """
Yuto Takano39639672021-08-05 19:47:48 +0100832 problems = []
Yuto Takano39639672021-08-05 19:47:48 +0100833
Yuto Takanod70d4462021-08-09 12:45:51 +0100834 # Set comprehension, equivalent to a list comprehension wrapped by set()
Yuto Takanod93fa372021-08-06 23:05:55 +0100835 all_caps_names = {
836 match.name
837 for match
Janos Follath99387192022-08-10 11:11:34 +0100838 in self.parse_result["public_macros"] +
839 self.parse_result["internal_macros"] +
840 self.parse_result["enum_consts"]
841 }
Ronald Cron7975fae2021-09-13 14:50:42 +0200842 typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$|"
843 r"MBEDTLS_TEST_LIBTESTDRIVER*")
Yuto Takano39639672021-08-05 19:47:48 +0100844
Yuto Takanod93fa372021-08-06 23:05:55 +0100845 for name_match in self.parse_result["mbed_words"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100846 found = name_match.name in all_caps_names
847
848 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
849 # PSA driver, they will not exist as macros. However, they
850 # should still be checked for typos using the equivalent
851 # BUILTINs that exist.
852 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
853 found = name_match.name.replace(
854 "MBEDTLS_PSA_ACCEL_",
855 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
856
Yuto Takanod93fa372021-08-06 23:05:55 +0100857 if not found and not typo_exclusion.search(name_match.name):
Yuto Takanod70d4462021-08-09 12:45:51 +0100858 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100859
Yuto Takano5473be22021-08-17 10:14:01 +0100860 self.output_check_result("Likely typos", problems)
Yuto Takano81528c02021-08-06 16:22:06 +0100861 return len(problems)
862
Yuto Takano5473be22021-08-17 10:14:01 +0100863 def output_check_result(self, name, problems):
Yuto Takano81528c02021-08-06 16:22:06 +0100864 """
865 Write out the PASS/FAIL status of a performed check depending on whether
866 there were problems.
Yuto Takanod70d4462021-08-09 12:45:51 +0100867
868 Args:
Yuto Takanod70d4462021-08-09 12:45:51 +0100869 * name: the name of the test
870 * problems: a List of encountered Problems
Yuto Takano81528c02021-08-06 16:22:06 +0100871 """
Yuto Takano39639672021-08-05 19:47:48 +0100872 if problems:
Yuto Takano55614b52021-08-07 01:00:18 +0100873 self.log.info("{}: FAIL\n".format(name))
874 for problem in problems:
875 self.log.warning(str(problem))
Darryl Greend5802922018-05-08 15:30:59 +0100876 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100877 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100878
Yuto Takano39639672021-08-05 19:47:48 +0100879def main():
880 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100881 Perform argument parsing, and create an instance of CodeParser and
882 NameChecker to begin the core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100883 """
Yuto Takanof005c332021-08-09 13:56:36 +0100884 parser = argparse.ArgumentParser(
Yuto Takano39639672021-08-05 19:47:48 +0100885 formatter_class=argparse.RawDescriptionHelpFormatter,
886 description=(
887 "This script confirms that the naming of all symbols and identifiers "
888 "in Mbed TLS are consistent with the house style and are also "
889 "self-consistent.\n\n"
Yuto Takanof005c332021-08-09 13:56:36 +0100890 "Expected to be run from the MbedTLS root directory.")
891 )
892 parser.add_argument(
893 "-v", "--verbose",
894 action="store_true",
895 help="show parse results"
896 )
897 parser.add_argument(
898 "-q", "--quiet",
899 action="store_true",
900 help="hide unnecessary text, explanations, and highlighs"
901 )
Darryl Greend5802922018-05-08 15:30:59 +0100902
Yuto Takanof005c332021-08-09 13:56:36 +0100903 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100904
Yuto Takano55c6c872021-08-09 15:35:19 +0100905 # Configure the global logger, which is then passed to the classes below
906 log = logging.getLogger()
907 log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
908 log.addHandler(logging.StreamHandler())
909
Darryl Greend5802922018-05-08 15:30:59 +0100910 try:
Yuto Takano55c6c872021-08-09 15:35:19 +0100911 code_parser = CodeParser(log)
912 parse_result = code_parser.comprehensive_parse()
Yuto Takanod93fa372021-08-06 23:05:55 +0100913 except Exception: # pylint: disable=broad-except
Darryl Greend5802922018-05-08 15:30:59 +0100914 traceback.print_exc()
915 sys.exit(2)
916
Yuto Takano55c6c872021-08-09 15:35:19 +0100917 name_checker = NameChecker(parse_result, log)
918 return_code = name_checker.perform_checks(quiet=args.quiet)
919
920 sys.exit(return_code)
921
Darryl Greend5802922018-05-08 15:30:59 +0100922if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100923 main()