blob: 5213bf4da390d70272039906e83aefee60bba8e5 [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
23The script performs the following checks:
Yuto Takano81528c02021-08-06 16:22:06 +010024
25- All exported and available symbols in the library object files, are explicitly
Yuto Takano159255a2021-08-06 17:00:28 +010026 declared in the header files. This uses the nm command.
Yuto Takano81528c02021-08-06 16:22:06 +010027- All macros, constants, and identifiers (function names, struct names, etc)
28 follow the required pattern.
29- Typo checking: All words that begin with MBED exist as macros or constants.
Darryl Greend5802922018-05-08 15:30:59 +010030"""
Yuto Takano39639672021-08-05 19:47:48 +010031
32import argparse
33import textwrap
Darryl Greend5802922018-05-08 15:30:59 +010034import os
35import sys
36import traceback
37import re
38import shutil
39import subprocess
40import logging
41
Yuto Takano81528c02021-08-06 16:22:06 +010042# Naming patterns to check against. These are defined outside the NameCheck
43# class for ease of modification.
Yuto Takanobb7dca42021-08-05 19:57:58 +010044MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
Yuto Takano81528c02021-08-06 16:22:06 +010045CONSTANTS_PATTERN = MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010046IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010047
48class Match(object):
Yuto Takano81528c02021-08-06 16:22:06 +010049 """
50 A class representing a match, together with its found position.
51
52 Fields:
53 * filename: the file that the match was in.
54 * line: the full line containing the match.
Yuto Takanoa4e75122021-08-06 17:23:28 +010055 * line_no: the line number of the file.
Yuto Takano81528c02021-08-06 16:22:06 +010056 * pos: a tuple of (start, end) positions on the line where the match is.
57 * name: the match itself.
58 """
Yuto Takanoa4e75122021-08-06 17:23:28 +010059 def __init__(self, filename, line, line_no, pos, name):
Yuto Takano39639672021-08-05 19:47:48 +010060 self.filename = filename
61 self.line = line
Yuto Takanoa4e75122021-08-06 17:23:28 +010062 self.line_no = line_no
Yuto Takano39639672021-08-05 19:47:48 +010063 self.pos = pos
64 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010065
Yuto Takanoa4e75122021-08-06 17:23:28 +010066 def __str__(self):
67 return (
68 " |\n" +
69 " | {}".format(self.line) +
70 " | " + self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
71 )
Yuto Takano39639672021-08-05 19:47:48 +010072class Problem(object):
Yuto Takano81528c02021-08-06 16:22:06 +010073 """
74 A parent class representing a form of static analysis error.
75
76 Fields:
77 * textwrapper: a TextWrapper instance to format problems nicely.
78 """
Yuto Takano39639672021-08-05 19:47:48 +010079 def __init__(self):
80 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +010081 self.textwrapper.width = 80
Yuto Takanoa4e75122021-08-06 17:23:28 +010082 self.textwrapper.initial_indent = " > "
Yuto Takano81528c02021-08-06 16:22:06 +010083 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +010084
85class SymbolNotInHeader(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +010086 """
87 A problem that occurs when an exported/available symbol in the object file
88 is not explicitly declared in header files. Created with
89 NameCheck.check_symbols_declared_in_header()
90
91 Fields:
92 * symbol_name: the name of the symbol.
93 """
Yuto Takano39639672021-08-05 19:47:48 +010094 def __init__(self, symbol_name):
95 self.symbol_name = symbol_name
96 Problem.__init__(self)
97
98 def __str__(self):
99 return self.textwrapper.fill(
100 "'{0}' was found as an available symbol in the output of nm, "
101 "however it was not declared in any header files."
102 .format(self.symbol_name))
103
104class PatternMismatch(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +0100105 """
106 A problem that occurs when something doesn't match the expected pattern.
107 Created with NameCheck.check_match_pattern()
108
109 Fields:
110 * pattern: the expected regex pattern
111 * match: the Match object in question
112 """
Yuto Takano39639672021-08-05 19:47:48 +0100113 def __init__(self, pattern, match):
114 self.pattern = pattern
115 self.match = match
116 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100117
Yuto Takano39639672021-08-05 19:47:48 +0100118 def __str__(self):
119 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100120 "{0}:{1}: '{2}' does not match the required pattern '{3}'."
121 .format(
122 self.match.filename,
123 self.match.line_no,
124 self.match.name,
125 self.pattern)) + "\n" + str(self.match)
Yuto Takano39639672021-08-05 19:47:48 +0100126
127class Typo(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +0100128 """
129 A problem that occurs when a word using MBED doesn't appear to be defined as
130 constants nor enum values. Created with NameCheck.check_for_typos()
131
132 Fields:
133 * match: the Match object of the MBED name in question.
134 """
Yuto Takano39639672021-08-05 19:47:48 +0100135 def __init__(self, match):
136 self.match = match
137 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100138
Yuto Takano39639672021-08-05 19:47:48 +0100139 def __str__(self):
Yuto Takanoa4e75122021-08-06 17:23:28 +0100140 match_len = self.match.pos[1] - self.match.pos[0]
Yuto Takano39639672021-08-05 19:47:48 +0100141 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100142 "{0}:{1}: '{2}' looks like a typo. It was not found in any "
143 "macros or any enums. If this is not a typo, put "
144 "//no-check-names after it."
145 .format(
146 self.match.filename,
147 self.match.line_no,
148 self.match.name)) + "\n" + str(self.match)
Darryl Greend5802922018-05-08 15:30:59 +0100149
150class NameCheck(object):
Yuto Takano81528c02021-08-06 16:22:06 +0100151 """
152 Representation of the core name checking operation performed by this script.
153 Shares a common logger, common excluded filenames, and a shared return_code.
154 """
Darryl Greend5802922018-05-08 15:30:59 +0100155 def __init__(self):
156 self.log = None
Darryl Greend5802922018-05-08 15:30:59 +0100157 self.check_repo_path()
158 self.return_code = 0
Yuto Takano81528c02021-08-06 16:22:06 +0100159 self.excluded_files = ["bn_mul", "compat-2.x.h"]
Darryl Greend5802922018-05-08 15:30:59 +0100160
161 def set_return_code(self, return_code):
162 if return_code > self.return_code:
Yuto Takano201f9e82021-08-06 16:36:54 +0100163 self.log.debug("Setting new return code to {}".format(return_code))
Darryl Greend5802922018-05-08 15:30:59 +0100164 self.return_code = return_code
165
Yuto Takano39639672021-08-05 19:47:48 +0100166 def setup_logger(self, verbose=False):
167 """
168 Set up a logger and set the change the default logging level from
Yuto Takano81528c02021-08-06 16:22:06 +0100169 WARNING to INFO. Loggers are better than print statements since their
Yuto Takano39639672021-08-05 19:47:48 +0100170 verbosity can be controlled.
171 """
Darryl Greend5802922018-05-08 15:30:59 +0100172 self.log = logging.getLogger()
Yuto Takano39639672021-08-05 19:47:48 +0100173 if verbose:
174 self.log.setLevel(logging.DEBUG)
175 else:
176 self.log.setLevel(logging.INFO)
Darryl Greend5802922018-05-08 15:30:59 +0100177 self.log.addHandler(logging.StreamHandler())
178
179 def check_repo_path(self):
Yuto Takano39639672021-08-05 19:47:48 +0100180 """
181 Check that the current working directory is the project root, and throw
182 an exception if not.
183 """
Yuto Takano5939a2a2021-08-06 16:40:30 +0100184 if (not os.path.isdir("include") or
185 not os.path.isdir("tests") or
186 not os.path.isdir("library")):
187 raise Exception("This script must be run from Mbed TLS root")
Darryl Greend5802922018-05-08 15:30:59 +0100188
Yuto Takano157444c2021-08-05 20:10:45 +0100189 def get_files(self, extension, directory):
Yuto Takano81528c02021-08-06 16:22:06 +0100190 """
191 Get all files that end with .extension in the specified directory
192 recursively.
193
194 Args:
195 * extension: the file extension to search for, without the dot
196 * directory: the directory to recursively search for
197
198 Returns a List of relative filepaths.
199 """
Darryl Greend5802922018-05-08 15:30:59 +0100200 filenames = []
201 for root, dirs, files in sorted(os.walk(directory)):
202 for filename in sorted(files):
203 if (filename not in self.excluded_files and
Yuto Takano157444c2021-08-05 20:10:45 +0100204 filename.endswith("." + extension)):
Darryl Greend5802922018-05-08 15:30:59 +0100205 filenames.append(os.path.join(root, filename))
206 return filenames
207
Yuto Takano81528c02021-08-06 16:22:06 +0100208 def parse_names_in_source(self):
209 """
210 Calls each parsing function to retrieve various elements of the code,
211 together with their source location. Puts the parsed values in the
212 internal variable self.parse_result.
213 """
214 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100215 self.log.debug(
216 "The following files are excluded from the search: {}"
217 .format(str(self.excluded_files))
218 )
Yuto Takano81528c02021-08-06 16:22:06 +0100219
220 m_headers = self.get_files("h", os.path.join("include", "mbedtls"))
221 p_headers = self.get_files("h", os.path.join("include", "psa"))
222 t_headers = ["3rdparty/everest/include/everest/everest.h",
223 "3rdparty/everest/include/everest/x25519.h"]
224 d_headers = self.get_files("h", os.path.join("tests", "include", "test", "drivers"))
225 l_headers = self.get_files("h", "library")
226 libraries = self.get_files("c", "library") + [
227 "3rdparty/everest/library/everest.c",
228 "3rdparty/everest/library/x25519.c"]
229
230 all_macros = self.parse_macros(
231 m_headers + p_headers + t_headers + l_headers + d_headers)
232 enum_consts = self.parse_enum_consts(
233 m_headers + l_headers + t_headers)
234 identifiers = self.parse_identifiers(
235 m_headers + p_headers + t_headers + l_headers)
236 mbed_names = self.parse_MBED_names(
237 m_headers + p_headers + t_headers + l_headers + libraries)
238 symbols = self.parse_symbols()
239
240 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
241 identifiers_justname = [x.name for x in identifiers]
242 actual_macros = []
243 for macro in all_macros:
244 if macro.name not in identifiers_justname:
245 actual_macros.append(macro)
246
247 self.log.debug("Found:")
248 self.log.debug(" {} Macros".format(len(all_macros)))
249 self.log.debug(" {} Non-identifier Macros".format(len(actual_macros)))
250 self.log.debug(" {} Enum Constants".format(len(enum_consts)))
251 self.log.debug(" {} Identifiers".format(len(identifiers)))
252 self.log.debug(" {} Exported Symbols".format(len(symbols)))
253 self.log.info("Analysing...")
254
255 self.parse_result = {
256 "macros": actual_macros,
257 "enum_consts": enum_consts,
258 "identifiers": identifiers,
259 "symbols": symbols,
260 "mbed_names": mbed_names
261 }
262
Yuto Takano39639672021-08-05 19:47:48 +0100263 def parse_macros(self, header_files):
264 """
265 Parse all macros defined by #define preprocessor directives.
266
267 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100268 * header_files: A List of filepaths to look through.
269
270 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100271 """
Yuto Takano5c1acf22021-08-06 16:44:08 +0100272 MACRO_REGEX = r"# *define +(?P<macro>\w+)"
Yuto Takano39639672021-08-05 19:47:48 +0100273 NON_MACROS = (
274 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
275 )
276
277 macros = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100278 self.log.debug("Looking for macros in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100279 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100280 with open(header_file, "r") as header:
Yuto Takanoa4e75122021-08-06 17:23:28 +0100281 line_no = 0
Yuto Takano39639672021-08-05 19:47:48 +0100282 for line in header:
Yuto Takanoa4e75122021-08-06 17:23:28 +0100283 line_no += 1
Yuto Takano81528c02021-08-06 16:22:06 +0100284 for macro in re.finditer(MACRO_REGEX, line):
285 if not macro.group("macro").startswith(NON_MACROS):
286 macros.append(Match(
287 header_file,
288 line,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100289 line_no,
Yuto Takano81528c02021-08-06 16:22:06 +0100290 (macro.start(), macro.end()),
291 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100292
Yuto Takano39639672021-08-05 19:47:48 +0100293 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100294
Yuto Takanobb7dca42021-08-05 19:57:58 +0100295 def parse_MBED_names(self, files):
Yuto Takano39639672021-08-05 19:47:48 +0100296 """
297 Parse all words in the file that begin with MBED. Includes macros.
Yuto Takano81528c02021-08-06 16:22:06 +0100298 There have been typos of TLS, hence the broader check than MBEDTLS.
Yuto Takano39639672021-08-05 19:47:48 +0100299
300 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100301 * files: a List of filepaths to look through.
302
303 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100304 """
305 MBED_names = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100306 self.log.debug("Looking for MBED names in {} files".format(len(files)))
Yuto Takanobb7dca42021-08-05 19:57:58 +0100307 for filename in files:
Yuto Takano39639672021-08-05 19:47:48 +0100308 with open(filename, "r") as fp:
Yuto Takanoa4e75122021-08-06 17:23:28 +0100309 line_no = 0
Yuto Takano39639672021-08-05 19:47:48 +0100310 for line in fp:
Yuto Takanoa4e75122021-08-06 17:23:28 +0100311 line_no += 1
Yuto Takano81528c02021-08-06 16:22:06 +0100312 # Ignore any names that are deliberately opted-out or in
313 # legacy error directives
314 if re.search(r"// *no-check-names|#error", line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100315 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100316
Yuto Takano39639672021-08-05 19:47:48 +0100317 for name in re.finditer(r"\bMBED.+?_[A-Z0-9_]*", line):
318 MBED_names.append(Match(
319 filename,
320 line,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100321 line_no,
Yuto Takano39639672021-08-05 19:47:48 +0100322 (name.start(), name.end()),
323 name.group(0)
324 ))
325
326 return MBED_names
327
328 def parse_enum_consts(self, header_files):
329 """
330 Parse all enum value constants that are declared.
331
332 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100333 * header_files: A List of filepaths to look through.
Yuto Takano39639672021-08-05 19:47:48 +0100334
Yuto Takano81528c02021-08-06 16:22:06 +0100335 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100336 """
337
338 enum_consts = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100339 self.log.debug("Looking for enum consts in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100340 for header_file in header_files:
341 # Emulate a finite state machine to parse enum declarations.
Yuto Takano81528c02021-08-06 16:22:06 +0100342 # 0 = not in enum
343 # 1 = inside enum
344 # 2 = almost inside enum
Darryl Greend5802922018-05-08 15:30:59 +0100345 state = 0
346 with open(header_file, "r") as header:
Yuto Takanoa4e75122021-08-06 17:23:28 +0100347 line_no = 0
Yuto Takano39639672021-08-05 19:47:48 +0100348 for line in header:
Yuto Takanoa4e75122021-08-06 17:23:28 +0100349 line_no += 1
Yuto Takano13ecd992021-08-06 16:56:52 +0100350 # Match typedefs and brackets only when they are at the
351 # beginning of the line -- if they are indented, they might
352 # be sub-structures within structs, etc.
353 if state is 0 and re.match(r"^(typedef +)?enum +{", line):
Darryl Greend5802922018-05-08 15:30:59 +0100354 state = 1
Yuto Takano13ecd992021-08-06 16:56:52 +0100355 elif state is 0 and re.match(r"^(typedef +)?enum", line):
Darryl Greend5802922018-05-08 15:30:59 +0100356 state = 2
357 elif state is 2 and re.match(r"^{", line):
358 state = 1
359 elif state is 1 and re.match(r"^}", line):
360 state = 0
Yuto Takano13ecd992021-08-06 16:56:52 +0100361 elif state is 1 and not re.match(r" *#", line):
362 enum_const = re.match(r" *(?P<enum_const>\w+)", line)
Darryl Greend5802922018-05-08 15:30:59 +0100363 if enum_const:
Yuto Takano39639672021-08-05 19:47:48 +0100364 enum_consts.append(Match(
365 header_file,
366 line,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100367 line_no,
Yuto Takano39639672021-08-05 19:47:48 +0100368 (enum_const.start(), enum_const.end()),
369 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100370
Yuto Takano39639672021-08-05 19:47:48 +0100371 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100372
Yuto Takano39639672021-08-05 19:47:48 +0100373 def parse_identifiers(self, header_files):
374 """
375 Parse all lines of a header where a function identifier is declared,
Yuto Takano81528c02021-08-06 16:22:06 +0100376 based on some huersitics. Highly dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100377
Yuto Takano39639672021-08-05 19:47:48 +0100378 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100379 * header_files: A List of filepaths to look through.
380
381 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100382 """
Yuto Takano81528c02021-08-06 16:22:06 +0100383 EXCLUDED_LINES = (
384 r"^("
Yuto Takano13ecd992021-08-06 16:56:52 +0100385 r"extern +\"C\"|"
386 r"(typedef +)?(struct|union|enum)( *{)?$|"
387 r"} *;?$|"
Yuto Takano81528c02021-08-06 16:22:06 +0100388 r"$|"
389 r"//|"
390 r"#"
391 r")"
Darryl Greend5802922018-05-08 15:30:59 +0100392 )
Darryl Greend5802922018-05-08 15:30:59 +0100393
Yuto Takano39639672021-08-05 19:47:48 +0100394 identifiers = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100395 self.log.debug("Looking for identifiers in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100396 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100397 with open(header_file, "r") as header:
Yuto Takanoa4e75122021-08-06 17:23:28 +0100398 line_no = 0
Yuto Takano39639672021-08-05 19:47:48 +0100399 in_block_comment = False
Yuto Takano81528c02021-08-06 16:22:06 +0100400 previous_line = None
Darryl Greend5802922018-05-08 15:30:59 +0100401
Yuto Takano39639672021-08-05 19:47:48 +0100402 for line in header:
Yuto Takanoa4e75122021-08-06 17:23:28 +0100403 line_no += 1
Yuto Takano81528c02021-08-06 16:22:06 +0100404 # Skip parsing this line if a block comment ends on it,
405 # but don't skip if it has just started -- there is a chance
406 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100407 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100408 in_block_comment = not in_block_comment
409 if re.search(r"\*/", line):
410 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100411 continue
412
Yuto Takano81528c02021-08-06 16:22:06 +0100413 if in_block_comment:
414 previous_line = None
415 continue
416
417 if re.match(EXCLUDED_LINES, line):
418 previous_line = None
419 continue
420
421 # Match "^something something$", with optional inline/static
422 # This *might* be a function with its argument brackets on
423 # the next line, or a struct declaration, so keep note of it
424 if re.match(
Yuto Takano13ecd992021-08-06 16:56:52 +0100425 r"(inline +|static +|typedef +)*\w+ +\w+$",
Yuto Takano81528c02021-08-06 16:22:06 +0100426 line):
427 previous_line = line
428 continue
429
430 # If previous line seemed to start an unfinished declaration
431 # (as above), and this line begins with a bracket, concat
432 # them and treat them as one line.
433 if previous_line and re.match(" *[\({]", line):
434 line = previous_line.strip() + line.strip()
435 previous_line = None
436
437 # Skip parsing if line has a space in front = hueristic to
438 # skip function argument lines (highly subject to formatting
439 # changes)
440 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100441 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100442
Yuto Takano39639672021-08-05 19:47:48 +0100443 identifier = re.search(
Yuto Takano13ecd992021-08-06 16:56:52 +0100444 # Match " something(" or " *something(". function calls.
Yuto Takano81528c02021-08-06 16:22:06 +0100445 r".* \**(\w+)\(|"
446 # Match (*something)(
447 r".*\( *\* *(\w+) *\) *\(|"
448 # Match names of named data structures
449 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
450 # Match names of typedef instances, after closing bracket
451 r"}? *(\w+)[;[].*",
Yuto Takano39639672021-08-05 19:47:48 +0100452 line
453 )
454
455 if identifier:
Yuto Takano81528c02021-08-06 16:22:06 +0100456 # Find the group that matched, and append it
Yuto Takano39639672021-08-05 19:47:48 +0100457 for group in identifier.groups():
458 if group:
459 identifiers.append(Match(
460 header_file,
461 line,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100462 line_no,
Yuto Takano39639672021-08-05 19:47:48 +0100463 (identifier.start(), identifier.end()),
Yuto Takano81528c02021-08-06 16:22:06 +0100464 group))
Yuto Takano39639672021-08-05 19:47:48 +0100465
466 return identifiers
467
468 def parse_symbols(self):
469 """
470 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
471 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100472 Exceptions thrown here are rethrown because they would be critical
473 errors that void several tests, and thus needs to halt the program. This
474 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100475
Yuto Takano81528c02021-08-06 16:22:06 +0100476 Returns a List of unique symbols defined and used in the libraries.
477 """
478 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100479 symbols = []
480
481 # Back up the config and atomically compile with the full configratuion.
482 shutil.copy("include/mbedtls/mbedtls_config.h",
Yuto Takano81528c02021-08-06 16:22:06 +0100483 "include/mbedtls/mbedtls_config.h.bak")
Darryl Greend5802922018-05-08 15:30:59 +0100484 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100485 # Use check=True in all subprocess calls so that failures are raised
486 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100487 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100488 ["python3", "scripts/config.py", "full"],
Yuto Takano39639672021-08-05 19:47:48 +0100489 encoding=sys.stdout.encoding,
490 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100491 )
492 my_environment = os.environ.copy()
493 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano39639672021-08-05 19:47:48 +0100494 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100495 ["make", "clean", "lib"],
496 env=my_environment,
Yuto Takano39639672021-08-05 19:47:48 +0100497 encoding=sys.stdout.encoding,
498 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100499 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100500 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100501 )
Yuto Takano39639672021-08-05 19:47:48 +0100502
503 # Perform object file analysis using nm
504 symbols = self.parse_symbols_from_nm(
505 ["library/libmbedcrypto.a",
506 "library/libmbedtls.a",
507 "library/libmbedx509.a"])
508
509 symbols.sort()
510
511 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100512 ["make", "clean"],
Yuto Takano39639672021-08-05 19:47:48 +0100513 encoding=sys.stdout.encoding,
514 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100515 )
516 except subprocess.CalledProcessError as error:
Darryl Greend5802922018-05-08 15:30:59 +0100517 self.set_return_code(2)
Yuto Takano81528c02021-08-06 16:22:06 +0100518 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100519 finally:
520 shutil.move("include/mbedtls/mbedtls_config.h.bak",
521 "include/mbedtls/mbedtls_config.h")
522
523 return symbols
524
525 def parse_symbols_from_nm(self, object_files):
526 """
527 Run nm to retrieve the list of referenced symbols in each object file.
528 Does not return the position data since it is of no use.
529
Yuto Takano81528c02021-08-06 16:22:06 +0100530 Args:
531 * object_files: a List of compiled object files to search through.
532
533 Returns a List of unique symbols defined and used in any of the object
534 files.
Yuto Takano39639672021-08-05 19:47:48 +0100535 """
536 UNDEFINED_SYMBOL = r"^\S+: +U |^$|^\S+:$"
537 VALID_SYMBOL = r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)"
Yuto Takanoe77f6992021-08-05 20:22:59 +0100538 EXCLUSIONS = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100539
540 symbols = []
541
Yuto Takano81528c02021-08-06 16:22:06 +0100542 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100543 nm_output = ""
544 for lib in object_files:
545 nm_output += subprocess.run(
546 ["nm", "-og", lib],
547 encoding=sys.stdout.encoding,
548 stdout=subprocess.PIPE,
549 stderr=subprocess.STDOUT,
550 check=True
551 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100552
Yuto Takano39639672021-08-05 19:47:48 +0100553 for line in nm_output.splitlines():
554 if not re.match(UNDEFINED_SYMBOL, line):
555 symbol = re.match(VALID_SYMBOL, line)
Yuto Takanoe77f6992021-08-05 20:22:59 +0100556 if symbol and not symbol.group("symbol").startswith(EXCLUSIONS):
557 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100558 else:
559 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100560
Yuto Takano39639672021-08-05 19:47:48 +0100561 return symbols
562
Yuto Takano81528c02021-08-06 16:22:06 +0100563 def perform_checks(self, show_problems: True):
Yuto Takano39639672021-08-05 19:47:48 +0100564 """
565 Perform each check in order, output its PASS/FAIL status. Maintain an
566 overall test status, and output that at the end.
Yuto Takano81528c02021-08-06 16:22:06 +0100567
568 Args:
569 * show_problems: whether to show the problematic examples.
Yuto Takano39639672021-08-05 19:47:48 +0100570 """
Yuto Takano81528c02021-08-06 16:22:06 +0100571 self.log.info("=============")
Yuto Takano39639672021-08-05 19:47:48 +0100572 problems = 0
573
Yuto Takano81528c02021-08-06 16:22:06 +0100574 problems += self.check_symbols_declared_in_header(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100575
576 pattern_checks = [
577 ("macros", MACRO_PATTERN),
Yuto Takano81528c02021-08-06 16:22:06 +0100578 ("enum_consts", CONSTANTS_PATTERN),
Yuto Takano39639672021-08-05 19:47:48 +0100579 ("identifiers", IDENTIFIER_PATTERN)]
580 for group, check_pattern in pattern_checks:
Yuto Takano81528c02021-08-06 16:22:06 +0100581 problems += self.check_match_pattern(
582 show_problems, group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100583
Yuto Takano81528c02021-08-06 16:22:06 +0100584 problems += self.check_for_typos(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100585
586 self.log.info("=============")
587 if problems > 0:
588 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano81528c02021-08-06 16:22:06 +0100589 if not show_problems:
590 self.log.info("Remove --quiet to show the problems.")
Yuto Takano39639672021-08-05 19:47:48 +0100591 else:
592 self.log.info("PASS")
Darryl Greend5802922018-05-08 15:30:59 +0100593
Yuto Takano81528c02021-08-06 16:22:06 +0100594 def check_symbols_declared_in_header(self, show_problems):
Yuto Takano39639672021-08-05 19:47:48 +0100595 """
596 Perform a check that all detected symbols in the library object files
597 are properly declared in headers.
Darryl Greend5802922018-05-08 15:30:59 +0100598
Yuto Takano81528c02021-08-06 16:22:06 +0100599 Args:
600 * show_problems: whether to show the problematic examples.
601
602 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100603 """
604 problems = []
605 for symbol in self.parse_result["symbols"]:
606 found_symbol_declared = False
607 for identifier_match in self.parse_result["identifiers"]:
608 if symbol == identifier_match.name:
609 found_symbol_declared = True
610 break
Yuto Takano81528c02021-08-06 16:22:06 +0100611
Yuto Takano39639672021-08-05 19:47:48 +0100612 if not found_symbol_declared:
613 problems.append(SymbolNotInHeader(symbol))
614
Yuto Takano81528c02021-08-06 16:22:06 +0100615 self.output_check_result("All symbols in header", problems, show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100616 return len(problems)
617
Yuto Takano81528c02021-08-06 16:22:06 +0100618
619 def check_match_pattern(self, show_problems, group_to_check, check_pattern):
620 """
621 Perform a check that all items of a group conform to a regex pattern.
622
623 Args:
624 * show_problems: whether to show the problematic examples.
625 * group_to_check: string key to index into self.parse_result.
626 * check_pattern: the regex to check against.
627
628 Returns the number of problems that need fixing.
629 """
Yuto Takano39639672021-08-05 19:47:48 +0100630 problems = []
631 for item_match in self.parse_result[group_to_check]:
632 if not re.match(check_pattern, item_match.name):
633 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano201f9e82021-08-06 16:36:54 +0100634 # Double underscore is a reserved identifier, never to be used
Yuto Takanoc763cc32021-08-05 20:06:34 +0100635 if re.match(r".*__.*", item_match.name):
636 problems.append(PatternMismatch("double underscore", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100637
638 self.output_check_result(
639 "Naming patterns of {}".format(group_to_check),
640 problems,
641 show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100642 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100643
Yuto Takano81528c02021-08-06 16:22:06 +0100644 def check_for_typos(self, show_problems):
645 """
646 Perform a check that all words in the soure code beginning with MBED are
647 either defined as macros, or as enum constants.
648
649 Args:
650 * show_problems: whether to show the problematic examples.
651
652 Returns the number of problems that need fixing.
653 """
Yuto Takano39639672021-08-05 19:47:48 +0100654 problems = []
655 all_caps_names = list(set([
656 match.name for match
657 in self.parse_result["macros"] + self.parse_result["enum_consts"]]
Darryl Greend5802922018-05-08 15:30:59 +0100658 ))
Yuto Takano39639672021-08-05 19:47:48 +0100659
660 TYPO_EXCLUSION = r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$"
661
662 for name_match in self.parse_result["mbed_names"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100663 found = name_match.name in all_caps_names
664
665 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
666 # PSA driver, they will not exist as macros. However, they
667 # should still be checked for typos using the equivalent
668 # BUILTINs that exist.
669 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
670 found = name_match.name.replace(
671 "MBEDTLS_PSA_ACCEL_",
672 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
673
674 if not found and not re.search(TYPO_EXCLUSION, name_match.name):
Yuto Takano201f9e82021-08-06 16:36:54 +0100675 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100676
Yuto Takano81528c02021-08-06 16:22:06 +0100677 self.output_check_result("Likely typos", problems, show_problems)
678 return len(problems)
679
680 def output_check_result(self, name, problems, show_problems):
681 """
682 Write out the PASS/FAIL status of a performed check depending on whether
683 there were problems.
684
685 Args:
686 * show_problems: whether to show the problematic examples.
687 """
Yuto Takano39639672021-08-05 19:47:48 +0100688 if problems:
Darryl Greend5802922018-05-08 15:30:59 +0100689 self.set_return_code(1)
Yuto Takano81528c02021-08-06 16:22:06 +0100690 self.log.info("{}: FAIL".format(name))
691 if show_problems:
692 self.log.info("")
693 for problem in problems:
694 self.log.warn(str(problem) + "\n")
Darryl Greend5802922018-05-08 15:30:59 +0100695 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100696 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100697
Yuto Takano39639672021-08-05 19:47:48 +0100698def main():
699 """
Yuto Takano81528c02021-08-06 16:22:06 +0100700 Perform argument parsing, and create an instance of NameCheck to begin the
701 core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100702 """
Darryl Greend5802922018-05-08 15:30:59 +0100703
Yuto Takano39639672021-08-05 19:47:48 +0100704 parser = argparse.ArgumentParser(
705 formatter_class=argparse.RawDescriptionHelpFormatter,
706 description=(
707 "This script confirms that the naming of all symbols and identifiers "
708 "in Mbed TLS are consistent with the house style and are also "
709 "self-consistent.\n\n"
710 "Expected to be run from the MbedTLS root directory."))
Darryl Greend5802922018-05-08 15:30:59 +0100711
Yuto Takano39639672021-08-05 19:47:48 +0100712 parser.add_argument("-v", "--verbose",
713 action="store_true",
Yuto Takano81528c02021-08-06 16:22:06 +0100714 help="show parse results")
715
716 parser.add_argument("-q", "--quiet",
717 action="store_true",
718 help="hide unnecessary text and problematic examples")
719
Yuto Takano39639672021-08-05 19:47:48 +0100720 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100721
Darryl Greend5802922018-05-08 15:30:59 +0100722 try:
723 name_check = NameCheck()
Yuto Takano39639672021-08-05 19:47:48 +0100724 name_check.setup_logger(verbose=args.verbose)
725 name_check.parse_names_in_source()
Yuto Takano81528c02021-08-06 16:22:06 +0100726 name_check.perform_checks(show_problems=not args.quiet)
727 sys.exit(name_check.return_code)
728 except subprocess.CalledProcessError as error:
729 traceback.print_exc()
730 print("!! Compilation faced a critical error, "
731 "check-names can't continue further.")
Darryl Greend5802922018-05-08 15:30:59 +0100732 sys.exit(name_check.return_code)
733 except Exception:
734 traceback.print_exc()
735 sys.exit(2)
736
Darryl Greend5802922018-05-08 15:30:59 +0100737if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100738 main()