blob: 406810b8c42960e1fbef042e320b0ed41df24b21 [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...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100196 self.log.debug(
197 "The following files are excluded from the search: {}"
198 .format(str(self.excluded_files))
199 )
Yuto Takano81528c02021-08-06 16:22:06 +0100200
201 m_headers = self.get_files("h", os.path.join("include", "mbedtls"))
202 p_headers = self.get_files("h", os.path.join("include", "psa"))
203 t_headers = ["3rdparty/everest/include/everest/everest.h",
204 "3rdparty/everest/include/everest/x25519.h"]
205 d_headers = self.get_files("h", os.path.join("tests", "include", "test", "drivers"))
206 l_headers = self.get_files("h", "library")
207 libraries = self.get_files("c", "library") + [
208 "3rdparty/everest/library/everest.c",
209 "3rdparty/everest/library/x25519.c"]
210
211 all_macros = self.parse_macros(
212 m_headers + p_headers + t_headers + l_headers + d_headers)
213 enum_consts = self.parse_enum_consts(
214 m_headers + l_headers + t_headers)
215 identifiers = self.parse_identifiers(
216 m_headers + p_headers + t_headers + l_headers)
217 mbed_names = self.parse_MBED_names(
218 m_headers + p_headers + t_headers + l_headers + libraries)
219 symbols = self.parse_symbols()
220
221 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
222 identifiers_justname = [x.name for x in identifiers]
223 actual_macros = []
224 for macro in all_macros:
225 if macro.name not in identifiers_justname:
226 actual_macros.append(macro)
227
228 self.log.debug("Found:")
229 self.log.debug(" {} Macros".format(len(all_macros)))
230 self.log.debug(" {} Non-identifier Macros".format(len(actual_macros)))
231 self.log.debug(" {} Enum Constants".format(len(enum_consts)))
232 self.log.debug(" {} Identifiers".format(len(identifiers)))
233 self.log.debug(" {} Exported Symbols".format(len(symbols)))
234 self.log.info("Analysing...")
235
236 self.parse_result = {
237 "macros": actual_macros,
238 "enum_consts": enum_consts,
239 "identifiers": identifiers,
240 "symbols": symbols,
241 "mbed_names": mbed_names
242 }
243
Yuto Takano39639672021-08-05 19:47:48 +0100244 def parse_macros(self, header_files):
245 """
246 Parse all macros defined by #define preprocessor directives.
247
248 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100249 * header_files: A List of filepaths to look through.
250
251 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100252 """
253 MACRO_REGEX = r"#define (?P<macro>\w+)"
254 NON_MACROS = (
255 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
256 )
257
258 macros = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100259 self.log.debug("Looking for macros in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100260 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100261 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100262 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100263 for macro in re.finditer(MACRO_REGEX, line):
264 if not macro.group("macro").startswith(NON_MACROS):
265 macros.append(Match(
266 header_file,
267 line,
268 (macro.start(), macro.end()),
269 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100270
Yuto Takano39639672021-08-05 19:47:48 +0100271 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100272
Yuto Takanobb7dca42021-08-05 19:57:58 +0100273 def parse_MBED_names(self, files):
Yuto Takano39639672021-08-05 19:47:48 +0100274 """
275 Parse all words in the file that begin with MBED. Includes macros.
Yuto Takano81528c02021-08-06 16:22:06 +0100276 There have been typos of TLS, hence the broader check than MBEDTLS.
Yuto Takano39639672021-08-05 19:47:48 +0100277
278 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100279 * files: a List of filepaths to look through.
280
281 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100282 """
283 MBED_names = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100284 self.log.debug("Looking for MBED names in {} files".format(len(files)))
Yuto Takanobb7dca42021-08-05 19:57:58 +0100285 for filename in files:
Yuto Takano39639672021-08-05 19:47:48 +0100286 with open(filename, "r") as fp:
287 for line in fp:
Yuto Takano81528c02021-08-06 16:22:06 +0100288 # Ignore any names that are deliberately opted-out or in
289 # legacy error directives
290 if re.search(r"// *no-check-names|#error", line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100291 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100292
Yuto Takano39639672021-08-05 19:47:48 +0100293 for name in re.finditer(r"\bMBED.+?_[A-Z0-9_]*", line):
294 MBED_names.append(Match(
295 filename,
296 line,
297 (name.start(), name.end()),
298 name.group(0)
299 ))
300
301 return MBED_names
302
303 def parse_enum_consts(self, header_files):
304 """
305 Parse all enum value constants that are declared.
306
307 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100308 * header_files: A List of filepaths to look through.
Yuto Takano39639672021-08-05 19:47:48 +0100309
Yuto Takano81528c02021-08-06 16:22:06 +0100310 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100311 """
312
313 enum_consts = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100314 self.log.debug("Looking for enum consts in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100315 for header_file in header_files:
316 # Emulate a finite state machine to parse enum declarations.
Yuto Takano81528c02021-08-06 16:22:06 +0100317 # 0 = not in enum
318 # 1 = inside enum
319 # 2 = almost inside enum
Darryl Greend5802922018-05-08 15:30:59 +0100320 state = 0
321 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100322 for line in header:
Darryl Greend5802922018-05-08 15:30:59 +0100323 if state is 0 and re.match(r"^(typedef )?enum {", line):
324 state = 1
325 elif state is 0 and re.match(r"^(typedef )?enum", line):
326 state = 2
327 elif state is 2 and re.match(r"^{", line):
328 state = 1
329 elif state is 1 and re.match(r"^}", line):
330 state = 0
Yuto Takano0fd48f72021-08-05 20:32:55 +0100331 elif state is 1 and not re.match(r"^#", line):
Darryl Greend5802922018-05-08 15:30:59 +0100332 enum_const = re.match(r"^\s*(?P<enum_const>\w+)", line)
333 if enum_const:
Yuto Takano39639672021-08-05 19:47:48 +0100334 enum_consts.append(Match(
335 header_file,
336 line,
337 (enum_const.start(), enum_const.end()),
338 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100339
Yuto Takano39639672021-08-05 19:47:48 +0100340 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100341
Yuto Takano39639672021-08-05 19:47:48 +0100342 def parse_identifiers(self, header_files):
343 """
344 Parse all lines of a header where a function identifier is declared,
Yuto Takano81528c02021-08-06 16:22:06 +0100345 based on some huersitics. Highly dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100346
Yuto Takano39639672021-08-05 19:47:48 +0100347 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100348 * header_files: A List of filepaths to look through.
349
350 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100351 """
Yuto Takano81528c02021-08-06 16:22:06 +0100352 EXCLUDED_LINES = (
353 r"^("
354 r"extern \"C\"|"
355 r"(typedef )?(struct|union|enum)( {)?$|"
356 r"};?$|"
357 r"$|"
358 r"//|"
359 r"#"
360 r")"
Darryl Greend5802922018-05-08 15:30:59 +0100361 )
Darryl Greend5802922018-05-08 15:30:59 +0100362
Yuto Takano39639672021-08-05 19:47:48 +0100363 identifiers = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100364 self.log.debug("Looking for identifiers in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100365 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100366 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100367 in_block_comment = False
Yuto Takano81528c02021-08-06 16:22:06 +0100368 previous_line = None
Darryl Greend5802922018-05-08 15:30:59 +0100369
Yuto Takano39639672021-08-05 19:47:48 +0100370 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100371 # Skip parsing this line if a block comment ends on it,
372 # but don't skip if it has just started -- there is a chance
373 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100374 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100375 in_block_comment = not in_block_comment
376 if re.search(r"\*/", line):
377 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100378 continue
379
Yuto Takano81528c02021-08-06 16:22:06 +0100380 if in_block_comment:
381 previous_line = None
382 continue
383
384 if re.match(EXCLUDED_LINES, line):
385 previous_line = None
386 continue
387
388 # Match "^something something$", with optional inline/static
389 # This *might* be a function with its argument brackets on
390 # the next line, or a struct declaration, so keep note of it
391 if re.match(
392 r"(inline |static |typedef )*\w+ \w+$",
393 line):
394 previous_line = line
395 continue
396
397 # If previous line seemed to start an unfinished declaration
398 # (as above), and this line begins with a bracket, concat
399 # them and treat them as one line.
400 if previous_line and re.match(" *[\({]", line):
401 line = previous_line.strip() + line.strip()
402 previous_line = None
403
404 # Skip parsing if line has a space in front = hueristic to
405 # skip function argument lines (highly subject to formatting
406 # changes)
407 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100408 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100409
Yuto Takano39639672021-08-05 19:47:48 +0100410 identifier = re.search(
Yuto Takano81528c02021-08-06 16:22:06 +0100411 # Match something(
412 r".* \**(\w+)\(|"
413 # Match (*something)(
414 r".*\( *\* *(\w+) *\) *\(|"
415 # Match names of named data structures
416 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
417 # Match names of typedef instances, after closing bracket
418 r"}? *(\w+)[;[].*",
Yuto Takano39639672021-08-05 19:47:48 +0100419 line
420 )
421
422 if identifier:
Yuto Takano81528c02021-08-06 16:22:06 +0100423 # Find the group that matched, and append it
Yuto Takano39639672021-08-05 19:47:48 +0100424 for group in identifier.groups():
425 if group:
426 identifiers.append(Match(
427 header_file,
428 line,
429 (identifier.start(), identifier.end()),
Yuto Takano81528c02021-08-06 16:22:06 +0100430 group))
Yuto Takano39639672021-08-05 19:47:48 +0100431
432 return identifiers
433
434 def parse_symbols(self):
435 """
436 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
437 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100438 Exceptions thrown here are rethrown because they would be critical
439 errors that void several tests, and thus needs to halt the program. This
440 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100441
Yuto Takano81528c02021-08-06 16:22:06 +0100442 Returns a List of unique symbols defined and used in the libraries.
443 """
444 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100445 symbols = []
446
447 # Back up the config and atomically compile with the full configratuion.
448 shutil.copy("include/mbedtls/mbedtls_config.h",
Yuto Takano81528c02021-08-06 16:22:06 +0100449 "include/mbedtls/mbedtls_config.h.bak")
Darryl Greend5802922018-05-08 15:30:59 +0100450 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100451 # Use check=True in all subprocess calls so that failures are raised
452 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100453 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100454 ["python3", "scripts/config.py", "full"],
Yuto Takano39639672021-08-05 19:47:48 +0100455 encoding=sys.stdout.encoding,
456 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100457 )
458 my_environment = os.environ.copy()
459 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano39639672021-08-05 19:47:48 +0100460 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100461 ["make", "clean", "lib"],
462 env=my_environment,
Yuto Takano39639672021-08-05 19:47:48 +0100463 encoding=sys.stdout.encoding,
464 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100465 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100466 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100467 )
Yuto Takano39639672021-08-05 19:47:48 +0100468
469 # Perform object file analysis using nm
470 symbols = self.parse_symbols_from_nm(
471 ["library/libmbedcrypto.a",
472 "library/libmbedtls.a",
473 "library/libmbedx509.a"])
474
475 symbols.sort()
476
477 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100478 ["make", "clean"],
Yuto Takano39639672021-08-05 19:47:48 +0100479 encoding=sys.stdout.encoding,
480 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100481 )
482 except subprocess.CalledProcessError as error:
Darryl Greend5802922018-05-08 15:30:59 +0100483 self.set_return_code(2)
Yuto Takano81528c02021-08-06 16:22:06 +0100484 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100485 finally:
486 shutil.move("include/mbedtls/mbedtls_config.h.bak",
487 "include/mbedtls/mbedtls_config.h")
488
489 return symbols
490
491 def parse_symbols_from_nm(self, object_files):
492 """
493 Run nm to retrieve the list of referenced symbols in each object file.
494 Does not return the position data since it is of no use.
495
Yuto Takano81528c02021-08-06 16:22:06 +0100496 Args:
497 * object_files: a List of compiled object files to search through.
498
499 Returns a List of unique symbols defined and used in any of the object
500 files.
Yuto Takano39639672021-08-05 19:47:48 +0100501 """
502 UNDEFINED_SYMBOL = r"^\S+: +U |^$|^\S+:$"
503 VALID_SYMBOL = r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)"
Yuto Takanoe77f6992021-08-05 20:22:59 +0100504 EXCLUSIONS = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100505
506 symbols = []
507
Yuto Takano81528c02021-08-06 16:22:06 +0100508 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100509 nm_output = ""
510 for lib in object_files:
511 nm_output += subprocess.run(
512 ["nm", "-og", lib],
513 encoding=sys.stdout.encoding,
514 stdout=subprocess.PIPE,
515 stderr=subprocess.STDOUT,
516 check=True
517 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100518
Yuto Takano39639672021-08-05 19:47:48 +0100519 for line in nm_output.splitlines():
520 if not re.match(UNDEFINED_SYMBOL, line):
521 symbol = re.match(VALID_SYMBOL, line)
Yuto Takanoe77f6992021-08-05 20:22:59 +0100522 if symbol and not symbol.group("symbol").startswith(EXCLUSIONS):
523 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100524 else:
525 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100526
Yuto Takano39639672021-08-05 19:47:48 +0100527 return symbols
528
Yuto Takano81528c02021-08-06 16:22:06 +0100529 def perform_checks(self, show_problems: True):
Yuto Takano39639672021-08-05 19:47:48 +0100530 """
531 Perform each check in order, output its PASS/FAIL status. Maintain an
532 overall test status, and output that at the end.
Yuto Takano81528c02021-08-06 16:22:06 +0100533
534 Args:
535 * show_problems: whether to show the problematic examples.
Yuto Takano39639672021-08-05 19:47:48 +0100536 """
Yuto Takano81528c02021-08-06 16:22:06 +0100537 self.log.info("=============")
Yuto Takano39639672021-08-05 19:47:48 +0100538 problems = 0
539
Yuto Takano81528c02021-08-06 16:22:06 +0100540 problems += self.check_symbols_declared_in_header(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100541
542 pattern_checks = [
543 ("macros", MACRO_PATTERN),
Yuto Takano81528c02021-08-06 16:22:06 +0100544 ("enum_consts", CONSTANTS_PATTERN),
Yuto Takano39639672021-08-05 19:47:48 +0100545 ("identifiers", IDENTIFIER_PATTERN)]
546 for group, check_pattern in pattern_checks:
Yuto Takano81528c02021-08-06 16:22:06 +0100547 problems += self.check_match_pattern(
548 show_problems, group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100549
Yuto Takano81528c02021-08-06 16:22:06 +0100550 problems += self.check_for_typos(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100551
552 self.log.info("=============")
553 if problems > 0:
554 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano81528c02021-08-06 16:22:06 +0100555 if not show_problems:
556 self.log.info("Remove --quiet to show the problems.")
Yuto Takano39639672021-08-05 19:47:48 +0100557 else:
558 self.log.info("PASS")
Darryl Greend5802922018-05-08 15:30:59 +0100559
Yuto Takano81528c02021-08-06 16:22:06 +0100560 def check_symbols_declared_in_header(self, show_problems):
Yuto Takano39639672021-08-05 19:47:48 +0100561 """
562 Perform a check that all detected symbols in the library object files
563 are properly declared in headers.
Darryl Greend5802922018-05-08 15:30:59 +0100564
Yuto Takano81528c02021-08-06 16:22:06 +0100565 Args:
566 * show_problems: whether to show the problematic examples.
567
568 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100569 """
570 problems = []
571 for symbol in self.parse_result["symbols"]:
572 found_symbol_declared = False
573 for identifier_match in self.parse_result["identifiers"]:
574 if symbol == identifier_match.name:
575 found_symbol_declared = True
576 break
Yuto Takano81528c02021-08-06 16:22:06 +0100577
Yuto Takano39639672021-08-05 19:47:48 +0100578 if not found_symbol_declared:
579 problems.append(SymbolNotInHeader(symbol))
580
Yuto Takano81528c02021-08-06 16:22:06 +0100581 self.output_check_result("All symbols in header", problems, show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100582 return len(problems)
583
Yuto Takano81528c02021-08-06 16:22:06 +0100584
585 def check_match_pattern(self, show_problems, group_to_check, check_pattern):
586 """
587 Perform a check that all items of a group conform to a regex pattern.
588
589 Args:
590 * show_problems: whether to show the problematic examples.
591 * group_to_check: string key to index into self.parse_result.
592 * check_pattern: the regex to check against.
593
594 Returns the number of problems that need fixing.
595 """
Yuto Takano39639672021-08-05 19:47:48 +0100596 problems = []
597 for item_match in self.parse_result[group_to_check]:
598 if not re.match(check_pattern, item_match.name):
599 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano201f9e82021-08-06 16:36:54 +0100600 # Double underscore is a reserved identifier, never to be used
Yuto Takanoc763cc32021-08-05 20:06:34 +0100601 if re.match(r".*__.*", item_match.name):
602 problems.append(PatternMismatch("double underscore", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100603
604 self.output_check_result(
605 "Naming patterns of {}".format(group_to_check),
606 problems,
607 show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100608 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100609
Yuto Takano81528c02021-08-06 16:22:06 +0100610 def check_for_typos(self, show_problems):
611 """
612 Perform a check that all words in the soure code beginning with MBED are
613 either defined as macros, or as enum constants.
614
615 Args:
616 * show_problems: whether to show the problematic examples.
617
618 Returns the number of problems that need fixing.
619 """
Yuto Takano39639672021-08-05 19:47:48 +0100620 problems = []
621 all_caps_names = list(set([
622 match.name for match
623 in self.parse_result["macros"] + self.parse_result["enum_consts"]]
Darryl Greend5802922018-05-08 15:30:59 +0100624 ))
Yuto Takano39639672021-08-05 19:47:48 +0100625
626 TYPO_EXCLUSION = r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$"
627
628 for name_match in self.parse_result["mbed_names"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100629 found = name_match.name in all_caps_names
630
631 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
632 # PSA driver, they will not exist as macros. However, they
633 # should still be checked for typos using the equivalent
634 # BUILTINs that exist.
635 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
636 found = name_match.name.replace(
637 "MBEDTLS_PSA_ACCEL_",
638 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
639
640 if not found and not re.search(TYPO_EXCLUSION, name_match.name):
Yuto Takano201f9e82021-08-06 16:36:54 +0100641 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100642
Yuto Takano81528c02021-08-06 16:22:06 +0100643 self.output_check_result("Likely typos", problems, show_problems)
644 return len(problems)
645
646 def output_check_result(self, name, problems, show_problems):
647 """
648 Write out the PASS/FAIL status of a performed check depending on whether
649 there were problems.
650
651 Args:
652 * show_problems: whether to show the problematic examples.
653 """
Yuto Takano39639672021-08-05 19:47:48 +0100654 if problems:
Darryl Greend5802922018-05-08 15:30:59 +0100655 self.set_return_code(1)
Yuto Takano81528c02021-08-06 16:22:06 +0100656 self.log.info("{}: FAIL".format(name))
657 if show_problems:
658 self.log.info("")
659 for problem in problems:
660 self.log.warn(str(problem) + "\n")
Darryl Greend5802922018-05-08 15:30:59 +0100661 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100662 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100663
Yuto Takano39639672021-08-05 19:47:48 +0100664def main():
665 """
Yuto Takano81528c02021-08-06 16:22:06 +0100666 Perform argument parsing, and create an instance of NameCheck to begin the
667 core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100668 """
Darryl Greend5802922018-05-08 15:30:59 +0100669
Yuto Takano39639672021-08-05 19:47:48 +0100670 parser = argparse.ArgumentParser(
671 formatter_class=argparse.RawDescriptionHelpFormatter,
672 description=(
673 "This script confirms that the naming of all symbols and identifiers "
674 "in Mbed TLS are consistent with the house style and are also "
675 "self-consistent.\n\n"
676 "Expected to be run from the MbedTLS root directory."))
Darryl Greend5802922018-05-08 15:30:59 +0100677
Yuto Takano39639672021-08-05 19:47:48 +0100678 parser.add_argument("-v", "--verbose",
679 action="store_true",
Yuto Takano81528c02021-08-06 16:22:06 +0100680 help="show parse results")
681
682 parser.add_argument("-q", "--quiet",
683 action="store_true",
684 help="hide unnecessary text and problematic examples")
685
Yuto Takano39639672021-08-05 19:47:48 +0100686 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100687
Darryl Greend5802922018-05-08 15:30:59 +0100688 try:
689 name_check = NameCheck()
Yuto Takano39639672021-08-05 19:47:48 +0100690 name_check.setup_logger(verbose=args.verbose)
691 name_check.parse_names_in_source()
Yuto Takano81528c02021-08-06 16:22:06 +0100692 name_check.perform_checks(show_problems=not args.quiet)
693 sys.exit(name_check.return_code)
694 except subprocess.CalledProcessError as error:
695 traceback.print_exc()
696 print("!! Compilation faced a critical error, "
697 "check-names can't continue further.")
Darryl Greend5802922018-05-08 15:30:59 +0100698 sys.exit(name_check.return_code)
699 except Exception:
700 traceback.print_exc()
701 sys.exit(2)
702
Darryl Greend5802922018-05-08 15:30:59 +0100703if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100704 main()