blob: f812929c707058f9b687ced5b97c4cf574d2e876 [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
Yuto Takano55c6c872021-08-09 15:35:19 +010023It contains two major Python classes, CodeParser and NameChecker. They both have
24a comprehensive "run-all" function (comprehensive_parse() and perform_checks())
25but the individual functions can also be used for specific needs.
26
27CodeParser makes heavy use of regular expressions to parse the code, and is
28dependent on the current code formatting. Many Python C parser libraries require
29preprocessed C code, which means no macro parsing. Compiler tools are also not
30very helpful when we want the exact location in the original source (which
31becomes impossible when e.g. comments are stripped).
32
33NameChecker performs the following checks:
Yuto Takano81528c02021-08-06 16:22:06 +010034
35- All exported and available symbols in the library object files, are explicitly
Yuto Takano159255a2021-08-06 17:00:28 +010036 declared in the header files. This uses the nm command.
Yuto Takano81528c02021-08-06 16:22:06 +010037- All macros, constants, and identifiers (function names, struct names, etc)
Yuto Takano55c6c872021-08-09 15:35:19 +010038 follow the required regex pattern.
Pengyu Lvcdac0d52022-11-08 15:55:00 +080039- Typo checking: All words that begin with MBED|PSA exist as macros or constants.
Yuto Takanofc54dfb2021-08-07 17:18:28 +010040
Yuto Takano55c6c872021-08-09 15:35:19 +010041The script returns 0 on success, 1 on test failure, and 2 if there is a script
Yuto Takano8246eb82021-08-16 10:37:24 +010042error. It must be run from Mbed TLS root.
Darryl Greend5802922018-05-08 15:30:59 +010043"""
Yuto Takano39639672021-08-05 19:47:48 +010044
Yuto Takanofc1e9ff2021-08-23 13:54:56 +010045import abc
Yuto Takano39639672021-08-05 19:47:48 +010046import argparse
Gilles Peskine89458d12021-09-27 19:20:17 +020047import fnmatch
Yuto Takano977e07f2021-08-09 11:56:15 +010048import glob
Yuto Takano39639672021-08-05 19:47:48 +010049import textwrap
Darryl Greend5802922018-05-08 15:30:59 +010050import os
51import sys
52import traceback
53import re
Yuto Takanob1417b42021-08-17 10:30:20 +010054import enum
Darryl Greend5802922018-05-08 15:30:59 +010055import shutil
56import subprocess
57import logging
58
Gilles Peskined9071e72022-09-18 21:17:09 +020059import scripts_path # pylint: disable=unused-import
60from mbedtls_dev import build_tree
61
62
Yuto Takano81528c02021-08-06 16:22:06 +010063# Naming patterns to check against. These are defined outside the NameCheck
64# class for ease of modification.
Janos Follath99387192022-08-10 11:11:34 +010065PUBLIC_MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
66INTERNAL_MACRO_PATTERN = r"^[0-9A-Za-z_]*[0-9A-Z]$"
67CONSTANTS_PATTERN = PUBLIC_MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010068IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010069
Yuto Takanod93fa372021-08-06 23:05:55 +010070class Match(): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010071 """
72 A class representing a match, together with its found position.
73
74 Fields:
75 * filename: the file that the match was in.
76 * line: the full line containing the match.
Yuto Takano704b0f72021-08-17 10:41:23 +010077 * line_no: the line number.
78 * pos: a tuple of (start, end) positions on the line where the match is.
Yuto Takano81528c02021-08-06 16:22:06 +010079 * name: the match itself.
80 """
Yuto Takano704b0f72021-08-17 10:41:23 +010081 def __init__(self, filename, line, line_no, pos, name):
82 # pylint: disable=too-many-arguments
Yuto Takano39639672021-08-05 19:47:48 +010083 self.filename = filename
84 self.line = line
Yuto Takano704b0f72021-08-17 10:41:23 +010085 self.line_no = line_no
Yuto Takano39639672021-08-05 19:47:48 +010086 self.pos = pos
87 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010088
Yuto Takanoa4e75122021-08-06 17:23:28 +010089 def __str__(self):
Yuto Takanofb86ac72021-08-16 10:32:40 +010090 """
91 Return a formatted code listing representation of the erroneous line.
92 """
Yuto Takano704b0f72021-08-17 10:41:23 +010093 gutter = format(self.line_no, "4d")
94 underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
Yuto Takano381fda82021-08-06 23:37:20 +010095
Yuto Takanoa4e75122021-08-06 17:23:28 +010096 return (
Yuto Takanofb86ac72021-08-16 10:32:40 +010097 " {0} |\n".format(" " * len(gutter)) +
Yuto Takano381fda82021-08-06 23:37:20 +010098 " {0} | {1}".format(gutter, self.line) +
Yuto Takanofb86ac72021-08-16 10:32:40 +010099 " {0} | {1}\n".format(" " * len(gutter), underline)
Yuto Takanoa4e75122021-08-06 17:23:28 +0100100 )
Yuto Takanod93fa372021-08-06 23:05:55 +0100101
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100102class Problem(abc.ABC): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100103 """
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100104 An abstract parent class representing a form of static analysis error.
105 It extends an Abstract Base Class, which means it is not instantiable, and
106 it also mandates certain abstract methods to be implemented in subclasses.
Yuto Takano81528c02021-08-06 16:22:06 +0100107 """
Yuto Takano5473be22021-08-17 10:14:01 +0100108 # Class variable to control the quietness of all problems
109 quiet = False
Yuto Takano39639672021-08-05 19:47:48 +0100110 def __init__(self):
111 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +0100112 self.textwrapper.width = 80
Yuto Takanoa4e75122021-08-06 17:23:28 +0100113 self.textwrapper.initial_indent = " > "
Yuto Takano81528c02021-08-06 16:22:06 +0100114 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +0100115
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100116 def __str__(self):
117 """
118 Unified string representation method for all Problems.
119 """
120 if self.__class__.quiet:
121 return self.quiet_output()
122 return self.verbose_output()
123
124 @abc.abstractmethod
125 def quiet_output(self):
126 """
127 The output when --quiet is enabled.
128 """
129 pass
130
131 @abc.abstractmethod
132 def verbose_output(self):
133 """
134 The default output with explanation and code snippet if appropriate.
135 """
136 pass
137
Yuto Takanod93fa372021-08-06 23:05:55 +0100138class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100139 """
140 A problem that occurs when an exported/available symbol in the object file
141 is not explicitly declared in header files. Created with
142 NameCheck.check_symbols_declared_in_header()
143
144 Fields:
145 * symbol_name: the name of the symbol.
146 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100147 def __init__(self, symbol_name):
Yuto Takano39639672021-08-05 19:47:48 +0100148 self.symbol_name = symbol_name
149 Problem.__init__(self)
150
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100151 def quiet_output(self):
152 return "{0}".format(self.symbol_name)
Yuto Takano55614b52021-08-07 01:00:18 +0100153
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100154 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100155 return self.textwrapper.fill(
156 "'{0}' was found as an available symbol in the output of nm, "
157 "however it was not declared in any header files."
158 .format(self.symbol_name))
159
Yuto Takanod93fa372021-08-06 23:05:55 +0100160class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100161 """
162 A problem that occurs when something doesn't match the expected pattern.
163 Created with NameCheck.check_match_pattern()
164
165 Fields:
166 * pattern: the expected regex pattern
167 * match: the Match object in question
168 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100169 def __init__(self, pattern, match):
Yuto Takano39639672021-08-05 19:47:48 +0100170 self.pattern = pattern
171 self.match = match
172 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100173
Yuto Takano55614b52021-08-07 01:00:18 +0100174
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100175 def quiet_output(self):
176 return (
177 "{0}:{1}:{2}"
178 .format(self.match.filename, self.match.line_no, self.match.name)
179 )
180
181 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100182 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100183 "{0}:{1}: '{2}' does not match the required pattern '{3}'."
184 .format(
185 self.match.filename,
Yuto Takano5f831712021-08-18 18:03:24 +0100186 self.match.line_no,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100187 self.match.name,
Yuto Takanod70d4462021-08-09 12:45:51 +0100188 self.pattern
189 )
190 ) + "\n" + str(self.match)
Yuto Takano39639672021-08-05 19:47:48 +0100191
Yuto Takanod93fa372021-08-06 23:05:55 +0100192class Typo(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100193 """
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800194 A problem that occurs when a word using MBED or PSA doesn't
195 appear to be defined as constants nor enum values. Created with
196 NameCheck.check_for_typos()
Yuto Takano81528c02021-08-06 16:22:06 +0100197
198 Fields:
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800199 * match: the Match object of the MBED|PSA name in question.
Yuto Takano81528c02021-08-06 16:22:06 +0100200 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100201 def __init__(self, match):
Yuto Takano39639672021-08-05 19:47:48 +0100202 self.match = match
203 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100204
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100205 def quiet_output(self):
206 return (
207 "{0}:{1}:{2}"
208 .format(self.match.filename, self.match.line_no, self.match.name)
209 )
Yuto Takano55614b52021-08-07 01:00:18 +0100210
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100211 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100212 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100213 "{0}:{1}: '{2}' looks like a typo. It was not found in any "
214 "macros or any enums. If this is not a typo, put "
215 "//no-check-names after it."
Yuto Takano5f831712021-08-18 18:03:24 +0100216 .format(self.match.filename, self.match.line_no, self.match.name)
Yuto Takanod70d4462021-08-09 12:45:51 +0100217 ) + "\n" + str(self.match)
Darryl Greend5802922018-05-08 15:30:59 +0100218
Yuto Takano55c6c872021-08-09 15:35:19 +0100219class CodeParser():
Yuto Takano81528c02021-08-06 16:22:06 +0100220 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100221 Class for retrieving files and parsing the code. This can be used
222 independently of the checks that NameChecker performs, for example for
223 list_internal_identifiers.py.
Yuto Takano81528c02021-08-06 16:22:06 +0100224 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100225 def __init__(self, log):
226 self.log = log
Gilles Peskined9071e72022-09-18 21:17:09 +0200227 build_tree.check_repo_path()
Yuto Takano977e07f2021-08-09 11:56:15 +0100228
Yuto Takano8e9a2192021-08-09 14:48:53 +0100229 # Memo for storing "glob expression": set(filepaths)
230 self.files = {}
231
Gilles Peskine89458d12021-09-27 19:20:17 +0200232 # Globally excluded filenames.
233 # Note that "*" can match directory separators in exclude lists.
234 self.excluded_files = ["*/bn_mul", "*/compat-2.x.h"]
Yuto Takano977e07f2021-08-09 11:56:15 +0100235
Yuto Takano55c6c872021-08-09 15:35:19 +0100236 def comprehensive_parse(self):
Yuto Takano39639672021-08-05 19:47:48 +0100237 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100238 Comprehensive ("default") function to call each parsing function and
239 retrieve various elements of the code, together with the source location.
Darryl Greend5802922018-05-08 15:30:59 +0100240
Yuto Takano55c6c872021-08-09 15:35:19 +0100241 Returns a dict of parsed item key to the corresponding List of Matches.
Yuto Takano81528c02021-08-06 16:22:06 +0100242 """
243 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100244 self.log.debug(
Yuto Takano50953432021-08-09 14:54:36 +0100245 "The following files are excluded from the search: {}"
Yuto Takanod24e0372021-08-06 16:42:33 +0100246 .format(str(self.excluded_files))
247 )
Yuto Takano81528c02021-08-06 16:22:06 +0100248
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800249 all_macros = {"public": [], "internal": [], "private":[]}
Janos Follath99387192022-08-10 11:11:34 +0100250 all_macros["public"] = self.parse_macros([
Yuto Takano8e9a2192021-08-09 14:48:53 +0100251 "include/mbedtls/*.h",
252 "include/psa/*.h",
Yuto Takanod70d4462021-08-09 12:45:51 +0100253 "3rdparty/everest/include/everest/everest.h",
254 "3rdparty/everest/include/everest/x25519.h"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100255 ])
Janos Follath99387192022-08-10 11:11:34 +0100256 all_macros["internal"] = self.parse_macros([
257 "library/*.h",
258 "tests/include/test/drivers/*.h",
259 ])
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800260 all_macros["private"] = self.parse_macros([
261 "library/*.c",
262 ])
Yuto Takano8e9a2192021-08-09 14:48:53 +0100263 enum_consts = self.parse_enum_consts([
264 "include/mbedtls/*.h",
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800265 "include/psa/*.h",
Yuto Takano8e9a2192021-08-09 14:48:53 +0100266 "library/*.h",
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800267 "library/*.c",
Yuto Takano8e9a2192021-08-09 14:48:53 +0100268 "3rdparty/everest/include/everest/everest.h",
269 "3rdparty/everest/include/everest/x25519.h"
270 ])
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000271 identifiers, excluded_identifiers = self.parse_identifiers([
Yuto Takano8e9a2192021-08-09 14:48:53 +0100272 "include/mbedtls/*.h",
273 "include/psa/*.h",
274 "library/*.h",
275 "3rdparty/everest/include/everest/everest.h",
276 "3rdparty/everest/include/everest/x25519.h"
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000277 ], ["3rdparty/p256-m/p256-m/p256-m.h"])
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800278 mbed_psa_words = self.parse_mbed_psa_words([
Yuto Takano8e9a2192021-08-09 14:48:53 +0100279 "include/mbedtls/*.h",
280 "include/psa/*.h",
281 "library/*.h",
282 "3rdparty/everest/include/everest/everest.h",
283 "3rdparty/everest/include/everest/x25519.h",
284 "library/*.c",
Yuto Takano81528c02021-08-06 16:22:06 +0100285 "3rdparty/everest/library/everest.c",
Yuto Takanod70d4462021-08-09 12:45:51 +0100286 "3rdparty/everest/library/x25519.c"
Xiaokang Qianfe9666b2023-09-11 10:36:20 +0000287 ], ["library/psa_crypto_driver_wrappers.h"])
Yuto Takano81528c02021-08-06 16:22:06 +0100288 symbols = self.parse_symbols()
289
290 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
291 identifiers_justname = [x.name for x in identifiers]
Janos Follath99387192022-08-10 11:11:34 +0100292 actual_macros = {"public": [], "internal": []}
293 for scope in actual_macros:
294 for macro in all_macros[scope]:
295 if macro.name not in identifiers_justname:
296 actual_macros[scope].append(macro)
Yuto Takano81528c02021-08-06 16:22:06 +0100297
298 self.log.debug("Found:")
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100299 # Aligns the counts on the assumption that none exceeds 4 digits
Janos Follath99387192022-08-10 11:11:34 +0100300 for scope in actual_macros:
301 self.log.debug(" {:4} Total {} Macros"
Janos Follath8d59c862022-08-10 15:35:35 +0100302 .format(len(all_macros[scope]), scope))
Janos Follath99387192022-08-10 11:11:34 +0100303 self.log.debug(" {:4} {} Non-identifier Macros"
Janos Follath8d59c862022-08-10 15:35:35 +0100304 .format(len(actual_macros[scope]), scope))
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100305 self.log.debug(" {:4} Enum Constants".format(len(enum_consts)))
306 self.log.debug(" {:4} Identifiers".format(len(identifiers)))
307 self.log.debug(" {:4} Exported Symbols".format(len(symbols)))
Yuto Takano55c6c872021-08-09 15:35:19 +0100308 return {
Janos Follath99387192022-08-10 11:11:34 +0100309 "public_macros": actual_macros["public"],
310 "internal_macros": actual_macros["internal"],
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800311 "private_macros": all_macros["private"],
Yuto Takano81528c02021-08-06 16:22:06 +0100312 "enum_consts": enum_consts,
313 "identifiers": identifiers,
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000314 "excluded_identifiers": excluded_identifiers,
Yuto Takano81528c02021-08-06 16:22:06 +0100315 "symbols": symbols,
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800316 "mbed_psa_words": mbed_psa_words
Yuto Takano81528c02021-08-06 16:22:06 +0100317 }
318
Gilles Peskine89458d12021-09-27 19:20:17 +0200319 def is_file_excluded(self, path, exclude_wildcards):
Gilles Peskine8a832242021-09-28 10:12:49 +0200320 """Whether the given file path is excluded."""
Gilles Peskine89458d12021-09-27 19:20:17 +0200321 # exclude_wildcards may be None. Also, consider the global exclusions.
322 exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
323 for pattern in exclude_wildcards:
324 if fnmatch.fnmatch(path, pattern):
325 return True
326 return False
327
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000328 def get_all_files(self, include_wildcards, exclude_wildcards):
Yuto Takano55c6c872021-08-09 15:35:19 +0100329 """
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000330 Get all files that match any of the included UNIX-style wildcards
331 and filter them into included and excluded lists.
332 While the check_names script is designed only for use on UNIX/macOS
333 (due to nm), this function alone will work fine on Windows even with
334 forward slashes in the wildcard.
335
336 Args:
337 * include_wildcards: a List of shell-style wildcards to match filepaths.
338 * exclude_wildcards: a List of shell-style wildcards to exclude.
339
340 Returns:
341 * inc_files: A List of relative filepaths for included files.
342 * exc_files: A List of relative filepaths for excluded files.
343 """
344 accumulator = set()
Aditya Deshpande0584df42023-01-16 16:36:31 +0000345 all_wildcards = include_wildcards + (exclude_wildcards or [])
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000346 for wildcard in all_wildcards:
347 accumulator = accumulator.union(glob.iglob(wildcard))
348
349 inc_files = []
350 exc_files = []
351 for path in accumulator:
352 if self.is_file_excluded(path, exclude_wildcards):
353 exc_files.append(path)
354 else:
355 inc_files.append(path)
356 return (inc_files, exc_files)
357
358 def get_included_files(self, include_wildcards, exclude_wildcards):
359 """
360 Get all files that match any of the included UNIX-style wildcards.
361 While the check_names script is designed only for use on UNIX/macOS
362 (due to nm), this function alone will work fine on Windows even with
363 forward slashes in the wildcard.
Yuto Takano55c6c872021-08-09 15:35:19 +0100364
365 Args:
366 * include_wildcards: a List of shell-style wildcards to match filepaths.
367 * exclude_wildcards: a List of shell-style wildcards to exclude.
368
369 Returns a List of relative filepaths.
370 """
371 accumulator = set()
372
Yuto Takano55c6c872021-08-09 15:35:19 +0100373 for include_wildcard in include_wildcards:
Gilles Peskine89458d12021-09-27 19:20:17 +0200374 accumulator = accumulator.union(glob.iglob(include_wildcard))
Yuto Takano55c6c872021-08-09 15:35:19 +0100375
Gilles Peskine89458d12021-09-27 19:20:17 +0200376 return list(path for path in accumulator
377 if not self.is_file_excluded(path, exclude_wildcards))
Yuto Takano55c6c872021-08-09 15:35:19 +0100378
Yuto Takano8e9a2192021-08-09 14:48:53 +0100379 def parse_macros(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100380 """
381 Parse all macros defined by #define preprocessor directives.
382
383 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100384 * include: A List of glob expressions to look for files through.
385 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100386
387 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100388 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100389 macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
390 exclusions = (
Yuto Takano39639672021-08-05 19:47:48 +0100391 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
392 )
393
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000394 files = self.get_included_files(include, exclude)
Yuto Takano50953432021-08-09 14:54:36 +0100395 self.log.debug("Looking for macros in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100396
Yuto Takano50953432021-08-09 14:54:36 +0100397 macros = []
398 for header_file in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100399 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100400 for line_no, line in enumerate(header):
Yuto Takanod93fa372021-08-06 23:05:55 +0100401 for macro in macro_regex.finditer(line):
Yuto Takanod70d4462021-08-09 12:45:51 +0100402 if macro.group("macro").startswith(exclusions):
403 continue
404
405 macros.append(Match(
406 header_file,
407 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100408 line_no,
409 macro.span("macro"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100410 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100411
Yuto Takano39639672021-08-05 19:47:48 +0100412 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100413
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800414 def parse_mbed_psa_words(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100415 """
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800416 Parse all words in the file that begin with MBED|PSA, in and out of
417 macros, comments, anything.
Yuto Takano39639672021-08-05 19:47:48 +0100418
419 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100420 * include: A List of glob expressions to look for files through.
421 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100422
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800423 Returns a List of Match objects for words beginning with MBED|PSA.
Yuto Takano39639672021-08-05 19:47:48 +0100424 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100425 # Typos of TLS are common, hence the broader check below than MBEDTLS.
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800426 mbed_regex = re.compile(r"\b(MBED.+?|PSA)_[A-Z0-9_]*")
Yuto Takanod93fa372021-08-06 23:05:55 +0100427 exclusions = re.compile(r"// *no-check-names|#error")
428
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000429 files = self.get_included_files(include, exclude)
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800430 self.log.debug(
431 "Looking for MBED|PSA words in {} files"
432 .format(len(files))
433 )
Yuto Takanod93fa372021-08-06 23:05:55 +0100434
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800435 mbed_psa_words = []
Yuto Takano50953432021-08-09 14:54:36 +0100436 for filename in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100437 with open(filename, "r", encoding="utf-8") as fp:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100438 for line_no, line in enumerate(fp):
Yuto Takanod93fa372021-08-06 23:05:55 +0100439 if exclusions.search(line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100440 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100441
Yuto Takanod93fa372021-08-06 23:05:55 +0100442 for name in mbed_regex.finditer(line):
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800443 mbed_psa_words.append(Match(
Yuto Takano39639672021-08-05 19:47:48 +0100444 filename,
445 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100446 line_no,
447 name.span(0),
448 name.group(0)))
Yuto Takano39639672021-08-05 19:47:48 +0100449
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800450 return mbed_psa_words
Yuto Takano39639672021-08-05 19:47:48 +0100451
Yuto Takano8e9a2192021-08-09 14:48:53 +0100452 def parse_enum_consts(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100453 """
454 Parse all enum value constants that are declared.
455
456 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100457 * include: A List of glob expressions to look for files through.
458 * exclude: A List of glob expressions for excluding files.
Yuto Takano39639672021-08-05 19:47:48 +0100459
Yuto Takano81528c02021-08-06 16:22:06 +0100460 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100461 """
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000462 files = self.get_included_files(include, exclude)
Yuto Takano50953432021-08-09 14:54:36 +0100463 self.log.debug("Looking for enum consts in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100464
Yuto Takanob1417b42021-08-17 10:30:20 +0100465 # Emulate a finite state machine to parse enum declarations.
466 # OUTSIDE_KEYWORD = outside the enum keyword
467 # IN_BRACES = inside enum opening braces
468 # IN_BETWEEN = between enum keyword and opening braces
469 states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
Yuto Takano50953432021-08-09 14:54:36 +0100470 enum_consts = []
471 for header_file in files:
Yuto Takanob1417b42021-08-17 10:30:20 +0100472 state = states.OUTSIDE_KEYWORD
Yuto Takanoa083d152021-08-07 00:25:59 +0100473 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100474 for line_no, line in enumerate(header):
Yuto Takano13ecd992021-08-06 16:56:52 +0100475 # Match typedefs and brackets only when they are at the
476 # beginning of the line -- if they are indented, they might
477 # be sub-structures within structs, etc.
David Horstmannf91090e2022-12-16 13:39:04 +0000478 optional_c_identifier = r"([_a-zA-Z][_a-zA-Z0-9]*)?"
Yuto Takanob1417b42021-08-17 10:30:20 +0100479 if (state == states.OUTSIDE_KEYWORD and
David Horstmannf91090e2022-12-16 13:39:04 +0000480 re.search(r"^(typedef +)?enum " + \
481 optional_c_identifier + \
482 r" *{", line)):
Yuto Takanob1417b42021-08-17 10:30:20 +0100483 state = states.IN_BRACES
484 elif (state == states.OUTSIDE_KEYWORD and
485 re.search(r"^(typedef +)?enum", line)):
486 state = states.IN_BETWEEN
487 elif (state == states.IN_BETWEEN and
488 re.search(r"^{", line)):
489 state = states.IN_BRACES
490 elif (state == states.IN_BRACES and
491 re.search(r"^}", line)):
492 state = states.OUTSIDE_KEYWORD
493 elif (state == states.IN_BRACES and
494 not re.search(r"^ *#", line)):
Yuto Takano90bc0262021-08-16 11:34:10 +0100495 enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
Yuto Takanod70d4462021-08-09 12:45:51 +0100496 if not enum_const:
497 continue
498
499 enum_consts.append(Match(
500 header_file,
501 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100502 line_no,
503 enum_const.span("enum_const"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100504 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100505
Yuto Takano39639672021-08-05 19:47:48 +0100506 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100507
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100508 IGNORED_CHUNK_REGEX = re.compile('|'.join([
509 r'/\*.*?\*/', # block comment entirely on one line
510 r'//.*', # line comment
511 r'(?P<string>")(?:[^\\\"]|\\.)*"', # string literal
512 ]))
513
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100514 def strip_comments_and_literals(self, line, in_block_comment):
515 """Strip comments and string literals from line.
516
517 Continuation lines are not supported.
518
519 If in_block_comment is true, assume that the line starts inside a
520 block comment.
521
522 Return updated values of (line, in_block_comment) where:
523 * Comments in line have been replaced by a space (or nothing at the
524 start or end of the line).
525 * String contents have been removed.
526 * in_block_comment indicates whether the line ends inside a block
527 comment that continues on the next line.
528 """
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100529
530 # Terminate current multiline comment?
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100531 if in_block_comment:
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100532 m = re.search(r"\*/", line)
533 if m:
534 in_block_comment = False
535 line = line[m.end(0):]
536 else:
537 return '', True
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100538
539 # Remove full comments and string literals.
540 # Do it all together to handle cases like "/*" correctly.
541 # Note that continuation lines are not supported.
542 line = re.sub(self.IGNORED_CHUNK_REGEX,
543 lambda s: '""' if s.group('string') else ' ',
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100544 line)
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100545
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100546 # Start an unfinished comment?
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100547 # (If `/*` was part of a complete comment, it's already been removed.)
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100548 m = re.search(r"/\*", line)
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100549 if m:
550 in_block_comment = True
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100551 line = line[:m.start(0)]
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100552
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100553 return line, in_block_comment
554
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100555 IDENTIFIER_REGEX = re.compile('|'.join([
Gilles Peskine152de232021-11-16 20:56:47 +0100556 # Match " something(a" or " *something(a". Functions.
557 # Assumptions:
558 # - function definition from return type to one of its arguments is
559 # all on one line
560 # - function definition line only contains alphanumeric, asterisk,
561 # underscore, and open bracket
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100562 r".* \**(\w+) *\( *\w",
Gilles Peskine152de232021-11-16 20:56:47 +0100563 # Match "(*something)(".
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100564 r".*\( *\* *(\w+) *\) *\(",
Gilles Peskine152de232021-11-16 20:56:47 +0100565 # Match names of named data structures.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100566 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$",
Gilles Peskine152de232021-11-16 20:56:47 +0100567 # Match names of typedef instances, after closing bracket.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100568 r"}? *(\w+)[;[].*",
569 ]))
Gilles Peskine152de232021-11-16 20:56:47 +0100570 # The regex below is indented for clarity.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100571 EXCLUSION_LINES = re.compile("|".join([
572 r"extern +\"C\"",
573 r"(typedef +)?(struct|union|enum)( *{)?$",
574 r"} *;?$",
575 r"$",
576 r"//",
577 r"#",
578 ]))
Gilles Peskine152de232021-11-16 20:56:47 +0100579
580 def parse_identifiers_in_file(self, header_file, identifiers):
581 """
582 Parse all lines of a header where a function/enum/struct/union/typedef
583 identifier is declared, based on some regex and heuristics. Highly
584 dependent on formatting style.
585
586 Append found matches to the list ``identifiers``.
587 """
588
589 with open(header_file, "r", encoding="utf-8") as header:
590 in_block_comment = False
591 # The previous line variable is used for concatenating lines
592 # when identifiers are formatted and spread across multiple
593 # lines.
594 previous_line = ""
595
596 for line_no, line in enumerate(header):
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100597 line, in_block_comment = \
598 self.strip_comments_and_literals(line, in_block_comment)
Gilles Peskine152de232021-11-16 20:56:47 +0100599
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100600 if self.EXCLUSION_LINES.match(line):
Gilles Peskine152de232021-11-16 20:56:47 +0100601 previous_line = ""
602 continue
603
604 # If the line contains only space-separated alphanumeric
Gilles Peskinebc1e8f62021-11-17 20:39:56 +0100605 # characters (or underscore, asterisk, or open parenthesis),
Gilles Peskine152de232021-11-16 20:56:47 +0100606 # and nothing else, high chance it's a declaration that
607 # continues on the next line
608 if re.search(r"^([\w\*\(]+\s+)+$", line):
609 previous_line += line
610 continue
611
612 # If previous line seemed to start an unfinished declaration
613 # (as above), concat and treat them as one.
614 if previous_line:
615 line = previous_line.strip() + " " + line.strip() + "\n"
616 previous_line = ""
617
618 # Skip parsing if line has a space in front = heuristic to
619 # skip function argument lines (highly subject to formatting
620 # changes)
621 if line[0] == " ":
622 continue
623
624 identifier = self.IDENTIFIER_REGEX.search(line)
625
626 if not identifier:
627 continue
628
629 # Find the group that matched, and append it
630 for group in identifier.groups():
631 if not group:
632 continue
633
634 identifiers.append(Match(
635 header_file,
636 line,
637 line_no,
638 identifier.span(),
639 group))
640
Yuto Takano8e9a2192021-08-09 14:48:53 +0100641 def parse_identifiers(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100642 """
Yuto Takano8246eb82021-08-16 10:37:24 +0100643 Parse all lines of a header where a function/enum/struct/union/typedef
Yuto Takanob1417b42021-08-17 10:30:20 +0100644 identifier is declared, based on some regex and heuristics. Highly
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000645 dependent on formatting style. Identifiers in excluded files are still
646 parsed
Darryl Greend5802922018-05-08 15:30:59 +0100647
Yuto Takano39639672021-08-05 19:47:48 +0100648 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100649 * include: A List of glob expressions to look for files through.
650 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100651
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000652 Returns: a Tuple of two Lists of Match objects with identifiers.
653 * included_identifiers: A List of Match objects with identifiers from
654 included files.
655 * excluded_identifiers: A List of Match objects with identifiers from
656 excluded files.
Yuto Takano39639672021-08-05 19:47:48 +0100657 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100658
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000659 included_files, excluded_files = \
660 self.get_all_files(include, exclude)
Yuto Takano50953432021-08-09 14:54:36 +0100661
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000662 self.log.debug("Looking for included identifiers in {} files".format \
663 (len(included_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100664
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000665 included_identifiers = []
666 excluded_identifiers = []
667 for header_file in included_files:
668 self.parse_identifiers_in_file(header_file, included_identifiers)
669 for header_file in excluded_files:
670 self.parse_identifiers_in_file(header_file, excluded_identifiers)
671
672 return (included_identifiers, excluded_identifiers)
Yuto Takano39639672021-08-05 19:47:48 +0100673
674 def parse_symbols(self):
675 """
676 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
677 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100678 Exceptions thrown here are rethrown because they would be critical
679 errors that void several tests, and thus needs to halt the program. This
680 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100681
Yuto Takano81528c02021-08-06 16:22:06 +0100682 Returns a List of unique symbols defined and used in the libraries.
683 """
684 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100685 symbols = []
686
Tom Cosgrove1797b052022-12-04 17:19:59 +0000687 # Back up the config and atomically compile with the full configuration.
Yuto Takanod70d4462021-08-09 12:45:51 +0100688 shutil.copy(
689 "include/mbedtls/mbedtls_config.h",
690 "include/mbedtls/mbedtls_config.h.bak"
691 )
Darryl Greend5802922018-05-08 15:30:59 +0100692 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100693 # Use check=True in all subprocess calls so that failures are raised
694 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100695 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100696 ["python3", "scripts/config.py", "full"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100697 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100698 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100699 )
700 my_environment = os.environ.copy()
701 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100702 # Run make clean separately to lib to prevent unwanted behavior when
703 # make is invoked with parallelism.
Yuto Takano39639672021-08-05 19:47:48 +0100704 subprocess.run(
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100705 ["make", "clean"],
706 universal_newlines=True,
707 check=True
708 )
709 subprocess.run(
710 ["make", "lib"],
Darryl Greend5802922018-05-08 15:30:59 +0100711 env=my_environment,
Yuto Takanobcc3d992021-08-06 23:14:58 +0100712 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100713 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100714 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100715 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100716 )
Yuto Takano39639672021-08-05 19:47:48 +0100717
718 # Perform object file analysis using nm
Yuto Takanod70d4462021-08-09 12:45:51 +0100719 symbols = self.parse_symbols_from_nm([
720 "library/libmbedcrypto.a",
721 "library/libmbedtls.a",
722 "library/libmbedx509.a"
723 ])
Yuto Takano39639672021-08-05 19:47:48 +0100724
725 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100726 ["make", "clean"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100727 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100728 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100729 )
730 except subprocess.CalledProcessError as error:
Yuto Takano25eeb7b2021-08-06 21:27:59 +0100731 self.log.debug(error.output)
Yuto Takano81528c02021-08-06 16:22:06 +0100732 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100733 finally:
Yuto Takano6fececf2021-08-07 17:28:23 +0100734 # Put back the original config regardless of there being errors.
735 # Works also for keyboard interrupts.
Yuto Takanod70d4462021-08-09 12:45:51 +0100736 shutil.move(
737 "include/mbedtls/mbedtls_config.h.bak",
738 "include/mbedtls/mbedtls_config.h"
739 )
Yuto Takano39639672021-08-05 19:47:48 +0100740
741 return symbols
742
743 def parse_symbols_from_nm(self, object_files):
744 """
745 Run nm to retrieve the list of referenced symbols in each object file.
746 Does not return the position data since it is of no use.
747
Yuto Takano81528c02021-08-06 16:22:06 +0100748 Args:
Yuto Takano55c6c872021-08-09 15:35:19 +0100749 * object_files: a List of compiled object filepaths to search through.
Yuto Takano81528c02021-08-06 16:22:06 +0100750
751 Returns a List of unique symbols defined and used in any of the object
752 files.
Yuto Takano39639672021-08-05 19:47:48 +0100753 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100754 nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
755 nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100756 exclusions = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100757
758 symbols = []
759
Yuto Takano81528c02021-08-06 16:22:06 +0100760 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100761 nm_output = ""
762 for lib in object_files:
763 nm_output += subprocess.run(
764 ["nm", "-og", lib],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100765 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100766 stdout=subprocess.PIPE,
767 stderr=subprocess.STDOUT,
768 check=True
769 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100770
Yuto Takano39639672021-08-05 19:47:48 +0100771 for line in nm_output.splitlines():
Yuto Takano90bc0262021-08-16 11:34:10 +0100772 if not nm_undefined_regex.search(line):
773 symbol = nm_valid_regex.search(line)
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100774 if (symbol and not symbol.group("symbol").startswith(exclusions)):
Yuto Takanoe77f6992021-08-05 20:22:59 +0100775 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100776 else:
777 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100778
Yuto Takano39639672021-08-05 19:47:48 +0100779 return symbols
780
Yuto Takano55c6c872021-08-09 15:35:19 +0100781class NameChecker():
782 """
783 Representation of the core name checking operation performed by this script.
784 """
785 def __init__(self, parse_result, log):
786 self.parse_result = parse_result
787 self.log = log
788
Yuto Takano55614b52021-08-07 01:00:18 +0100789 def perform_checks(self, quiet=False):
Yuto Takano39639672021-08-05 19:47:48 +0100790 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100791 A comprehensive checker that performs each check in order, and outputs
792 a final verdict.
Yuto Takano81528c02021-08-06 16:22:06 +0100793
794 Args:
Yuto Takano55614b52021-08-07 01:00:18 +0100795 * quiet: whether to hide detailed problem explanation.
Yuto Takano39639672021-08-05 19:47:48 +0100796 """
Yuto Takano81528c02021-08-06 16:22:06 +0100797 self.log.info("=============")
Yuto Takano5473be22021-08-17 10:14:01 +0100798 Problem.quiet = quiet
Yuto Takano39639672021-08-05 19:47:48 +0100799 problems = 0
Yuto Takano5473be22021-08-17 10:14:01 +0100800 problems += self.check_symbols_declared_in_header()
Yuto Takano39639672021-08-05 19:47:48 +0100801
Yuto Takanod70d4462021-08-09 12:45:51 +0100802 pattern_checks = [
Janos Follath99387192022-08-10 11:11:34 +0100803 ("public_macros", PUBLIC_MACRO_PATTERN),
804 ("internal_macros", INTERNAL_MACRO_PATTERN),
Yuto Takanod70d4462021-08-09 12:45:51 +0100805 ("enum_consts", CONSTANTS_PATTERN),
806 ("identifiers", IDENTIFIER_PATTERN)
807 ]
Yuto Takano39639672021-08-05 19:47:48 +0100808 for group, check_pattern in pattern_checks:
Yuto Takano5473be22021-08-17 10:14:01 +0100809 problems += self.check_match_pattern(group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100810
Yuto Takano5473be22021-08-17 10:14:01 +0100811 problems += self.check_for_typos()
Yuto Takano39639672021-08-05 19:47:48 +0100812
813 self.log.info("=============")
814 if problems > 0:
815 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano55614b52021-08-07 01:00:18 +0100816 if quiet:
817 self.log.info("Remove --quiet to see explanations.")
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100818 else:
819 self.log.info("Use --quiet for minimal output.")
Yuto Takano55c6c872021-08-09 15:35:19 +0100820 return 1
Yuto Takano39639672021-08-05 19:47:48 +0100821 else:
822 self.log.info("PASS")
Yuto Takano55c6c872021-08-09 15:35:19 +0100823 return 0
Darryl Greend5802922018-05-08 15:30:59 +0100824
Yuto Takano5473be22021-08-17 10:14:01 +0100825 def check_symbols_declared_in_header(self):
Yuto Takano39639672021-08-05 19:47:48 +0100826 """
827 Perform a check that all detected symbols in the library object files
828 are properly declared in headers.
Yuto Takano977e07f2021-08-09 11:56:15 +0100829 Assumes parse_names_in_source() was called before this.
Darryl Greend5802922018-05-08 15:30:59 +0100830
Yuto Takano81528c02021-08-06 16:22:06 +0100831 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100832 """
833 problems = []
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000834 all_identifiers = self.parse_result["identifiers"] + \
835 self.parse_result["excluded_identifiers"]
Yuto Takanod93fa372021-08-06 23:05:55 +0100836
Yuto Takano39639672021-08-05 19:47:48 +0100837 for symbol in self.parse_result["symbols"]:
838 found_symbol_declared = False
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000839 for identifier_match in all_identifiers:
Yuto Takano39639672021-08-05 19:47:48 +0100840 if symbol == identifier_match.name:
841 found_symbol_declared = True
842 break
Yuto Takano81528c02021-08-06 16:22:06 +0100843
Yuto Takano39639672021-08-05 19:47:48 +0100844 if not found_symbol_declared:
Yuto Takanod70d4462021-08-09 12:45:51 +0100845 problems.append(SymbolNotInHeader(symbol))
Yuto Takano39639672021-08-05 19:47:48 +0100846
Yuto Takano5473be22021-08-17 10:14:01 +0100847 self.output_check_result("All symbols in header", problems)
Yuto Takano39639672021-08-05 19:47:48 +0100848 return len(problems)
849
Yuto Takano5473be22021-08-17 10:14:01 +0100850 def check_match_pattern(self, group_to_check, check_pattern):
Yuto Takano81528c02021-08-06 16:22:06 +0100851 """
852 Perform a check that all items of a group conform to a regex pattern.
Yuto Takano977e07f2021-08-09 11:56:15 +0100853 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100854
855 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100856 * group_to_check: string key to index into self.parse_result.
857 * check_pattern: the regex to check against.
858
859 Returns the number of problems that need fixing.
860 """
Yuto Takano39639672021-08-05 19:47:48 +0100861 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100862
Yuto Takano39639672021-08-05 19:47:48 +0100863 for item_match in self.parse_result[group_to_check]:
Yuto Takano90bc0262021-08-16 11:34:10 +0100864 if not re.search(check_pattern, item_match.name):
Yuto Takano39639672021-08-05 19:47:48 +0100865 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano90bc0262021-08-16 11:34:10 +0100866 # Double underscore should not be used for names
867 if re.search(r".*__.*", item_match.name):
Yuto Takano704b0f72021-08-17 10:41:23 +0100868 problems.append(
869 PatternMismatch("no double underscore allowed", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100870
871 self.output_check_result(
872 "Naming patterns of {}".format(group_to_check),
Yuto Takano55614b52021-08-07 01:00:18 +0100873 problems)
Yuto Takano39639672021-08-05 19:47:48 +0100874 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100875
Yuto Takano5473be22021-08-17 10:14:01 +0100876 def check_for_typos(self):
Yuto Takano81528c02021-08-06 16:22:06 +0100877 """
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800878 Perform a check that all words in the source code beginning with MBED are
Yuto Takano81528c02021-08-06 16:22:06 +0100879 either defined as macros, or as enum constants.
Yuto Takano977e07f2021-08-09 11:56:15 +0100880 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100881
Yuto Takano81528c02021-08-06 16:22:06 +0100882 Returns the number of problems that need fixing.
883 """
Yuto Takano39639672021-08-05 19:47:48 +0100884 problems = []
Yuto Takano39639672021-08-05 19:47:48 +0100885
Yuto Takanod70d4462021-08-09 12:45:51 +0100886 # Set comprehension, equivalent to a list comprehension wrapped by set()
Yuto Takanod93fa372021-08-06 23:05:55 +0100887 all_caps_names = {
888 match.name
889 for match
Janos Follath99387192022-08-10 11:11:34 +0100890 in self.parse_result["public_macros"] +
Janos Follath8d59c862022-08-10 15:35:35 +0100891 self.parse_result["internal_macros"] +
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800892 self.parse_result["private_macros"] +
Janos Follath8d59c862022-08-10 15:35:35 +0100893 self.parse_result["enum_consts"]
Janos Follath99387192022-08-10 11:11:34 +0100894 }
Ronald Cron7975fae2021-09-13 14:50:42 +0200895 typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$|"
Pengyu Lvf3f1f812022-11-08 16:56:51 +0800896 r"MBEDTLS_TEST_LIBTESTDRIVER*|"
Pengyu Lv3bb0e432022-11-24 17:29:05 +0800897 r"PSA_CRYPTO_DRIVER_TEST")
Yuto Takano39639672021-08-05 19:47:48 +0100898
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800899 for name_match in self.parse_result["mbed_psa_words"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100900 found = name_match.name in all_caps_names
901
902 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
903 # PSA driver, they will not exist as macros. However, they
904 # should still be checked for typos using the equivalent
905 # BUILTINs that exist.
906 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
907 found = name_match.name.replace(
908 "MBEDTLS_PSA_ACCEL_",
909 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
910
Yuto Takanod93fa372021-08-06 23:05:55 +0100911 if not found and not typo_exclusion.search(name_match.name):
Yuto Takanod70d4462021-08-09 12:45:51 +0100912 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100913
Yuto Takano5473be22021-08-17 10:14:01 +0100914 self.output_check_result("Likely typos", problems)
Yuto Takano81528c02021-08-06 16:22:06 +0100915 return len(problems)
916
Yuto Takano5473be22021-08-17 10:14:01 +0100917 def output_check_result(self, name, problems):
Yuto Takano81528c02021-08-06 16:22:06 +0100918 """
919 Write out the PASS/FAIL status of a performed check depending on whether
920 there were problems.
Yuto Takanod70d4462021-08-09 12:45:51 +0100921
922 Args:
Yuto Takanod70d4462021-08-09 12:45:51 +0100923 * name: the name of the test
924 * problems: a List of encountered Problems
Yuto Takano81528c02021-08-06 16:22:06 +0100925 """
Yuto Takano39639672021-08-05 19:47:48 +0100926 if problems:
Yuto Takano55614b52021-08-07 01:00:18 +0100927 self.log.info("{}: FAIL\n".format(name))
928 for problem in problems:
929 self.log.warning(str(problem))
Darryl Greend5802922018-05-08 15:30:59 +0100930 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100931 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100932
Yuto Takano39639672021-08-05 19:47:48 +0100933def main():
934 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100935 Perform argument parsing, and create an instance of CodeParser and
936 NameChecker to begin the core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100937 """
Yuto Takanof005c332021-08-09 13:56:36 +0100938 parser = argparse.ArgumentParser(
Yuto Takano39639672021-08-05 19:47:48 +0100939 formatter_class=argparse.RawDescriptionHelpFormatter,
940 description=(
941 "This script confirms that the naming of all symbols and identifiers "
942 "in Mbed TLS are consistent with the house style and are also "
943 "self-consistent.\n\n"
Yuto Takanof005c332021-08-09 13:56:36 +0100944 "Expected to be run from the MbedTLS root directory.")
945 )
946 parser.add_argument(
947 "-v", "--verbose",
948 action="store_true",
949 help="show parse results"
950 )
951 parser.add_argument(
952 "-q", "--quiet",
953 action="store_true",
Tom Cosgrove1797b052022-12-04 17:19:59 +0000954 help="hide unnecessary text, explanations, and highlights"
Yuto Takanof005c332021-08-09 13:56:36 +0100955 )
Darryl Greend5802922018-05-08 15:30:59 +0100956
Yuto Takanof005c332021-08-09 13:56:36 +0100957 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100958
Yuto Takano55c6c872021-08-09 15:35:19 +0100959 # Configure the global logger, which is then passed to the classes below
960 log = logging.getLogger()
961 log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
962 log.addHandler(logging.StreamHandler())
963
Darryl Greend5802922018-05-08 15:30:59 +0100964 try:
Yuto Takano55c6c872021-08-09 15:35:19 +0100965 code_parser = CodeParser(log)
966 parse_result = code_parser.comprehensive_parse()
Yuto Takanod93fa372021-08-06 23:05:55 +0100967 except Exception: # pylint: disable=broad-except
Darryl Greend5802922018-05-08 15:30:59 +0100968 traceback.print_exc()
969 sys.exit(2)
970
Yuto Takano55c6c872021-08-09 15:35:19 +0100971 name_checker = NameChecker(parse_result, log)
972 return_code = name_checker.perform_checks(quiet=args.quiet)
973
974 sys.exit(return_code)
975
Darryl Greend5802922018-05-08 15:30:59 +0100976if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100977 main()