Darryl Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python2 |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 2 | """ |
| 3 | This file is part of Mbed TLS (https://tls.mbed.org) |
| 4 | |
| 5 | Copyright (c) 2018, Arm Limited, All Rights Reserved |
| 6 | |
| 7 | Purpose |
| 8 | |
| 9 | This script confirms that the naming of all symbols and identifiers in mbed |
| 10 | TLS are consistent with the house style and are also self-consistent. |
| 11 | """ |
| 12 | import os |
| 13 | import sys |
| 14 | import traceback |
| 15 | import re |
| 16 | import shutil |
| 17 | import subprocess |
| 18 | import logging |
| 19 | |
| 20 | |
| 21 | class 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 Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 149 | subprocess.check_output( |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 150 | ["perl", "scripts/config.pl", "full"], |
Darryl Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 151 | universal_newlines=True, |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 152 | ) |
| 153 | my_environment = os.environ.copy() |
| 154 | my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables" |
Darryl Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 155 | subprocess.check_output( |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 156 | ["make", "clean", "lib"], |
| 157 | env=my_environment, |
Darryl Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 158 | universal_newlines=True, |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 159 | stderr=subprocess.STDOUT, |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 160 | ) |
| 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 Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 167 | nm_output += subprocess.check_output( |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 168 | ["nm", "-og", lib], |
Darryl Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 169 | universal_newlines=True, |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 170 | stderr=subprocess.STDOUT, |
Darryl Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 171 | ) |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 172 | 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 Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 180 | subprocess.check_output( |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 181 | ["make", "clean"], |
Darryl Green | 6c79b5d | 2018-05-17 14:14:50 +0100 | [diff] [blame] | 182 | universal_newlines=True, |
Darryl Green | d580292 | 2018-05-08 15:30:59 +0100 | [diff] [blame] | 183 | ) |
| 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 | |
| 258 | def 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 | |
| 269 | if __name__ == "__main__": |
| 270 | run_main() |