blob: 6af6f8d5455e41e31c89ba816d3a2341b00f573f [file] [log] [blame]
#!/usr/bin/env python3
"""
This file is part of Mbed TLS (https://tls.mbed.org)
Copyright (c) 2018, Arm Limited, All Rights Reserved
Purpose
This script confirms that the naming of all symbols and identifiers in mbed
TLS are consistent with the house style and are also self-consistent.
"""
import os
import sys
import traceback
import re
import shutil
import subprocess
import logging
class NameCheck(object):
def __init__(self):
self.log = None
self.setup_logger()
self.check_repo_path()
self.return_code = 0
self.excluded_files = ["compat-1.3.h"]
self.header_files = self.get_files(os.path.join("include", "mbedtls"))
self.library_files = self.get_files("library")
self.macros = []
self.MBED_names = []
self.enum_consts = []
self.identifiers = []
self.actual_macros = []
self.symbols = []
self.macro_pattern = r"#define (?P<macro>\w+)"
self.MBED_pattern = r"\bMBED.+?_[A-Z0-9_]*"
self.symbol_pattern = r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)"
self.identifier_check_pattern = r"^mbedtls_[0-9a-z_]*[0-9a-z]$"
self.decls_pattern = (
r"^(extern \"C\"|(typedef )?(struct|enum)( {)?$|};?$|$)"
)
self.macro_const_check_pattern = (
r"^MBEDTLS_[0-9A-Z_]*[0-9A-Z]$|^YOTTA_[0-9A-Z_]*[0-9A-Z]$"
)
self.typo_check_pattern = r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$"
self.non_macros = (
"asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
)
def set_return_code(self, return_code):
if return_code > self.return_code:
self.return_code = return_code
def setup_logger(self):
self.log = logging.getLogger()
self.log.setLevel(logging.INFO)
self.log.addHandler(logging.StreamHandler())
def check_repo_path(self):
current_dir = os.path.realpath('.')
root_dir = os.path.dirname(os.path.dirname(
os.path.dirname(os.path.realpath(__file__))))
if current_dir != root_dir:
raise Exception("Must be run from Mbed TLS root")
def get_files(self, directory):
filenames = []
for root, dirs, files in sorted(os.walk(directory)):
for filename in sorted(files):
if (filename not in self.excluded_files and
filename.endswith((".c", ".h"))):
filenames.append(os.path.join(root, filename))
return filenames
def get_macros(self):
for header_file in self.header_files:
with open(header_file, "r") as header:
for line in iter(header.readline, ""):
macro = re.search(self.macro_pattern, line)
if (macro and not
macro.group("macro").startswith(self.non_macros)):
self.macros.append((macro.group("macro"), header_file))
self.macros = list(set(self.macros))
def get_MBED_names(self):
for file_group in [self.header_files, self.library_files]:
for filename in file_group:
with open(filename, "r") as f:
for line in iter(f.readline, ""):
mbed_names = re.findall(self.MBED_pattern, line)
if mbed_names:
for name in mbed_names:
self.MBED_names.append((name, filename))
self.MBED_names = list(set(self.MBED_names))
def get_enum_consts(self):
for header_file in self.header_files:
state = 0
with open(header_file, "r") as header:
for line in iter(header.readline, ""):
if state is 0 and re.match(r"^(typedef )?enum {", line):
state = 1
elif state is 0 and re.match(r"^(typedef )?enum", line):
state = 2
elif state is 2 and re.match(r"^{", line):
state = 1
elif state is 1 and re.match(r"^}", line):
state = 0
elif state is 1:
enum_const = re.match(r"^\s*(?P<enum_const>\w+)", line)
if enum_const:
self.enum_consts.append(
(enum_const.group("enum_const"), header_file)
)
def line_contains_declaration(self, line):
return (re.match(r"^[^ /#{]", line)
and not re.match(self.decls_pattern, line))
def get_identifier_from_declaration(self, declaration):
identifier = re.search(
r"([a-zA-Z_][a-zA-Z0-9_]*)\(|"
r"\(\*(.+)\)\(|"
r"(\w+)\W*$",
declaration
)
if identifier:
for group in identifier.groups():
if group:
return group
self.log.error(declaration)
raise Exception("No identifier found")
def get_identifiers(self):
for header_file in self.header_files:
with open(header_file, "r") as header:
for line in iter(header.readline, ""):
if self.line_contains_declaration(line):
self.identifiers.append(
(self.get_identifier_from_declaration(line),
header_file)
)
def get_symbols(self):
try:
shutil.copy("include/mbedtls/config.h",
"include/mbedtls/config.h.bak")
subprocess.run(
["perl", "scripts/config.pl", "full"],
encoding=sys.stdout.encoding,
check=True
)
my_environment = os.environ.copy()
my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
subprocess.run(
["make", "clean", "lib"],
env=my_environment,
encoding=sys.stdout.encoding,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True
)
shutil.move("include/mbedtls/config.h.bak",
"include/mbedtls/config.h")
nm_output = ""
for lib in ["library/libmbedcrypto.a",
"library/libmbedtls.a",
"library/libmbedx509.a"]:
nm_output += subprocess.run(
["nm", "-og", lib],
encoding=sys.stdout.encoding,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True
).stdout
for line in nm_output.splitlines():
if not re.match(r"^\S+: +U |^$|^\S+:$", line):
symbol = re.match(self.symbol_pattern, line)
if symbol:
self.symbols.append(symbol.group('symbol'))
else:
self.log.error(line)
self.symbols.sort()
subprocess.run(
["make", "clean"],
encoding=sys.stdout.encoding,
check=True
)
except subprocess.CalledProcessError as error:
self.log.error(error)
self.set_return_code(2)
def check_symbols_declared_in_header(self):
identifiers = [x[0] for x in self.identifiers]
bad_names = []
for symbol in self.symbols:
if symbol not in identifiers:
bad_names.append(symbol)
if bad_names:
self.set_return_code(1)
self.log.info("Names of identifiers: FAIL")
for name in bad_names:
self.log.info(name)
else:
self.log.info("Names of identifiers: PASS")
def check_group(self, group_to_check, check_pattern, name):
bad_names = []
for item in group_to_check:
if not re.match(check_pattern, item[0]):
bad_names.append("{} - {}".format(item[0], item[1]))
if bad_names:
self.set_return_code(1)
self.log.info("Names of {}: FAIL".format(name))
for name in bad_names:
self.log.info(name)
else:
self.log.info("Names of {}: PASS".format(name))
def check_for_typos(self):
bad_names = []
all_caps_names = list(set(
[x[0] for x in self.actual_macros + self.enum_consts]
))
for name in self.MBED_names:
if name[0] not in all_caps_names:
if not re.search(self.typo_check_pattern, name[0]):
bad_names.append("{} - {}".format(name[0], name[1]))
if bad_names:
self.set_return_code(1)
self.log.info("Likely typos: FAIL")
for name in bad_names:
self.log.info(name)
else:
self.log.info("Likely typos: PASS")
def get_names_from_source_code(self):
self.log.info("Analysing source code...")
self.get_macros()
self.get_enum_consts()
self.get_identifiers()
self.get_symbols()
self.get_MBED_names()
self.actual_macros = list(set(self.macros) - set(self.identifiers))
self.log.info("{} macros".format(len(self.macros)))
self.log.info("{} enum-consts".format(len(self.enum_consts)))
self.log.info("{} identifiers".format(len(self.identifiers)))
self.log.info("{} exported-symbols".format(len(self.symbols)))
def check_names(self):
self.check_symbols_declared_in_header()
for group, check_pattern, name in [
(self.actual_macros, self.macro_const_check_pattern,
"actual-macros"),
(self.enum_consts, self.macro_const_check_pattern,
"enum-consts"),
(self.identifiers, self.identifier_check_pattern,
"identifiers")]:
self.check_group(group, check_pattern, name)
self.check_for_typos()
def run_main():
try:
name_check = NameCheck()
name_check.get_names_from_source_code()
name_check.check_names()
sys.exit(name_check.return_code)
except Exception:
traceback.print_exc()
sys.exit(2)
if __name__ == "__main__":
run_main()