blob: 8a8e2dbbe6988a1a156c3d5ad4e25473aeb089a2 [file] [log] [blame]
Darryl Green6c79b5d2018-05-17 14:14:50 +01001#!/usr/bin/env python2
Darryl Greend5802922018-05-08 15:30:59 +01002"""
3This file is part of Mbed TLS (https://tls.mbed.org)
4
5Copyright (c) 2018, Arm Limited, All Rights Reserved
6
7Purpose
8
9This script confirms that the naming of all symbols and identifiers in mbed
10TLS are consistent with the house style and are also self-consistent.
11"""
12import os
13import sys
14import traceback
15import re
16import shutil
17import subprocess
18import logging
19
20
21class NameCheck(object):
22 def __init__(self):
23 self.log = None
24 self.setup_logger()
25 self.check_repo_path()
26 self.return_code = 0
27 self.excluded_files = ["compat-1.3.h"]
28 self.header_files = self.get_files(os.path.join("include", "mbedtls"))
29 self.library_files = self.get_files("library")
30 self.macros = []
31 self.MBED_names = []
32 self.enum_consts = []
33 self.identifiers = []
34 self.actual_macros = []
35 self.symbols = []
36 self.macro_pattern = r"#define (?P<macro>\w+)"
37 self.MBED_pattern = r"\bMBED.+?_[A-Z0-9_]*"
38 self.symbol_pattern = r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)"
39 self.identifier_check_pattern = r"^mbedtls_[0-9a-z_]*[0-9a-z]$"
40 self.decls_pattern = (
41 r"^(extern \"C\"|(typedef )?(struct|enum)( {)?$|};?$|$)"
42 )
43 self.macro_const_check_pattern = (
44 r"^MBEDTLS_[0-9A-Z_]*[0-9A-Z]$|^YOTTA_[0-9A-Z_]*[0-9A-Z]$"
45 )
46 self.typo_check_pattern = r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$"
47 self.non_macros = (
48 "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
49 )
50
51 def set_return_code(self, return_code):
52 if return_code > self.return_code:
53 self.return_code = return_code
54
55 def setup_logger(self):
56 self.log = logging.getLogger()
57 self.log.setLevel(logging.INFO)
58 self.log.addHandler(logging.StreamHandler())
59
60 def check_repo_path(self):
61 current_dir = os.path.realpath('.')
62 root_dir = os.path.dirname(os.path.dirname(
63 os.path.dirname(os.path.realpath(__file__))))
64 if current_dir != root_dir:
65 raise Exception("Must be run from Mbed TLS root")
66
67 def get_files(self, directory):
68 filenames = []
69 for root, dirs, files in sorted(os.walk(directory)):
70 for filename in sorted(files):
71 if (filename not in self.excluded_files and
72 filename.endswith((".c", ".h"))):
73 filenames.append(os.path.join(root, filename))
74 return filenames
75
76 def get_macros(self):
77 for header_file in self.header_files:
78 with open(header_file, "r") as header:
79 for line in iter(header.readline, ""):
80 macro = re.search(self.macro_pattern, line)
81 if (macro and not
82 macro.group("macro").startswith(self.non_macros)):
83 self.macros.append((macro.group("macro"), header_file))
84 self.macros = list(set(self.macros))
85
86 def get_MBED_names(self):
87 for file_group in [self.header_files, self.library_files]:
88 for filename in file_group:
89 with open(filename, "r") as f:
90 for line in iter(f.readline, ""):
91 mbed_names = re.findall(self.MBED_pattern, line)
92 if mbed_names:
93 for name in mbed_names:
94 self.MBED_names.append((name, filename))
95 self.MBED_names = list(set(self.MBED_names))
96
97 def get_enum_consts(self):
98 for header_file in self.header_files:
99 state = 0
100 with open(header_file, "r") as header:
101 for line in iter(header.readline, ""):
102 if state is 0 and re.match(r"^(typedef )?enum {", line):
103 state = 1
104 elif state is 0 and re.match(r"^(typedef )?enum", line):
105 state = 2
106 elif state is 2 and re.match(r"^{", line):
107 state = 1
108 elif state is 1 and re.match(r"^}", line):
109 state = 0
110 elif state is 1:
111 enum_const = re.match(r"^\s*(?P<enum_const>\w+)", line)
112 if enum_const:
113 self.enum_consts.append(
114 (enum_const.group("enum_const"), header_file)
115 )
116
117 def line_contains_declaration(self, line):
118 return (re.match(r"^[^ /#{]", line)
119 and not re.match(self.decls_pattern, line))
120
121 def get_identifier_from_declaration(self, declaration):
122 identifier = re.search(
123 r"([a-zA-Z_][a-zA-Z0-9_]*)\(|"
124 r"\(\*(.+)\)\(|"
125 r"(\w+)\W*$",
126 declaration
127 )
128 if identifier:
129 for group in identifier.groups():
130 if group:
131 return group
132 self.log.error(declaration)
133 raise Exception("No identifier found")
134
135 def get_identifiers(self):
136 for header_file in self.header_files:
137 with open(header_file, "r") as header:
138 for line in iter(header.readline, ""):
139 if self.line_contains_declaration(line):
140 self.identifiers.append(
141 (self.get_identifier_from_declaration(line),
142 header_file)
143 )
144
145 def get_symbols(self):
146 try:
147 shutil.copy("include/mbedtls/config.h",
148 "include/mbedtls/config.h.bak")
Darryl Green6c79b5d2018-05-17 14:14:50 +0100149 subprocess.check_output(
Darryl Greend5802922018-05-08 15:30:59 +0100150 ["perl", "scripts/config.pl", "full"],
Darryl Green6c79b5d2018-05-17 14:14:50 +0100151 universal_newlines=True,
Darryl Greend5802922018-05-08 15:30:59 +0100152 )
153 my_environment = os.environ.copy()
154 my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
Darryl Green6c79b5d2018-05-17 14:14:50 +0100155 subprocess.check_output(
Darryl Greend5802922018-05-08 15:30:59 +0100156 ["make", "clean", "lib"],
157 env=my_environment,
Darryl Green6c79b5d2018-05-17 14:14:50 +0100158 universal_newlines=True,
Darryl Greend5802922018-05-08 15:30:59 +0100159 stderr=subprocess.STDOUT,
Darryl Greend5802922018-05-08 15:30:59 +0100160 )
161 shutil.move("include/mbedtls/config.h.bak",
162 "include/mbedtls/config.h")
163 nm_output = ""
164 for lib in ["library/libmbedcrypto.a",
165 "library/libmbedtls.a",
166 "library/libmbedx509.a"]:
Darryl Green6c79b5d2018-05-17 14:14:50 +0100167 nm_output += subprocess.check_output(
Darryl Greend5802922018-05-08 15:30:59 +0100168 ["nm", "-og", lib],
Darryl Green6c79b5d2018-05-17 14:14:50 +0100169 universal_newlines=True,
Darryl Greend5802922018-05-08 15:30:59 +0100170 stderr=subprocess.STDOUT,
Darryl Green6c79b5d2018-05-17 14:14:50 +0100171 )
Darryl Greend5802922018-05-08 15:30:59 +0100172 for line in nm_output.splitlines():
173 if not re.match(r"^\S+: +U |^$|^\S+:$", line):
174 symbol = re.match(self.symbol_pattern, line)
175 if symbol:
176 self.symbols.append(symbol.group('symbol'))
177 else:
178 self.log.error(line)
179 self.symbols.sort()
Darryl Green6c79b5d2018-05-17 14:14:50 +0100180 subprocess.check_output(
Darryl Greend5802922018-05-08 15:30:59 +0100181 ["make", "clean"],
Darryl Green6c79b5d2018-05-17 14:14:50 +0100182 universal_newlines=True,
Darryl Greend5802922018-05-08 15:30:59 +0100183 )
184 except subprocess.CalledProcessError as error:
185 self.log.error(error)
186 self.set_return_code(2)
187
188 def check_symbols_declared_in_header(self):
189 identifiers = [x[0] for x in self.identifiers]
190 bad_names = []
191 for symbol in self.symbols:
192 if symbol not in identifiers:
193 bad_names.append(symbol)
194 if bad_names:
195 self.set_return_code(1)
196 self.log.info("Names of identifiers: FAIL")
197 for name in bad_names:
198 self.log.info(name)
199 else:
200 self.log.info("Names of identifiers: PASS")
201
202 def check_group(self, group_to_check, check_pattern, name):
203 bad_names = []
204 for item in group_to_check:
205 if not re.match(check_pattern, item[0]):
206 bad_names.append("{} - {}".format(item[0], item[1]))
207 if bad_names:
208 self.set_return_code(1)
209 self.log.info("Names of {}: FAIL".format(name))
210 for name in bad_names:
211 self.log.info(name)
212 else:
213 self.log.info("Names of {}: PASS".format(name))
214
215 def check_for_typos(self):
216 bad_names = []
217 all_caps_names = list(set(
218 [x[0] for x in self.actual_macros + self.enum_consts]
219 ))
220 for name in self.MBED_names:
221 if name[0] not in all_caps_names:
222 if not re.search(self.typo_check_pattern, name[0]):
223 bad_names.append("{} - {}".format(name[0], name[1]))
224 if bad_names:
225 self.set_return_code(1)
226 self.log.info("Likely typos: FAIL")
227 for name in bad_names:
228 self.log.info(name)
229 else:
230 self.log.info("Likely typos: PASS")
231
232 def get_names_from_source_code(self):
233 self.log.info("Analysing source code...")
234 self.get_macros()
235 self.get_enum_consts()
236 self.get_identifiers()
237 self.get_symbols()
238 self.get_MBED_names()
239 self.actual_macros = list(set(self.macros) - set(self.identifiers))
240 self.log.info("{} macros".format(len(self.macros)))
241 self.log.info("{} enum-consts".format(len(self.enum_consts)))
242 self.log.info("{} identifiers".format(len(self.identifiers)))
243 self.log.info("{} exported-symbols".format(len(self.symbols)))
244
245 def check_names(self):
246 self.check_symbols_declared_in_header()
247 for group, check_pattern, name in [
248 (self.actual_macros, self.macro_const_check_pattern,
249 "actual-macros"),
250 (self.enum_consts, self.macro_const_check_pattern,
251 "enum-consts"),
252 (self.identifiers, self.identifier_check_pattern,
253 "identifiers")]:
254 self.check_group(group, check_pattern, name)
255 self.check_for_typos()
256
257
258def run_main():
259 try:
260 name_check = NameCheck()
261 name_check.get_names_from_source_code()
262 name_check.check_names()
263 sys.exit(name_check.return_code)
264 except Exception:
265 traceback.print_exc()
266 sys.exit(2)
267
268
269if __name__ == "__main__":
270 run_main()