blob: b9e028bdd30e7d519624474b274225075194cf77 [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 Takano81528c02021-08-06 16:22:06 +010020are consistent with the house style and are also self-consistent. It performs
21the following checks:
22
23- All exported and available symbols in the library object files, are explicitly
24 declared in the header files.
25- All macros, constants, and identifiers (function names, struct names, etc)
26 follow the required pattern.
27- Typo checking: All words that begin with MBED exist as macros or constants.
Darryl Greend5802922018-05-08 15:30:59 +010028"""
Yuto Takano39639672021-08-05 19:47:48 +010029
30import argparse
31import textwrap
Darryl Greend5802922018-05-08 15:30:59 +010032import os
33import sys
34import traceback
35import re
36import shutil
37import subprocess
38import logging
39
Yuto Takano81528c02021-08-06 16:22:06 +010040# Naming patterns to check against. These are defined outside the NameCheck
41# class for ease of modification.
Yuto Takanobb7dca42021-08-05 19:57:58 +010042MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
Yuto Takano81528c02021-08-06 16:22:06 +010043CONSTANTS_PATTERN = MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010044IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010045
46class Match(object):
Yuto Takano81528c02021-08-06 16:22:06 +010047 """
48 A class representing a match, together with its found position.
49
50 Fields:
51 * filename: the file that the match was in.
52 * line: the full line containing the match.
53 * pos: a tuple of (start, end) positions on the line where the match is.
54 * name: the match itself.
55 """
Yuto Takano39639672021-08-05 19:47:48 +010056 def __init__(self, filename, line, pos, name):
57 self.filename = filename
58 self.line = line
59 self.pos = pos
60 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010061
62class Problem(object):
Yuto Takano81528c02021-08-06 16:22:06 +010063 """
64 A parent class representing a form of static analysis error.
65
66 Fields:
67 * textwrapper: a TextWrapper instance to format problems nicely.
68 """
Yuto Takano39639672021-08-05 19:47:48 +010069 def __init__(self):
70 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +010071 self.textwrapper.width = 80
72 self.textwrapper.initial_indent = " * "
73 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +010074
75class SymbolNotInHeader(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +010076 """
77 A problem that occurs when an exported/available symbol in the object file
78 is not explicitly declared in header files. Created with
79 NameCheck.check_symbols_declared_in_header()
80
81 Fields:
82 * symbol_name: the name of the symbol.
83 """
Yuto Takano39639672021-08-05 19:47:48 +010084 def __init__(self, symbol_name):
85 self.symbol_name = symbol_name
86 Problem.__init__(self)
87
88 def __str__(self):
89 return self.textwrapper.fill(
90 "'{0}' was found as an available symbol in the output of nm, "
91 "however it was not declared in any header files."
92 .format(self.symbol_name))
93
94class PatternMismatch(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +010095 """
96 A problem that occurs when something doesn't match the expected pattern.
97 Created with NameCheck.check_match_pattern()
98
99 Fields:
100 * pattern: the expected regex pattern
101 * match: the Match object in question
102 """
Yuto Takano39639672021-08-05 19:47:48 +0100103 def __init__(self, pattern, match):
104 self.pattern = pattern
105 self.match = match
106 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100107
Yuto Takano39639672021-08-05 19:47:48 +0100108 def __str__(self):
109 return self.textwrapper.fill(
110 "{0}: '{1}' does not match the required pattern '{2}'."
111 .format(self.match.filename, self.match.name, self.pattern))
112
113class Typo(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +0100114 """
115 A problem that occurs when a word using MBED doesn't appear to be defined as
116 constants nor enum values. Created with NameCheck.check_for_typos()
117
118 Fields:
119 * match: the Match object of the MBED name in question.
120 """
Yuto Takano39639672021-08-05 19:47:48 +0100121 def __init__(self, match):
122 self.match = match
123 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100124
Yuto Takano39639672021-08-05 19:47:48 +0100125 def __str__(self):
126 return self.textwrapper.fill(
127 "{0}: '{1}' looks like a typo. It was not found in any macros or "
128 "any enums. If this is not a typo, put //no-check-names after it."
129 .format(self.match.filename, self.match.name))
Darryl Greend5802922018-05-08 15:30:59 +0100130
131class NameCheck(object):
Yuto Takano81528c02021-08-06 16:22:06 +0100132 """
133 Representation of the core name checking operation performed by this script.
134 Shares a common logger, common excluded filenames, and a shared return_code.
135 """
Darryl Greend5802922018-05-08 15:30:59 +0100136 def __init__(self):
137 self.log = None
Darryl Greend5802922018-05-08 15:30:59 +0100138 self.check_repo_path()
139 self.return_code = 0
Yuto Takano81528c02021-08-06 16:22:06 +0100140 self.excluded_files = ["bn_mul", "compat-2.x.h"]
Darryl Greend5802922018-05-08 15:30:59 +0100141
142 def set_return_code(self, return_code):
143 if return_code > self.return_code:
Yuto Takano201f9e82021-08-06 16:36:54 +0100144 self.log.debug("Setting new return code to {}".format(return_code))
Darryl Greend5802922018-05-08 15:30:59 +0100145 self.return_code = return_code
146
Yuto Takano39639672021-08-05 19:47:48 +0100147 def setup_logger(self, verbose=False):
148 """
149 Set up a logger and set the change the default logging level from
Yuto Takano81528c02021-08-06 16:22:06 +0100150 WARNING to INFO. Loggers are better than print statements since their
Yuto Takano39639672021-08-05 19:47:48 +0100151 verbosity can be controlled.
152 """
Darryl Greend5802922018-05-08 15:30:59 +0100153 self.log = logging.getLogger()
Yuto Takano39639672021-08-05 19:47:48 +0100154 if verbose:
155 self.log.setLevel(logging.DEBUG)
156 else:
157 self.log.setLevel(logging.INFO)
Darryl Greend5802922018-05-08 15:30:59 +0100158 self.log.addHandler(logging.StreamHandler())
159
160 def check_repo_path(self):
Yuto Takano39639672021-08-05 19:47:48 +0100161 """
162 Check that the current working directory is the project root, and throw
163 an exception if not.
164 """
Yuto Takano5939a2a2021-08-06 16:40:30 +0100165 if (not os.path.isdir("include") or
166 not os.path.isdir("tests") or
167 not os.path.isdir("library")):
168 raise Exception("This script must be run from Mbed TLS root")
Darryl Greend5802922018-05-08 15:30:59 +0100169
Yuto Takano157444c2021-08-05 20:10:45 +0100170 def get_files(self, extension, directory):
Yuto Takano81528c02021-08-06 16:22:06 +0100171 """
172 Get all files that end with .extension in the specified directory
173 recursively.
174
175 Args:
176 * extension: the file extension to search for, without the dot
177 * directory: the directory to recursively search for
178
179 Returns a List of relative filepaths.
180 """
Darryl Greend5802922018-05-08 15:30:59 +0100181 filenames = []
182 for root, dirs, files in sorted(os.walk(directory)):
183 for filename in sorted(files):
184 if (filename not in self.excluded_files and
Yuto Takano157444c2021-08-05 20:10:45 +0100185 filename.endswith("." + extension)):
Darryl Greend5802922018-05-08 15:30:59 +0100186 filenames.append(os.path.join(root, filename))
187 return filenames
188
Yuto Takano81528c02021-08-06 16:22:06 +0100189 def parse_names_in_source(self):
190 """
191 Calls each parsing function to retrieve various elements of the code,
192 together with their source location. Puts the parsed values in the
193 internal variable self.parse_result.
194 """
195 self.log.info("Parsing source code...")
196
197 m_headers = self.get_files("h", os.path.join("include", "mbedtls"))
198 p_headers = self.get_files("h", os.path.join("include", "psa"))
199 t_headers = ["3rdparty/everest/include/everest/everest.h",
200 "3rdparty/everest/include/everest/x25519.h"]
201 d_headers = self.get_files("h", os.path.join("tests", "include", "test", "drivers"))
202 l_headers = self.get_files("h", "library")
203 libraries = self.get_files("c", "library") + [
204 "3rdparty/everest/library/everest.c",
205 "3rdparty/everest/library/x25519.c"]
206
207 all_macros = self.parse_macros(
208 m_headers + p_headers + t_headers + l_headers + d_headers)
209 enum_consts = self.parse_enum_consts(
210 m_headers + l_headers + t_headers)
211 identifiers = self.parse_identifiers(
212 m_headers + p_headers + t_headers + l_headers)
213 mbed_names = self.parse_MBED_names(
214 m_headers + p_headers + t_headers + l_headers + libraries)
215 symbols = self.parse_symbols()
216
217 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
218 identifiers_justname = [x.name for x in identifiers]
219 actual_macros = []
220 for macro in all_macros:
221 if macro.name not in identifiers_justname:
222 actual_macros.append(macro)
223
224 self.log.debug("Found:")
225 self.log.debug(" {} Macros".format(len(all_macros)))
226 self.log.debug(" {} Non-identifier Macros".format(len(actual_macros)))
227 self.log.debug(" {} Enum Constants".format(len(enum_consts)))
228 self.log.debug(" {} Identifiers".format(len(identifiers)))
229 self.log.debug(" {} Exported Symbols".format(len(symbols)))
230 self.log.info("Analysing...")
231
232 self.parse_result = {
233 "macros": actual_macros,
234 "enum_consts": enum_consts,
235 "identifiers": identifiers,
236 "symbols": symbols,
237 "mbed_names": mbed_names
238 }
239
Yuto Takano39639672021-08-05 19:47:48 +0100240 def parse_macros(self, header_files):
241 """
242 Parse all macros defined by #define preprocessor directives.
243
244 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100245 * header_files: A List of filepaths to look through.
246
247 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100248 """
249 MACRO_REGEX = r"#define (?P<macro>\w+)"
250 NON_MACROS = (
251 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
252 )
253
254 macros = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100255 self.log.debug("Looking for macros in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100256 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100257 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100258 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100259 for macro in re.finditer(MACRO_REGEX, line):
260 if not macro.group("macro").startswith(NON_MACROS):
261 macros.append(Match(
262 header_file,
263 line,
264 (macro.start(), macro.end()),
265 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100266
Yuto Takano39639672021-08-05 19:47:48 +0100267 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100268
Yuto Takanobb7dca42021-08-05 19:57:58 +0100269 def parse_MBED_names(self, files):
Yuto Takano39639672021-08-05 19:47:48 +0100270 """
271 Parse all words in the file that begin with MBED. Includes macros.
Yuto Takano81528c02021-08-06 16:22:06 +0100272 There have been typos of TLS, hence the broader check than MBEDTLS.
Yuto Takano39639672021-08-05 19:47:48 +0100273
274 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100275 * files: a List of filepaths to look through.
276
277 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100278 """
279 MBED_names = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100280 self.log.debug("Looking for MBED names in {} files".format(len(files)))
Yuto Takanobb7dca42021-08-05 19:57:58 +0100281 for filename in files:
Yuto Takano39639672021-08-05 19:47:48 +0100282 with open(filename, "r") as fp:
283 for line in fp:
Yuto Takano81528c02021-08-06 16:22:06 +0100284 # Ignore any names that are deliberately opted-out or in
285 # legacy error directives
286 if re.search(r"// *no-check-names|#error", line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100287 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100288
Yuto Takano39639672021-08-05 19:47:48 +0100289 for name in re.finditer(r"\bMBED.+?_[A-Z0-9_]*", line):
290 MBED_names.append(Match(
291 filename,
292 line,
293 (name.start(), name.end()),
294 name.group(0)
295 ))
296
297 return MBED_names
298
299 def parse_enum_consts(self, header_files):
300 """
301 Parse all enum value constants that are declared.
302
303 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100304 * header_files: A List of filepaths to look through.
Yuto Takano39639672021-08-05 19:47:48 +0100305
Yuto Takano81528c02021-08-06 16:22:06 +0100306 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100307 """
308
309 enum_consts = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100310 self.log.debug("Looking for enum consts in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100311 for header_file in header_files:
312 # Emulate a finite state machine to parse enum declarations.
Yuto Takano81528c02021-08-06 16:22:06 +0100313 # 0 = not in enum
314 # 1 = inside enum
315 # 2 = almost inside enum
Darryl Greend5802922018-05-08 15:30:59 +0100316 state = 0
317 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100318 for line in header:
Darryl Greend5802922018-05-08 15:30:59 +0100319 if state is 0 and re.match(r"^(typedef )?enum {", line):
320 state = 1
321 elif state is 0 and re.match(r"^(typedef )?enum", line):
322 state = 2
323 elif state is 2 and re.match(r"^{", line):
324 state = 1
325 elif state is 1 and re.match(r"^}", line):
326 state = 0
Yuto Takano0fd48f72021-08-05 20:32:55 +0100327 elif state is 1 and not re.match(r"^#", line):
Darryl Greend5802922018-05-08 15:30:59 +0100328 enum_const = re.match(r"^\s*(?P<enum_const>\w+)", line)
329 if enum_const:
Yuto Takano39639672021-08-05 19:47:48 +0100330 enum_consts.append(Match(
331 header_file,
332 line,
333 (enum_const.start(), enum_const.end()),
334 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100335
Yuto Takano39639672021-08-05 19:47:48 +0100336 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100337
Yuto Takano39639672021-08-05 19:47:48 +0100338 def parse_identifiers(self, header_files):
339 """
340 Parse all lines of a header where a function identifier is declared,
Yuto Takano81528c02021-08-06 16:22:06 +0100341 based on some huersitics. Highly dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100342
Yuto Takano39639672021-08-05 19:47:48 +0100343 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100344 * header_files: A List of filepaths to look through.
345
346 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100347 """
Yuto Takano81528c02021-08-06 16:22:06 +0100348 EXCLUDED_LINES = (
349 r"^("
350 r"extern \"C\"|"
351 r"(typedef )?(struct|union|enum)( {)?$|"
352 r"};?$|"
353 r"$|"
354 r"//|"
355 r"#"
356 r")"
Darryl Greend5802922018-05-08 15:30:59 +0100357 )
Darryl Greend5802922018-05-08 15:30:59 +0100358
Yuto Takano39639672021-08-05 19:47:48 +0100359 identifiers = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100360 self.log.debug("Looking for identifiers in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100361 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100362 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100363 in_block_comment = False
Yuto Takano81528c02021-08-06 16:22:06 +0100364 previous_line = None
Darryl Greend5802922018-05-08 15:30:59 +0100365
Yuto Takano39639672021-08-05 19:47:48 +0100366 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100367 # Skip parsing this line if a block comment ends on it,
368 # but don't skip if it has just started -- there is a chance
369 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100370 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100371 in_block_comment = not in_block_comment
372 if re.search(r"\*/", line):
373 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100374 continue
375
Yuto Takano81528c02021-08-06 16:22:06 +0100376 if in_block_comment:
377 previous_line = None
378 continue
379
380 if re.match(EXCLUDED_LINES, line):
381 previous_line = None
382 continue
383
384 # Match "^something something$", with optional inline/static
385 # This *might* be a function with its argument brackets on
386 # the next line, or a struct declaration, so keep note of it
387 if re.match(
388 r"(inline |static |typedef )*\w+ \w+$",
389 line):
390 previous_line = line
391 continue
392
393 # If previous line seemed to start an unfinished declaration
394 # (as above), and this line begins with a bracket, concat
395 # them and treat them as one line.
396 if previous_line and re.match(" *[\({]", line):
397 line = previous_line.strip() + line.strip()
398 previous_line = None
399
400 # Skip parsing if line has a space in front = hueristic to
401 # skip function argument lines (highly subject to formatting
402 # changes)
403 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100404 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100405
Yuto Takano39639672021-08-05 19:47:48 +0100406 identifier = re.search(
Yuto Takano81528c02021-08-06 16:22:06 +0100407 # Match something(
408 r".* \**(\w+)\(|"
409 # Match (*something)(
410 r".*\( *\* *(\w+) *\) *\(|"
411 # Match names of named data structures
412 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
413 # Match names of typedef instances, after closing bracket
414 r"}? *(\w+)[;[].*",
Yuto Takano39639672021-08-05 19:47:48 +0100415 line
416 )
417
418 if identifier:
Yuto Takano81528c02021-08-06 16:22:06 +0100419 # Find the group that matched, and append it
Yuto Takano39639672021-08-05 19:47:48 +0100420 for group in identifier.groups():
421 if group:
422 identifiers.append(Match(
423 header_file,
424 line,
425 (identifier.start(), identifier.end()),
Yuto Takano81528c02021-08-06 16:22:06 +0100426 group))
Yuto Takano39639672021-08-05 19:47:48 +0100427
428 return identifiers
429
430 def parse_symbols(self):
431 """
432 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
433 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100434 Exceptions thrown here are rethrown because they would be critical
435 errors that void several tests, and thus needs to halt the program. This
436 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100437
Yuto Takano81528c02021-08-06 16:22:06 +0100438 Returns a List of unique symbols defined and used in the libraries.
439 """
440 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100441 symbols = []
442
443 # Back up the config and atomically compile with the full configratuion.
444 shutil.copy("include/mbedtls/mbedtls_config.h",
Yuto Takano81528c02021-08-06 16:22:06 +0100445 "include/mbedtls/mbedtls_config.h.bak")
Darryl Greend5802922018-05-08 15:30:59 +0100446 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100447 # Use check=True in all subprocess calls so that failures are raised
448 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100449 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100450 ["python3", "scripts/config.py", "full"],
Yuto Takano39639672021-08-05 19:47:48 +0100451 encoding=sys.stdout.encoding,
452 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100453 )
454 my_environment = os.environ.copy()
455 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano39639672021-08-05 19:47:48 +0100456 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100457 ["make", "clean", "lib"],
458 env=my_environment,
Yuto Takano39639672021-08-05 19:47:48 +0100459 encoding=sys.stdout.encoding,
460 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100461 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100462 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100463 )
Yuto Takano39639672021-08-05 19:47:48 +0100464
465 # Perform object file analysis using nm
466 symbols = self.parse_symbols_from_nm(
467 ["library/libmbedcrypto.a",
468 "library/libmbedtls.a",
469 "library/libmbedx509.a"])
470
471 symbols.sort()
472
473 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100474 ["make", "clean"],
Yuto Takano39639672021-08-05 19:47:48 +0100475 encoding=sys.stdout.encoding,
476 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100477 )
478 except subprocess.CalledProcessError as error:
Darryl Greend5802922018-05-08 15:30:59 +0100479 self.set_return_code(2)
Yuto Takano81528c02021-08-06 16:22:06 +0100480 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100481 finally:
482 shutil.move("include/mbedtls/mbedtls_config.h.bak",
483 "include/mbedtls/mbedtls_config.h")
484
485 return symbols
486
487 def parse_symbols_from_nm(self, object_files):
488 """
489 Run nm to retrieve the list of referenced symbols in each object file.
490 Does not return the position data since it is of no use.
491
Yuto Takano81528c02021-08-06 16:22:06 +0100492 Args:
493 * object_files: a List of compiled object files to search through.
494
495 Returns a List of unique symbols defined and used in any of the object
496 files.
Yuto Takano39639672021-08-05 19:47:48 +0100497 """
498 UNDEFINED_SYMBOL = r"^\S+: +U |^$|^\S+:$"
499 VALID_SYMBOL = r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)"
Yuto Takanoe77f6992021-08-05 20:22:59 +0100500 EXCLUSIONS = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100501
502 symbols = []
503
Yuto Takano81528c02021-08-06 16:22:06 +0100504 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100505 nm_output = ""
506 for lib in object_files:
507 nm_output += subprocess.run(
508 ["nm", "-og", lib],
509 encoding=sys.stdout.encoding,
510 stdout=subprocess.PIPE,
511 stderr=subprocess.STDOUT,
512 check=True
513 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100514
Yuto Takano39639672021-08-05 19:47:48 +0100515 for line in nm_output.splitlines():
516 if not re.match(UNDEFINED_SYMBOL, line):
517 symbol = re.match(VALID_SYMBOL, line)
Yuto Takanoe77f6992021-08-05 20:22:59 +0100518 if symbol and not symbol.group("symbol").startswith(EXCLUSIONS):
519 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100520 else:
521 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100522
Yuto Takano39639672021-08-05 19:47:48 +0100523 return symbols
524
Yuto Takano81528c02021-08-06 16:22:06 +0100525 def perform_checks(self, show_problems: True):
Yuto Takano39639672021-08-05 19:47:48 +0100526 """
527 Perform each check in order, output its PASS/FAIL status. Maintain an
528 overall test status, and output that at the end.
Yuto Takano81528c02021-08-06 16:22:06 +0100529
530 Args:
531 * show_problems: whether to show the problematic examples.
Yuto Takano39639672021-08-05 19:47:48 +0100532 """
Yuto Takano81528c02021-08-06 16:22:06 +0100533 self.log.info("=============")
Yuto Takano39639672021-08-05 19:47:48 +0100534 problems = 0
535
Yuto Takano81528c02021-08-06 16:22:06 +0100536 problems += self.check_symbols_declared_in_header(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100537
538 pattern_checks = [
539 ("macros", MACRO_PATTERN),
Yuto Takano81528c02021-08-06 16:22:06 +0100540 ("enum_consts", CONSTANTS_PATTERN),
Yuto Takano39639672021-08-05 19:47:48 +0100541 ("identifiers", IDENTIFIER_PATTERN)]
542 for group, check_pattern in pattern_checks:
Yuto Takano81528c02021-08-06 16:22:06 +0100543 problems += self.check_match_pattern(
544 show_problems, group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100545
Yuto Takano81528c02021-08-06 16:22:06 +0100546 problems += self.check_for_typos(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100547
548 self.log.info("=============")
549 if problems > 0:
550 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano81528c02021-08-06 16:22:06 +0100551 if not show_problems:
552 self.log.info("Remove --quiet to show the problems.")
Yuto Takano39639672021-08-05 19:47:48 +0100553 else:
554 self.log.info("PASS")
Darryl Greend5802922018-05-08 15:30:59 +0100555
Yuto Takano81528c02021-08-06 16:22:06 +0100556 def check_symbols_declared_in_header(self, show_problems):
Yuto Takano39639672021-08-05 19:47:48 +0100557 """
558 Perform a check that all detected symbols in the library object files
559 are properly declared in headers.
Darryl Greend5802922018-05-08 15:30:59 +0100560
Yuto Takano81528c02021-08-06 16:22:06 +0100561 Args:
562 * show_problems: whether to show the problematic examples.
563
564 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100565 """
566 problems = []
567 for symbol in self.parse_result["symbols"]:
568 found_symbol_declared = False
569 for identifier_match in self.parse_result["identifiers"]:
570 if symbol == identifier_match.name:
571 found_symbol_declared = True
572 break
Yuto Takano81528c02021-08-06 16:22:06 +0100573
Yuto Takano39639672021-08-05 19:47:48 +0100574 if not found_symbol_declared:
575 problems.append(SymbolNotInHeader(symbol))
576
Yuto Takano81528c02021-08-06 16:22:06 +0100577 self.output_check_result("All symbols in header", problems, show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100578 return len(problems)
579
Yuto Takano81528c02021-08-06 16:22:06 +0100580
581 def check_match_pattern(self, show_problems, group_to_check, check_pattern):
582 """
583 Perform a check that all items of a group conform to a regex pattern.
584
585 Args:
586 * show_problems: whether to show the problematic examples.
587 * group_to_check: string key to index into self.parse_result.
588 * check_pattern: the regex to check against.
589
590 Returns the number of problems that need fixing.
591 """
Yuto Takano39639672021-08-05 19:47:48 +0100592 problems = []
593 for item_match in self.parse_result[group_to_check]:
594 if not re.match(check_pattern, item_match.name):
595 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano201f9e82021-08-06 16:36:54 +0100596 # Double underscore is a reserved identifier, never to be used
Yuto Takanoc763cc32021-08-05 20:06:34 +0100597 if re.match(r".*__.*", item_match.name):
598 problems.append(PatternMismatch("double underscore", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100599
600 self.output_check_result(
601 "Naming patterns of {}".format(group_to_check),
602 problems,
603 show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100604 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100605
Yuto Takano81528c02021-08-06 16:22:06 +0100606 def check_for_typos(self, show_problems):
607 """
608 Perform a check that all words in the soure code beginning with MBED are
609 either defined as macros, or as enum constants.
610
611 Args:
612 * show_problems: whether to show the problematic examples.
613
614 Returns the number of problems that need fixing.
615 """
Yuto Takano39639672021-08-05 19:47:48 +0100616 problems = []
617 all_caps_names = list(set([
618 match.name for match
619 in self.parse_result["macros"] + self.parse_result["enum_consts"]]
Darryl Greend5802922018-05-08 15:30:59 +0100620 ))
Yuto Takano39639672021-08-05 19:47:48 +0100621
622 TYPO_EXCLUSION = r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$"
623
624 for name_match in self.parse_result["mbed_names"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100625 found = name_match.name in all_caps_names
626
627 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
628 # PSA driver, they will not exist as macros. However, they
629 # should still be checked for typos using the equivalent
630 # BUILTINs that exist.
631 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
632 found = name_match.name.replace(
633 "MBEDTLS_PSA_ACCEL_",
634 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
635
636 if not found and not re.search(TYPO_EXCLUSION, name_match.name):
Yuto Takano201f9e82021-08-06 16:36:54 +0100637 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100638
Yuto Takano81528c02021-08-06 16:22:06 +0100639 self.output_check_result("Likely typos", problems, show_problems)
640 return len(problems)
641
642 def output_check_result(self, name, problems, show_problems):
643 """
644 Write out the PASS/FAIL status of a performed check depending on whether
645 there were problems.
646
647 Args:
648 * show_problems: whether to show the problematic examples.
649 """
Yuto Takano39639672021-08-05 19:47:48 +0100650 if problems:
Darryl Greend5802922018-05-08 15:30:59 +0100651 self.set_return_code(1)
Yuto Takano81528c02021-08-06 16:22:06 +0100652 self.log.info("{}: FAIL".format(name))
653 if show_problems:
654 self.log.info("")
655 for problem in problems:
656 self.log.warn(str(problem) + "\n")
Darryl Greend5802922018-05-08 15:30:59 +0100657 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100658 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100659
Yuto Takano39639672021-08-05 19:47:48 +0100660def main():
661 """
Yuto Takano81528c02021-08-06 16:22:06 +0100662 Perform argument parsing, and create an instance of NameCheck to begin the
663 core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100664 """
Darryl Greend5802922018-05-08 15:30:59 +0100665
Yuto Takano39639672021-08-05 19:47:48 +0100666 parser = argparse.ArgumentParser(
667 formatter_class=argparse.RawDescriptionHelpFormatter,
668 description=(
669 "This script confirms that the naming of all symbols and identifiers "
670 "in Mbed TLS are consistent with the house style and are also "
671 "self-consistent.\n\n"
672 "Expected to be run from the MbedTLS root directory."))
Darryl Greend5802922018-05-08 15:30:59 +0100673
Yuto Takano39639672021-08-05 19:47:48 +0100674 parser.add_argument("-v", "--verbose",
675 action="store_true",
Yuto Takano81528c02021-08-06 16:22:06 +0100676 help="show parse results")
677
678 parser.add_argument("-q", "--quiet",
679 action="store_true",
680 help="hide unnecessary text and problematic examples")
681
Yuto Takano39639672021-08-05 19:47:48 +0100682 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100683
Darryl Greend5802922018-05-08 15:30:59 +0100684 try:
685 name_check = NameCheck()
Yuto Takano39639672021-08-05 19:47:48 +0100686 name_check.setup_logger(verbose=args.verbose)
687 name_check.parse_names_in_source()
Yuto Takano81528c02021-08-06 16:22:06 +0100688 name_check.perform_checks(show_problems=not args.quiet)
689 sys.exit(name_check.return_code)
690 except subprocess.CalledProcessError as error:
691 traceback.print_exc()
692 print("!! Compilation faced a critical error, "
693 "check-names can't continue further.")
Darryl Greend5802922018-05-08 15:30:59 +0100694 sys.exit(name_check.return_code)
695 except Exception:
696 traceback.print_exc()
697 sys.exit(2)
698
Darryl Greend5802922018-05-08 15:30:59 +0100699if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100700 main()