blob: 81fd5ffb8451e49ee8d71ff492838a1fc4ed3465 [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
Yuto Takanod93fa372021-08-06 23:05:55 +010048class Match(): # pylint: disable=too-few-public-methods
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.
Yuto Takanod93fa372021-08-06 23:05:55 +010055 * pos: a tuple of (line_no, start, end) positions on the file line where the
56 match is.
Yuto Takano81528c02021-08-06 16:22:06 +010057 * name: the match itself.
58 """
Yuto Takanod93fa372021-08-06 23:05:55 +010059 def __init__(self, filename, line, pos, name):
Yuto Takano39639672021-08-05 19:47:48 +010060 self.filename = filename
61 self.line = line
62 self.pos = pos
63 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010064
Yuto Takanoa4e75122021-08-06 17:23:28 +010065 def __str__(self):
Yuto Takano381fda82021-08-06 23:37:20 +010066 ln_str = str(self.pos[0])
67 gutter_len = max(4, len(ln_str))
68 gutter = (gutter_len - len(ln_str)) * " " + ln_str
69 underline = self.pos[1] * " " + (self.pos[2] - self.pos[1]) * "^"
70
Yuto Takanoa4e75122021-08-06 17:23:28 +010071 return (
Yuto Takano381fda82021-08-06 23:37:20 +010072 " {0} |\n".format(gutter_len * " ") +
73 " {0} | {1}".format(gutter, self.line) +
74 " {0} | {1}".format(gutter_len * " ", underline)
Yuto Takanoa4e75122021-08-06 17:23:28 +010075 )
Yuto Takanod93fa372021-08-06 23:05:55 +010076
77class Problem(): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010078 """
79 A parent class representing a form of static analysis error.
80
81 Fields:
82 * textwrapper: a TextWrapper instance to format problems nicely.
83 """
Yuto Takano39639672021-08-05 19:47:48 +010084 def __init__(self):
85 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +010086 self.textwrapper.width = 80
Yuto Takanoa4e75122021-08-06 17:23:28 +010087 self.textwrapper.initial_indent = " > "
Yuto Takano81528c02021-08-06 16:22:06 +010088 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +010089
Yuto Takanod93fa372021-08-06 23:05:55 +010090class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010091 """
92 A problem that occurs when an exported/available symbol in the object file
93 is not explicitly declared in header files. Created with
94 NameCheck.check_symbols_declared_in_header()
95
96 Fields:
97 * symbol_name: the name of the symbol.
98 """
Yuto Takano39639672021-08-05 19:47:48 +010099 def __init__(self, symbol_name):
100 self.symbol_name = symbol_name
101 Problem.__init__(self)
102
103 def __str__(self):
104 return self.textwrapper.fill(
105 "'{0}' was found as an available symbol in the output of nm, "
106 "however it was not declared in any header files."
107 .format(self.symbol_name))
108
Yuto Takanod93fa372021-08-06 23:05:55 +0100109class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100110 """
111 A problem that occurs when something doesn't match the expected pattern.
112 Created with NameCheck.check_match_pattern()
113
114 Fields:
115 * pattern: the expected regex pattern
116 * match: the Match object in question
117 """
Yuto Takano39639672021-08-05 19:47:48 +0100118 def __init__(self, pattern, match):
119 self.pattern = pattern
120 self.match = match
121 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100122
Yuto Takano39639672021-08-05 19:47:48 +0100123 def __str__(self):
124 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100125 "{0}:{1}: '{2}' does not match the required pattern '{3}'."
126 .format(
127 self.match.filename,
Yuto Takanod93fa372021-08-06 23:05:55 +0100128 self.match.pos[0],
Yuto Takanoa4e75122021-08-06 17:23:28 +0100129 self.match.name,
130 self.pattern)) + "\n" + str(self.match)
Yuto Takano39639672021-08-05 19:47:48 +0100131
Yuto Takanod93fa372021-08-06 23:05:55 +0100132class Typo(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100133 """
134 A problem that occurs when a word using MBED doesn't appear to be defined as
135 constants nor enum values. Created with NameCheck.check_for_typos()
136
137 Fields:
138 * match: the Match object of the MBED name in question.
139 """
Yuto Takano39639672021-08-05 19:47:48 +0100140 def __init__(self, match):
141 self.match = match
142 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100143
Yuto Takano39639672021-08-05 19:47:48 +0100144 def __str__(self):
145 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100146 "{0}:{1}: '{2}' looks like a typo. It was not found in any "
147 "macros or any enums. If this is not a typo, put "
148 "//no-check-names after it."
149 .format(
150 self.match.filename,
Yuto Takanod93fa372021-08-06 23:05:55 +0100151 self.match.pos[0],
Yuto Takanoa4e75122021-08-06 17:23:28 +0100152 self.match.name)) + "\n" + str(self.match)
Darryl Greend5802922018-05-08 15:30:59 +0100153
Yuto Takanod93fa372021-08-06 23:05:55 +0100154class NameCheck():
Yuto Takano81528c02021-08-06 16:22:06 +0100155 """
156 Representation of the core name checking operation performed by this script.
157 Shares a common logger, common excluded filenames, and a shared return_code.
158 """
Darryl Greend5802922018-05-08 15:30:59 +0100159 def __init__(self):
160 self.log = None
Darryl Greend5802922018-05-08 15:30:59 +0100161 self.return_code = 0
Yuto Takano81528c02021-08-06 16:22:06 +0100162 self.excluded_files = ["bn_mul", "compat-2.x.h"]
Yuto Takanod93fa372021-08-06 23:05:55 +0100163 self.parse_result = {}
Darryl Greend5802922018-05-08 15:30:59 +0100164
165 def set_return_code(self, return_code):
166 if return_code > self.return_code:
Yuto Takano201f9e82021-08-06 16:36:54 +0100167 self.log.debug("Setting new return code to {}".format(return_code))
Darryl Greend5802922018-05-08 15:30:59 +0100168 self.return_code = return_code
169
Yuto Takano39639672021-08-05 19:47:48 +0100170 def setup_logger(self, verbose=False):
171 """
172 Set up a logger and set the change the default logging level from
Yuto Takano81528c02021-08-06 16:22:06 +0100173 WARNING to INFO. Loggers are better than print statements since their
Yuto Takano39639672021-08-05 19:47:48 +0100174 verbosity can be controlled.
175 """
Darryl Greend5802922018-05-08 15:30:59 +0100176 self.log = logging.getLogger()
Yuto Takano39639672021-08-05 19:47:48 +0100177 if verbose:
178 self.log.setLevel(logging.DEBUG)
179 else:
180 self.log.setLevel(logging.INFO)
Darryl Greend5802922018-05-08 15:30:59 +0100181 self.log.addHandler(logging.StreamHandler())
182
Yuto Takano157444c2021-08-05 20:10:45 +0100183 def get_files(self, extension, directory):
Yuto Takano81528c02021-08-06 16:22:06 +0100184 """
185 Get all files that end with .extension in the specified directory
186 recursively.
187
188 Args:
189 * extension: the file extension to search for, without the dot
190 * directory: the directory to recursively search for
191
192 Returns a List of relative filepaths.
193 """
Darryl Greend5802922018-05-08 15:30:59 +0100194 filenames = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100195 for root, _, files in sorted(os.walk(directory)):
Darryl Greend5802922018-05-08 15:30:59 +0100196 for filename in sorted(files):
197 if (filename not in self.excluded_files and
Yuto Takano157444c2021-08-05 20:10:45 +0100198 filename.endswith("." + extension)):
Darryl Greend5802922018-05-08 15:30:59 +0100199 filenames.append(os.path.join(root, filename))
200 return filenames
201
Yuto Takano81528c02021-08-06 16:22:06 +0100202 def parse_names_in_source(self):
203 """
204 Calls each parsing function to retrieve various elements of the code,
205 together with their source location. Puts the parsed values in the
206 internal variable self.parse_result.
207 """
208 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100209 self.log.debug(
210 "The following files are excluded from the search: {}"
211 .format(str(self.excluded_files))
212 )
Yuto Takano81528c02021-08-06 16:22:06 +0100213
214 m_headers = self.get_files("h", os.path.join("include", "mbedtls"))
215 p_headers = self.get_files("h", os.path.join("include", "psa"))
216 t_headers = ["3rdparty/everest/include/everest/everest.h",
217 "3rdparty/everest/include/everest/x25519.h"]
218 d_headers = self.get_files("h", os.path.join("tests", "include", "test", "drivers"))
219 l_headers = self.get_files("h", "library")
220 libraries = self.get_files("c", "library") + [
221 "3rdparty/everest/library/everest.c",
222 "3rdparty/everest/library/x25519.c"]
223
224 all_macros = self.parse_macros(
225 m_headers + p_headers + t_headers + l_headers + d_headers)
226 enum_consts = self.parse_enum_consts(
227 m_headers + l_headers + t_headers)
228 identifiers = self.parse_identifiers(
229 m_headers + p_headers + t_headers + l_headers)
Yuto Takanod93fa372021-08-06 23:05:55 +0100230 mbed_words = self.parse_mbed_words(
Yuto Takano81528c02021-08-06 16:22:06 +0100231 m_headers + p_headers + t_headers + l_headers + libraries)
232 symbols = self.parse_symbols()
233
234 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
235 identifiers_justname = [x.name for x in identifiers]
236 actual_macros = []
237 for macro in all_macros:
238 if macro.name not in identifiers_justname:
239 actual_macros.append(macro)
240
241 self.log.debug("Found:")
242 self.log.debug(" {} Macros".format(len(all_macros)))
243 self.log.debug(" {} Non-identifier Macros".format(len(actual_macros)))
244 self.log.debug(" {} Enum Constants".format(len(enum_consts)))
245 self.log.debug(" {} Identifiers".format(len(identifiers)))
246 self.log.debug(" {} Exported Symbols".format(len(symbols)))
247 self.log.info("Analysing...")
248
249 self.parse_result = {
250 "macros": actual_macros,
251 "enum_consts": enum_consts,
252 "identifiers": identifiers,
253 "symbols": symbols,
Yuto Takanod93fa372021-08-06 23:05:55 +0100254 "mbed_words": mbed_words
Yuto Takano81528c02021-08-06 16:22:06 +0100255 }
256
Yuto Takano39639672021-08-05 19:47:48 +0100257 def parse_macros(self, header_files):
258 """
259 Parse all macros defined by #define preprocessor directives.
260
261 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100262 * header_files: A List of filepaths to look through.
263
264 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100265 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100266 macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
267 exclusions = (
Yuto Takano39639672021-08-05 19:47:48 +0100268 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
269 )
270
Yuto Takano201f9e82021-08-06 16:36:54 +0100271 self.log.debug("Looking for macros in {} files".format(len(header_files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100272
273 macros = []
274
Yuto Takano39639672021-08-05 19:47:48 +0100275 for header_file in header_files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100276 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100277 for line_no, line in enumerate(header):
Yuto Takanod93fa372021-08-06 23:05:55 +0100278 for macro in macro_regex.finditer(line):
279 if not macro.group("macro").startswith(exclusions):
Yuto Takano81528c02021-08-06 16:22:06 +0100280 macros.append(Match(
281 header_file,
282 line,
Yuto Takanod93fa372021-08-06 23:05:55 +0100283 (line_no, macro.start(), macro.end()),
Yuto Takano81528c02021-08-06 16:22:06 +0100284 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100285
Yuto Takano39639672021-08-05 19:47:48 +0100286 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100287
Yuto Takanod93fa372021-08-06 23:05:55 +0100288 def parse_mbed_words(self, files):
Yuto Takano39639672021-08-05 19:47:48 +0100289 """
290 Parse all words in the file that begin with MBED. Includes macros.
Yuto Takano81528c02021-08-06 16:22:06 +0100291 There have been typos of TLS, hence the broader check than MBEDTLS.
Yuto Takano39639672021-08-05 19:47:48 +0100292
293 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100294 * files: a List of filepaths to look through.
295
296 Returns a List of Match objects for words beginning with MBED.
Yuto Takano39639672021-08-05 19:47:48 +0100297 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100298 mbed_regex = re.compile(r"\bMBED.+?_[A-Z0-9_]*")
299 exclusions = re.compile(r"// *no-check-names|#error")
300
Yuto Takano201f9e82021-08-06 16:36:54 +0100301 self.log.debug("Looking for MBED names in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100302
303 mbed_words = []
304
Yuto Takanobb7dca42021-08-05 19:57:58 +0100305 for filename in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100306 with open(filename, "r", encoding="utf-8") as fp:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100307 for line_no, line in enumerate(fp):
Yuto Takanod93fa372021-08-06 23:05:55 +0100308 if exclusions.search(line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100309 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100310
Yuto Takanod93fa372021-08-06 23:05:55 +0100311 for name in mbed_regex.finditer(line):
312 mbed_words.append(Match(
Yuto Takano39639672021-08-05 19:47:48 +0100313 filename,
314 line,
Yuto Takanod93fa372021-08-06 23:05:55 +0100315 (line_no, name.start(), name.end()),
Yuto Takano39639672021-08-05 19:47:48 +0100316 name.group(0)
317 ))
318
Yuto Takanod93fa372021-08-06 23:05:55 +0100319 return mbed_words
Yuto Takano39639672021-08-05 19:47:48 +0100320
321 def parse_enum_consts(self, header_files):
322 """
323 Parse all enum value constants that are declared.
324
325 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100326 * header_files: A List of filepaths to look through.
Yuto Takano39639672021-08-05 19:47:48 +0100327
Yuto Takano81528c02021-08-06 16:22:06 +0100328 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100329 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100330 self.log.debug("Looking for enum consts in {} files".format(len(header_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100331
332 enum_consts = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100333
Yuto Takano39639672021-08-05 19:47:48 +0100334 for header_file in header_files:
335 # Emulate a finite state machine to parse enum declarations.
Yuto Takano81528c02021-08-06 16:22:06 +0100336 # 0 = not in enum
337 # 1 = inside enum
338 # 2 = almost inside enum
Darryl Greend5802922018-05-08 15:30:59 +0100339 state = 0
Yuto Takanoa083d152021-08-07 00:25:59 +0100340 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100341 for line_no, line in enumerate(header):
Yuto Takano13ecd992021-08-06 16:56:52 +0100342 # Match typedefs and brackets only when they are at the
343 # beginning of the line -- if they are indented, they might
344 # be sub-structures within structs, etc.
Yuto Takanod93fa372021-08-06 23:05:55 +0100345 if state == 0 and re.match(r"^(typedef +)?enum +{", line):
Darryl Greend5802922018-05-08 15:30:59 +0100346 state = 1
Yuto Takanod93fa372021-08-06 23:05:55 +0100347 elif state == 0 and re.match(r"^(typedef +)?enum", line):
Darryl Greend5802922018-05-08 15:30:59 +0100348 state = 2
Yuto Takanod93fa372021-08-06 23:05:55 +0100349 elif state == 2 and re.match(r"^{", line):
Darryl Greend5802922018-05-08 15:30:59 +0100350 state = 1
Yuto Takanod93fa372021-08-06 23:05:55 +0100351 elif state == 1 and re.match(r"^}", line):
Darryl Greend5802922018-05-08 15:30:59 +0100352 state = 0
Yuto Takanod93fa372021-08-06 23:05:55 +0100353 elif state == 1 and not re.match(r" *#", line):
Yuto Takano13ecd992021-08-06 16:56:52 +0100354 enum_const = re.match(r" *(?P<enum_const>\w+)", line)
Darryl Greend5802922018-05-08 15:30:59 +0100355 if enum_const:
Yuto Takano39639672021-08-05 19:47:48 +0100356 enum_consts.append(Match(
357 header_file,
358 line,
Yuto Takanod93fa372021-08-06 23:05:55 +0100359 (line_no, enum_const.start(), enum_const.end()),
Yuto Takano39639672021-08-05 19:47:48 +0100360 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100361
Yuto Takano39639672021-08-05 19:47:48 +0100362 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100363
Yuto Takano39639672021-08-05 19:47:48 +0100364 def parse_identifiers(self, header_files):
365 """
366 Parse all lines of a header where a function identifier is declared,
Yuto Takano81528c02021-08-06 16:22:06 +0100367 based on some huersitics. Highly dependent on formatting style.
Darryl Greend5802922018-05-08 15:30:59 +0100368
Yuto Takano39639672021-08-05 19:47:48 +0100369 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100370 * header_files: A List of filepaths to look through.
371
372 Returns a List of Match objects with identifiers.
Yuto Takano39639672021-08-05 19:47:48 +0100373 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100374 identifier_regex = re.compile(
375 # Match " something(a" or " *something(a". Functions.
376 # Assumptions:
377 # - function definition from return type to one of its arguments is
378 # all on one line (enforced by the previous_line concat below)
379 # - function definition line only contains alphanumeric, asterisk,
380 # underscore, and open bracket
381 r".* \**(\w+) *\( *\w|"
382 # Match "(*something)(". Flexible with spaces.
383 r".*\( *\* *(\w+) *\) *\(|"
384 # Match names of named data structures.
385 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
386 # Match names of typedef instances, after closing bracket.
387 r"}? *(\w+)[;[].*")
388 exclusion_lines = re.compile(r"^("
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100389 r"extern +\"C\"|"
390 r"(typedef +)?(struct|union|enum)( *{)?$|"
391 r"} *;?$|"
392 r"$|"
393 r"//|"
394 r"#"
395 r")")
Yuto Takanod93fa372021-08-06 23:05:55 +0100396
397 self.log.debug("Looking for identifiers in {} files".format(len(header_files)))
Darryl Greend5802922018-05-08 15:30:59 +0100398
Yuto Takano39639672021-08-05 19:47:48 +0100399 identifiers = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100400
Yuto Takano39639672021-08-05 19:47:48 +0100401 for header_file in header_files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100402 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano39639672021-08-05 19:47:48 +0100403 in_block_comment = False
Yuto Takanod93fa372021-08-06 23:05:55 +0100404 previous_line = ""
Darryl Greend5802922018-05-08 15:30:59 +0100405
Yuto Takano8f457cf2021-08-06 17:54:58 +0100406 for line_no, line in enumerate(header):
Yuto Takano81528c02021-08-06 16:22:06 +0100407 # Skip parsing this line if a block comment ends on it,
408 # but don't skip if it has just started -- there is a chance
409 # it ends on the same line.
Yuto Takano39639672021-08-05 19:47:48 +0100410 if re.search(r"/\*", line):
Yuto Takano81528c02021-08-06 16:22:06 +0100411 in_block_comment = not in_block_comment
412 if re.search(r"\*/", line):
413 in_block_comment = not in_block_comment
Yuto Takano39639672021-08-05 19:47:48 +0100414 continue
415
Yuto Takano81528c02021-08-06 16:22:06 +0100416 if in_block_comment:
Yuto Takanod93fa372021-08-06 23:05:55 +0100417 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100418 continue
419
Yuto Takanod93fa372021-08-06 23:05:55 +0100420 if exclusion_lines.match(line):
421 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100422 continue
423
Yuto Takanocfc9e4a2021-08-06 20:02:32 +0100424 # If the line contains only space-separated alphanumeric
425 # characters (or underscore, asterisk, or, open bracket),
426 # and nothing else, high chance it's a declaration that
427 # continues on the next line
428 if re.match(r"^([\w\*\(]+\s+)+$", line):
Yuto Takanod93fa372021-08-06 23:05:55 +0100429 previous_line += line
Yuto Takano81528c02021-08-06 16:22:06 +0100430 continue
431
432 # If previous line seemed to start an unfinished declaration
Yuto Takanocfc9e4a2021-08-06 20:02:32 +0100433 # (as above), concat and treat them as one.
434 if previous_line:
435 line = previous_line.strip() + " " + line.strip()
Yuto Takanod93fa372021-08-06 23:05:55 +0100436 previous_line = ""
Yuto Takano81528c02021-08-06 16:22:06 +0100437
438 # Skip parsing if line has a space in front = hueristic to
439 # skip function argument lines (highly subject to formatting
440 # changes)
441 if line[0] == " ":
Yuto Takano39639672021-08-05 19:47:48 +0100442 continue
Yuto Takano6f38ab32021-08-05 21:07:14 +0100443
Yuto Takanod93fa372021-08-06 23:05:55 +0100444 identifier = identifier_regex.search(line)
Yuto Takano39639672021-08-05 19:47:48 +0100445
446 if identifier:
Yuto Takano81528c02021-08-06 16:22:06 +0100447 # Find the group that matched, and append it
Yuto Takano39639672021-08-05 19:47:48 +0100448 for group in identifier.groups():
449 if group:
450 identifiers.append(Match(
451 header_file,
452 line,
Yuto Takanod93fa372021-08-06 23:05:55 +0100453 (line_no, identifier.start(), identifier.end()),
Yuto Takano81528c02021-08-06 16:22:06 +0100454 group))
Yuto Takano39639672021-08-05 19:47:48 +0100455
456 return identifiers
457
458 def parse_symbols(self):
459 """
460 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
461 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100462 Exceptions thrown here are rethrown because they would be critical
463 errors that void several tests, and thus needs to halt the program. This
464 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100465
Yuto Takano81528c02021-08-06 16:22:06 +0100466 Returns a List of unique symbols defined and used in the libraries.
467 """
468 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100469 symbols = []
470
471 # Back up the config and atomically compile with the full configratuion.
472 shutil.copy("include/mbedtls/mbedtls_config.h",
Yuto Takano81528c02021-08-06 16:22:06 +0100473 "include/mbedtls/mbedtls_config.h.bak")
Darryl Greend5802922018-05-08 15:30:59 +0100474 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100475 # Use check=True in all subprocess calls so that failures are raised
476 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100477 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100478 ["python3", "scripts/config.py", "full"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100479 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100480 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100481 )
482 my_environment = os.environ.copy()
483 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano39639672021-08-05 19:47:48 +0100484 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100485 ["make", "clean", "lib"],
486 env=my_environment,
Yuto Takanobcc3d992021-08-06 23:14:58 +0100487 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100488 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100489 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100490 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100491 )
Yuto Takano39639672021-08-05 19:47:48 +0100492
493 # Perform object file analysis using nm
494 symbols = self.parse_symbols_from_nm(
495 ["library/libmbedcrypto.a",
Yuto Takanod93fa372021-08-06 23:05:55 +0100496 "library/libmbedtls.a",
497 "library/libmbedx509.a"])
Yuto Takano39639672021-08-05 19:47:48 +0100498
499 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100500 ["make", "clean"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100501 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100502 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100503 )
504 except subprocess.CalledProcessError as error:
Yuto Takano25eeb7b2021-08-06 21:27:59 +0100505 self.log.debug(error.output)
Darryl Greend5802922018-05-08 15:30:59 +0100506 self.set_return_code(2)
Yuto Takano81528c02021-08-06 16:22:06 +0100507 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100508 finally:
509 shutil.move("include/mbedtls/mbedtls_config.h.bak",
510 "include/mbedtls/mbedtls_config.h")
511
512 return symbols
513
514 def parse_symbols_from_nm(self, object_files):
515 """
516 Run nm to retrieve the list of referenced symbols in each object file.
517 Does not return the position data since it is of no use.
518
Yuto Takano81528c02021-08-06 16:22:06 +0100519 Args:
520 * object_files: a List of compiled object files to search through.
521
522 Returns a List of unique symbols defined and used in any of the object
523 files.
Yuto Takano39639672021-08-05 19:47:48 +0100524 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100525 nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
526 nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100527 exclusions = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100528
529 symbols = []
530
Yuto Takano81528c02021-08-06 16:22:06 +0100531 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100532 nm_output = ""
533 for lib in object_files:
534 nm_output += subprocess.run(
535 ["nm", "-og", lib],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100536 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100537 stdout=subprocess.PIPE,
538 stderr=subprocess.STDOUT,
539 check=True
540 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100541
Yuto Takano39639672021-08-05 19:47:48 +0100542 for line in nm_output.splitlines():
Yuto Takanod93fa372021-08-06 23:05:55 +0100543 if not nm_undefined_regex.match(line):
544 symbol = nm_valid_regex.match(line)
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100545 if (symbol and not symbol.group("symbol").startswith(exclusions)):
Yuto Takanoe77f6992021-08-05 20:22:59 +0100546 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100547 else:
548 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100549
Yuto Takano39639672021-08-05 19:47:48 +0100550 return symbols
551
Yuto Takano9e0e0e92021-08-06 22:01:37 +0100552 def perform_checks(self, show_problems=True):
Yuto Takano39639672021-08-05 19:47:48 +0100553 """
554 Perform each check in order, output its PASS/FAIL status. Maintain an
555 overall test status, and output that at the end.
Yuto Takano81528c02021-08-06 16:22:06 +0100556
557 Args:
558 * show_problems: whether to show the problematic examples.
Yuto Takano39639672021-08-05 19:47:48 +0100559 """
Yuto Takano81528c02021-08-06 16:22:06 +0100560 self.log.info("=============")
Yuto Takano39639672021-08-05 19:47:48 +0100561 problems = 0
562
Yuto Takano81528c02021-08-06 16:22:06 +0100563 problems += self.check_symbols_declared_in_header(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100564
Yuto Takanod93fa372021-08-06 23:05:55 +0100565 pattern_checks = [("macros", MACRO_PATTERN),
566 ("enum_consts", CONSTANTS_PATTERN),
567 ("identifiers", IDENTIFIER_PATTERN)]
Yuto Takano39639672021-08-05 19:47:48 +0100568 for group, check_pattern in pattern_checks:
Yuto Takano81528c02021-08-06 16:22:06 +0100569 problems += self.check_match_pattern(
570 show_problems, group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100571
Yuto Takano81528c02021-08-06 16:22:06 +0100572 problems += self.check_for_typos(show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100573
574 self.log.info("=============")
575 if problems > 0:
576 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano81528c02021-08-06 16:22:06 +0100577 if not show_problems:
578 self.log.info("Remove --quiet to show the problems.")
Yuto Takano39639672021-08-05 19:47:48 +0100579 else:
580 self.log.info("PASS")
Darryl Greend5802922018-05-08 15:30:59 +0100581
Yuto Takano81528c02021-08-06 16:22:06 +0100582 def check_symbols_declared_in_header(self, show_problems):
Yuto Takano39639672021-08-05 19:47:48 +0100583 """
584 Perform a check that all detected symbols in the library object files
585 are properly declared in headers.
Darryl Greend5802922018-05-08 15:30:59 +0100586
Yuto Takano81528c02021-08-06 16:22:06 +0100587 Args:
588 * show_problems: whether to show the problematic examples.
589
590 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100591 """
592 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100593
Yuto Takano39639672021-08-05 19:47:48 +0100594 for symbol in self.parse_result["symbols"]:
595 found_symbol_declared = False
596 for identifier_match in self.parse_result["identifiers"]:
597 if symbol == identifier_match.name:
598 found_symbol_declared = True
599 break
Yuto Takano81528c02021-08-06 16:22:06 +0100600
Yuto Takano39639672021-08-05 19:47:48 +0100601 if not found_symbol_declared:
602 problems.append(SymbolNotInHeader(symbol))
603
Yuto Takano81528c02021-08-06 16:22:06 +0100604 self.output_check_result("All symbols in header", problems, show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100605 return len(problems)
606
Yuto Takano81528c02021-08-06 16:22:06 +0100607
608 def check_match_pattern(self, show_problems, group_to_check, check_pattern):
609 """
610 Perform a check that all items of a group conform to a regex pattern.
611
612 Args:
613 * show_problems: whether to show the problematic examples.
614 * group_to_check: string key to index into self.parse_result.
615 * check_pattern: the regex to check against.
616
617 Returns the number of problems that need fixing.
618 """
Yuto Takano39639672021-08-05 19:47:48 +0100619 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100620
Yuto Takano39639672021-08-05 19:47:48 +0100621 for item_match in self.parse_result[group_to_check]:
622 if not re.match(check_pattern, item_match.name):
623 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano201f9e82021-08-06 16:36:54 +0100624 # Double underscore is a reserved identifier, never to be used
Yuto Takanoc763cc32021-08-05 20:06:34 +0100625 if re.match(r".*__.*", item_match.name):
626 problems.append(PatternMismatch("double underscore", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100627
628 self.output_check_result(
629 "Naming patterns of {}".format(group_to_check),
630 problems,
631 show_problems)
Yuto Takano39639672021-08-05 19:47:48 +0100632 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100633
Yuto Takano81528c02021-08-06 16:22:06 +0100634 def check_for_typos(self, show_problems):
635 """
636 Perform a check that all words in the soure code beginning with MBED are
637 either defined as macros, or as enum constants.
638
639 Args:
640 * show_problems: whether to show the problematic examples.
641
642 Returns the number of problems that need fixing.
643 """
Yuto Takano39639672021-08-05 19:47:48 +0100644 problems = []
Yuto Takano39639672021-08-05 19:47:48 +0100645
Yuto Takanod93fa372021-08-06 23:05:55 +0100646 # Set comprehension, equivalent to a list comprehension inside set()
647 all_caps_names = {
648 match.name
649 for match
650 in self.parse_result["macros"] + self.parse_result["enum_consts"]}
651 typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$")
Yuto Takano39639672021-08-05 19:47:48 +0100652
Yuto Takanod93fa372021-08-06 23:05:55 +0100653 for name_match in self.parse_result["mbed_words"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100654 found = name_match.name in all_caps_names
655
656 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
657 # PSA driver, they will not exist as macros. However, they
658 # should still be checked for typos using the equivalent
659 # BUILTINs that exist.
660 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
661 found = name_match.name.replace(
662 "MBEDTLS_PSA_ACCEL_",
663 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
664
Yuto Takanod93fa372021-08-06 23:05:55 +0100665 if not found and not typo_exclusion.search(name_match.name):
Yuto Takano201f9e82021-08-06 16:36:54 +0100666 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100667
Yuto Takano81528c02021-08-06 16:22:06 +0100668 self.output_check_result("Likely typos", problems, show_problems)
669 return len(problems)
670
671 def output_check_result(self, name, problems, show_problems):
672 """
673 Write out the PASS/FAIL status of a performed check depending on whether
674 there were problems.
675
676 Args:
677 * show_problems: whether to show the problematic examples.
678 """
Yuto Takano39639672021-08-05 19:47:48 +0100679 if problems:
Darryl Greend5802922018-05-08 15:30:59 +0100680 self.set_return_code(1)
Yuto Takano81528c02021-08-06 16:22:06 +0100681 self.log.info("{}: FAIL".format(name))
682 if show_problems:
683 self.log.info("")
684 for problem in problems:
Yuto Takanod93fa372021-08-06 23:05:55 +0100685 self.log.warning("{}\n".format(str(problem)))
Darryl Greend5802922018-05-08 15:30:59 +0100686 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100687 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100688
Yuto Takanod93fa372021-08-06 23:05:55 +0100689def check_repo_path():
690 """
691 Check that the current working directory is the project root, and throw
692 an exception if not.
693 """
694 if (not os.path.isdir("include") or
695 not os.path.isdir("tests") or
696 not os.path.isdir("library")):
697 raise Exception("This script must be run from Mbed TLS root")
698
Yuto Takano39639672021-08-05 19:47:48 +0100699def main():
700 """
Yuto Takano81528c02021-08-06 16:22:06 +0100701 Perform argument parsing, and create an instance of NameCheck to begin the
702 core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100703 """
Yuto Takano39639672021-08-05 19:47:48 +0100704 parser = argparse.ArgumentParser(
705 formatter_class=argparse.RawDescriptionHelpFormatter,
706 description=(
707 "This script confirms that the naming of all symbols and identifiers "
708 "in Mbed TLS are consistent with the house style and are also "
709 "self-consistent.\n\n"
710 "Expected to be run from the MbedTLS root directory."))
Darryl Greend5802922018-05-08 15:30:59 +0100711
Yuto Takano39639672021-08-05 19:47:48 +0100712 parser.add_argument("-v", "--verbose",
713 action="store_true",
Yuto Takano81528c02021-08-06 16:22:06 +0100714 help="show parse results")
715
716 parser.add_argument("-q", "--quiet",
717 action="store_true",
718 help="hide unnecessary text and problematic examples")
719
Yuto Takano39639672021-08-05 19:47:48 +0100720 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100721
Darryl Greend5802922018-05-08 15:30:59 +0100722 try:
Yuto Takanod93fa372021-08-06 23:05:55 +0100723 check_repo_path()
Darryl Greend5802922018-05-08 15:30:59 +0100724 name_check = NameCheck()
Yuto Takano39639672021-08-05 19:47:48 +0100725 name_check.setup_logger(verbose=args.verbose)
726 name_check.parse_names_in_source()
Yuto Takano81528c02021-08-06 16:22:06 +0100727 name_check.perform_checks(show_problems=not args.quiet)
728 sys.exit(name_check.return_code)
Yuto Takanod93fa372021-08-06 23:05:55 +0100729 except Exception: # pylint: disable=broad-except
Darryl Greend5802922018-05-08 15:30:59 +0100730 traceback.print_exc()
731 sys.exit(2)
732
Darryl Greend5802922018-05-08 15:30:59 +0100733if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100734 main()