blob: 100001beebc771af81e0a6879211585bc731a8b8 [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 """
Yuto Takano5c1acf22021-08-06 16:44:08 +0100253 MACRO_REGEX = r"# *define +(?P<macro>\w+)"
Yuto Takano39639672021-08-05 19:47:48 +0100254 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:
Yuto Takano13ecd992021-08-06 16:56:52 +0100323 # Match typedefs and brackets only when they are at the
324 # beginning of the line -- if they are indented, they might
325 # be sub-structures within structs, etc.
326 if state is 0 and re.match(r"^(typedef +)?enum +{", line):
Darryl Greend5802922018-05-08 15:30:59 +0100327 state = 1
Yuto Takano13ecd992021-08-06 16:56:52 +0100328 elif state is 0 and re.match(r"^(typedef +)?enum", line):
Darryl Greend5802922018-05-08 15:30:59 +0100329 state = 2
330 elif state is 2 and re.match(r"^{", line):
331 state = 1
332 elif state is 1 and re.match(r"^}", line):
333 state = 0
Yuto Takano13ecd992021-08-06 16:56:52 +0100334 elif state is 1 and not re.match(r" *#", line):
335 enum_const = re.match(r" *(?P<enum_const>\w+)", line)
Darryl Greend5802922018-05-08 15:30:59 +0100336 if enum_const:
Yuto Takano39639672021-08-05 19:47:48 +0100337 enum_consts.append(Match(
338 header_file,
339 line,
340 (enum_const.start(), enum_const.end()),
341 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100342
Yuto Takano39639672021-08-05 19:47:48 +0100343 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100344
Yuto Takano39639672021-08-05 19:47:48 +0100345 def parse_identifiers(self, header_files):
346 """
347 Parse all lines of a header where a function identifier is declared,
Yuto Takano81528c02021-08-06 16:22:06 +0100348 based on some huersitics. Highly dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100349
Yuto Takano39639672021-08-05 19:47:48 +0100350 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100351 * header_files: A List of filepaths to look through.
352
353 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100354 """
Yuto Takano81528c02021-08-06 16:22:06 +0100355 EXCLUDED_LINES = (
356 r"^("
Yuto Takano13ecd992021-08-06 16:56:52 +0100357 r"extern +\"C\"|"
358 r"(typedef +)?(struct|union|enum)( *{)?$|"
359 r"} *;?$|"
Yuto Takano81528c02021-08-06 16:22:06 +0100360 r"$|"
361 r"//|"
362 r"#"
363 r")"
Darryl Greend5802922018-05-08 15:30:59 +0100364 )
Darryl Greend5802922018-05-08 15:30:59 +0100365
Yuto Takano39639672021-08-05 19:47:48 +0100366 identifiers = []
Yuto Takano201f9e82021-08-06 16:36:54 +0100367 self.log.debug("Looking for identifiers in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100368 for header_file in header_files:
Darryl Greend5802922018-05-08 15:30:59 +0100369 with open(header_file, "r") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100370 in_block_comment = False
Yuto Takano81528c02021-08-06 16:22:06 +0100371 previous_line = None
Darryl Greend5802922018-05-08 15:30:59 +0100372
Yuto Takano39639672021-08-05 19:47:48 +0100373 for line in header:
Yuto Takano81528c02021-08-06 16:22:06 +0100374 # Skip parsing this line if a block comment ends on it,
375 # but don't skip if it has just started -- there is a chance
376 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100377 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100378 in_block_comment = not in_block_comment
379 if re.search(r"\*/", line):
380 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100381 continue
382
Yuto Takano81528c02021-08-06 16:22:06 +0100383 if in_block_comment:
384 previous_line = None
385 continue
386
387 if re.match(EXCLUDED_LINES, line):
388 previous_line = None
389 continue
390
391 # Match "^something something$", with optional inline/static
392 # This *might* be a function with its argument brackets on
393 # the next line, or a struct declaration, so keep note of it
394 if re.match(
Yuto Takano13ecd992021-08-06 16:56:52 +0100395 r"(inline +|static +|typedef +)*\w+ +\w+$",
Yuto Takano81528c02021-08-06 16:22:06 +0100396 line):
397 previous_line = line
398 continue
399
400 # If previous line seemed to start an unfinished declaration
401 # (as above), and this line begins with a bracket, concat
402 # them and treat them as one line.
403 if previous_line and re.match(" *[\({]", line):
404 line = previous_line.strip() + line.strip()
405 previous_line = None
406
407 # Skip parsing if line has a space in front = hueristic to
408 # skip function argument lines (highly subject to formatting
409 # changes)
410 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100411 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100412
Yuto Takano39639672021-08-05 19:47:48 +0100413 identifier = re.search(
Yuto Takano13ecd992021-08-06 16:56:52 +0100414 # Match " something(" or " *something(". function calls.
Yuto Takano81528c02021-08-06 16:22:06 +0100415 r".* \**(\w+)\(|"
416 # Match (*something)(
417 r".*\( *\* *(\w+) *\) *\(|"
418 # Match names of named data structures
419 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
420 # Match names of typedef instances, after closing bracket
421 r"}? *(\w+)[;[].*",
Yuto Takano39639672021-08-05 19:47:48 +0100422 line
423 )
424
425 if identifier:
Yuto Takano81528c02021-08-06 16:22:06 +0100426 # Find the group that matched, and append it
Yuto Takano39639672021-08-05 19:47:48 +0100427 for group in identifier.groups():
428 if group:
429 identifiers.append(Match(
430 header_file,
431 line,
432 (identifier.start(), identifier.end()),
Yuto Takano81528c02021-08-06 16:22:06 +0100433 group))
Yuto Takano39639672021-08-05 19:47:48 +0100434
435 return identifiers
436
437 def parse_symbols(self):
438 """
439 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
440 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100441 Exceptions thrown here are rethrown because they would be critical
442 errors that void several tests, and thus needs to halt the program. This
443 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100444
Yuto Takano81528c02021-08-06 16:22:06 +0100445 Returns a List of unique symbols defined and used in the libraries.
446 """
447 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100448 symbols = []
449
450 # Back up the config and atomically compile with the full configratuion.
451 shutil.copy("include/mbedtls/mbedtls_config.h",
Yuto Takano81528c02021-08-06 16:22:06 +0100452 "include/mbedtls/mbedtls_config.h.bak")
Darryl Greend5802922018-05-08 15:30:59 +0100453 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100454 # Use check=True in all subprocess calls so that failures are raised
455 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100456 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100457 ["python3", "scripts/config.py", "full"],
Yuto Takano39639672021-08-05 19:47:48 +0100458 encoding=sys.stdout.encoding,
459 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100460 )
461 my_environment = os.environ.copy()
462 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano39639672021-08-05 19:47:48 +0100463 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100464 ["make", "clean", "lib"],
465 env=my_environment,
Yuto Takano39639672021-08-05 19:47:48 +0100466 encoding=sys.stdout.encoding,
467 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100468 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100469 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100470 )
Yuto Takano39639672021-08-05 19:47:48 +0100471
472 # Perform object file analysis using nm
473 symbols = self.parse_symbols_from_nm(
474 ["library/libmbedcrypto.a",
475 "library/libmbedtls.a",
476 "library/libmbedx509.a"])
477
478 symbols.sort()
479
480 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100481 ["make", "clean"],
Yuto Takano39639672021-08-05 19:47:48 +0100482 encoding=sys.stdout.encoding,
483 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100484 )
485 except subprocess.CalledProcessError as error:
Darryl Greend5802922018-05-08 15:30:59 +0100486 self.set_return_code(2)
Yuto Takano81528c02021-08-06 16:22:06 +0100487 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100488 finally:
489 shutil.move("include/mbedtls/mbedtls_config.h.bak",
490 "include/mbedtls/mbedtls_config.h")
491
492 return symbols
493
494 def parse_symbols_from_nm(self, object_files):
495 """
496 Run nm to retrieve the list of referenced symbols in each object file.
497 Does not return the position data since it is of no use.
498
Yuto Takano81528c02021-08-06 16:22:06 +0100499 Args:
500 * object_files: a List of compiled object files to search through.
501
502 Returns a List of unique symbols defined and used in any of the object
503 files.
Yuto Takano39639672021-08-05 19:47:48 +0100504 """
505 UNDEFINED_SYMBOL = r"^\S+: +U |^$|^\S+:$"
506 VALID_SYMBOL = r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)"
Yuto Takanoe77f6992021-08-05 20:22:59 +0100507 EXCLUSIONS = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100508
509 symbols = []
510
Yuto Takano81528c02021-08-06 16:22:06 +0100511 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100512 nm_output = ""
513 for lib in object_files:
514 nm_output += subprocess.run(
515 ["nm", "-og", lib],
516 encoding=sys.stdout.encoding,
517 stdout=subprocess.PIPE,
518 stderr=subprocess.STDOUT,
519 check=True
520 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100521
Yuto Takano39639672021-08-05 19:47:48 +0100522 for line in nm_output.splitlines():
523 if not re.match(UNDEFINED_SYMBOL, line):
524 symbol = re.match(VALID_SYMBOL, line)
Yuto Takanoe77f6992021-08-05 20:22:59 +0100525 if symbol and not symbol.group("symbol").startswith(EXCLUSIONS):
526 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100527 else:
528 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100529
Yuto Takano39639672021-08-05 19:47:48 +0100530 return symbols
531
Yuto Takano81528c02021-08-06 16:22:06 +0100532 def perform_checks(self, show_problems: True):
Yuto Takano39639672021-08-05 19:47:48 +0100533 """
534 Perform each check in order, output its PASS/FAIL status. Maintain an
535 overall test status, and output that at the end.
Yuto Takano81528c02021-08-06 16:22:06 +0100536
537 Args:
538 * show_problems: whether to show the problematic examples.
Yuto Takano39639672021-08-05 19:47:48 +0100539 """
Yuto Takano81528c02021-08-06 16:22:06 +0100540 self.log.info("=============")
Yuto Takano39639672021-08-05 19:47:48 +0100541 problems = 0
542
Yuto Takano81528c02021-08-06 16:22:06 +0100543 problems += self.check_symbols_declared_in_header(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100544
545 pattern_checks = [
546 ("macros", MACRO_PATTERN),
Yuto Takano81528c02021-08-06 16:22:06 +0100547 ("enum_consts", CONSTANTS_PATTERN),
Yuto Takano39639672021-08-05 19:47:48 +0100548 ("identifiers", IDENTIFIER_PATTERN)]
549 for group, check_pattern in pattern_checks:
Yuto Takano81528c02021-08-06 16:22:06 +0100550 problems += self.check_match_pattern(
551 show_problems, group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100552
Yuto Takano81528c02021-08-06 16:22:06 +0100553 problems += self.check_for_typos(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100554
555 self.log.info("=============")
556 if problems > 0:
557 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano81528c02021-08-06 16:22:06 +0100558 if not show_problems:
559 self.log.info("Remove --quiet to show the problems.")
Yuto Takano39639672021-08-05 19:47:48 +0100560 else:
561 self.log.info("PASS")
Darryl Greend5802922018-05-08 15:30:59 +0100562
Yuto Takano81528c02021-08-06 16:22:06 +0100563 def check_symbols_declared_in_header(self, show_problems):
Yuto Takano39639672021-08-05 19:47:48 +0100564 """
565 Perform a check that all detected symbols in the library object files
566 are properly declared in headers.
Darryl Greend5802922018-05-08 15:30:59 +0100567
Yuto Takano81528c02021-08-06 16:22:06 +0100568 Args:
569 * show_problems: whether to show the problematic examples.
570
571 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100572 """
573 problems = []
574 for symbol in self.parse_result["symbols"]:
575 found_symbol_declared = False
576 for identifier_match in self.parse_result["identifiers"]:
577 if symbol == identifier_match.name:
578 found_symbol_declared = True
579 break
Yuto Takano81528c02021-08-06 16:22:06 +0100580
Yuto Takano39639672021-08-05 19:47:48 +0100581 if not found_symbol_declared:
582 problems.append(SymbolNotInHeader(symbol))
583
Yuto Takano81528c02021-08-06 16:22:06 +0100584 self.output_check_result("All symbols in header", problems, show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100585 return len(problems)
586
Yuto Takano81528c02021-08-06 16:22:06 +0100587
588 def check_match_pattern(self, show_problems, group_to_check, check_pattern):
589 """
590 Perform a check that all items of a group conform to a regex pattern.
591
592 Args:
593 * show_problems: whether to show the problematic examples.
594 * group_to_check: string key to index into self.parse_result.
595 * check_pattern: the regex to check against.
596
597 Returns the number of problems that need fixing.
598 """
Yuto Takano39639672021-08-05 19:47:48 +0100599 problems = []
600 for item_match in self.parse_result[group_to_check]:
601 if not re.match(check_pattern, item_match.name):
602 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano201f9e82021-08-06 16:36:54 +0100603 # Double underscore is a reserved identifier, never to be used
Yuto Takanoc763cc32021-08-05 20:06:34 +0100604 if re.match(r".*__.*", item_match.name):
605 problems.append(PatternMismatch("double underscore", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100606
607 self.output_check_result(
608 "Naming patterns of {}".format(group_to_check),
609 problems,
610 show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100611 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100612
Yuto Takano81528c02021-08-06 16:22:06 +0100613 def check_for_typos(self, show_problems):
614 """
615 Perform a check that all words in the soure code beginning with MBED are
616 either defined as macros, or as enum constants.
617
618 Args:
619 * show_problems: whether to show the problematic examples.
620
621 Returns the number of problems that need fixing.
622 """
Yuto Takano39639672021-08-05 19:47:48 +0100623 problems = []
624 all_caps_names = list(set([
625 match.name for match
626 in self.parse_result["macros"] + self.parse_result["enum_consts"]]
Darryl Greend5802922018-05-08 15:30:59 +0100627 ))
Yuto Takano39639672021-08-05 19:47:48 +0100628
629 TYPO_EXCLUSION = r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$"
630
631 for name_match in self.parse_result["mbed_names"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100632 found = name_match.name in all_caps_names
633
634 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
635 # PSA driver, they will not exist as macros. However, they
636 # should still be checked for typos using the equivalent
637 # BUILTINs that exist.
638 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
639 found = name_match.name.replace(
640 "MBEDTLS_PSA_ACCEL_",
641 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
642
643 if not found and not re.search(TYPO_EXCLUSION, name_match.name):
Yuto Takano201f9e82021-08-06 16:36:54 +0100644 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100645
Yuto Takano81528c02021-08-06 16:22:06 +0100646 self.output_check_result("Likely typos", problems, show_problems)
647 return len(problems)
648
649 def output_check_result(self, name, problems, show_problems):
650 """
651 Write out the PASS/FAIL status of a performed check depending on whether
652 there were problems.
653
654 Args:
655 * show_problems: whether to show the problematic examples.
656 """
Yuto Takano39639672021-08-05 19:47:48 +0100657 if problems:
Darryl Greend5802922018-05-08 15:30:59 +0100658 self.set_return_code(1)
Yuto Takano81528c02021-08-06 16:22:06 +0100659 self.log.info("{}: FAIL".format(name))
660 if show_problems:
661 self.log.info("")
662 for problem in problems:
663 self.log.warn(str(problem) + "\n")
Darryl Greend5802922018-05-08 15:30:59 +0100664 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100665 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100666
Yuto Takano39639672021-08-05 19:47:48 +0100667def main():
668 """
Yuto Takano81528c02021-08-06 16:22:06 +0100669 Perform argument parsing, and create an instance of NameCheck to begin the
670 core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100671 """
Darryl Greend5802922018-05-08 15:30:59 +0100672
Yuto Takano39639672021-08-05 19:47:48 +0100673 parser = argparse.ArgumentParser(
674 formatter_class=argparse.RawDescriptionHelpFormatter,
675 description=(
676 "This script confirms that the naming of all symbols and identifiers "
677 "in Mbed TLS are consistent with the house style and are also "
678 "self-consistent.\n\n"
679 "Expected to be run from the MbedTLS root directory."))
Darryl Greend5802922018-05-08 15:30:59 +0100680
Yuto Takano39639672021-08-05 19:47:48 +0100681 parser.add_argument("-v", "--verbose",
682 action="store_true",
Yuto Takano81528c02021-08-06 16:22:06 +0100683 help="show parse results")
684
685 parser.add_argument("-q", "--quiet",
686 action="store_true",
687 help="hide unnecessary text and problematic examples")
688
Yuto Takano39639672021-08-05 19:47:48 +0100689 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100690
Darryl Greend5802922018-05-08 15:30:59 +0100691 try:
692 name_check = NameCheck()
Yuto Takano39639672021-08-05 19:47:48 +0100693 name_check.setup_logger(verbose=args.verbose)
694 name_check.parse_names_in_source()
Yuto Takano81528c02021-08-06 16:22:06 +0100695 name_check.perform_checks(show_problems=not args.quiet)
696 sys.exit(name_check.return_code)
697 except subprocess.CalledProcessError as error:
698 traceback.print_exc()
699 print("!! Compilation faced a critical error, "
700 "check-names can't continue further.")
Darryl Greend5802922018-05-08 15:30:59 +0100701 sys.exit(name_check.return_code)
702 except Exception:
703 traceback.print_exc()
704 sys.exit(2)
705
Darryl Greend5802922018-05-08 15:30:59 +0100706if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100707 main()