#!/usr/bin/env python3 """ Script to check/fix header guards in C header files. Usage: scripts/c_header_guards.py [--fix] The script must be called from the TF-PSA-Crypto root folder. If called without any additional parameter the script checks current guards and in case of any mismatching with respect to the expected format it print the errors that it found and it returns an error code. If called with '--fix' input parameter, instead, it checks and modifies headers' guards if needed. The expected format for the headers' guards is as follows: 1. "__H" for public headers; 2. "TF_PSA_CRYPTO___H" for private ones; where: - 'TF_PSA_CRYPTO_' is an hardcoded prefix; - 'rel_path' is the path used in C files to include such header file. """ ## Copyright The Mbed TLS Contributors ## SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later import sys import os import re import argparse from typing import List, Tuple, Match import framework_scripts_path # pylint: disable=unused-import from mbedtls_framework import build_tree PREFIX = 'TF_PSA_CRYPTO_' # Dictionary containing the folders where header files are to be fixed/checked: # - key: path of header files to check; # - value: path that must be trimmed from the key value in order to get the # final guard. FOLDERS_TO_CHECK = { 'drivers/builtin/include/mbedtls/': 'drivers/builtin/include/', 'drivers/builtin/include/mbedtls/private/': 'drivers/builtin/include/', 'drivers/builtin/src/': 'drivers/builtin/src/', 'drivers/everest/include/tf-psa-crypto/private/everest/': 'drivers/everest/include/tf-psa-crypto/private/everest/', 'drivers/p256-m/': 'drivers/p256-m/', 'drivers/p256-m/p256-m/': 'drivers/p256-m/', 'include/mbedtls/': 'include/', 'include/psa/': 'include/', 'include/tf-psa-crypto/': 'include/', 'core/': 'core/', 'dispatch/': 'dispatch/', 'extras/': 'extras/', 'platform/': 'platform/', 'utilities/': 'utilities/', } def generate_guard_name(file_path: str, trim_path: str) -> str: """Generate the new header guard.""" adjusted_name = file_path # Trim prefix if adjusted_name.startswith(trim_path): adjusted_name = adjusted_name[len(trim_path):] # Remove suffix if adjusted_name.endswith('.h'): adjusted_name = adjusted_name[:-2] adjusted_name = re.sub(r'[^a-zA-Z0-9]', '_', adjusted_name).upper() guard_elements = [] # Add prefix only to non public header files if not file_path.startswith('include'): guard_elements.append('TF_PSA_CRYPTO') guard_elements.extend([adjusted_name, 'H']) return '_'.join(guard_elements) def find_line(lines: List[str], search_range, regex: str) -> Tuple[int, Match]: """Try to match the regex expression on the range of lines given in input. The first successful match is returned; an exception is returned if no match is found in all the given lines. Input params: lines: content of the file given as list of strings; search_range: range() to use when going through the list of lines; regex: the regex expression to be used for the match. Returns: a tuple where int: the first line where the match was successful; Match: the Match object returned on the matching line. """ for i in search_range: stripped = lines[i].strip() match = re.match(regex, stripped) if match is not None: return i, match raise ValueError(f'Error: {regex} line not found') def process_header(file_path, trim_path, fix) -> None: """Check/fix header guard in a single file. On failure an exception is thrown. """ with open(file_path, 'r', encoding='utf-8') as f: lines = f.readlines() correct_guard = generate_guard_name(file_path, trim_path) ifndef_line, match = find_line(lines, range(0, len(lines)), r'#ifndef\s+(\w+)\b') expected_line = f'#ifndef {correct_guard}' if expected_line not in lines[ifndef_line]: if fix: lines[ifndef_line] = expected_line else: raise ValueError(f'Invalid guard format:\n' f' Expected:{expected_line}' f' Found:{match.group(0)}') define_line, match = find_line(lines, range(ifndef_line, len(lines)), r'#define\s+(\w+)\b') expected_line = f'#define {correct_guard}' if expected_line not in lines[define_line]: if fix: lines[define_line] = expected_line else: raise ValueError(f'Invalid guard format:\n' f' Expected:{expected_line}' f' Found:{match.group(0)}') define_line, match = find_line(lines, range(len(lines) - 1, -1, -1), r'#endif\s*\/\*\s*([\w+.]+)\s*\*\/') expected_line = f'#endif /* {correct_guard} */' if expected_line not in lines[define_line]: if fix: lines[define_line] = expected_line else: raise ValueError(f'Invalid guard format:\n' f' Expected:{expected_line}' f' Found:{match.group(0)}') # Write back to file new_content = ''.join(lines) with open(file_path, 'w', encoding='utf-8') as f: f.write(new_content) def main(): current_path = os.getcwd() arg_parser = argparse.ArgumentParser() arg_parser.add_argument('--fix', action='store_true', help="Fix guards in header files. Default behavior is to check only.") args = arg_parser.parse_args() if not build_tree.looks_like_tf_psa_crypto_root(current_path): print("Error: this script must be launched from TF-PSA-Crypto root") sys.exit(1) had_failure = False for folder in FOLDERS_TO_CHECK: files = [filename for filename in os.listdir(folder) if re.match(r'.*\.h$', filename)] for file in files: full_file_path = os.path.join(folder, file) try: process_header(full_file_path, FOLDERS_TO_CHECK[folder], args.fix) except ValueError as e: print(f"Error processing {full_file_path}: {e}") had_failure = True if had_failure: sys.exit(1) if __name__ == "__main__": main()