1"""Generate and run C code. 2""" 3 4# Copyright The Mbed TLS Contributors 5# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 6# 7 8import os 9import platform 10import subprocess 11import sys 12import tempfile 13 14def remove_file_if_exists(filename): 15 """Remove the specified file, ignoring errors.""" 16 if not filename: 17 return 18 try: 19 os.remove(filename) 20 except OSError: 21 pass 22 23def create_c_file(file_label): 24 """Create a temporary C file. 25 26 * ``file_label``: a string that will be included in the file name. 27 28 Return ```(c_file, c_name, exe_name)``` where ``c_file`` is a Python 29 stream open for writing to the file, ``c_name`` is the name of the file 30 and ``exe_name`` is the name of the executable that will be produced 31 by compiling the file. 32 """ 33 c_fd, c_name = tempfile.mkstemp(prefix='tmp-{}-'.format(file_label), 34 suffix='.c') 35 exe_suffix = '.exe' if platform.system() == 'Windows' else '' 36 exe_name = c_name[:-2] + exe_suffix 37 remove_file_if_exists(exe_name) 38 c_file = os.fdopen(c_fd, 'w', encoding='ascii') 39 return c_file, c_name, exe_name 40 41def generate_c_printf_expressions(c_file, cast_to, printf_format, expressions): 42 """Generate C instructions to print the value of ``expressions``. 43 44 Write the code with ``c_file``'s ``write`` method. 45 46 Each expression is cast to the type ``cast_to`` and printed with the 47 printf format ``printf_format``. 48 """ 49 for expr in expressions: 50 c_file.write(' printf("{}\\n", ({}) {});\n' 51 .format(printf_format, cast_to, expr)) 52 53def generate_c_file(c_file, 54 caller, header, 55 main_generator): 56 """Generate a temporary C source file. 57 58 * ``c_file`` is an open stream on the C source file. 59 * ``caller``: an informational string written in a comment at the top 60 of the file. 61 * ``header``: extra code to insert before any function in the generated 62 C file. 63 * ``main_generator``: a function called with ``c_file`` as its sole argument 64 to generate the body of the ``main()`` function. 65 """ 66 c_file.write('/* Generated by {} */' 67 .format(caller)) 68 c_file.write(''' 69#include <stdio.h> 70''') 71 c_file.write(header) 72 c_file.write(''' 73int main(void) 74{ 75''') 76 main_generator(c_file) 77 c_file.write(''' return 0; 78} 79''') 80 81def compile_c_file(c_filename, exe_filename, include_dirs): 82 """Compile a C source file with the host compiler. 83 84 * ``c_filename``: the name of the source file to compile. 85 * ``exe_filename``: the name for the executable to be created. 86 * ``include_dirs``: a list of paths to include directories to be passed 87 with the -I switch. 88 """ 89 # Respect $HOSTCC if it is set 90 cc = os.getenv('HOSTCC', None) 91 if cc is None: 92 cc = os.getenv('CC', 'cc') 93 cmd = [cc] 94 95 proc = subprocess.Popen(cmd, 96 stdout=subprocess.DEVNULL, 97 stderr=subprocess.PIPE, 98 universal_newlines=True) 99 cc_is_msvc = 'Microsoft (R) C/C++' in proc.communicate()[1] 100 101 cmd += ['-I' + dir for dir in include_dirs] 102 if cc_is_msvc: 103 # MSVC has deprecated using -o to specify the output file, 104 # and produces an object file in the working directory by default. 105 obj_filename = exe_filename[:-4] + '.obj' 106 cmd += ['-Fe' + exe_filename, '-Fo' + obj_filename] 107 else: 108 cmd += ['-o' + exe_filename] 109 110 subprocess.check_call(cmd + [c_filename]) 111 112def get_c_expression_values( 113 cast_to, printf_format, 114 expressions, 115 caller=__name__, file_label='', 116 header='', include_path=None, 117 keep_c=False, 118): # pylint: disable=too-many-arguments, too-many-locals 119 """Generate and run a program to print out numerical values for expressions. 120 121 * ``cast_to``: a C type. 122 * ``printf_format``: a printf format suitable for the type ``cast_to``. 123 * ``header``: extra code to insert before any function in the generated 124 C file. 125 * ``expressions``: a list of C language expressions that have the type 126 ``cast_to``. 127 * ``include_path``: a list of directories containing header files. 128 * ``keep_c``: if true, keep the temporary C file (presumably for debugging 129 purposes). 130 131 Use the C compiler specified by the ``CC`` environment variable, defaulting 132 to ``cc``. If ``CC`` looks like MSVC, use its command line syntax, 133 otherwise assume the compiler supports Unix traditional ``-I`` and ``-o``. 134 135 Return the list of values of the ``expressions``. 136 """ 137 if include_path is None: 138 include_path = [] 139 c_name = None 140 exe_name = None 141 obj_name = None 142 try: 143 c_file, c_name, exe_name = create_c_file(file_label) 144 generate_c_file( 145 c_file, caller, header, 146 lambda c_file: generate_c_printf_expressions(c_file, 147 cast_to, printf_format, 148 expressions) 149 ) 150 c_file.close() 151 152 compile_c_file(c_name, exe_name, include_path) 153 if keep_c: 154 sys.stderr.write('List of {} tests kept at {}\n' 155 .format(caller, c_name)) 156 else: 157 os.remove(c_name) 158 output = subprocess.check_output([exe_name]) 159 return output.decode('ascii').strip().split('\n') 160 finally: 161 remove_file_if_exists(exe_name) 162 remove_file_if_exists(obj_name) 163