blob: 8f344448e69486f5c909be188fb2983025e2152b [file] [log] [blame]
Yuto Takano39639672021-08-05 19:47:48 +01001#!/usr/bin/env python3
2#
3# Copyright The Mbed TLS Contributors
Dave Rodgman16799db2023-11-02 19:47:20 +00004# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Yuto Takano39639672021-08-05 19:47:48 +01005
Darryl Greend5802922018-05-08 15:30:59 +01006"""
Yuto Takano39639672021-08-05 19:47:48 +01007This script confirms that the naming of all symbols and identifiers in Mbed TLS
Yuto Takano159255a2021-08-06 17:00:28 +01008are consistent with the house style and are also self-consistent. It only runs
9on Linux and macOS since it depends on nm.
10
Yuto Takano55c6c872021-08-09 15:35:19 +010011It contains two major Python classes, CodeParser and NameChecker. They both have
12a comprehensive "run-all" function (comprehensive_parse() and perform_checks())
13but the individual functions can also be used for specific needs.
14
15CodeParser makes heavy use of regular expressions to parse the code, and is
16dependent on the current code formatting. Many Python C parser libraries require
17preprocessed C code, which means no macro parsing. Compiler tools are also not
18very helpful when we want the exact location in the original source (which
19becomes impossible when e.g. comments are stripped).
20
21NameChecker performs the following checks:
Yuto Takano81528c02021-08-06 16:22:06 +010022
23- All exported and available symbols in the library object files, are explicitly
Yuto Takano159255a2021-08-06 17:00:28 +010024 declared in the header files. This uses the nm command.
Yuto Takano81528c02021-08-06 16:22:06 +010025- All macros, constants, and identifiers (function names, struct names, etc)
Yuto Takano55c6c872021-08-09 15:35:19 +010026 follow the required regex pattern.
Pengyu Lvcdac0d52022-11-08 15:55:00 +080027- Typo checking: All words that begin with MBED|PSA exist as macros or constants.
Yuto Takanofc54dfb2021-08-07 17:18:28 +010028
Yuto Takano55c6c872021-08-09 15:35:19 +010029The script returns 0 on success, 1 on test failure, and 2 if there is a script
Yuto Takano8246eb82021-08-16 10:37:24 +010030error. It must be run from Mbed TLS root.
Darryl Greend5802922018-05-08 15:30:59 +010031"""
Yuto Takano39639672021-08-05 19:47:48 +010032
Yuto Takanofc1e9ff2021-08-23 13:54:56 +010033import abc
Yuto Takano39639672021-08-05 19:47:48 +010034import argparse
Gilles Peskine89458d12021-09-27 19:20:17 +020035import fnmatch
Yuto Takano977e07f2021-08-09 11:56:15 +010036import glob
Yuto Takano39639672021-08-05 19:47:48 +010037import textwrap
Darryl Greend5802922018-05-08 15:30:59 +010038import os
39import sys
40import traceback
41import re
Yuto Takanob1417b42021-08-17 10:30:20 +010042import enum
Darryl Greend5802922018-05-08 15:30:59 +010043import shutil
44import subprocess
45import logging
46
Gilles Peskined9071e72022-09-18 21:17:09 +020047import scripts_path # pylint: disable=unused-import
David Horstmanncd84bb22024-05-03 14:36:12 +010048from mbedtls_framework import build_tree
Gilles Peskined9071e72022-09-18 21:17:09 +020049
50
Yuto Takano81528c02021-08-06 16:22:06 +010051# Naming patterns to check against. These are defined outside the NameCheck
52# class for ease of modification.
Minos Galanakisa8c85f82024-11-29 16:15:41 +000053PUBLIC_MACRO_PATTERN = r"^(MBEDTLS|PSA|TF_PSA)_[0-9A-Z_]*[0-9A-Z]$"
Janos Follath99387192022-08-10 11:11:34 +010054INTERNAL_MACRO_PATTERN = r"^[0-9A-Za-z_]*[0-9A-Z]$"
55CONSTANTS_PATTERN = PUBLIC_MACRO_PATTERN
Yuto Takanoc1838932021-08-05 19:52:09 +010056IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
Yuto Takano39639672021-08-05 19:47:48 +010057
Yuto Takanod93fa372021-08-06 23:05:55 +010058class Match(): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010059 """
60 A class representing a match, together with its found position.
61
62 Fields:
63 * filename: the file that the match was in.
64 * line: the full line containing the match.
Yuto Takano704b0f72021-08-17 10:41:23 +010065 * line_no: the line number.
66 * pos: a tuple of (start, end) positions on the line where the match is.
Yuto Takano81528c02021-08-06 16:22:06 +010067 * name: the match itself.
68 """
Yuto Takano704b0f72021-08-17 10:41:23 +010069 def __init__(self, filename, line, line_no, pos, name):
70 # pylint: disable=too-many-arguments
Yuto Takano39639672021-08-05 19:47:48 +010071 self.filename = filename
72 self.line = line
Yuto Takano704b0f72021-08-17 10:41:23 +010073 self.line_no = line_no
Yuto Takano39639672021-08-05 19:47:48 +010074 self.pos = pos
75 self.name = name
Yuto Takano39639672021-08-05 19:47:48 +010076
Yuto Takanoa4e75122021-08-06 17:23:28 +010077 def __str__(self):
Yuto Takanofb86ac72021-08-16 10:32:40 +010078 """
79 Return a formatted code listing representation of the erroneous line.
80 """
Yuto Takano704b0f72021-08-17 10:41:23 +010081 gutter = format(self.line_no, "4d")
82 underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
Yuto Takano381fda82021-08-06 23:37:20 +010083
Yuto Takanoa4e75122021-08-06 17:23:28 +010084 return (
Yuto Takanofb86ac72021-08-16 10:32:40 +010085 " {0} |\n".format(" " * len(gutter)) +
Yuto Takano381fda82021-08-06 23:37:20 +010086 " {0} | {1}".format(gutter, self.line) +
Yuto Takanofb86ac72021-08-16 10:32:40 +010087 " {0} | {1}\n".format(" " * len(gutter), underline)
Yuto Takanoa4e75122021-08-06 17:23:28 +010088 )
Yuto Takanod93fa372021-08-06 23:05:55 +010089
Yuto Takanofc1e9ff2021-08-23 13:54:56 +010090class Problem(abc.ABC): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +010091 """
Yuto Takanofc1e9ff2021-08-23 13:54:56 +010092 An abstract parent class representing a form of static analysis error.
93 It extends an Abstract Base Class, which means it is not instantiable, and
94 it also mandates certain abstract methods to be implemented in subclasses.
Yuto Takano81528c02021-08-06 16:22:06 +010095 """
Yuto Takano5473be22021-08-17 10:14:01 +010096 # Class variable to control the quietness of all problems
97 quiet = False
Yuto Takano39639672021-08-05 19:47:48 +010098 def __init__(self):
99 self.textwrapper = textwrap.TextWrapper()
Yuto Takano81528c02021-08-06 16:22:06 +0100100 self.textwrapper.width = 80
Yuto Takanoa4e75122021-08-06 17:23:28 +0100101 self.textwrapper.initial_indent = " > "
Yuto Takano81528c02021-08-06 16:22:06 +0100102 self.textwrapper.subsequent_indent = " "
Yuto Takano39639672021-08-05 19:47:48 +0100103
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100104 def __str__(self):
105 """
106 Unified string representation method for all Problems.
107 """
108 if self.__class__.quiet:
109 return self.quiet_output()
110 return self.verbose_output()
111
112 @abc.abstractmethod
113 def quiet_output(self):
114 """
115 The output when --quiet is enabled.
116 """
117 pass
118
119 @abc.abstractmethod
120 def verbose_output(self):
121 """
122 The default output with explanation and code snippet if appropriate.
123 """
124 pass
125
Yuto Takanod93fa372021-08-06 23:05:55 +0100126class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100127 """
128 A problem that occurs when an exported/available symbol in the object file
129 is not explicitly declared in header files. Created with
130 NameCheck.check_symbols_declared_in_header()
131
132 Fields:
133 * symbol_name: the name of the symbol.
134 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100135 def __init__(self, symbol_name):
Yuto Takano39639672021-08-05 19:47:48 +0100136 self.symbol_name = symbol_name
137 Problem.__init__(self)
138
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100139 def quiet_output(self):
140 return "{0}".format(self.symbol_name)
Yuto Takano55614b52021-08-07 01:00:18 +0100141
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100142 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100143 return self.textwrapper.fill(
144 "'{0}' was found as an available symbol in the output of nm, "
145 "however it was not declared in any header files."
146 .format(self.symbol_name))
147
Yuto Takanod93fa372021-08-06 23:05:55 +0100148class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100149 """
150 A problem that occurs when something doesn't match the expected pattern.
151 Created with NameCheck.check_match_pattern()
152
153 Fields:
154 * pattern: the expected regex pattern
155 * match: the Match object in question
156 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100157 def __init__(self, pattern, match):
Yuto Takano39639672021-08-05 19:47:48 +0100158 self.pattern = pattern
159 self.match = match
160 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100161
Yuto Takano55614b52021-08-07 01:00:18 +0100162
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100163 def quiet_output(self):
164 return (
165 "{0}:{1}:{2}"
166 .format(self.match.filename, self.match.line_no, self.match.name)
167 )
168
169 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100170 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100171 "{0}:{1}: '{2}' does not match the required pattern '{3}'."
172 .format(
173 self.match.filename,
Yuto Takano5f831712021-08-18 18:03:24 +0100174 self.match.line_no,
Yuto Takanoa4e75122021-08-06 17:23:28 +0100175 self.match.name,
Yuto Takanod70d4462021-08-09 12:45:51 +0100176 self.pattern
177 )
178 ) + "\n" + str(self.match)
Yuto Takano39639672021-08-05 19:47:48 +0100179
Yuto Takanod93fa372021-08-06 23:05:55 +0100180class Typo(Problem): # pylint: disable=too-few-public-methods
Yuto Takano81528c02021-08-06 16:22:06 +0100181 """
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800182 A problem that occurs when a word using MBED or PSA doesn't
183 appear to be defined as constants nor enum values. Created with
184 NameCheck.check_for_typos()
Yuto Takano81528c02021-08-06 16:22:06 +0100185
186 Fields:
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800187 * match: the Match object of the MBED|PSA name in question.
Yuto Takano81528c02021-08-06 16:22:06 +0100188 """
Yuto Takanod70d4462021-08-09 12:45:51 +0100189 def __init__(self, match):
Yuto Takano39639672021-08-05 19:47:48 +0100190 self.match = match
191 Problem.__init__(self)
Yuto Takano81528c02021-08-06 16:22:06 +0100192
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100193 def quiet_output(self):
194 return (
195 "{0}:{1}:{2}"
196 .format(self.match.filename, self.match.line_no, self.match.name)
197 )
Yuto Takano55614b52021-08-07 01:00:18 +0100198
Yuto Takanofc1e9ff2021-08-23 13:54:56 +0100199 def verbose_output(self):
Yuto Takano39639672021-08-05 19:47:48 +0100200 return self.textwrapper.fill(
Yuto Takanoa4e75122021-08-06 17:23:28 +0100201 "{0}:{1}: '{2}' looks like a typo. It was not found in any "
202 "macros or any enums. If this is not a typo, put "
203 "//no-check-names after it."
Yuto Takano5f831712021-08-18 18:03:24 +0100204 .format(self.match.filename, self.match.line_no, self.match.name)
Yuto Takanod70d4462021-08-09 12:45:51 +0100205 ) + "\n" + str(self.match)
Darryl Greend5802922018-05-08 15:30:59 +0100206
Yuto Takano55c6c872021-08-09 15:35:19 +0100207class CodeParser():
Yuto Takano81528c02021-08-06 16:22:06 +0100208 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100209 Class for retrieving files and parsing the code. This can be used
210 independently of the checks that NameChecker performs, for example for
211 list_internal_identifiers.py.
Yuto Takano81528c02021-08-06 16:22:06 +0100212 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100213 def __init__(self, log):
214 self.log = log
Gilles Peskined9071e72022-09-18 21:17:09 +0200215 build_tree.check_repo_path()
Yuto Takano977e07f2021-08-09 11:56:15 +0100216
Yuto Takano8e9a2192021-08-09 14:48:53 +0100217 # Memo for storing "glob expression": set(filepaths)
218 self.files = {}
219
Gilles Peskine89458d12021-09-27 19:20:17 +0200220 # Globally excluded filenames.
221 # Note that "*" can match directory separators in exclude lists.
222 self.excluded_files = ["*/bn_mul", "*/compat-2.x.h"]
Yuto Takano977e07f2021-08-09 11:56:15 +0100223
Yuto Takano55c6c872021-08-09 15:35:19 +0100224 def comprehensive_parse(self):
Yuto Takano39639672021-08-05 19:47:48 +0100225 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100226 Comprehensive ("default") function to call each parsing function and
227 retrieve various elements of the code, together with the source location.
Darryl Greend5802922018-05-08 15:30:59 +0100228
Yuto Takano55c6c872021-08-09 15:35:19 +0100229 Returns a dict of parsed item key to the corresponding List of Matches.
Yuto Takano81528c02021-08-06 16:22:06 +0100230 """
231 self.log.info("Parsing source code...")
Yuto Takanod24e0372021-08-06 16:42:33 +0100232 self.log.debug(
Yuto Takano50953432021-08-09 14:54:36 +0100233 "The following files are excluded from the search: {}"
Yuto Takanod24e0372021-08-06 16:42:33 +0100234 .format(str(self.excluded_files))
235 )
Yuto Takano81528c02021-08-06 16:22:06 +0100236
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800237 all_macros = {"public": [], "internal": [], "private":[]}
Janos Follath99387192022-08-10 11:11:34 +0100238 all_macros["public"] = self.parse_macros([
Yuto Takano8e9a2192021-08-09 14:48:53 +0100239 "include/mbedtls/*.h",
240 "include/psa/*.h",
Ronald Cron36c3ae92024-06-10 16:05:00 +0200241 "tf-psa-crypto/include/psa/*.h",
Ronald Cron51f228c2024-11-06 14:32:52 +0100242 "tf-psa-crypto/include/tf-psa-crypto/*.h",
Ronald Cron71609eb2024-06-19 16:18:01 +0200243 "tf-psa-crypto/drivers/builtin/include/mbedtls/*.h",
Ronald Cron0070d052024-07-02 08:54:06 +0200244 "tf-psa-crypto/drivers/everest/include/everest/everest.h",
245 "tf-psa-crypto/drivers/everest/include/everest/x25519.h"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100246 ])
Janos Follath99387192022-08-10 11:11:34 +0100247 all_macros["internal"] = self.parse_macros([
248 "library/*.h",
Ronald Cron6921d542024-07-02 08:33:05 +0200249 "tf-psa-crypto/core/*.h",
250 "tf-psa-crypto/drivers/builtin/src/*.h",
David Horstmann5b93d972024-10-31 15:36:05 +0000251 "framework/tests/include/test/drivers/*.h",
Janos Follath99387192022-08-10 11:11:34 +0100252 ])
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800253 all_macros["private"] = self.parse_macros([
254 "library/*.c",
Ronald Cron6921d542024-07-02 08:33:05 +0200255 "tf-psa-crypto/core/*.c",
256 "tf-psa-crypto/drivers/builtin/src/*.c",
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800257 ])
Yuto Takano8e9a2192021-08-09 14:48:53 +0100258 enum_consts = self.parse_enum_consts([
259 "include/mbedtls/*.h",
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800260 "include/psa/*.h",
Ronald Cron36c3ae92024-06-10 16:05:00 +0200261 "tf-psa-crypto/include/psa/*.h",
Ronald Cron51f228c2024-11-06 14:32:52 +0100262 "tf-psa-crypto/include/tf-psa-crypto/*.h",
Ronald Cron71609eb2024-06-19 16:18:01 +0200263 "tf-psa-crypto/drivers/builtin/include/mbedtls/*.h",
Yuto Takano8e9a2192021-08-09 14:48:53 +0100264 "library/*.h",
Ronald Cron6921d542024-07-02 08:33:05 +0200265 "tf-psa-crypto/core/*.h",
266 "tf-psa-crypto/drivers/builtin/src/*.h",
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800267 "library/*.c",
Ronald Cron6921d542024-07-02 08:33:05 +0200268 "tf-psa-crypto/core/*.c",
269 "tf-psa-crypto/drivers/builtin/src/*.c",
Ronald Cron0070d052024-07-02 08:54:06 +0200270 "tf-psa-crypto/drivers/everest/include/everest/everest.h",
271 "tf-psa-crypto/drivers/everest/include/everest/x25519.h"
Yuto Takano8e9a2192021-08-09 14:48:53 +0100272 ])
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000273 identifiers, excluded_identifiers = self.parse_identifiers([
Yuto Takano8e9a2192021-08-09 14:48:53 +0100274 "include/mbedtls/*.h",
275 "include/psa/*.h",
Ronald Cron36c3ae92024-06-10 16:05:00 +0200276 "tf-psa-crypto/include/psa/*.h",
Ronald Cron51f228c2024-11-06 14:32:52 +0100277 "tf-psa-crypto/include/tf-psa-crypto/*.h",
Ronald Cron71609eb2024-06-19 16:18:01 +0200278 "tf-psa-crypto/drivers/builtin/include/mbedtls/*.h",
Yuto Takano8e9a2192021-08-09 14:48:53 +0100279 "library/*.h",
Ronald Cron6921d542024-07-02 08:33:05 +0200280 "tf-psa-crypto/core/*.h",
281 "tf-psa-crypto/drivers/builtin/src/*.h",
Ronald Cron0070d052024-07-02 08:54:06 +0200282 "tf-psa-crypto/drivers/everest/include/everest/everest.h",
283 "tf-psa-crypto/drivers/everest/include/everest/x25519.h"
284 ], ["tf-psa-crypto/drivers/p256-m/p256-m/p256-m.h"])
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800285 mbed_psa_words = self.parse_mbed_psa_words([
Yuto Takano8e9a2192021-08-09 14:48:53 +0100286 "include/mbedtls/*.h",
287 "include/psa/*.h",
Ronald Cron36c3ae92024-06-10 16:05:00 +0200288 "tf-psa-crypto/include/psa/*.h",
Ronald Cron51f228c2024-11-06 14:32:52 +0100289 "tf-psa-crypto/include/tf-psa-crypto/*.h",
Ronald Cron71609eb2024-06-19 16:18:01 +0200290 "tf-psa-crypto/drivers/builtin/include/mbedtls/*.h",
Yuto Takano8e9a2192021-08-09 14:48:53 +0100291 "library/*.h",
Ronald Cron6921d542024-07-02 08:33:05 +0200292 "tf-psa-crypto/core/*.h",
293 "tf-psa-crypto/drivers/builtin/src/*.h",
Ronald Cron0070d052024-07-02 08:54:06 +0200294 "tf-psa-crypto/drivers/everest/include/everest/everest.h",
295 "tf-psa-crypto/drivers/everest/include/everest/x25519.h",
Yuto Takano8e9a2192021-08-09 14:48:53 +0100296 "library/*.c",
Ronald Cron6921d542024-07-02 08:33:05 +0200297 "tf-psa-crypto/core/*.c",
298 "tf-psa-crypto/drivers/builtin/src/*.c",
Ronald Cron0070d052024-07-02 08:54:06 +0200299 "tf-psa-crypto/drivers/everest/library/everest.c",
300 "tf-psa-crypto/drivers/everest/library/x25519.c"
Ronald Cron6921d542024-07-02 08:33:05 +0200301 ], ["tf-psa-crypto/core/psa_crypto_driver_wrappers.h"])
Yuto Takano81528c02021-08-06 16:22:06 +0100302 symbols = self.parse_symbols()
303
304 # Remove identifier macros like mbedtls_printf or mbedtls_calloc
305 identifiers_justname = [x.name for x in identifiers]
Janos Follath99387192022-08-10 11:11:34 +0100306 actual_macros = {"public": [], "internal": []}
307 for scope in actual_macros:
308 for macro in all_macros[scope]:
309 if macro.name not in identifiers_justname:
310 actual_macros[scope].append(macro)
Yuto Takano81528c02021-08-06 16:22:06 +0100311
312 self.log.debug("Found:")
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100313 # Aligns the counts on the assumption that none exceeds 4 digits
Janos Follath99387192022-08-10 11:11:34 +0100314 for scope in actual_macros:
315 self.log.debug(" {:4} Total {} Macros"
Janos Follath8d59c862022-08-10 15:35:35 +0100316 .format(len(all_macros[scope]), scope))
Janos Follath99387192022-08-10 11:11:34 +0100317 self.log.debug(" {:4} {} Non-identifier Macros"
Janos Follath8d59c862022-08-10 15:35:35 +0100318 .format(len(actual_macros[scope]), scope))
Yuto Takano9d9c6dc2021-08-16 10:43:45 +0100319 self.log.debug(" {:4} Enum Constants".format(len(enum_consts)))
320 self.log.debug(" {:4} Identifiers".format(len(identifiers)))
321 self.log.debug(" {:4} Exported Symbols".format(len(symbols)))
Yuto Takano55c6c872021-08-09 15:35:19 +0100322 return {
Janos Follath99387192022-08-10 11:11:34 +0100323 "public_macros": actual_macros["public"],
324 "internal_macros": actual_macros["internal"],
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800325 "private_macros": all_macros["private"],
Yuto Takano81528c02021-08-06 16:22:06 +0100326 "enum_consts": enum_consts,
327 "identifiers": identifiers,
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000328 "excluded_identifiers": excluded_identifiers,
Yuto Takano81528c02021-08-06 16:22:06 +0100329 "symbols": symbols,
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800330 "mbed_psa_words": mbed_psa_words
Yuto Takano81528c02021-08-06 16:22:06 +0100331 }
332
Gilles Peskine89458d12021-09-27 19:20:17 +0200333 def is_file_excluded(self, path, exclude_wildcards):
Gilles Peskine8a832242021-09-28 10:12:49 +0200334 """Whether the given file path is excluded."""
Gilles Peskine89458d12021-09-27 19:20:17 +0200335 # exclude_wildcards may be None. Also, consider the global exclusions.
336 exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
337 for pattern in exclude_wildcards:
338 if fnmatch.fnmatch(path, pattern):
339 return True
340 return False
341
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000342 def get_all_files(self, include_wildcards, exclude_wildcards):
Yuto Takano55c6c872021-08-09 15:35:19 +0100343 """
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000344 Get all files that match any of the included UNIX-style wildcards
345 and filter them into included and excluded lists.
346 While the check_names script is designed only for use on UNIX/macOS
347 (due to nm), this function alone will work fine on Windows even with
348 forward slashes in the wildcard.
349
350 Args:
351 * include_wildcards: a List of shell-style wildcards to match filepaths.
352 * exclude_wildcards: a List of shell-style wildcards to exclude.
353
354 Returns:
355 * inc_files: A List of relative filepaths for included files.
356 * exc_files: A List of relative filepaths for excluded files.
357 """
358 accumulator = set()
Aditya Deshpande0584df42023-01-16 16:36:31 +0000359 all_wildcards = include_wildcards + (exclude_wildcards or [])
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000360 for wildcard in all_wildcards:
361 accumulator = accumulator.union(glob.iglob(wildcard))
362
363 inc_files = []
364 exc_files = []
365 for path in accumulator:
366 if self.is_file_excluded(path, exclude_wildcards):
367 exc_files.append(path)
368 else:
369 inc_files.append(path)
370 return (inc_files, exc_files)
371
372 def get_included_files(self, include_wildcards, exclude_wildcards):
373 """
374 Get all files that match any of the included UNIX-style wildcards.
375 While the check_names script is designed only for use on UNIX/macOS
376 (due to nm), this function alone will work fine on Windows even with
377 forward slashes in the wildcard.
Yuto Takano55c6c872021-08-09 15:35:19 +0100378
379 Args:
380 * include_wildcards: a List of shell-style wildcards to match filepaths.
381 * exclude_wildcards: a List of shell-style wildcards to exclude.
382
383 Returns a List of relative filepaths.
384 """
385 accumulator = set()
386
Yuto Takano55c6c872021-08-09 15:35:19 +0100387 for include_wildcard in include_wildcards:
Gilles Peskine89458d12021-09-27 19:20:17 +0200388 accumulator = accumulator.union(glob.iglob(include_wildcard))
Yuto Takano55c6c872021-08-09 15:35:19 +0100389
Gilles Peskine89458d12021-09-27 19:20:17 +0200390 return list(path for path in accumulator
391 if not self.is_file_excluded(path, exclude_wildcards))
Yuto Takano55c6c872021-08-09 15:35:19 +0100392
Yuto Takano8e9a2192021-08-09 14:48:53 +0100393 def parse_macros(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100394 """
395 Parse all macros defined by #define preprocessor directives.
396
397 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100398 * include: A List of glob expressions to look for files through.
399 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100400
401 Returns a List of Match objects for the found macros.
Yuto Takano39639672021-08-05 19:47:48 +0100402 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100403 macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
404 exclusions = (
Yuto Takano39639672021-08-05 19:47:48 +0100405 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
406 )
407
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000408 files = self.get_included_files(include, exclude)
Yuto Takano50953432021-08-09 14:54:36 +0100409 self.log.debug("Looking for macros in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100410
Yuto Takano50953432021-08-09 14:54:36 +0100411 macros = []
412 for header_file in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100413 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100414 for line_no, line in enumerate(header):
Yuto Takanod93fa372021-08-06 23:05:55 +0100415 for macro in macro_regex.finditer(line):
Yuto Takanod70d4462021-08-09 12:45:51 +0100416 if macro.group("macro").startswith(exclusions):
417 continue
418
419 macros.append(Match(
420 header_file,
421 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100422 line_no,
423 macro.span("macro"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100424 macro.group("macro")))
Darryl Greend5802922018-05-08 15:30:59 +0100425
Yuto Takano39639672021-08-05 19:47:48 +0100426 return macros
Darryl Greend5802922018-05-08 15:30:59 +0100427
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800428 def parse_mbed_psa_words(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100429 """
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800430 Parse all words in the file that begin with MBED|PSA, in and out of
431 macros, comments, anything.
Yuto Takano39639672021-08-05 19:47:48 +0100432
433 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100434 * include: A List of glob expressions to look for files through.
435 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100436
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800437 Returns a List of Match objects for words beginning with MBED|PSA.
Yuto Takano39639672021-08-05 19:47:48 +0100438 """
Yuto Takanob47b5042021-08-07 00:42:54 +0100439 # Typos of TLS are common, hence the broader check below than MBEDTLS.
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800440 mbed_regex = re.compile(r"\b(MBED.+?|PSA)_[A-Z0-9_]*")
Yuto Takanod93fa372021-08-06 23:05:55 +0100441 exclusions = re.compile(r"// *no-check-names|#error")
442
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000443 files = self.get_included_files(include, exclude)
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800444 self.log.debug(
445 "Looking for MBED|PSA words in {} files"
446 .format(len(files))
447 )
Yuto Takanod93fa372021-08-06 23:05:55 +0100448
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800449 mbed_psa_words = []
Yuto Takano50953432021-08-09 14:54:36 +0100450 for filename in files:
Yuto Takanoa083d152021-08-07 00:25:59 +0100451 with open(filename, "r", encoding="utf-8") as fp:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100452 for line_no, line in enumerate(fp):
Yuto Takanod93fa372021-08-06 23:05:55 +0100453 if exclusions.search(line):
Yuto Takanoc62b4082021-08-05 20:17:07 +0100454 continue
Yuto Takano81528c02021-08-06 16:22:06 +0100455
Yuto Takanod93fa372021-08-06 23:05:55 +0100456 for name in mbed_regex.finditer(line):
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800457 mbed_psa_words.append(Match(
Yuto Takano39639672021-08-05 19:47:48 +0100458 filename,
459 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100460 line_no,
461 name.span(0),
462 name.group(0)))
Yuto Takano39639672021-08-05 19:47:48 +0100463
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800464 return mbed_psa_words
Yuto Takano39639672021-08-05 19:47:48 +0100465
Yuto Takano8e9a2192021-08-09 14:48:53 +0100466 def parse_enum_consts(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100467 """
468 Parse all enum value constants that are declared.
469
470 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100471 * include: A List of glob expressions to look for files through.
472 * exclude: A List of glob expressions for excluding files.
Yuto Takano39639672021-08-05 19:47:48 +0100473
Yuto Takano81528c02021-08-06 16:22:06 +0100474 Returns a List of Match objects for the findings.
Yuto Takano39639672021-08-05 19:47:48 +0100475 """
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000476 files = self.get_included_files(include, exclude)
Yuto Takano50953432021-08-09 14:54:36 +0100477 self.log.debug("Looking for enum consts in {} files".format(len(files)))
Yuto Takanod93fa372021-08-06 23:05:55 +0100478
Yuto Takanob1417b42021-08-17 10:30:20 +0100479 # Emulate a finite state machine to parse enum declarations.
480 # OUTSIDE_KEYWORD = outside the enum keyword
481 # IN_BRACES = inside enum opening braces
482 # IN_BETWEEN = between enum keyword and opening braces
483 states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
Yuto Takano50953432021-08-09 14:54:36 +0100484 enum_consts = []
485 for header_file in files:
Yuto Takanob1417b42021-08-17 10:30:20 +0100486 state = states.OUTSIDE_KEYWORD
Yuto Takanoa083d152021-08-07 00:25:59 +0100487 with open(header_file, "r", encoding="utf-8") as header:
Yuto Takano8f457cf2021-08-06 17:54:58 +0100488 for line_no, line in enumerate(header):
Yuto Takano13ecd992021-08-06 16:56:52 +0100489 # Match typedefs and brackets only when they are at the
490 # beginning of the line -- if they are indented, they might
491 # be sub-structures within structs, etc.
David Horstmannf91090e2022-12-16 13:39:04 +0000492 optional_c_identifier = r"([_a-zA-Z][_a-zA-Z0-9]*)?"
Yuto Takanob1417b42021-08-17 10:30:20 +0100493 if (state == states.OUTSIDE_KEYWORD and
David Horstmannf91090e2022-12-16 13:39:04 +0000494 re.search(r"^(typedef +)?enum " + \
495 optional_c_identifier + \
496 r" *{", line)):
Yuto Takanob1417b42021-08-17 10:30:20 +0100497 state = states.IN_BRACES
498 elif (state == states.OUTSIDE_KEYWORD and
499 re.search(r"^(typedef +)?enum", line)):
500 state = states.IN_BETWEEN
501 elif (state == states.IN_BETWEEN and
502 re.search(r"^{", line)):
503 state = states.IN_BRACES
504 elif (state == states.IN_BRACES and
505 re.search(r"^}", line)):
506 state = states.OUTSIDE_KEYWORD
507 elif (state == states.IN_BRACES and
508 not re.search(r"^ *#", line)):
Yuto Takano90bc0262021-08-16 11:34:10 +0100509 enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
Yuto Takanod70d4462021-08-09 12:45:51 +0100510 if not enum_const:
511 continue
512
513 enum_consts.append(Match(
514 header_file,
515 line,
Yuto Takano704b0f72021-08-17 10:41:23 +0100516 line_no,
517 enum_const.span("enum_const"),
Yuto Takanod70d4462021-08-09 12:45:51 +0100518 enum_const.group("enum_const")))
Yuto Takano81528c02021-08-06 16:22:06 +0100519
Yuto Takano39639672021-08-05 19:47:48 +0100520 return enum_consts
Darryl Greend5802922018-05-08 15:30:59 +0100521
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100522 IGNORED_CHUNK_REGEX = re.compile('|'.join([
523 r'/\*.*?\*/', # block comment entirely on one line
524 r'//.*', # line comment
525 r'(?P<string>")(?:[^\\\"]|\\.)*"', # string literal
526 ]))
527
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100528 def strip_comments_and_literals(self, line, in_block_comment):
529 """Strip comments and string literals from line.
530
531 Continuation lines are not supported.
532
533 If in_block_comment is true, assume that the line starts inside a
534 block comment.
535
536 Return updated values of (line, in_block_comment) where:
537 * Comments in line have been replaced by a space (or nothing at the
538 start or end of the line).
539 * String contents have been removed.
540 * in_block_comment indicates whether the line ends inside a block
541 comment that continues on the next line.
542 """
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100543
544 # Terminate current multiline comment?
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100545 if in_block_comment:
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100546 m = re.search(r"\*/", line)
547 if m:
548 in_block_comment = False
549 line = line[m.end(0):]
550 else:
551 return '', True
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100552
553 # Remove full comments and string literals.
554 # Do it all together to handle cases like "/*" correctly.
555 # Note that continuation lines are not supported.
556 line = re.sub(self.IGNORED_CHUNK_REGEX,
557 lambda s: '""' if s.group('string') else ' ',
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100558 line)
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100559
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100560 # Start an unfinished comment?
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100561 # (If `/*` was part of a complete comment, it's already been removed.)
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100562 m = re.search(r"/\*", line)
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100563 if m:
564 in_block_comment = True
Gilles Peskinef303c0d2021-11-17 20:45:39 +0100565 line = line[:m.start(0)]
Gilles Peskineb4b18c12021-11-17 20:43:35 +0100566
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100567 return line, in_block_comment
568
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100569 IDENTIFIER_REGEX = re.compile('|'.join([
Gilles Peskine152de232021-11-16 20:56:47 +0100570 # Match " something(a" or " *something(a". Functions.
571 # Assumptions:
572 # - function definition from return type to one of its arguments is
573 # all on one line
574 # - function definition line only contains alphanumeric, asterisk,
575 # underscore, and open bracket
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100576 r".* \**(\w+) *\( *\w",
Gilles Peskine152de232021-11-16 20:56:47 +0100577 # Match "(*something)(".
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100578 r".*\( *\* *(\w+) *\) *\(",
Gilles Peskine152de232021-11-16 20:56:47 +0100579 # Match names of named data structures.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100580 r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$",
Gilles Peskine152de232021-11-16 20:56:47 +0100581 # Match names of typedef instances, after closing bracket.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100582 r"}? *(\w+)[;[].*",
583 ]))
Gilles Peskine152de232021-11-16 20:56:47 +0100584 # The regex below is indented for clarity.
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100585 EXCLUSION_LINES = re.compile("|".join([
586 r"extern +\"C\"",
587 r"(typedef +)?(struct|union|enum)( *{)?$",
588 r"} *;?$",
589 r"$",
590 r"//",
591 r"#",
592 ]))
Gilles Peskine152de232021-11-16 20:56:47 +0100593
594 def parse_identifiers_in_file(self, header_file, identifiers):
595 """
596 Parse all lines of a header where a function/enum/struct/union/typedef
597 identifier is declared, based on some regex and heuristics. Highly
598 dependent on formatting style.
599
600 Append found matches to the list ``identifiers``.
601 """
602
603 with open(header_file, "r", encoding="utf-8") as header:
604 in_block_comment = False
605 # The previous line variable is used for concatenating lines
606 # when identifiers are formatted and spread across multiple
607 # lines.
608 previous_line = ""
609
610 for line_no, line in enumerate(header):
Gilles Peskineb9fc4882021-11-17 20:32:31 +0100611 line, in_block_comment = \
612 self.strip_comments_and_literals(line, in_block_comment)
Gilles Peskine152de232021-11-16 20:56:47 +0100613
Gilles Peskine9b2fa722021-11-17 20:23:18 +0100614 if self.EXCLUSION_LINES.match(line):
Gilles Peskine152de232021-11-16 20:56:47 +0100615 previous_line = ""
616 continue
617
618 # If the line contains only space-separated alphanumeric
Gilles Peskinebc1e8f62021-11-17 20:39:56 +0100619 # characters (or underscore, asterisk, or open parenthesis),
Gilles Peskine152de232021-11-16 20:56:47 +0100620 # and nothing else, high chance it's a declaration that
621 # continues on the next line
622 if re.search(r"^([\w\*\(]+\s+)+$", line):
623 previous_line += line
624 continue
625
626 # If previous line seemed to start an unfinished declaration
627 # (as above), concat and treat them as one.
628 if previous_line:
629 line = previous_line.strip() + " " + line.strip() + "\n"
630 previous_line = ""
631
632 # Skip parsing if line has a space in front = heuristic to
633 # skip function argument lines (highly subject to formatting
634 # changes)
635 if line[0] == " ":
636 continue
637
638 identifier = self.IDENTIFIER_REGEX.search(line)
639
640 if not identifier:
641 continue
642
643 # Find the group that matched, and append it
644 for group in identifier.groups():
645 if not group:
646 continue
647
648 identifiers.append(Match(
649 header_file,
650 line,
651 line_no,
652 identifier.span(),
653 group))
654
Yuto Takano8e9a2192021-08-09 14:48:53 +0100655 def parse_identifiers(self, include, exclude=None):
Yuto Takano39639672021-08-05 19:47:48 +0100656 """
Yuto Takano8246eb82021-08-16 10:37:24 +0100657 Parse all lines of a header where a function/enum/struct/union/typedef
Yuto Takanob1417b42021-08-17 10:30:20 +0100658 identifier is declared, based on some regex and heuristics. Highly
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000659 dependent on formatting style. Identifiers in excluded files are still
660 parsed
Darryl Greend5802922018-05-08 15:30:59 +0100661
Yuto Takano39639672021-08-05 19:47:48 +0100662 Args:
Yuto Takano8e9a2192021-08-09 14:48:53 +0100663 * include: A List of glob expressions to look for files through.
664 * exclude: A List of glob expressions for excluding files.
Yuto Takano81528c02021-08-06 16:22:06 +0100665
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000666 Returns: a Tuple of two Lists of Match objects with identifiers.
667 * included_identifiers: A List of Match objects with identifiers from
668 included files.
669 * excluded_identifiers: A List of Match objects with identifiers from
670 excluded files.
Yuto Takano39639672021-08-05 19:47:48 +0100671 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100672
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000673 included_files, excluded_files = \
674 self.get_all_files(include, exclude)
Yuto Takano50953432021-08-09 14:54:36 +0100675
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000676 self.log.debug("Looking for included identifiers in {} files".format \
677 (len(included_files)))
Yuto Takano39639672021-08-05 19:47:48 +0100678
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000679 included_identifiers = []
680 excluded_identifiers = []
681 for header_file in included_files:
682 self.parse_identifiers_in_file(header_file, included_identifiers)
683 for header_file in excluded_files:
684 self.parse_identifiers_in_file(header_file, excluded_identifiers)
685
686 return (included_identifiers, excluded_identifiers)
Yuto Takano39639672021-08-05 19:47:48 +0100687
688 def parse_symbols(self):
689 """
690 Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
691 object files using nm to retrieve the list of referenced symbols.
Yuto Takano81528c02021-08-06 16:22:06 +0100692 Exceptions thrown here are rethrown because they would be critical
693 errors that void several tests, and thus needs to halt the program. This
694 is explicitly done for clarity.
Yuto Takano39639672021-08-05 19:47:48 +0100695
Yuto Takano81528c02021-08-06 16:22:06 +0100696 Returns a List of unique symbols defined and used in the libraries.
697 """
698 self.log.info("Compiling...")
Yuto Takano39639672021-08-05 19:47:48 +0100699 symbols = []
700
Tom Cosgrove1797b052022-12-04 17:19:59 +0000701 # Back up the config and atomically compile with the full configuration.
Yuto Takanod70d4462021-08-09 12:45:51 +0100702 shutil.copy(
703 "include/mbedtls/mbedtls_config.h",
704 "include/mbedtls/mbedtls_config.h.bak"
705 )
Darryl Greend5802922018-05-08 15:30:59 +0100706 try:
Yuto Takano81528c02021-08-06 16:22:06 +0100707 # Use check=True in all subprocess calls so that failures are raised
708 # as exceptions and logged.
Yuto Takano39639672021-08-05 19:47:48 +0100709 subprocess.run(
Yuto Takano81528c02021-08-06 16:22:06 +0100710 ["python3", "scripts/config.py", "full"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100711 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100712 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100713 )
714 my_environment = os.environ.copy()
715 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100716 # Run make clean separately to lib to prevent unwanted behavior when
717 # make is invoked with parallelism.
Yuto Takano39639672021-08-05 19:47:48 +0100718 subprocess.run(
Yuto Takano4b7d23d2021-08-17 10:48:22 +0100719 ["make", "clean"],
720 universal_newlines=True,
721 check=True
722 )
723 subprocess.run(
724 ["make", "lib"],
Darryl Greend5802922018-05-08 15:30:59 +0100725 env=my_environment,
Yuto Takanobcc3d992021-08-06 23:14:58 +0100726 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100727 stdout=subprocess.PIPE,
Darryl Greend5802922018-05-08 15:30:59 +0100728 stderr=subprocess.STDOUT,
Yuto Takano39639672021-08-05 19:47:48 +0100729 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100730 )
Yuto Takano39639672021-08-05 19:47:48 +0100731
732 # Perform object file analysis using nm
Yuto Takanod70d4462021-08-09 12:45:51 +0100733 symbols = self.parse_symbols_from_nm([
734 "library/libmbedcrypto.a",
735 "library/libmbedtls.a",
736 "library/libmbedx509.a"
737 ])
Yuto Takano39639672021-08-05 19:47:48 +0100738
739 subprocess.run(
Darryl Greend5802922018-05-08 15:30:59 +0100740 ["make", "clean"],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100741 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100742 check=True
Darryl Greend5802922018-05-08 15:30:59 +0100743 )
744 except subprocess.CalledProcessError as error:
Yuto Takano25eeb7b2021-08-06 21:27:59 +0100745 self.log.debug(error.output)
Yuto Takano81528c02021-08-06 16:22:06 +0100746 raise error
Yuto Takano39639672021-08-05 19:47:48 +0100747 finally:
Yuto Takano6fececf2021-08-07 17:28:23 +0100748 # Put back the original config regardless of there being errors.
749 # Works also for keyboard interrupts.
Yuto Takanod70d4462021-08-09 12:45:51 +0100750 shutil.move(
751 "include/mbedtls/mbedtls_config.h.bak",
752 "include/mbedtls/mbedtls_config.h"
753 )
Yuto Takano39639672021-08-05 19:47:48 +0100754
755 return symbols
756
757 def parse_symbols_from_nm(self, object_files):
758 """
759 Run nm to retrieve the list of referenced symbols in each object file.
760 Does not return the position data since it is of no use.
761
Yuto Takano81528c02021-08-06 16:22:06 +0100762 Args:
Yuto Takano55c6c872021-08-09 15:35:19 +0100763 * object_files: a List of compiled object filepaths to search through.
Yuto Takano81528c02021-08-06 16:22:06 +0100764
765 Returns a List of unique symbols defined and used in any of the object
766 files.
Yuto Takano39639672021-08-05 19:47:48 +0100767 """
Yuto Takanod93fa372021-08-06 23:05:55 +0100768 nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
769 nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100770 exclusions = ("FStar", "Hacl")
Yuto Takano39639672021-08-05 19:47:48 +0100771
772 symbols = []
773
Yuto Takano81528c02021-08-06 16:22:06 +0100774 # Gather all outputs of nm
Yuto Takano39639672021-08-05 19:47:48 +0100775 nm_output = ""
776 for lib in object_files:
777 nm_output += subprocess.run(
778 ["nm", "-og", lib],
Yuto Takanobcc3d992021-08-06 23:14:58 +0100779 universal_newlines=True,
Yuto Takano39639672021-08-05 19:47:48 +0100780 stdout=subprocess.PIPE,
781 stderr=subprocess.STDOUT,
782 check=True
783 ).stdout
Yuto Takano81528c02021-08-06 16:22:06 +0100784
Yuto Takano39639672021-08-05 19:47:48 +0100785 for line in nm_output.splitlines():
Yuto Takano90bc0262021-08-16 11:34:10 +0100786 if not nm_undefined_regex.search(line):
787 symbol = nm_valid_regex.search(line)
Yuto Takano12a7ecd2021-08-07 00:40:29 +0100788 if (symbol and not symbol.group("symbol").startswith(exclusions)):
Yuto Takanoe77f6992021-08-05 20:22:59 +0100789 symbols.append(symbol.group("symbol"))
Yuto Takano39639672021-08-05 19:47:48 +0100790 else:
791 self.log.error(line)
Yuto Takano81528c02021-08-06 16:22:06 +0100792
Yuto Takano39639672021-08-05 19:47:48 +0100793 return symbols
794
Yuto Takano55c6c872021-08-09 15:35:19 +0100795class NameChecker():
796 """
797 Representation of the core name checking operation performed by this script.
798 """
799 def __init__(self, parse_result, log):
800 self.parse_result = parse_result
801 self.log = log
802
Yuto Takano55614b52021-08-07 01:00:18 +0100803 def perform_checks(self, quiet=False):
Yuto Takano39639672021-08-05 19:47:48 +0100804 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100805 A comprehensive checker that performs each check in order, and outputs
806 a final verdict.
Yuto Takano81528c02021-08-06 16:22:06 +0100807
808 Args:
Yuto Takano55614b52021-08-07 01:00:18 +0100809 * quiet: whether to hide detailed problem explanation.
Yuto Takano39639672021-08-05 19:47:48 +0100810 """
Yuto Takano81528c02021-08-06 16:22:06 +0100811 self.log.info("=============")
Yuto Takano5473be22021-08-17 10:14:01 +0100812 Problem.quiet = quiet
Yuto Takano39639672021-08-05 19:47:48 +0100813 problems = 0
Yuto Takano5473be22021-08-17 10:14:01 +0100814 problems += self.check_symbols_declared_in_header()
Yuto Takano39639672021-08-05 19:47:48 +0100815
Yuto Takanod70d4462021-08-09 12:45:51 +0100816 pattern_checks = [
Janos Follath99387192022-08-10 11:11:34 +0100817 ("public_macros", PUBLIC_MACRO_PATTERN),
818 ("internal_macros", INTERNAL_MACRO_PATTERN),
Yuto Takanod70d4462021-08-09 12:45:51 +0100819 ("enum_consts", CONSTANTS_PATTERN),
820 ("identifiers", IDENTIFIER_PATTERN)
821 ]
Yuto Takano39639672021-08-05 19:47:48 +0100822 for group, check_pattern in pattern_checks:
Yuto Takano5473be22021-08-17 10:14:01 +0100823 problems += self.check_match_pattern(group, check_pattern)
Yuto Takano39639672021-08-05 19:47:48 +0100824
Yuto Takano5473be22021-08-17 10:14:01 +0100825 problems += self.check_for_typos()
Yuto Takano39639672021-08-05 19:47:48 +0100826
827 self.log.info("=============")
828 if problems > 0:
829 self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
Yuto Takano55614b52021-08-07 01:00:18 +0100830 if quiet:
831 self.log.info("Remove --quiet to see explanations.")
Yuto Takanofc54dfb2021-08-07 17:18:28 +0100832 else:
833 self.log.info("Use --quiet for minimal output.")
Yuto Takano55c6c872021-08-09 15:35:19 +0100834 return 1
Yuto Takano39639672021-08-05 19:47:48 +0100835 else:
836 self.log.info("PASS")
Yuto Takano55c6c872021-08-09 15:35:19 +0100837 return 0
Darryl Greend5802922018-05-08 15:30:59 +0100838
Yuto Takano5473be22021-08-17 10:14:01 +0100839 def check_symbols_declared_in_header(self):
Yuto Takano39639672021-08-05 19:47:48 +0100840 """
841 Perform a check that all detected symbols in the library object files
842 are properly declared in headers.
Yuto Takano977e07f2021-08-09 11:56:15 +0100843 Assumes parse_names_in_source() was called before this.
Darryl Greend5802922018-05-08 15:30:59 +0100844
Yuto Takano81528c02021-08-06 16:22:06 +0100845 Returns the number of problems that need fixing.
Yuto Takano39639672021-08-05 19:47:48 +0100846 """
847 problems = []
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000848 all_identifiers = self.parse_result["identifiers"] + \
849 self.parse_result["excluded_identifiers"]
Yuto Takanod93fa372021-08-06 23:05:55 +0100850
Yuto Takano39639672021-08-05 19:47:48 +0100851 for symbol in self.parse_result["symbols"]:
852 found_symbol_declared = False
Aditya Deshpandedd8ac672023-01-16 14:57:21 +0000853 for identifier_match in all_identifiers:
Yuto Takano39639672021-08-05 19:47:48 +0100854 if symbol == identifier_match.name:
855 found_symbol_declared = True
856 break
Yuto Takano81528c02021-08-06 16:22:06 +0100857
Yuto Takano39639672021-08-05 19:47:48 +0100858 if not found_symbol_declared:
Yuto Takanod70d4462021-08-09 12:45:51 +0100859 problems.append(SymbolNotInHeader(symbol))
Yuto Takano39639672021-08-05 19:47:48 +0100860
Yuto Takano5473be22021-08-17 10:14:01 +0100861 self.output_check_result("All symbols in header", problems)
Yuto Takano39639672021-08-05 19:47:48 +0100862 return len(problems)
863
Yuto Takano5473be22021-08-17 10:14:01 +0100864 def check_match_pattern(self, group_to_check, check_pattern):
Yuto Takano81528c02021-08-06 16:22:06 +0100865 """
866 Perform a check that all items of a group conform to a regex pattern.
Yuto Takano977e07f2021-08-09 11:56:15 +0100867 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100868
869 Args:
Yuto Takano81528c02021-08-06 16:22:06 +0100870 * group_to_check: string key to index into self.parse_result.
871 * check_pattern: the regex to check against.
872
873 Returns the number of problems that need fixing.
874 """
Yuto Takano39639672021-08-05 19:47:48 +0100875 problems = []
Yuto Takanod93fa372021-08-06 23:05:55 +0100876
Yuto Takano39639672021-08-05 19:47:48 +0100877 for item_match in self.parse_result[group_to_check]:
Yuto Takano90bc0262021-08-16 11:34:10 +0100878 if not re.search(check_pattern, item_match.name):
Yuto Takano39639672021-08-05 19:47:48 +0100879 problems.append(PatternMismatch(check_pattern, item_match))
Yuto Takano90bc0262021-08-16 11:34:10 +0100880 # Double underscore should not be used for names
881 if re.search(r".*__.*", item_match.name):
Yuto Takano704b0f72021-08-17 10:41:23 +0100882 problems.append(
883 PatternMismatch("no double underscore allowed", item_match))
Yuto Takano81528c02021-08-06 16:22:06 +0100884
885 self.output_check_result(
886 "Naming patterns of {}".format(group_to_check),
Yuto Takano55614b52021-08-07 01:00:18 +0100887 problems)
Yuto Takano39639672021-08-05 19:47:48 +0100888 return len(problems)
Darryl Greend5802922018-05-08 15:30:59 +0100889
Yuto Takano5473be22021-08-17 10:14:01 +0100890 def check_for_typos(self):
Yuto Takano81528c02021-08-06 16:22:06 +0100891 """
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800892 Perform a check that all words in the source code beginning with MBED are
Yuto Takano81528c02021-08-06 16:22:06 +0100893 either defined as macros, or as enum constants.
Yuto Takano977e07f2021-08-09 11:56:15 +0100894 Assumes parse_names_in_source() was called before this.
Yuto Takano81528c02021-08-06 16:22:06 +0100895
Yuto Takano81528c02021-08-06 16:22:06 +0100896 Returns the number of problems that need fixing.
897 """
Yuto Takano39639672021-08-05 19:47:48 +0100898 problems = []
Yuto Takano39639672021-08-05 19:47:48 +0100899
Yuto Takanod70d4462021-08-09 12:45:51 +0100900 # Set comprehension, equivalent to a list comprehension wrapped by set()
Yuto Takanod93fa372021-08-06 23:05:55 +0100901 all_caps_names = {
902 match.name
903 for match
Janos Follath99387192022-08-10 11:11:34 +0100904 in self.parse_result["public_macros"] +
Janos Follath8d59c862022-08-10 15:35:35 +0100905 self.parse_result["internal_macros"] +
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800906 self.parse_result["private_macros"] +
Janos Follath8d59c862022-08-10 15:35:35 +0100907 self.parse_result["enum_consts"]
Janos Follath99387192022-08-10 11:11:34 +0100908 }
Ronald Cron7975fae2021-09-13 14:50:42 +0200909 typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$|"
Pengyu Lvf3f1f812022-11-08 16:56:51 +0800910 r"MBEDTLS_TEST_LIBTESTDRIVER*|"
Pengyu Lv3bb0e432022-11-24 17:29:05 +0800911 r"PSA_CRYPTO_DRIVER_TEST")
Yuto Takano39639672021-08-05 19:47:48 +0100912
Pengyu Lvcdac0d52022-11-08 15:55:00 +0800913 for name_match in self.parse_result["mbed_psa_words"]:
Yuto Takano81528c02021-08-06 16:22:06 +0100914 found = name_match.name in all_caps_names
915
916 # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
917 # PSA driver, they will not exist as macros. However, they
918 # should still be checked for typos using the equivalent
919 # BUILTINs that exist.
920 if "MBEDTLS_PSA_ACCEL_" in name_match.name:
921 found = name_match.name.replace(
922 "MBEDTLS_PSA_ACCEL_",
923 "MBEDTLS_PSA_BUILTIN_") in all_caps_names
924
Yuto Takanod93fa372021-08-06 23:05:55 +0100925 if not found and not typo_exclusion.search(name_match.name):
Yuto Takanod70d4462021-08-09 12:45:51 +0100926 problems.append(Typo(name_match))
Yuto Takano39639672021-08-05 19:47:48 +0100927
Yuto Takano5473be22021-08-17 10:14:01 +0100928 self.output_check_result("Likely typos", problems)
Yuto Takano81528c02021-08-06 16:22:06 +0100929 return len(problems)
930
Yuto Takano5473be22021-08-17 10:14:01 +0100931 def output_check_result(self, name, problems):
Yuto Takano81528c02021-08-06 16:22:06 +0100932 """
933 Write out the PASS/FAIL status of a performed check depending on whether
934 there were problems.
Yuto Takanod70d4462021-08-09 12:45:51 +0100935
936 Args:
Yuto Takanod70d4462021-08-09 12:45:51 +0100937 * name: the name of the test
938 * problems: a List of encountered Problems
Yuto Takano81528c02021-08-06 16:22:06 +0100939 """
Yuto Takano39639672021-08-05 19:47:48 +0100940 if problems:
Yuto Takano55614b52021-08-07 01:00:18 +0100941 self.log.info("{}: FAIL\n".format(name))
942 for problem in problems:
943 self.log.warning(str(problem))
Darryl Greend5802922018-05-08 15:30:59 +0100944 else:
Yuto Takano81528c02021-08-06 16:22:06 +0100945 self.log.info("{}: PASS".format(name))
Darryl Greend5802922018-05-08 15:30:59 +0100946
Yuto Takano39639672021-08-05 19:47:48 +0100947def main():
948 """
Yuto Takano55c6c872021-08-09 15:35:19 +0100949 Perform argument parsing, and create an instance of CodeParser and
950 NameChecker to begin the core operation.
Yuto Takano39639672021-08-05 19:47:48 +0100951 """
Yuto Takanof005c332021-08-09 13:56:36 +0100952 parser = argparse.ArgumentParser(
Yuto Takano39639672021-08-05 19:47:48 +0100953 formatter_class=argparse.RawDescriptionHelpFormatter,
954 description=(
955 "This script confirms that the naming of all symbols and identifiers "
956 "in Mbed TLS are consistent with the house style and are also "
957 "self-consistent.\n\n"
Thomas Daubney540324c2023-10-06 17:07:24 +0100958 "Expected to be run from the Mbed TLS root directory.")
Yuto Takanof005c332021-08-09 13:56:36 +0100959 )
960 parser.add_argument(
961 "-v", "--verbose",
962 action="store_true",
963 help="show parse results"
964 )
965 parser.add_argument(
966 "-q", "--quiet",
967 action="store_true",
Tom Cosgrove1797b052022-12-04 17:19:59 +0000968 help="hide unnecessary text, explanations, and highlights"
Yuto Takanof005c332021-08-09 13:56:36 +0100969 )
Darryl Greend5802922018-05-08 15:30:59 +0100970
Yuto Takanof005c332021-08-09 13:56:36 +0100971 args = parser.parse_args()
Darryl Greend5802922018-05-08 15:30:59 +0100972
Yuto Takano55c6c872021-08-09 15:35:19 +0100973 # Configure the global logger, which is then passed to the classes below
974 log = logging.getLogger()
975 log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
976 log.addHandler(logging.StreamHandler())
977
Darryl Greend5802922018-05-08 15:30:59 +0100978 try:
Yuto Takano55c6c872021-08-09 15:35:19 +0100979 code_parser = CodeParser(log)
980 parse_result = code_parser.comprehensive_parse()
Yuto Takanod93fa372021-08-06 23:05:55 +0100981 except Exception: # pylint: disable=broad-except
Darryl Greend5802922018-05-08 15:30:59 +0100982 traceback.print_exc()
983 sys.exit(2)
984
Yuto Takano55c6c872021-08-09 15:35:19 +0100985 name_checker = NameChecker(parse_result, log)
986 return_code = name_checker.perform_checks(quiet=args.quiet)
987
988 sys.exit(return_code)
989
Darryl Greend5802922018-05-08 15:30:59 +0100990if __name__ == "__main__":
Yuto Takano39639672021-08-05 19:47:48 +0100991 main()