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