blob: aece1ef0604e5a4d145371393ef15979e3fb9f5f [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
Gilles Peskined9071e72022-09-18 21:17:09 +020059import scripts_path # pylint: disable=unused-import
60from mbedtls_dev import build_tree
61
62
Yuto Takano81528c02021-08-06 16:22:06 +010063# Naming patterns to check against. These are defined outside the NameCheck
64# class for ease of modification.
Janos Follath99387192022-08-10 11:11:34 +010065PUBLIC_MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
66INTERNAL_MACRO_PATTERN = r"^[0-9A-Za-z_]*[0-9A-Z]$"
67CONSTANTS_PATTERN = PUBLIC_MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010068IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010069
Yuto Takanod93fa372021-08-06 23:05:55 +010070class Match(): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010071 """
72 A class representing a match, together with its found position.
73
74 Fields:
75 * filename: the file that the match was in.
76 * line: the full line containing the match.
Yuto Takano704b0f72021-08-17 10:41:23 +010077 * line_no: the line number.
78 * pos: a tuple of (start, end) positions on the line where the match is.
Yuto Takano81528c02021-08-06 16:22:06 +010079 * name: the match itself.
80 """
Yuto Takano704b0f72021-08-17 10:41:23 +010081 def __init__(self, filename, line, line_no, pos, name):
82 # pylint: disable=too-many-arguments
Yuto Takano39639672021-08-05 19:47:48 +010083 self.filename = filename
84 self.line = line
Yuto Takano704b0f72021-08-17 10:41:23 +010085 self.line_no = line_no
Yuto Takano39639672021-08-05 19:47:48 +010086 self.pos = pos
87 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010088
Yuto Takanoa4e75122021-08-06 17:23:28 +010089 def __str__(self):
Yuto Takanofb86ac72021-08-16 10:32:40 +010090 """
91 Return a formatted code listing representation of the erroneous line.
92 """
Yuto Takano704b0f72021-08-17 10:41:23 +010093 gutter = format(self.line_no, "4d")
94 underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
Yuto Takano381fda82021-08-06 23:37:20 +010095
Yuto Takanoa4e75122021-08-06 17:23:28 +010096 return (
Yuto Takanofb86ac72021-08-16 10:32:40 +010097 " {0} |\n".format(" " * len(gutter)) +
Yuto Takano381fda82021-08-06 23:37:20 +010098 " {0} | {1}".format(gutter, self.line) +
Yuto Takanofb86ac72021-08-16 10:32:40 +010099 " {0} | {1}\n".format(" " * len(gutter), underline)
Yuto Takanoa4e75122021-08-06 17:23:28 +0100100 )
Yuto Takanod93fa372021-08-06 23:05:55 +0100101
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100102class Problem(abc.ABC): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100103 """
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100104 An abstract parent class representing a form of static analysis error.
105 It extends an Abstract Base Class, which means it is not instantiable, and
106 it also mandates certain abstract methods to be implemented in subclasses.
Yuto Takano81528c02021-08-06 16:22:06 +0100107 """
Yuto Takano5473be22021-08-17 10:14:01 +0100108 # Class variable to control the quietness of all problems
109 quiet = False
Yuto Takano39639672021-08-05 19:47:48 +0100110 def __init__(self):
111 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +0100112 self.textwrapper.width = 80
Yuto Takanoa4e75122021-08-06 17:23:28 +0100113 self.textwrapper.initial_indent = " > "
Yuto Takano81528c02021-08-06 16:22:06 +0100114 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +0100115
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100116 def __str__(self):
117 """
118 Unified string representation method for all Problems.
119 """
120 if self.__class__.quiet:
121 return self.quiet_output()
122 return self.verbose_output()
123
124 @abc.abstractmethod
125 def quiet_output(self):
126 """
127 The output when --quiet is enabled.
128 """
129 pass
130
131 @abc.abstractmethod
132 def verbose_output(self):
133 """
134 The default output with explanation and code snippet if appropriate.
135 """
136 pass
137
Yuto Takanod93fa372021-08-06 23:05:55 +0100138class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100139 """
140 A problem that occurs when an exported/available symbol in the object file
141 is not explicitly declared in header files. Created with
142 NameCheck.check_symbols_declared_in_header()
143
144 Fields:
145 * symbol_name: the name of the symbol.
146 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100147 def __init__(self, symbol_name):
Yuto Takano39639672021-08-05 19:47:48 +0100148 self.symbol_name = symbol_name
149 Problem.__init__(self)
150
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100151 def quiet_output(self):
152 return "{0}".format(self.symbol_name)
Yuto Takano55614b52021-08-07 01:00:18 +0100153
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100154 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100155 return self.textwrapper.fill(
156 "'{0}' was found as an available symbol in the output of nm, "
157 "however it was not declared in any header files."
158 .format(self.symbol_name))
159
Yuto Takanod93fa372021-08-06 23:05:55 +0100160class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100161 """
162 A problem that occurs when something doesn't match the expected pattern.
163 Created with NameCheck.check_match_pattern()
164
165 Fields:
166 * pattern: the expected regex pattern
167 * match: the Match object in question
168 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100169 def __init__(self, pattern, match):
Yuto Takano39639672021-08-05 19:47:48 +0100170 self.pattern = pattern
171 self.match = match
172 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100173
Yuto Takano55614b52021-08-07 01:00:18 +0100174
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100175 def quiet_output(self):
176 return (
177 "{0}:{1}:{2}"
178 .format(self.match.filename, self.match.line_no, self.match.name)
179 )
180
181 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100182 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100183 "{0}:{1}: '{2}' does not match the required pattern '{3}'."
184 .format(
185 self.match.filename,
Yuto Takano5f831712021-08-18 18:03:24 +0100186 self.match.line_no,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100187 self.match.name,
Yuto Takanod70d4462021-08-09 12:45:51 +0100188 self.pattern
189 )
190 ) + "\n" + str(self.match)
Yuto Takano39639672021-08-05 19:47:48 +0100191
Yuto Takanod93fa372021-08-06 23:05:55 +0100192class Typo(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100193 """
194 A problem that occurs when a word using MBED doesn't appear to be defined as
195 constants nor enum values. Created with NameCheck.check_for_typos()
196
197 Fields:
198 * match: the Match object of the MBED name in question.
199 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100200 def __init__(self, match):
Yuto Takano39639672021-08-05 19:47:48 +0100201 self.match = match
202 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100203
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100204 def quiet_output(self):
205 return (
206 "{0}:{1}:{2}"
207 .format(self.match.filename, self.match.line_no, self.match.name)
208 )
Yuto Takano55614b52021-08-07 01:00:18 +0100209
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100210 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100211 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100212 "{0}:{1}: '{2}' looks like a typo. It was not found in any "
213 "macros or any enums. If this is not a typo, put "
214 "//no-check-names after it."
Yuto Takano5f831712021-08-18 18:03:24 +0100215 .format(self.match.filename, self.match.line_no, self.match.name)
Yuto Takanod70d4462021-08-09 12:45:51 +0100216 ) + "\n" + str(self.match)
Darryl Greend5802922018-05-08 15:30:59 +0100217
Yuto Takano55c6c872021-08-09 15:35:19 +0100218class CodeParser():
Yuto Takano81528c02021-08-06 16:22:06 +0100219 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100220 Class for retrieving files and parsing the code. This can be used
221 independently of the checks that NameChecker performs, for example for
222 list_internal_identifiers.py.
Yuto Takano81528c02021-08-06 16:22:06 +0100223 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100224 def __init__(self, log):
225 self.log = log
Gilles Peskined9071e72022-09-18 21:17:09 +0200226 build_tree.check_repo_path()
Yuto Takano977e07f2021-08-09 11:56:15 +0100227
Yuto Takano8e9a2192021-08-09 14:48:53 +0100228 # Memo for storing "glob expression": set(filepaths)
229 self.files = {}
230
Gilles Peskine89458d12021-09-27 19:20:17 +0200231 # Globally excluded filenames.
232 # Note that "*" can match directory separators in exclude lists.
233 self.excluded_files = ["*/bn_mul", "*/compat-2.x.h"]
Yuto Takano977e07f2021-08-09 11:56:15 +0100234
Yuto Takano55c6c872021-08-09 15:35:19 +0100235 def comprehensive_parse(self):
Yuto Takano39639672021-08-05 19:47:48 +0100236 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100237 Comprehensive ("default") function to call each parsing function and
238 retrieve various elements of the code, together with the source location.
Darryl Greend5802922018-05-08 15:30:59 +0100239
Yuto Takano55c6c872021-08-09 15:35:19 +0100240 Returns a dict of parsed item key to the corresponding List of Matches.
Yuto Takano81528c02021-08-06 16:22:06 +0100241 """
242 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100243 self.log.debug(
Yuto Takano50953432021-08-09 14:54:36 +0100244 "The following files are excluded from the search: {}"
Yuto Takanod24e0372021-08-06 16:42:33 +0100245 .format(str(self.excluded_files))
246 )
Yuto Takano81528c02021-08-06 16:22:06 +0100247
Janos Follath99387192022-08-10 11:11:34 +0100248 all_macros = {"public": [], "internal": []}
249 all_macros["public"] = self.parse_macros([
Yuto Takano8e9a2192021-08-09 14:48:53 +0100250 "include/mbedtls/*.h",
251 "include/psa/*.h",
Yuto Takanod70d4462021-08-09 12:45:51 +0100252 "3rdparty/everest/include/everest/everest.h",
253 "3rdparty/everest/include/everest/x25519.h"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100254 ])
Janos Follath99387192022-08-10 11:11:34 +0100255 all_macros["internal"] = self.parse_macros([
256 "library/*.h",
257 "tests/include/test/drivers/*.h",
258 ])
Yuto Takano8e9a2192021-08-09 14:48:53 +0100259 enum_consts = self.parse_enum_consts([
260 "include/mbedtls/*.h",
261 "library/*.h",
262 "3rdparty/everest/include/everest/everest.h",
263 "3rdparty/everest/include/everest/x25519.h"
264 ])
265 identifiers = self.parse_identifiers([
266 "include/mbedtls/*.h",
267 "include/psa/*.h",
268 "library/*.h",
269 "3rdparty/everest/include/everest/everest.h",
270 "3rdparty/everest/include/everest/x25519.h"
271 ])
272 mbed_words = self.parse_mbed_words([
273 "include/mbedtls/*.h",
274 "include/psa/*.h",
275 "library/*.h",
276 "3rdparty/everest/include/everest/everest.h",
277 "3rdparty/everest/include/everest/x25519.h",
278 "library/*.c",
Yuto Takano81528c02021-08-06 16:22:06 +0100279 "3rdparty/everest/library/everest.c",
Yuto Takanod70d4462021-08-09 12:45:51 +0100280 "3rdparty/everest/library/x25519.c"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100281 ])
Yuto Takano81528c02021-08-06 16:22:06 +0100282 symbols = self.parse_symbols()
283
284 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
285 identifiers_justname = [x.name for x in identifiers]
Janos Follath99387192022-08-10 11:11:34 +0100286 actual_macros = {"public": [], "internal": []}
287 for scope in actual_macros:
288 for macro in all_macros[scope]:
289 if macro.name not in identifiers_justname:
290 actual_macros[scope].append(macro)
Yuto Takano81528c02021-08-06 16:22:06 +0100291
292 self.log.debug("Found:")
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100293 # Aligns the counts on the assumption that none exceeds 4 digits
Janos Follath99387192022-08-10 11:11:34 +0100294 for scope in actual_macros:
295 self.log.debug(" {:4} Total {} Macros"
Janos Follath8d59c862022-08-10 15:35:35 +0100296 .format(len(all_macros[scope]), scope))
Janos Follath99387192022-08-10 11:11:34 +0100297 self.log.debug(" {:4} {} Non-identifier Macros"
Janos Follath8d59c862022-08-10 15:35:35 +0100298 .format(len(actual_macros[scope]), scope))
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100299 self.log.debug(" {:4} Enum Constants".format(len(enum_consts)))
300 self.log.debug(" {:4} Identifiers".format(len(identifiers)))
301 self.log.debug(" {:4} Exported Symbols".format(len(symbols)))
Yuto Takano55c6c872021-08-09 15:35:19 +0100302 return {
Janos Follath99387192022-08-10 11:11:34 +0100303 "public_macros": actual_macros["public"],
304 "internal_macros": actual_macros["internal"],
Yuto Takano81528c02021-08-06 16:22:06 +0100305 "enum_consts": enum_consts,
306 "identifiers": identifiers,
307 "symbols": symbols,
Yuto Takanod93fa372021-08-06 23:05:55 +0100308 "mbed_words": mbed_words
Yuto Takano81528c02021-08-06 16:22:06 +0100309 }
310
Gilles Peskine89458d12021-09-27 19:20:17 +0200311 def is_file_excluded(self, path, exclude_wildcards):
Gilles Peskine8a832242021-09-28 10:12:49 +0200312 """Whether the given file path is excluded."""
Gilles Peskine89458d12021-09-27 19:20:17 +0200313 # exclude_wildcards may be None. Also, consider the global exclusions.
314 exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
315 for pattern in exclude_wildcards:
316 if fnmatch.fnmatch(path, pattern):
317 return True
318 return False
319
Yuto Takano55c6c872021-08-09 15:35:19 +0100320 def get_files(self, include_wildcards, exclude_wildcards):
321 """
322 Get all files that match any of the UNIX-style wildcards. While the
323 check_names script is designed only for use on UNIX/macOS (due to nm),
324 this function alone would work fine on Windows even with forward slashes
325 in the wildcard.
326
327 Args:
328 * include_wildcards: a List of shell-style wildcards to match filepaths.
329 * exclude_wildcards: a List of shell-style wildcards to exclude.
330
331 Returns a List of relative filepaths.
332 """
333 accumulator = set()
334
Yuto Takano55c6c872021-08-09 15:35:19 +0100335 for include_wildcard in include_wildcards:
Gilles Peskine89458d12021-09-27 19:20:17 +0200336 accumulator = accumulator.union(glob.iglob(include_wildcard))
Yuto Takano55c6c872021-08-09 15:35:19 +0100337
Gilles Peskine89458d12021-09-27 19:20:17 +0200338 return list(path for path in accumulator
339 if not self.is_file_excluded(path, exclude_wildcards))
Yuto Takano55c6c872021-08-09 15:35:19 +0100340
Yuto Takano8e9a2192021-08-09 14:48:53 +0100341 def parse_macros(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100342 """
343 Parse all macros defined by #define preprocessor directives.
344
345 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100346 * include: A List of glob expressions to look for files through.
347 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100348
349 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100350 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100351 macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
352 exclusions = (
Yuto Takano39639672021-08-05 19:47:48 +0100353 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
354 )
355
Yuto Takano50953432021-08-09 14:54:36 +0100356 files = self.get_files(include, exclude)
357 self.log.debug("Looking for macros in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100358
Yuto Takano50953432021-08-09 14:54:36 +0100359 macros = []
360 for header_file in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100361 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100362 for line_no, line in enumerate(header):
Yuto Takanod93fa372021-08-06 23:05:55 +0100363 for macro in macro_regex.finditer(line):
Yuto Takanod70d4462021-08-09 12:45:51 +0100364 if macro.group("macro").startswith(exclusions):
365 continue
366
367 macros.append(Match(
368 header_file,
369 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100370 line_no,
371 macro.span("macro"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100372 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100373
Yuto Takano39639672021-08-05 19:47:48 +0100374 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100375
Yuto Takano8e9a2192021-08-09 14:48:53 +0100376 def parse_mbed_words(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100377 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100378 Parse all words in the file that begin with MBED, in and out of macros,
379 comments, anything.
Yuto Takano39639672021-08-05 19:47:48 +0100380
381 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100382 * include: A List of glob expressions to look for files through.
383 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100384
385 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100386 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100387 # Typos of TLS are common, hence the broader check below than MBEDTLS.
Yuto Takanod93fa372021-08-06 23:05:55 +0100388 mbed_regex = re.compile(r"\bMBED.+?_[A-Z0-9_]*")
389 exclusions = re.compile(r"// *no-check-names|#error")
390
Yuto Takano50953432021-08-09 14:54:36 +0100391 files = self.get_files(include, exclude)
392 self.log.debug("Looking for MBED words in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100393
Yuto Takano50953432021-08-09 14:54:36 +0100394 mbed_words = []
395 for filename in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100396 with open(filename, "r", encoding="utf-8") as fp:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100397 for line_no, line in enumerate(fp):
Yuto Takanod93fa372021-08-06 23:05:55 +0100398 if exclusions.search(line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100399 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100400
Yuto Takanod93fa372021-08-06 23:05:55 +0100401 for name in mbed_regex.finditer(line):
402 mbed_words.append(Match(
Yuto Takano39639672021-08-05 19:47:48 +0100403 filename,
404 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100405 line_no,
406 name.span(0),
407 name.group(0)))
Yuto Takano39639672021-08-05 19:47:48 +0100408
Yuto Takanod93fa372021-08-06 23:05:55 +0100409 return mbed_words
Yuto Takano39639672021-08-05 19:47:48 +0100410
Yuto Takano8e9a2192021-08-09 14:48:53 +0100411 def parse_enum_consts(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100412 """
413 Parse all enum value constants that are declared.
414
415 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100416 * include: A List of glob expressions to look for files through.
417 * exclude: A List of glob expressions for excluding files.
Yuto Takano39639672021-08-05 19:47:48 +0100418
Yuto Takano81528c02021-08-06 16:22:06 +0100419 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100420 """
Yuto Takano50953432021-08-09 14:54:36 +0100421 files = self.get_files(include, exclude)
422 self.log.debug("Looking for enum consts in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100423
Yuto Takanob1417b42021-08-17 10:30:20 +0100424 # Emulate a finite state machine to parse enum declarations.
425 # OUTSIDE_KEYWORD = outside the enum keyword
426 # IN_BRACES = inside enum opening braces
427 # IN_BETWEEN = between enum keyword and opening braces
428 states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
Yuto Takano50953432021-08-09 14:54:36 +0100429 enum_consts = []
430 for header_file in files:
Yuto Takanob1417b42021-08-17 10:30:20 +0100431 state = states.OUTSIDE_KEYWORD
Yuto Takanoa083d152021-08-07 00:25:59 +0100432 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100433 for line_no, line in enumerate(header):
Yuto Takano13ecd992021-08-06 16:56:52 +0100434 # Match typedefs and brackets only when they are at the
435 # beginning of the line -- if they are indented, they might
436 # be sub-structures within structs, etc.
Yuto Takanob1417b42021-08-17 10:30:20 +0100437 if (state == states.OUTSIDE_KEYWORD and
Yuto Takano35906912021-08-17 11:05:43 +0100438 re.search(r"^(typedef +)?enum +{", line)):
Yuto Takanob1417b42021-08-17 10:30:20 +0100439 state = states.IN_BRACES
440 elif (state == states.OUTSIDE_KEYWORD and
441 re.search(r"^(typedef +)?enum", line)):
442 state = states.IN_BETWEEN
443 elif (state == states.IN_BETWEEN and
444 re.search(r"^{", line)):
445 state = states.IN_BRACES
446 elif (state == states.IN_BRACES and
447 re.search(r"^}", line)):
448 state = states.OUTSIDE_KEYWORD
449 elif (state == states.IN_BRACES and
450 not re.search(r"^ *#", line)):
Yuto Takano90bc0262021-08-16 11:34:10 +0100451 enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
Yuto Takanod70d4462021-08-09 12:45:51 +0100452 if not enum_const:
453 continue
454
455 enum_consts.append(Match(
456 header_file,
457 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100458 line_no,
459 enum_const.span("enum_const"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100460 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100461
Yuto Takano39639672021-08-05 19:47:48 +0100462 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100463
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100464 IGNORED_CHUNK_REGEX = re.compile('|'.join([
465 r'/\*.*?\*/', # block comment entirely on one line
466 r'//.*', # line comment
467 r'(?P<string>")(?:[^\\\"]|\\.)*"', # string literal
468 ]))
469
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100470 def strip_comments_and_literals(self, line, in_block_comment):
471 """Strip comments and string literals from line.
472
473 Continuation lines are not supported.
474
475 If in_block_comment is true, assume that the line starts inside a
476 block comment.
477
478 Return updated values of (line, in_block_comment) where:
479 * Comments in line have been replaced by a space (or nothing at the
480 start or end of the line).
481 * String contents have been removed.
482 * in_block_comment indicates whether the line ends inside a block
483 comment that continues on the next line.
484 """
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100485
486 # Terminate current multiline comment?
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100487 if in_block_comment:
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100488 m = re.search(r"\*/", line)
489 if m:
490 in_block_comment = False
491 line = line[m.end(0):]
492 else:
493 return '', True
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100494
495 # Remove full comments and string literals.
496 # Do it all together to handle cases like "/*" correctly.
497 # Note that continuation lines are not supported.
498 line = re.sub(self.IGNORED_CHUNK_REGEX,
499 lambda s: '""' if s.group('string') else ' ',
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100500 line)
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100501
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100502 # Start an unfinished comment?
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100503 # (If `/*` was part of a complete comment, it's already been removed.)
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100504 m = re.search(r"/\*", line)
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100505 if m:
506 in_block_comment = True
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100507 line = line[:m.start(0)]
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100508
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100509 return line, in_block_comment
510
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100511 IDENTIFIER_REGEX = re.compile('|'.join([
Gilles Peskine152de232021-11-16 20:56:47 +0100512 # Match " something(a" or " *something(a". Functions.
513 # Assumptions:
514 # - function definition from return type to one of its arguments is
515 # all on one line
516 # - function definition line only contains alphanumeric, asterisk,
517 # underscore, and open bracket
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100518 r".* \**(\w+) *\( *\w",
Gilles Peskine152de232021-11-16 20:56:47 +0100519 # Match "(*something)(".
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100520 r".*\( *\* *(\w+) *\) *\(",
Gilles Peskine152de232021-11-16 20:56:47 +0100521 # Match names of named data structures.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100522 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$",
Gilles Peskine152de232021-11-16 20:56:47 +0100523 # Match names of typedef instances, after closing bracket.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100524 r"}? *(\w+)[;[].*",
525 ]))
Gilles Peskine152de232021-11-16 20:56:47 +0100526 # The regex below is indented for clarity.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100527 EXCLUSION_LINES = re.compile("|".join([
528 r"extern +\"C\"",
529 r"(typedef +)?(struct|union|enum)( *{)?$",
530 r"} *;?$",
531 r"$",
532 r"//",
533 r"#",
534 ]))
Gilles Peskine152de232021-11-16 20:56:47 +0100535
536 def parse_identifiers_in_file(self, header_file, identifiers):
537 """
538 Parse all lines of a header where a function/enum/struct/union/typedef
539 identifier is declared, based on some regex and heuristics. Highly
540 dependent on formatting style.
541
542 Append found matches to the list ``identifiers``.
543 """
544
545 with open(header_file, "r", encoding="utf-8") as header:
546 in_block_comment = False
547 # The previous line variable is used for concatenating lines
548 # when identifiers are formatted and spread across multiple
549 # lines.
550 previous_line = ""
551
552 for line_no, line in enumerate(header):
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100553 line, in_block_comment = \
554 self.strip_comments_and_literals(line, in_block_comment)
Gilles Peskine152de232021-11-16 20:56:47 +0100555
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100556 if self.EXCLUSION_LINES.match(line):
Gilles Peskine152de232021-11-16 20:56:47 +0100557 previous_line = ""
558 continue
559
560 # If the line contains only space-separated alphanumeric
Gilles Peskinebc1e8f62021-11-17 20:39:56 +0100561 # characters (or underscore, asterisk, or open parenthesis),
Gilles Peskine152de232021-11-16 20:56:47 +0100562 # and nothing else, high chance it's a declaration that
563 # continues on the next line
564 if re.search(r"^([\w\*\(]+\s+)+$", line):
565 previous_line += line
566 continue
567
568 # If previous line seemed to start an unfinished declaration
569 # (as above), concat and treat them as one.
570 if previous_line:
571 line = previous_line.strip() + " " + line.strip() + "\n"
572 previous_line = ""
573
574 # Skip parsing if line has a space in front = heuristic to
575 # skip function argument lines (highly subject to formatting
576 # changes)
577 if line[0] == " ":
578 continue
579
580 identifier = self.IDENTIFIER_REGEX.search(line)
581
582 if not identifier:
583 continue
584
585 # Find the group that matched, and append it
586 for group in identifier.groups():
587 if not group:
588 continue
589
590 identifiers.append(Match(
591 header_file,
592 line,
593 line_no,
594 identifier.span(),
595 group))
596
Yuto Takano8e9a2192021-08-09 14:48:53 +0100597 def parse_identifiers(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100598 """
Yuto Takano8246eb82021-08-16 10:37:24 +0100599 Parse all lines of a header where a function/enum/struct/union/typedef
Yuto Takanob1417b42021-08-17 10:30:20 +0100600 identifier is declared, based on some regex and heuristics. Highly
601 dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100602
Yuto Takano39639672021-08-05 19:47:48 +0100603 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100604 * include: A List of glob expressions to look for files through.
605 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100606
607 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100608 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100609
Yuto Takano50953432021-08-09 14:54:36 +0100610 files = self.get_files(include, exclude)
611 self.log.debug("Looking for identifiers in {} files".format(len(files)))
612
613 identifiers = []
614 for header_file in files:
Gilles Peskine152de232021-11-16 20:56:47 +0100615 self.parse_identifiers_in_file(header_file, identifiers)
Yuto Takano39639672021-08-05 19:47:48 +0100616
617 return identifiers
618
619 def parse_symbols(self):
620 """
621 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
622 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100623 Exceptions thrown here are rethrown because they would be critical
624 errors that void several tests, and thus needs to halt the program. This
625 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100626
Yuto Takano81528c02021-08-06 16:22:06 +0100627 Returns a List of unique symbols defined and used in the libraries.
628 """
629 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100630 symbols = []
631
632 # Back up the config and atomically compile with the full configratuion.
Yuto Takanod70d4462021-08-09 12:45:51 +0100633 shutil.copy(
634 "include/mbedtls/mbedtls_config.h",
635 "include/mbedtls/mbedtls_config.h.bak"
636 )
Darryl Greend5802922018-05-08 15:30:59 +0100637 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100638 # Use check=True in all subprocess calls so that failures are raised
639 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100640 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100641 ["python3", "scripts/config.py", "full"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100642 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100643 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100644 )
645 my_environment = os.environ.copy()
646 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100647 # Run make clean separately to lib to prevent unwanted behavior when
648 # make is invoked with parallelism.
Yuto Takano39639672021-08-05 19:47:48 +0100649 subprocess.run(
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100650 ["make", "clean"],
651 universal_newlines=True,
652 check=True
653 )
654 subprocess.run(
655 ["make", "lib"],
Darryl Greend5802922018-05-08 15:30:59 +0100656 env=my_environment,
Yuto Takanobcc3d992021-08-06 23:14:58 +0100657 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100658 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100659 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100660 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100661 )
Yuto Takano39639672021-08-05 19:47:48 +0100662
663 # Perform object file analysis using nm
Yuto Takanod70d4462021-08-09 12:45:51 +0100664 symbols = self.parse_symbols_from_nm([
665 "library/libmbedcrypto.a",
666 "library/libmbedtls.a",
667 "library/libmbedx509.a"
668 ])
Yuto Takano39639672021-08-05 19:47:48 +0100669
670 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100671 ["make", "clean"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100672 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100673 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100674 )
675 except subprocess.CalledProcessError as error:
Yuto Takano25eeb7b2021-08-06 21:27:59 +0100676 self.log.debug(error.output)
Yuto Takano81528c02021-08-06 16:22:06 +0100677 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100678 finally:
Yuto Takano6fececf2021-08-07 17:28:23 +0100679 # Put back the original config regardless of there being errors.
680 # Works also for keyboard interrupts.
Yuto Takanod70d4462021-08-09 12:45:51 +0100681 shutil.move(
682 "include/mbedtls/mbedtls_config.h.bak",
683 "include/mbedtls/mbedtls_config.h"
684 )
Yuto Takano39639672021-08-05 19:47:48 +0100685
686 return symbols
687
688 def parse_symbols_from_nm(self, object_files):
689 """
690 Run nm to retrieve the list of referenced symbols in each object file.
691 Does not return the position data since it is of no use.
692
Yuto Takano81528c02021-08-06 16:22:06 +0100693 Args:
Yuto Takano55c6c872021-08-09 15:35:19 +0100694 * object_files: a List of compiled object filepaths to search through.
Yuto Takano81528c02021-08-06 16:22:06 +0100695
696 Returns a List of unique symbols defined and used in any of the object
697 files.
Yuto Takano39639672021-08-05 19:47:48 +0100698 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100699 nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
700 nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100701 exclusions = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100702
703 symbols = []
704
Yuto Takano81528c02021-08-06 16:22:06 +0100705 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100706 nm_output = ""
707 for lib in object_files:
708 nm_output += subprocess.run(
709 ["nm", "-og", lib],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100710 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100711 stdout=subprocess.PIPE,
712 stderr=subprocess.STDOUT,
713 check=True
714 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100715
Yuto Takano39639672021-08-05 19:47:48 +0100716 for line in nm_output.splitlines():
Yuto Takano90bc0262021-08-16 11:34:10 +0100717 if not nm_undefined_regex.search(line):
718 symbol = nm_valid_regex.search(line)
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100719 if (symbol and not symbol.group("symbol").startswith(exclusions)):
Yuto Takanoe77f6992021-08-05 20:22:59 +0100720 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100721 else:
722 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100723
Yuto Takano39639672021-08-05 19:47:48 +0100724 return symbols
725
Yuto Takano55c6c872021-08-09 15:35:19 +0100726class NameChecker():
727 """
728 Representation of the core name checking operation performed by this script.
729 """
730 def __init__(self, parse_result, log):
731 self.parse_result = parse_result
732 self.log = log
733
Yuto Takano55614b52021-08-07 01:00:18 +0100734 def perform_checks(self, quiet=False):
Yuto Takano39639672021-08-05 19:47:48 +0100735 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100736 A comprehensive checker that performs each check in order, and outputs
737 a final verdict.
Yuto Takano81528c02021-08-06 16:22:06 +0100738
739 Args:
Yuto Takano55614b52021-08-07 01:00:18 +0100740 * quiet: whether to hide detailed problem explanation.
Yuto Takano39639672021-08-05 19:47:48 +0100741 """
Yuto Takano81528c02021-08-06 16:22:06 +0100742 self.log.info("=============")
Yuto Takano5473be22021-08-17 10:14:01 +0100743 Problem.quiet = quiet
Yuto Takano39639672021-08-05 19:47:48 +0100744 problems = 0
Yuto Takano5473be22021-08-17 10:14:01 +0100745 problems += self.check_symbols_declared_in_header()
Yuto Takano39639672021-08-05 19:47:48 +0100746
Yuto Takanod70d4462021-08-09 12:45:51 +0100747 pattern_checks = [
Janos Follath99387192022-08-10 11:11:34 +0100748 ("public_macros", PUBLIC_MACRO_PATTERN),
749 ("internal_macros", INTERNAL_MACRO_PATTERN),
Yuto Takanod70d4462021-08-09 12:45:51 +0100750 ("enum_consts", CONSTANTS_PATTERN),
751 ("identifiers", IDENTIFIER_PATTERN)
752 ]
Yuto Takano39639672021-08-05 19:47:48 +0100753 for group, check_pattern in pattern_checks:
Yuto Takano5473be22021-08-17 10:14:01 +0100754 problems += self.check_match_pattern(group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100755
Yuto Takano5473be22021-08-17 10:14:01 +0100756 problems += self.check_for_typos()
Yuto Takano39639672021-08-05 19:47:48 +0100757
758 self.log.info("=============")
759 if problems > 0:
760 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano55614b52021-08-07 01:00:18 +0100761 if quiet:
762 self.log.info("Remove --quiet to see explanations.")
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100763 else:
764 self.log.info("Use --quiet for minimal output.")
Yuto Takano55c6c872021-08-09 15:35:19 +0100765 return 1
Yuto Takano39639672021-08-05 19:47:48 +0100766 else:
767 self.log.info("PASS")
Yuto Takano55c6c872021-08-09 15:35:19 +0100768 return 0
Darryl Greend5802922018-05-08 15:30:59 +0100769
Yuto Takano5473be22021-08-17 10:14:01 +0100770 def check_symbols_declared_in_header(self):
Yuto Takano39639672021-08-05 19:47:48 +0100771 """
772 Perform a check that all detected symbols in the library object files
773 are properly declared in headers.
Yuto Takano977e07f2021-08-09 11:56:15 +0100774 Assumes parse_names_in_source() was called before this.
Darryl Greend5802922018-05-08 15:30:59 +0100775
Yuto Takano81528c02021-08-06 16:22:06 +0100776 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100777 """
778 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100779
Yuto Takano39639672021-08-05 19:47:48 +0100780 for symbol in self.parse_result["symbols"]:
781 found_symbol_declared = False
782 for identifier_match in self.parse_result["identifiers"]:
783 if symbol == identifier_match.name:
784 found_symbol_declared = True
785 break
Yuto Takano81528c02021-08-06 16:22:06 +0100786
Yuto Takano39639672021-08-05 19:47:48 +0100787 if not found_symbol_declared:
Yuto Takanod70d4462021-08-09 12:45:51 +0100788 problems.append(SymbolNotInHeader(symbol))
Yuto Takano39639672021-08-05 19:47:48 +0100789
Yuto Takano5473be22021-08-17 10:14:01 +0100790 self.output_check_result("All symbols in header", problems)
Yuto Takano39639672021-08-05 19:47:48 +0100791 return len(problems)
792
Yuto Takano5473be22021-08-17 10:14:01 +0100793 def check_match_pattern(self, group_to_check, check_pattern):
Yuto Takano81528c02021-08-06 16:22:06 +0100794 """
795 Perform a check that all items of a group conform to a regex pattern.
Yuto Takano977e07f2021-08-09 11:56:15 +0100796 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100797
798 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100799 * group_to_check: string key to index into self.parse_result.
800 * check_pattern: the regex to check against.
801
802 Returns the number of problems that need fixing.
803 """
Yuto Takano39639672021-08-05 19:47:48 +0100804 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100805
Yuto Takano39639672021-08-05 19:47:48 +0100806 for item_match in self.parse_result[group_to_check]:
Yuto Takano90bc0262021-08-16 11:34:10 +0100807 if not re.search(check_pattern, item_match.name):
Yuto Takano39639672021-08-05 19:47:48 +0100808 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano90bc0262021-08-16 11:34:10 +0100809 # Double underscore should not be used for names
810 if re.search(r".*__.*", item_match.name):
Yuto Takano704b0f72021-08-17 10:41:23 +0100811 problems.append(
812 PatternMismatch("no double underscore allowed", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100813
814 self.output_check_result(
815 "Naming patterns of {}".format(group_to_check),
Yuto Takano55614b52021-08-07 01:00:18 +0100816 problems)
Yuto Takano39639672021-08-05 19:47:48 +0100817 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100818
Yuto Takano5473be22021-08-17 10:14:01 +0100819 def check_for_typos(self):
Yuto Takano81528c02021-08-06 16:22:06 +0100820 """
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800821 Perform a check that all words in the source code beginning with MBED are
Yuto Takano81528c02021-08-06 16:22:06 +0100822 either defined as macros, or as enum constants.
Yuto Takano977e07f2021-08-09 11:56:15 +0100823 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100824
Yuto Takano81528c02021-08-06 16:22:06 +0100825 Returns the number of problems that need fixing.
826 """
Yuto Takano39639672021-08-05 19:47:48 +0100827 problems = []
Yuto Takano39639672021-08-05 19:47:48 +0100828
Yuto Takanod70d4462021-08-09 12:45:51 +0100829 # Set comprehension, equivalent to a list comprehension wrapped by set()
Yuto Takanod93fa372021-08-06 23:05:55 +0100830 all_caps_names = {
831 match.name
832 for match
Janos Follath99387192022-08-10 11:11:34 +0100833 in self.parse_result["public_macros"] +
Janos Follath8d59c862022-08-10 15:35:35 +0100834 self.parse_result["internal_macros"] +
835 self.parse_result["enum_consts"]
Janos Follath99387192022-08-10 11:11:34 +0100836 }
Ronald Cron7975fae2021-09-13 14:50:42 +0200837 typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$|"
838 r"MBEDTLS_TEST_LIBTESTDRIVER*")
Yuto Takano39639672021-08-05 19:47:48 +0100839
Yuto Takanod93fa372021-08-06 23:05:55 +0100840 for name_match in self.parse_result["mbed_words"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100841 found = name_match.name in all_caps_names
842
843 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
844 # PSA driver, they will not exist as macros. However, they
845 # should still be checked for typos using the equivalent
846 # BUILTINs that exist.
847 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
848 found = name_match.name.replace(
849 "MBEDTLS_PSA_ACCEL_",
850 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
851
Yuto Takanod93fa372021-08-06 23:05:55 +0100852 if not found and not typo_exclusion.search(name_match.name):
Yuto Takanod70d4462021-08-09 12:45:51 +0100853 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100854
Yuto Takano5473be22021-08-17 10:14:01 +0100855 self.output_check_result("Likely typos", problems)
Yuto Takano81528c02021-08-06 16:22:06 +0100856 return len(problems)
857
Yuto Takano5473be22021-08-17 10:14:01 +0100858 def output_check_result(self, name, problems):
Yuto Takano81528c02021-08-06 16:22:06 +0100859 """
860 Write out the PASS/FAIL status of a performed check depending on whether
861 there were problems.
Yuto Takanod70d4462021-08-09 12:45:51 +0100862
863 Args:
Yuto Takanod70d4462021-08-09 12:45:51 +0100864 * name: the name of the test
865 * problems: a List of encountered Problems
Yuto Takano81528c02021-08-06 16:22:06 +0100866 """
Yuto Takano39639672021-08-05 19:47:48 +0100867 if problems:
Yuto Takano55614b52021-08-07 01:00:18 +0100868 self.log.info("{}: FAIL\n".format(name))
869 for problem in problems:
870 self.log.warning(str(problem))
Darryl Greend5802922018-05-08 15:30:59 +0100871 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100872 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100873
Yuto Takano39639672021-08-05 19:47:48 +0100874def main():
875 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100876 Perform argument parsing, and create an instance of CodeParser and
877 NameChecker to begin the core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100878 """
Yuto Takanof005c332021-08-09 13:56:36 +0100879 parser = argparse.ArgumentParser(
Yuto Takano39639672021-08-05 19:47:48 +0100880 formatter_class=argparse.RawDescriptionHelpFormatter,
881 description=(
882 "This script confirms that the naming of all symbols and identifiers "
883 "in Mbed TLS are consistent with the house style and are also "
884 "self-consistent.\n\n"
Yuto Takanof005c332021-08-09 13:56:36 +0100885 "Expected to be run from the MbedTLS root directory.")
886 )
887 parser.add_argument(
888 "-v", "--verbose",
889 action="store_true",
890 help="show parse results"
891 )
892 parser.add_argument(
893 "-q", "--quiet",
894 action="store_true",
895 help="hide unnecessary text, explanations, and highlighs"
896 )
Darryl Greend5802922018-05-08 15:30:59 +0100897
Yuto Takanof005c332021-08-09 13:56:36 +0100898 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100899
Yuto Takano55c6c872021-08-09 15:35:19 +0100900 # Configure the global logger, which is then passed to the classes below
901 log = logging.getLogger()
902 log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
903 log.addHandler(logging.StreamHandler())
904
Darryl Greend5802922018-05-08 15:30:59 +0100905 try:
Yuto Takano55c6c872021-08-09 15:35:19 +0100906 code_parser = CodeParser(log)
907 parse_result = code_parser.comprehensive_parse()
Yuto Takanod93fa372021-08-06 23:05:55 +0100908 except Exception: # pylint: disable=broad-except
Darryl Greend5802922018-05-08 15:30:59 +0100909 traceback.print_exc()
910 sys.exit(2)
911
Yuto Takano55c6c872021-08-09 15:35:19 +0100912 name_checker = NameChecker(parse_result, log)
913 return_code = name_checker.perform_checks(quiet=args.quiet)
914
915 sys.exit(return_code)
916
Darryl Greend5802922018-05-08 15:30:59 +0100917if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100918 main()