blob: 659eda9039054e37a3563f8f53e23459fbbd65a0 [file] [log] [blame]
Yuto Takano39639672021-08-05 19:47:48 +01001#!/usr/bin/env python3
2#
3# Copyright The Mbed TLS Contributors
4# SPDX-License-Identifier: Apache-2.0
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
Darryl Greend5802922018-05-08 15:30:59 +010018"""
Yuto Takano39639672021-08-05 19:47:48 +010019This script confirms that the naming of all symbols and identifiers in Mbed TLS
Yuto Takano159255a2021-08-06 17:00:28 +010020are consistent with the house style and are also self-consistent. It only runs
21on Linux and macOS since it depends on nm.
22
23The script performs the following checks:
Yuto Takano81528c02021-08-06 16:22:06 +010024
25- All exported and available symbols in the library object files, are explicitly
Yuto Takano159255a2021-08-06 17:00:28 +010026 declared in the header files. This uses the nm command.
Yuto Takano81528c02021-08-06 16:22:06 +010027- All macros, constants, and identifiers (function names, struct names, etc)
28 follow the required pattern.
29- Typo checking: All words that begin with MBED exist as macros or constants.
Darryl Greend5802922018-05-08 15:30:59 +010030"""
Yuto Takano39639672021-08-05 19:47:48 +010031
32import argparse
33import textwrap
Darryl Greend5802922018-05-08 15:30:59 +010034import os
35import sys
36import traceback
37import re
38import shutil
39import subprocess
40import logging
41
Yuto Takano81528c02021-08-06 16:22:06 +010042# Naming patterns to check against. These are defined outside the NameCheck
43# class for ease of modification.
Yuto Takanobb7dca42021-08-05 19:57:58 +010044MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
Yuto Takano81528c02021-08-06 16:22:06 +010045CONSTANTS_PATTERN = MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010046IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010047
48class Match(object):
Yuto Takano81528c02021-08-06 16:22:06 +010049 """
50 A class representing a match, together with its found position.
51
52 Fields:
53 * filename: the file that the match was in.
54 * line: the full line containing the match.
55 * pos: a tuple of (start, end) positions on the line where the match is.
56 * name: the match itself.
57 """
Yuto Takano39639672021-08-05 19:47:48 +010058 def __init__(self, filename, line, pos, name):
59 self.filename = filename
60 self.line = line
61 self.pos = pos
62 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010063
64class Problem(object):
Yuto Takano81528c02021-08-06 16:22:06 +010065 """
66 A parent class representing a form of static analysis error.
67
68 Fields:
69 * textwrapper: a TextWrapper instance to format problems nicely.
70 """
Yuto Takano39639672021-08-05 19:47:48 +010071 def __init__(self):
72 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +010073 self.textwrapper.width = 80
74 self.textwrapper.initial_indent = " * "
75 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +010076
77class SymbolNotInHeader(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +010078 """
79 A problem that occurs when an exported/available symbol in the object file
80 is not explicitly declared in header files. Created with
81 NameCheck.check_symbols_declared_in_header()
82
83 Fields:
84 * symbol_name: the name of the symbol.
85 """
Yuto Takano39639672021-08-05 19:47:48 +010086 def __init__(self, symbol_name):
87 self.symbol_name = symbol_name
88 Problem.__init__(self)
89
90 def __str__(self):
91 return self.textwrapper.fill(
92 "'{0}' was found as an available symbol in the output of nm, "
93 "however it was not declared in any header files."
94 .format(self.symbol_name))
95
96class PatternMismatch(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +010097 """
98 A problem that occurs when something doesn't match the expected pattern.
99 Created with NameCheck.check_match_pattern()
100
101 Fields:
102 * pattern: the expected regex pattern
103 * match: the Match object in question
104 """
Yuto Takano39639672021-08-05 19:47:48 +0100105 def __init__(self, pattern, match):
106 self.pattern = pattern
107 self.match = match
108 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100109
Yuto Takano39639672021-08-05 19:47:48 +0100110 def __str__(self):
111 return self.textwrapper.fill(
112 "{0}: '{1}' does not match the required pattern '{2}'."
113 .format(self.match.filename, self.match.name, self.pattern))
114
115class Typo(Problem):
Yuto Takano81528c02021-08-06 16:22:06 +0100116 """
117 A problem that occurs when a word using MBED doesn't appear to be defined as
118 constants nor enum values. Created with NameCheck.check_for_typos()
119
120 Fields:
121 * match: the Match object of the MBED name in question.
122 """
Yuto Takano39639672021-08-05 19:47:48 +0100123 def __init__(self, match):
124 self.match = match
125 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100126
Yuto Takano39639672021-08-05 19:47:48 +0100127 def __str__(self):
128 return self.textwrapper.fill(
129 "{0}: '{1}' looks like a typo. It was not found in any macros or "
130 "any enums. If this is not a typo, put //no-check-names after it."
131 .format(self.match.filename, self.match.name))
Darryl Greend5802922018-05-08 15:30:59 +0100132
133class NameCheck(object):
Yuto Takano81528c02021-08-06 16:22:06 +0100134 """
135 Representation of the core name checking operation performed by this script.
136 Shares a common logger, common excluded filenames, and a shared return_code.
137 """
Darryl Greend5802922018-05-08 15:30:59 +0100138 def __init__(self):
139 self.log = None
Darryl Greend5802922018-05-08 15:30:59 +0100140 self.check_repo_path()
141 self.return_code = 0
Yuto Takano81528c02021-08-06 16:22:06 +0100142 self.excluded_files = ["bn_mul", "compat-2.x.h"]
Darryl Greend5802922018-05-08 15:30:59 +0100143
144 def set_return_code(self, return_code):
145 if return_code > self.return_code:
Yuto Takano201f9e82021-08-06 16:36:54 +0100146 self.log.debug("Setting new return code to {}".format(return_code))
Darryl Greend5802922018-05-08 15:30:59 +0100147 self.return_code = return_code
148
Yuto Takano39639672021-08-05 19:47:48 +0100149 def setup_logger(self, verbose=False):
150 """
151 Set up a logger and set the change the default logging level from
Yuto Takano81528c02021-08-06 16:22:06 +0100152 WARNING to INFO. Loggers are better than print statements since their
Yuto Takano39639672021-08-05 19:47:48 +0100153 verbosity can be controlled.
154 """
Darryl Greend5802922018-05-08 15:30:59 +0100155 self.log = logging.getLogger()
Yuto Takano39639672021-08-05 19:47:48 +0100156 if verbose:
157 self.log.setLevel(logging.DEBUG)
158 else:
159 self.log.setLevel(logging.INFO)
Darryl Greend5802922018-05-08 15:30:59 +0100160 self.log.addHandler(logging.StreamHandler())
161
162 def check_repo_path(self):
Yuto Takano39639672021-08-05 19:47:48 +0100163 """
164 Check that the current working directory is the project root, and throw
165 an exception if not.
166 """
Yuto Takano5939a2a2021-08-06 16:40:30 +0100167 if (not os.path.isdir("include") or
168 not os.path.isdir("tests") or
169 not os.path.isdir("library")):
170 raise Exception("This script must be run from Mbed TLS root")
Darryl Greend5802922018-05-08 15:30:59 +0100171
Yuto Takano157444c2021-08-05 20:10:45 +0100172 def get_files(self, extension, directory):
Yuto Takano81528c02021-08-06 16:22:06 +0100173 """
174 Get all files that end with .extension in the specified directory
175 recursively.
176
177 Args:
178 * extension: the file extension to search for, without the dot
179 * directory: the directory to recursively search for
180
181 Returns a List of relative filepaths.
182 """
Darryl Greend5802922018-05-08 15:30:59 +0100183 filenames = []
184 for root, dirs, files in sorted(os.walk(directory)):
185 for filename in sorted(files):
186 if (filename not in self.excluded_files and
Yuto Takano157444c2021-08-05 20:10:45 +0100187 filename.endswith("." + extension)):
Darryl Greend5802922018-05-08 15:30:59 +0100188 filenames.append(os.path.join(root, filename))
189 return filenames
190
Yuto Takano81528c02021-08-06 16:22:06 +0100191 def parse_names_in_source(self):
192 """
193 Calls each parsing function to retrieve various elements of the code,
194 together with their source location. Puts the parsed values in the
195 internal variable self.parse_result.
196 """
197 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100198 self.log.debug(
199 "The following files are excluded from the search: {}"
200 .format(str(self.excluded_files))
201 )
Yuto Takano81528c02021-08-06 16:22:06 +0100202
203 m_headers = self.get_files("h", os.path.join("include", "mbedtls"))
204 p_headers = self.get_files("h", os.path.join("include", "psa"))
205 t_headers = ["3rdparty/everest/include/everest/everest.h",
206 "3rdparty/everest/include/everest/x25519.h"]
207 d_headers = self.get_files("h", os.path.join("tests", "include", "test", "drivers"))
208 l_headers = self.get_files("h", "library")
209 libraries = self.get_files("c", "library") + [
210 "3rdparty/everest/library/everest.c",
211 "3rdparty/everest/library/x25519.c"]
212
213 all_macros = self.parse_macros(
214 m_headers + p_headers + t_headers + l_headers + d_headers)
215 enum_consts = self.parse_enum_consts(
216 m_headers + l_headers + t_headers)
217 identifiers = self.parse_identifiers(
218 m_headers + p_headers + t_headers + l_headers)
219 mbed_names = self.parse_MBED_names(
220 m_headers + p_headers + t_headers + l_headers + libraries)
221 symbols = self.parse_symbols()
222
223 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
224 identifiers_justname = [x.name for x in identifiers]
225 actual_macros = []
226 for macro in all_macros:
227 if macro.name not in identifiers_justname:
228 actual_macros.append(macro)
229
230 self.log.debug("Found:")
231 self.log.debug(" {} Macros".format(len(all_macros)))
232 self.log.debug(" {} Non-identifier Macros".format(len(actual_macros)))
233 self.log.debug(" {} Enum Constants".format(len(enum_consts)))
234 self.log.debug(" {} Identifiers".format(len(identifiers)))
235 self.log.debug(" {} Exported Symbols".format(len(symbols)))
236 self.log.info("Analysing...")
237
238 self.parse_result = {
239 "macros": actual_macros,
240 "enum_consts": enum_consts,
241 "identifiers": identifiers,
242 "symbols": symbols,
243 "mbed_names": mbed_names
244 }
245
Yuto Takano39639672021-08-05 19:47:48 +0100246 def parse_macros(self, header_files):
247 """
248 Parse all macros defined by #define preprocessor directives.
249
250 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100251 * header_files: A List of filepaths to look through.
252
253 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100254 """
Yuto Takano5c1acf22021-08-06 16:44:08 +0100255 MACRO_REGEX = r"# *define +(?P<macro>\w+)"
Yuto Takano39639672021-08-05 19:47:48 +0100256 NON_MACROS = (
257 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
258 )
259
260 macros = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100261 self.log.debug("Looking for macros in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100262 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100263 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100264 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100265 for macro in re.finditer(MACRO_REGEX, line):
266 if not macro.group("macro").startswith(NON_MACROS):
267 macros.append(Match(
268 header_file,
269 line,
270 (macro.start(), macro.end()),
271 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100272
Yuto Takano39639672021-08-05 19:47:48 +0100273 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100274
Yuto Takanobb7dca42021-08-05 19:57:58 +0100275 def parse_MBED_names(self, files):
Yuto Takano39639672021-08-05 19:47:48 +0100276 """
277 Parse all words in the file that begin with MBED. Includes macros.
Yuto Takano81528c02021-08-06 16:22:06 +0100278 There have been typos of TLS, hence the broader check than MBEDTLS.
Yuto Takano39639672021-08-05 19:47:48 +0100279
280 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100281 * files: a List of filepaths to look through.
282
283 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100284 """
285 MBED_names = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100286 self.log.debug("Looking for MBED names in {} files".format(len(files)))
Yuto Takanobb7dca42021-08-05 19:57:58 +0100287 for filename in files:
Yuto Takano39639672021-08-05 19:47:48 +0100288 with open(filename, "r") as fp:
289 for line in fp:
Yuto Takano81528c02021-08-06 16:22:06 +0100290 # Ignore any names that are deliberately opted-out or in
291 # legacy error directives
292 if re.search(r"// *no-check-names|#error", line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100293 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100294
Yuto Takano39639672021-08-05 19:47:48 +0100295 for name in re.finditer(r"\bMBED.+?_[A-Z0-9_]*", line):
296 MBED_names.append(Match(
297 filename,
298 line,
299 (name.start(), name.end()),
300 name.group(0)
301 ))
302
303 return MBED_names
304
305 def parse_enum_consts(self, header_files):
306 """
307 Parse all enum value constants that are declared.
308
309 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100310 * header_files: A List of filepaths to look through.
Yuto Takano39639672021-08-05 19:47:48 +0100311
Yuto Takano81528c02021-08-06 16:22:06 +0100312 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100313 """
314
315 enum_consts = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100316 self.log.debug("Looking for enum consts in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100317 for header_file in header_files:
318 # Emulate a finite state machine to parse enum declarations.
Yuto Takano81528c02021-08-06 16:22:06 +0100319 # 0 = not in enum
320 # 1 = inside enum
321 # 2 = almost inside enum
Darryl Greend5802922018-05-08 15:30:59 +0100322 state = 0
323 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100324 for line in header:
Yuto Takano13ecd992021-08-06 16:56:52 +0100325 # Match typedefs and brackets only when they are at the
326 # beginning of the line -- if they are indented, they might
327 # be sub-structures within structs, etc.
328 if state is 0 and re.match(r"^(typedef +)?enum +{", line):
Darryl Greend5802922018-05-08 15:30:59 +0100329 state = 1
Yuto Takano13ecd992021-08-06 16:56:52 +0100330 elif state is 0 and re.match(r"^(typedef +)?enum", line):
Darryl Greend5802922018-05-08 15:30:59 +0100331 state = 2
332 elif state is 2 and re.match(r"^{", line):
333 state = 1
334 elif state is 1 and re.match(r"^}", line):
335 state = 0
Yuto Takano13ecd992021-08-06 16:56:52 +0100336 elif state is 1 and not re.match(r" *#", line):
337 enum_const = re.match(r" *(?P<enum_const>\w+)", line)
Darryl Greend5802922018-05-08 15:30:59 +0100338 if enum_const:
Yuto Takano39639672021-08-05 19:47:48 +0100339 enum_consts.append(Match(
340 header_file,
341 line,
342 (enum_const.start(), enum_const.end()),
343 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100344
Yuto Takano39639672021-08-05 19:47:48 +0100345 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100346
Yuto Takano39639672021-08-05 19:47:48 +0100347 def parse_identifiers(self, header_files):
348 """
349 Parse all lines of a header where a function identifier is declared,
Yuto Takano81528c02021-08-06 16:22:06 +0100350 based on some huersitics. Highly dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100351
Yuto Takano39639672021-08-05 19:47:48 +0100352 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100353 * header_files: A List of filepaths to look through.
354
355 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100356 """
Yuto Takano81528c02021-08-06 16:22:06 +0100357 EXCLUDED_LINES = (
358 r"^("
Yuto Takano13ecd992021-08-06 16:56:52 +0100359 r"extern +\"C\"|"
360 r"(typedef +)?(struct|union|enum)( *{)?$|"
361 r"} *;?$|"
Yuto Takano81528c02021-08-06 16:22:06 +0100362 r"$|"
363 r"//|"
364 r"#"
365 r")"
Darryl Greend5802922018-05-08 15:30:59 +0100366 )
Darryl Greend5802922018-05-08 15:30:59 +0100367
Yuto Takano39639672021-08-05 19:47:48 +0100368 identifiers = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100369 self.log.debug("Looking for identifiers in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100370 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100371 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100372 in_block_comment = False
Yuto Takano81528c02021-08-06 16:22:06 +0100373 previous_line = None
Darryl Greend5802922018-05-08 15:30:59 +0100374
Yuto Takano39639672021-08-05 19:47:48 +0100375 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100376 # Skip parsing this line if a block comment ends on it,
377 # but don't skip if it has just started -- there is a chance
378 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100379 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100380 in_block_comment = not in_block_comment
381 if re.search(r"\*/", line):
382 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100383 continue
384
Yuto Takano81528c02021-08-06 16:22:06 +0100385 if in_block_comment:
386 previous_line = None
387 continue
388
389 if re.match(EXCLUDED_LINES, line):
390 previous_line = None
391 continue
392
393 # Match "^something something$", with optional inline/static
394 # This *might* be a function with its argument brackets on
395 # the next line, or a struct declaration, so keep note of it
396 if re.match(
Yuto Takano13ecd992021-08-06 16:56:52 +0100397 r"(inline +|static +|typedef +)*\w+ +\w+$",
Yuto Takano81528c02021-08-06 16:22:06 +0100398 line):
399 previous_line = line
400 continue
401
402 # If previous line seemed to start an unfinished declaration
403 # (as above), and this line begins with a bracket, concat
404 # them and treat them as one line.
405 if previous_line and re.match(" *[\({]", line):
406 line = previous_line.strip() + line.strip()
407 previous_line = None
408
409 # Skip parsing if line has a space in front = hueristic to
410 # skip function argument lines (highly subject to formatting
411 # changes)
412 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100413 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100414
Yuto Takano39639672021-08-05 19:47:48 +0100415 identifier = re.search(
Yuto Takano13ecd992021-08-06 16:56:52 +0100416 # Match " something(" or " *something(". function calls.
Yuto Takano81528c02021-08-06 16:22:06 +0100417 r".* \**(\w+)\(|"
418 # Match (*something)(
419 r".*\( *\* *(\w+) *\) *\(|"
420 # Match names of named data structures
421 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
422 # Match names of typedef instances, after closing bracket
423 r"}? *(\w+)[;[].*",
Yuto Takano39639672021-08-05 19:47:48 +0100424 line
425 )
426
427 if identifier:
Yuto Takano81528c02021-08-06 16:22:06 +0100428 # Find the group that matched, and append it
Yuto Takano39639672021-08-05 19:47:48 +0100429 for group in identifier.groups():
430 if group:
431 identifiers.append(Match(
432 header_file,
433 line,
434 (identifier.start(), identifier.end()),
Yuto Takano81528c02021-08-06 16:22:06 +0100435 group))
Yuto Takano39639672021-08-05 19:47:48 +0100436
437 return identifiers
438
439 def parse_symbols(self):
440 """
441 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
442 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100443 Exceptions thrown here are rethrown because they would be critical
444 errors that void several tests, and thus needs to halt the program. This
445 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100446
Yuto Takano81528c02021-08-06 16:22:06 +0100447 Returns a List of unique symbols defined and used in the libraries.
448 """
449 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100450 symbols = []
451
452 # Back up the config and atomically compile with the full configratuion.
453 shutil.copy("include/mbedtls/mbedtls_config.h",
Yuto Takano81528c02021-08-06 16:22:06 +0100454 "include/mbedtls/mbedtls_config.h.bak")
Darryl Greend5802922018-05-08 15:30:59 +0100455 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100456 # Use check=True in all subprocess calls so that failures are raised
457 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100458 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100459 ["python3", "scripts/config.py", "full"],
Yuto Takano39639672021-08-05 19:47:48 +0100460 encoding=sys.stdout.encoding,
461 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100462 )
463 my_environment = os.environ.copy()
464 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano39639672021-08-05 19:47:48 +0100465 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100466 ["make", "clean", "lib"],
467 env=my_environment,
Yuto Takano39639672021-08-05 19:47:48 +0100468 encoding=sys.stdout.encoding,
469 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100470 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100471 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100472 )
Yuto Takano39639672021-08-05 19:47:48 +0100473
474 # Perform object file analysis using nm
475 symbols = self.parse_symbols_from_nm(
476 ["library/libmbedcrypto.a",
477 "library/libmbedtls.a",
478 "library/libmbedx509.a"])
479
480 symbols.sort()
481
482 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100483 ["make", "clean"],
Yuto Takano39639672021-08-05 19:47:48 +0100484 encoding=sys.stdout.encoding,
485 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100486 )
487 except subprocess.CalledProcessError as error:
Darryl Greend5802922018-05-08 15:30:59 +0100488 self.set_return_code(2)
Yuto Takano81528c02021-08-06 16:22:06 +0100489 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100490 finally:
491 shutil.move("include/mbedtls/mbedtls_config.h.bak",
492 "include/mbedtls/mbedtls_config.h")
493
494 return symbols
495
496 def parse_symbols_from_nm(self, object_files):
497 """
498 Run nm to retrieve the list of referenced symbols in each object file.
499 Does not return the position data since it is of no use.
500
Yuto Takano81528c02021-08-06 16:22:06 +0100501 Args:
502 * object_files: a List of compiled object files to search through.
503
504 Returns a List of unique symbols defined and used in any of the object
505 files.
Yuto Takano39639672021-08-05 19:47:48 +0100506 """
507 UNDEFINED_SYMBOL = r"^\S+: +U |^$|^\S+:$"
508 VALID_SYMBOL = r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)"
Yuto Takanoe77f6992021-08-05 20:22:59 +0100509 EXCLUSIONS = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100510
511 symbols = []
512
Yuto Takano81528c02021-08-06 16:22:06 +0100513 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100514 nm_output = ""
515 for lib in object_files:
516 nm_output += subprocess.run(
517 ["nm", "-og", lib],
518 encoding=sys.stdout.encoding,
519 stdout=subprocess.PIPE,
520 stderr=subprocess.STDOUT,
521 check=True
522 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100523
Yuto Takano39639672021-08-05 19:47:48 +0100524 for line in nm_output.splitlines():
525 if not re.match(UNDEFINED_SYMBOL, line):
526 symbol = re.match(VALID_SYMBOL, line)
Yuto Takanoe77f6992021-08-05 20:22:59 +0100527 if symbol and not symbol.group("symbol").startswith(EXCLUSIONS):
528 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100529 else:
530 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100531
Yuto Takano39639672021-08-05 19:47:48 +0100532 return symbols
533
Yuto Takano81528c02021-08-06 16:22:06 +0100534 def perform_checks(self, show_problems: True):
Yuto Takano39639672021-08-05 19:47:48 +0100535 """
536 Perform each check in order, output its PASS/FAIL status. Maintain an
537 overall test status, and output that at the end.
Yuto Takano81528c02021-08-06 16:22:06 +0100538
539 Args:
540 * show_problems: whether to show the problematic examples.
Yuto Takano39639672021-08-05 19:47:48 +0100541 """
Yuto Takano81528c02021-08-06 16:22:06 +0100542 self.log.info("=============")
Yuto Takano39639672021-08-05 19:47:48 +0100543 problems = 0
544
Yuto Takano81528c02021-08-06 16:22:06 +0100545 problems += self.check_symbols_declared_in_header(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100546
547 pattern_checks = [
548 ("macros", MACRO_PATTERN),
Yuto Takano81528c02021-08-06 16:22:06 +0100549 ("enum_consts", CONSTANTS_PATTERN),
Yuto Takano39639672021-08-05 19:47:48 +0100550 ("identifiers", IDENTIFIER_PATTERN)]
551 for group, check_pattern in pattern_checks:
Yuto Takano81528c02021-08-06 16:22:06 +0100552 problems += self.check_match_pattern(
553 show_problems, group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100554
Yuto Takano81528c02021-08-06 16:22:06 +0100555 problems += self.check_for_typos(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100556
557 self.log.info("=============")
558 if problems > 0:
559 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano81528c02021-08-06 16:22:06 +0100560 if not show_problems:
561 self.log.info("Remove --quiet to show the problems.")
Yuto Takano39639672021-08-05 19:47:48 +0100562 else:
563 self.log.info("PASS")
Darryl Greend5802922018-05-08 15:30:59 +0100564
Yuto Takano81528c02021-08-06 16:22:06 +0100565 def check_symbols_declared_in_header(self, show_problems):
Yuto Takano39639672021-08-05 19:47:48 +0100566 """
567 Perform a check that all detected symbols in the library object files
568 are properly declared in headers.
Darryl Greend5802922018-05-08 15:30:59 +0100569
Yuto Takano81528c02021-08-06 16:22:06 +0100570 Args:
571 * show_problems: whether to show the problematic examples.
572
573 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100574 """
575 problems = []
576 for symbol in self.parse_result["symbols"]:
577 found_symbol_declared = False
578 for identifier_match in self.parse_result["identifiers"]:
579 if symbol == identifier_match.name:
580 found_symbol_declared = True
581 break
Yuto Takano81528c02021-08-06 16:22:06 +0100582
Yuto Takano39639672021-08-05 19:47:48 +0100583 if not found_symbol_declared:
584 problems.append(SymbolNotInHeader(symbol))
585
Yuto Takano81528c02021-08-06 16:22:06 +0100586 self.output_check_result("All symbols in header", problems, show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100587 return len(problems)
588
Yuto Takano81528c02021-08-06 16:22:06 +0100589
590 def check_match_pattern(self, show_problems, group_to_check, check_pattern):
591 """
592 Perform a check that all items of a group conform to a regex pattern.
593
594 Args:
595 * show_problems: whether to show the problematic examples.
596 * group_to_check: string key to index into self.parse_result.
597 * check_pattern: the regex to check against.
598
599 Returns the number of problems that need fixing.
600 """
Yuto Takano39639672021-08-05 19:47:48 +0100601 problems = []
602 for item_match in self.parse_result[group_to_check]:
603 if not re.match(check_pattern, item_match.name):
604 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano201f9e82021-08-06 16:36:54 +0100605 # Double underscore is a reserved identifier, never to be used
Yuto Takanoc763cc32021-08-05 20:06:34 +0100606 if re.match(r".*__.*", item_match.name):
607 problems.append(PatternMismatch("double underscore", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100608
609 self.output_check_result(
610 "Naming patterns of {}".format(group_to_check),
611 problems,
612 show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100613 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100614
Yuto Takano81528c02021-08-06 16:22:06 +0100615 def check_for_typos(self, show_problems):
616 """
617 Perform a check that all words in the soure code beginning with MBED are
618 either defined as macros, or as enum constants.
619
620 Args:
621 * show_problems: whether to show the problematic examples.
622
623 Returns the number of problems that need fixing.
624 """
Yuto Takano39639672021-08-05 19:47:48 +0100625 problems = []
626 all_caps_names = list(set([
627 match.name for match
628 in self.parse_result["macros"] + self.parse_result["enum_consts"]]
Darryl Greend5802922018-05-08 15:30:59 +0100629 ))
Yuto Takano39639672021-08-05 19:47:48 +0100630
631 TYPO_EXCLUSION = r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$"
632
633 for name_match in self.parse_result["mbed_names"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100634 found = name_match.name in all_caps_names
635
636 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
637 # PSA driver, they will not exist as macros. However, they
638 # should still be checked for typos using the equivalent
639 # BUILTINs that exist.
640 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
641 found = name_match.name.replace(
642 "MBEDTLS_PSA_ACCEL_",
643 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
644
645 if not found and not re.search(TYPO_EXCLUSION, name_match.name):
Yuto Takano201f9e82021-08-06 16:36:54 +0100646 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100647
Yuto Takano81528c02021-08-06 16:22:06 +0100648 self.output_check_result("Likely typos", problems, show_problems)
649 return len(problems)
650
651 def output_check_result(self, name, problems, show_problems):
652 """
653 Write out the PASS/FAIL status of a performed check depending on whether
654 there were problems.
655
656 Args:
657 * show_problems: whether to show the problematic examples.
658 """
Yuto Takano39639672021-08-05 19:47:48 +0100659 if problems:
Darryl Greend5802922018-05-08 15:30:59 +0100660 self.set_return_code(1)
Yuto Takano81528c02021-08-06 16:22:06 +0100661 self.log.info("{}: FAIL".format(name))
662 if show_problems:
663 self.log.info("")
664 for problem in problems:
665 self.log.warn(str(problem) + "\n")
Darryl Greend5802922018-05-08 15:30:59 +0100666 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100667 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100668
Yuto Takano39639672021-08-05 19:47:48 +0100669def main():
670 """
Yuto Takano81528c02021-08-06 16:22:06 +0100671 Perform argument parsing, and create an instance of NameCheck to begin the
672 core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100673 """
Darryl Greend5802922018-05-08 15:30:59 +0100674
Yuto Takano39639672021-08-05 19:47:48 +0100675 parser = argparse.ArgumentParser(
676 formatter_class=argparse.RawDescriptionHelpFormatter,
677 description=(
678 "This script confirms that the naming of all symbols and identifiers "
679 "in Mbed TLS are consistent with the house style and are also "
680 "self-consistent.\n\n"
681 "Expected to be run from the MbedTLS root directory."))
Darryl Greend5802922018-05-08 15:30:59 +0100682
Yuto Takano39639672021-08-05 19:47:48 +0100683 parser.add_argument("-v", "--verbose",
684 action="store_true",
Yuto Takano81528c02021-08-06 16:22:06 +0100685 help="show parse results")
686
687 parser.add_argument("-q", "--quiet",
688 action="store_true",
689 help="hide unnecessary text and problematic examples")
690
Yuto Takano39639672021-08-05 19:47:48 +0100691 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100692
Darryl Greend5802922018-05-08 15:30:59 +0100693 try:
694 name_check = NameCheck()
Yuto Takano39639672021-08-05 19:47:48 +0100695 name_check.setup_logger(verbose=args.verbose)
696 name_check.parse_names_in_source()
Yuto Takano81528c02021-08-06 16:22:06 +0100697 name_check.perform_checks(show_problems=not args.quiet)
698 sys.exit(name_check.return_code)
699 except subprocess.CalledProcessError as error:
700 traceback.print_exc()
701 print("!! Compilation faced a critical error, "
702 "check-names can't continue further.")
Darryl Greend5802922018-05-08 15:30:59 +0100703 sys.exit(name_check.return_code)
704 except Exception:
705 traceback.print_exc()
706 sys.exit(2)
707
Darryl Greend5802922018-05-08 15:30:59 +0100708if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100709 main()