blob: e3863cfc8def882fccb95dde2142b2de276d426c [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 """
Darryl Greend5802922018-05-08 15:30:59 +0100165 current_dir = os.path.realpath('.')
166 root_dir = os.path.dirname(os.path.dirname(
167 os.path.dirname(os.path.realpath(__file__))))
168 if current_dir != root_dir:
169 raise Exception("Must be run from Mbed TLS root")
170
Yuto Takano157444c2021-08-05 20:10:45 +0100171 def get_files(self, extension, directory):
Yuto Takano81528c02021-08-06 16:22:06 +0100172 """
173 Get all files that end with .extension in the specified directory
174 recursively.
175
176 Args:
177 * extension: the file extension to search for, without the dot
178 * directory: the directory to recursively search for
179
180 Returns a List of relative filepaths.
181 """
Darryl Greend5802922018-05-08 15:30:59 +0100182 filenames = []
183 for root, dirs, files in sorted(os.walk(directory)):
184 for filename in sorted(files):
185 if (filename not in self.excluded_files and
Yuto Takano157444c2021-08-05 20:10:45 +0100186 filename.endswith("." + extension)):
Darryl Greend5802922018-05-08 15:30:59 +0100187 filenames.append(os.path.join(root, filename))
188 return filenames
189
Yuto Takano81528c02021-08-06 16:22:06 +0100190 def parse_names_in_source(self):
191 """
192 Calls each parsing function to retrieve various elements of the code,
193 together with their source location. Puts the parsed values in the
194 internal variable self.parse_result.
195 """
196 self.log.info("Parsing source code...")
197
198 m_headers = self.get_files("h", os.path.join("include", "mbedtls"))
199 p_headers = self.get_files("h", os.path.join("include", "psa"))
200 t_headers = ["3rdparty/everest/include/everest/everest.h",
201 "3rdparty/everest/include/everest/x25519.h"]
202 d_headers = self.get_files("h", os.path.join("tests", "include", "test", "drivers"))
203 l_headers = self.get_files("h", "library")
204 libraries = self.get_files("c", "library") + [
205 "3rdparty/everest/library/everest.c",
206 "3rdparty/everest/library/x25519.c"]
207
208 all_macros = self.parse_macros(
209 m_headers + p_headers + t_headers + l_headers + d_headers)
210 enum_consts = self.parse_enum_consts(
211 m_headers + l_headers + t_headers)
212 identifiers = self.parse_identifiers(
213 m_headers + p_headers + t_headers + l_headers)
214 mbed_names = self.parse_MBED_names(
215 m_headers + p_headers + t_headers + l_headers + libraries)
216 symbols = self.parse_symbols()
217
218 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
219 identifiers_justname = [x.name for x in identifiers]
220 actual_macros = []
221 for macro in all_macros:
222 if macro.name not in identifiers_justname:
223 actual_macros.append(macro)
224
225 self.log.debug("Found:")
226 self.log.debug(" {} Macros".format(len(all_macros)))
227 self.log.debug(" {} Non-identifier Macros".format(len(actual_macros)))
228 self.log.debug(" {} Enum Constants".format(len(enum_consts)))
229 self.log.debug(" {} Identifiers".format(len(identifiers)))
230 self.log.debug(" {} Exported Symbols".format(len(symbols)))
231 self.log.info("Analysing...")
232
233 self.parse_result = {
234 "macros": actual_macros,
235 "enum_consts": enum_consts,
236 "identifiers": identifiers,
237 "symbols": symbols,
238 "mbed_names": mbed_names
239 }
240
Yuto Takano39639672021-08-05 19:47:48 +0100241 def parse_macros(self, header_files):
242 """
243 Parse all macros defined by #define preprocessor directives.
244
245 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100246 * header_files: A List of filepaths to look through.
247
248 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100249 """
250 MACRO_REGEX = r"#define (?P<macro>\w+)"
251 NON_MACROS = (
252 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
253 )
254
255 macros = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100256 self.log.debug("Looking for macros in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100257 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100258 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100259 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100260 for macro in re.finditer(MACRO_REGEX, line):
261 if not macro.group("macro").startswith(NON_MACROS):
262 macros.append(Match(
263 header_file,
264 line,
265 (macro.start(), macro.end()),
266 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100267
Yuto Takano39639672021-08-05 19:47:48 +0100268 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100269
Yuto Takanobb7dca42021-08-05 19:57:58 +0100270 def parse_MBED_names(self, files):
Yuto Takano39639672021-08-05 19:47:48 +0100271 """
272 Parse all words in the file that begin with MBED. Includes macros.
Yuto Takano81528c02021-08-06 16:22:06 +0100273 There have been typos of TLS, hence the broader check than MBEDTLS.
Yuto Takano39639672021-08-05 19:47:48 +0100274
275 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100276 * files: a List of filepaths to look through.
277
278 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100279 """
280 MBED_names = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100281 self.log.debug("Looking for MBED names in {} files".format(len(files)))
Yuto Takanobb7dca42021-08-05 19:57:58 +0100282 for filename in files:
Yuto Takano39639672021-08-05 19:47:48 +0100283 with open(filename, "r") as fp:
284 for line in fp:
Yuto Takano81528c02021-08-06 16:22:06 +0100285 # Ignore any names that are deliberately opted-out or in
286 # legacy error directives
287 if re.search(r"// *no-check-names|#error", line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100288 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100289
Yuto Takano39639672021-08-05 19:47:48 +0100290 for name in re.finditer(r"\bMBED.+?_[A-Z0-9_]*", line):
291 MBED_names.append(Match(
292 filename,
293 line,
294 (name.start(), name.end()),
295 name.group(0)
296 ))
297
298 return MBED_names
299
300 def parse_enum_consts(self, header_files):
301 """
302 Parse all enum value constants that are declared.
303
304 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100305 * header_files: A List of filepaths to look through.
Yuto Takano39639672021-08-05 19:47:48 +0100306
Yuto Takano81528c02021-08-06 16:22:06 +0100307 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100308 """
309
310 enum_consts = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100311 self.log.debug("Looking for enum consts in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100312 for header_file in header_files:
313 # Emulate a finite state machine to parse enum declarations.
Yuto Takano81528c02021-08-06 16:22:06 +0100314 # 0 = not in enum
315 # 1 = inside enum
316 # 2 = almost inside enum
Darryl Greend5802922018-05-08 15:30:59 +0100317 state = 0
318 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100319 for line in header:
Darryl Greend5802922018-05-08 15:30:59 +0100320 if state is 0 and re.match(r"^(typedef )?enum {", line):
321 state = 1
322 elif state is 0 and re.match(r"^(typedef )?enum", line):
323 state = 2
324 elif state is 2 and re.match(r"^{", line):
325 state = 1
326 elif state is 1 and re.match(r"^}", line):
327 state = 0
Yuto Takano0fd48f72021-08-05 20:32:55 +0100328 elif state is 1 and not re.match(r"^#", line):
Darryl Greend5802922018-05-08 15:30:59 +0100329 enum_const = re.match(r"^\s*(?P<enum_const>\w+)", line)
330 if enum_const:
Yuto Takano39639672021-08-05 19:47:48 +0100331 enum_consts.append(Match(
332 header_file,
333 line,
334 (enum_const.start(), enum_const.end()),
335 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100336
Yuto Takano39639672021-08-05 19:47:48 +0100337 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100338
Yuto Takano39639672021-08-05 19:47:48 +0100339 def parse_identifiers(self, header_files):
340 """
341 Parse all lines of a header where a function identifier is declared,
Yuto Takano81528c02021-08-06 16:22:06 +0100342 based on some huersitics. Highly dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100343
Yuto Takano39639672021-08-05 19:47:48 +0100344 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100345 * header_files: A List of filepaths to look through.
346
347 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100348 """
Yuto Takano81528c02021-08-06 16:22:06 +0100349 EXCLUDED_LINES = (
350 r"^("
351 r"extern \"C\"|"
352 r"(typedef )?(struct|union|enum)( {)?$|"
353 r"};?$|"
354 r"$|"
355 r"//|"
356 r"#"
357 r")"
Darryl Greend5802922018-05-08 15:30:59 +0100358 )
Darryl Greend5802922018-05-08 15:30:59 +0100359
Yuto Takano39639672021-08-05 19:47:48 +0100360 identifiers = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100361 self.log.debug("Looking for identifiers in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100362 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100363 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100364 in_block_comment = False
Yuto Takano81528c02021-08-06 16:22:06 +0100365 previous_line = None
Darryl Greend5802922018-05-08 15:30:59 +0100366
Yuto Takano39639672021-08-05 19:47:48 +0100367 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100368 # Skip parsing this line if a block comment ends on it,
369 # but don't skip if it has just started -- there is a chance
370 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100371 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100372 in_block_comment = not in_block_comment
373 if re.search(r"\*/", line):
374 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100375 continue
376
Yuto Takano81528c02021-08-06 16:22:06 +0100377 if in_block_comment:
378 previous_line = None
379 continue
380
381 if re.match(EXCLUDED_LINES, line):
382 previous_line = None
383 continue
384
385 # Match "^something something$", with optional inline/static
386 # This *might* be a function with its argument brackets on
387 # the next line, or a struct declaration, so keep note of it
388 if re.match(
389 r"(inline |static |typedef )*\w+ \w+$",
390 line):
391 previous_line = line
392 continue
393
394 # If previous line seemed to start an unfinished declaration
395 # (as above), and this line begins with a bracket, concat
396 # them and treat them as one line.
397 if previous_line and re.match(" *[\({]", line):
398 line = previous_line.strip() + line.strip()
399 previous_line = None
400
401 # Skip parsing if line has a space in front = hueristic to
402 # skip function argument lines (highly subject to formatting
403 # changes)
404 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100405 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100406
Yuto Takano39639672021-08-05 19:47:48 +0100407 identifier = re.search(
Yuto Takano81528c02021-08-06 16:22:06 +0100408 # Match something(
409 r".* \**(\w+)\(|"
410 # Match (*something)(
411 r".*\( *\* *(\w+) *\) *\(|"
412 # Match names of named data structures
413 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
414 # Match names of typedef instances, after closing bracket
415 r"}? *(\w+)[;[].*",
Yuto Takano39639672021-08-05 19:47:48 +0100416 line
417 )
418
419 if identifier:
Yuto Takano81528c02021-08-06 16:22:06 +0100420 # Find the group that matched, and append it
Yuto Takano39639672021-08-05 19:47:48 +0100421 for group in identifier.groups():
422 if group:
423 identifiers.append(Match(
424 header_file,
425 line,
426 (identifier.start(), identifier.end()),
Yuto Takano81528c02021-08-06 16:22:06 +0100427 group))
Yuto Takano39639672021-08-05 19:47:48 +0100428
429 return identifiers
430
431 def parse_symbols(self):
432 """
433 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
434 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100435 Exceptions thrown here are rethrown because they would be critical
436 errors that void several tests, and thus needs to halt the program. This
437 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100438
Yuto Takano81528c02021-08-06 16:22:06 +0100439 Returns a List of unique symbols defined and used in the libraries.
440 """
441 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100442 symbols = []
443
444 # Back up the config and atomically compile with the full configratuion.
445 shutil.copy("include/mbedtls/mbedtls_config.h",
Yuto Takano81528c02021-08-06 16:22:06 +0100446 "include/mbedtls/mbedtls_config.h.bak")
Darryl Greend5802922018-05-08 15:30:59 +0100447 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100448 # Use check=True in all subprocess calls so that failures are raised
449 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100450 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100451 ["python3", "scripts/config.py", "full"],
Yuto Takano39639672021-08-05 19:47:48 +0100452 encoding=sys.stdout.encoding,
453 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100454 )
455 my_environment = os.environ.copy()
456 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano39639672021-08-05 19:47:48 +0100457 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100458 ["make", "clean", "lib"],
459 env=my_environment,
Yuto Takano39639672021-08-05 19:47:48 +0100460 encoding=sys.stdout.encoding,
461 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100462 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100463 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100464 )
Yuto Takano39639672021-08-05 19:47:48 +0100465
466 # Perform object file analysis using nm
467 symbols = self.parse_symbols_from_nm(
468 ["library/libmbedcrypto.a",
469 "library/libmbedtls.a",
470 "library/libmbedx509.a"])
471
472 symbols.sort()
473
474 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100475 ["make", "clean"],
Yuto Takano39639672021-08-05 19:47:48 +0100476 encoding=sys.stdout.encoding,
477 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100478 )
479 except subprocess.CalledProcessError as error:
Darryl Greend5802922018-05-08 15:30:59 +0100480 self.set_return_code(2)
Yuto Takano81528c02021-08-06 16:22:06 +0100481 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100482 finally:
483 shutil.move("include/mbedtls/mbedtls_config.h.bak",
484 "include/mbedtls/mbedtls_config.h")
485
486 return symbols
487
488 def parse_symbols_from_nm(self, object_files):
489 """
490 Run nm to retrieve the list of referenced symbols in each object file.
491 Does not return the position data since it is of no use.
492
Yuto Takano81528c02021-08-06 16:22:06 +0100493 Args:
494 * object_files: a List of compiled object files to search through.
495
496 Returns a List of unique symbols defined and used in any of the object
497 files.
Yuto Takano39639672021-08-05 19:47:48 +0100498 """
499 UNDEFINED_SYMBOL = r"^\S+: +U |^$|^\S+:$"
500 VALID_SYMBOL = r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)"
Yuto Takanoe77f6992021-08-05 20:22:59 +0100501 EXCLUSIONS = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100502
503 symbols = []
504
Yuto Takano81528c02021-08-06 16:22:06 +0100505 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100506 nm_output = ""
507 for lib in object_files:
508 nm_output += subprocess.run(
509 ["nm", "-og", lib],
510 encoding=sys.stdout.encoding,
511 stdout=subprocess.PIPE,
512 stderr=subprocess.STDOUT,
513 check=True
514 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100515
Yuto Takano39639672021-08-05 19:47:48 +0100516 for line in nm_output.splitlines():
517 if not re.match(UNDEFINED_SYMBOL, line):
518 symbol = re.match(VALID_SYMBOL, line)
Yuto Takanoe77f6992021-08-05 20:22:59 +0100519 if symbol and not symbol.group("symbol").startswith(EXCLUSIONS):
520 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100521 else:
522 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100523
Yuto Takano39639672021-08-05 19:47:48 +0100524 return symbols
525
Yuto Takano81528c02021-08-06 16:22:06 +0100526 def perform_checks(self, show_problems: True):
Yuto Takano39639672021-08-05 19:47:48 +0100527 """
528 Perform each check in order, output its PASS/FAIL status. Maintain an
529 overall test status, and output that at the end.
Yuto Takano81528c02021-08-06 16:22:06 +0100530
531 Args:
532 * show_problems: whether to show the problematic examples.
Yuto Takano39639672021-08-05 19:47:48 +0100533 """
Yuto Takano81528c02021-08-06 16:22:06 +0100534 self.log.info("=============")
Yuto Takano39639672021-08-05 19:47:48 +0100535 problems = 0
536
Yuto Takano81528c02021-08-06 16:22:06 +0100537 problems += self.check_symbols_declared_in_header(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100538
539 pattern_checks = [
540 ("macros", MACRO_PATTERN),
Yuto Takano81528c02021-08-06 16:22:06 +0100541 ("enum_consts", CONSTANTS_PATTERN),
Yuto Takano39639672021-08-05 19:47:48 +0100542 ("identifiers", IDENTIFIER_PATTERN)]
543 for group, check_pattern in pattern_checks:
Yuto Takano81528c02021-08-06 16:22:06 +0100544 problems += self.check_match_pattern(
545 show_problems, group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100546
Yuto Takano81528c02021-08-06 16:22:06 +0100547 problems += self.check_for_typos(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100548
549 self.log.info("=============")
550 if problems > 0:
551 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano81528c02021-08-06 16:22:06 +0100552 if not show_problems:
553 self.log.info("Remove --quiet to show the problems.")
Yuto Takano39639672021-08-05 19:47:48 +0100554 else:
555 self.log.info("PASS")
Darryl Greend5802922018-05-08 15:30:59 +0100556
Yuto Takano81528c02021-08-06 16:22:06 +0100557 def check_symbols_declared_in_header(self, show_problems):
Yuto Takano39639672021-08-05 19:47:48 +0100558 """
559 Perform a check that all detected symbols in the library object files
560 are properly declared in headers.
Darryl Greend5802922018-05-08 15:30:59 +0100561
Yuto Takano81528c02021-08-06 16:22:06 +0100562 Args:
563 * show_problems: whether to show the problematic examples.
564
565 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100566 """
567 problems = []
568 for symbol in self.parse_result["symbols"]:
569 found_symbol_declared = False
570 for identifier_match in self.parse_result["identifiers"]:
571 if symbol == identifier_match.name:
572 found_symbol_declared = True
573 break
Yuto Takano81528c02021-08-06 16:22:06 +0100574
Yuto Takano39639672021-08-05 19:47:48 +0100575 if not found_symbol_declared:
576 problems.append(SymbolNotInHeader(symbol))
577
Yuto Takano81528c02021-08-06 16:22:06 +0100578 self.output_check_result("All symbols in header", problems, show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100579 return len(problems)
580
Yuto Takano81528c02021-08-06 16:22:06 +0100581
582 def check_match_pattern(self, show_problems, group_to_check, check_pattern):
583 """
584 Perform a check that all items of a group conform to a regex pattern.
585
586 Args:
587 * show_problems: whether to show the problematic examples.
588 * group_to_check: string key to index into self.parse_result.
589 * check_pattern: the regex to check against.
590
591 Returns the number of problems that need fixing.
592 """
Yuto Takano39639672021-08-05 19:47:48 +0100593 problems = []
594 for item_match in self.parse_result[group_to_check]:
595 if not re.match(check_pattern, item_match.name):
596 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano201f9e82021-08-06 16:36:54 +0100597 # Double underscore is a reserved identifier, never to be used
Yuto Takanoc763cc32021-08-05 20:06:34 +0100598 if re.match(r".*__.*", item_match.name):
599 problems.append(PatternMismatch("double underscore", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100600
601 self.output_check_result(
602 "Naming patterns of {}".format(group_to_check),
603 problems,
604 show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100605 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100606
Yuto Takano81528c02021-08-06 16:22:06 +0100607 def check_for_typos(self, show_problems):
608 """
609 Perform a check that all words in the soure code beginning with MBED are
610 either defined as macros, or as enum constants.
611
612 Args:
613 * show_problems: whether to show the problematic examples.
614
615 Returns the number of problems that need fixing.
616 """
Yuto Takano39639672021-08-05 19:47:48 +0100617 problems = []
618 all_caps_names = list(set([
619 match.name for match
620 in self.parse_result["macros"] + self.parse_result["enum_consts"]]
Darryl Greend5802922018-05-08 15:30:59 +0100621 ))
Yuto Takano39639672021-08-05 19:47:48 +0100622
623 TYPO_EXCLUSION = r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$"
624
625 for name_match in self.parse_result["mbed_names"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100626 found = name_match.name in all_caps_names
627
628 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
629 # PSA driver, they will not exist as macros. However, they
630 # should still be checked for typos using the equivalent
631 # BUILTINs that exist.
632 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
633 found = name_match.name.replace(
634 "MBEDTLS_PSA_ACCEL_",
635 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
636
637 if not found and not re.search(TYPO_EXCLUSION, name_match.name):
Yuto Takano201f9e82021-08-06 16:36:54 +0100638 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100639
Yuto Takano81528c02021-08-06 16:22:06 +0100640 self.output_check_result("Likely typos", problems, show_problems)
641 return len(problems)
642
643 def output_check_result(self, name, problems, show_problems):
644 """
645 Write out the PASS/FAIL status of a performed check depending on whether
646 there were problems.
647
648 Args:
649 * show_problems: whether to show the problematic examples.
650 """
Yuto Takano39639672021-08-05 19:47:48 +0100651 if problems:
Darryl Greend5802922018-05-08 15:30:59 +0100652 self.set_return_code(1)
Yuto Takano81528c02021-08-06 16:22:06 +0100653 self.log.info("{}: FAIL".format(name))
654 if show_problems:
655 self.log.info("")
656 for problem in problems:
657 self.log.warn(str(problem) + "\n")
Darryl Greend5802922018-05-08 15:30:59 +0100658 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100659 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100660
Yuto Takano39639672021-08-05 19:47:48 +0100661def main():
662 """
Yuto Takano81528c02021-08-06 16:22:06 +0100663 Perform argument parsing, and create an instance of NameCheck to begin the
664 core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100665 """
Darryl Greend5802922018-05-08 15:30:59 +0100666
Yuto Takano39639672021-08-05 19:47:48 +0100667 parser = argparse.ArgumentParser(
668 formatter_class=argparse.RawDescriptionHelpFormatter,
669 description=(
670 "This script confirms that the naming of all symbols and identifiers "
671 "in Mbed TLS are consistent with the house style and are also "
672 "self-consistent.\n\n"
673 "Expected to be run from the MbedTLS root directory."))
Darryl Greend5802922018-05-08 15:30:59 +0100674
Yuto Takano39639672021-08-05 19:47:48 +0100675 parser.add_argument("-v", "--verbose",
676 action="store_true",
Yuto Takano81528c02021-08-06 16:22:06 +0100677 help="show parse results")
678
679 parser.add_argument("-q", "--quiet",
680 action="store_true",
681 help="hide unnecessary text and problematic examples")
682
Yuto Takano39639672021-08-05 19:47:48 +0100683 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100684
Darryl Greend5802922018-05-08 15:30:59 +0100685 try:
686 name_check = NameCheck()
Yuto Takano39639672021-08-05 19:47:48 +0100687 name_check.setup_logger(verbose=args.verbose)
688 name_check.parse_names_in_source()
Yuto Takano81528c02021-08-06 16:22:06 +0100689 name_check.perform_checks(show_problems=not args.quiet)
690 sys.exit(name_check.return_code)
691 except subprocess.CalledProcessError as error:
692 traceback.print_exc()
693 print("!! Compilation faced a critical error, "
694 "check-names can't continue further.")
Darryl Greend5802922018-05-08 15:30:59 +0100695 sys.exit(name_check.return_code)
696 except Exception:
697 traceback.print_exc()
698 sys.exit(2)
699
Darryl Greend5802922018-05-08 15:30:59 +0100700if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100701 main()