blob: 154a94b35191d9d31eb07a71664d2052694ba14f [file] [log] [blame]
Gilles Peskine2adebc82020-12-11 00:30:53 +01001"""Generate and run C code.
2"""
3
4# Copyright The Mbed TLS Contributors
Dave Rodgman7ff79652023-11-03 12:04:52 +00005# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskine2adebc82020-12-11 00:30:53 +01006#
Gilles Peskine2adebc82020-12-11 00:30:53 +01007
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
David Horstmannddb09e42023-01-18 11:52:56 +000081def compile_c_file(c_filename, exe_filename, include_dirs):
David Horstmanne28f2ee2023-01-30 09:50:59 +000082 """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 """
David Horstmann0b1b97b2023-01-26 19:11:57 +000089 # Respect $HOSTCC if it is set
90 cc = os.getenv('HOSTCC', None)
91 if cc is None:
92 cc = os.getenv('CC', 'cc')
David Horstmannddb09e42023-01-18 11:52:56 +000093 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
Gilles Peskine2adebc82020-12-11 00:30:53 +0100112def 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
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
Gilles Peskine2991b5f2021-01-19 21:19:02 +0100126 ``cast_to``.
Gilles Peskine2adebc82020-12-11 00:30:53 +0100127 * ``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 Return the list of values of the ``expressions``.
132 """
133 if include_path is None:
134 include_path = []
135 c_name = None
136 exe_name = None
137 try:
138 c_file, c_name, exe_name = create_c_file(file_label)
139 generate_c_file(
140 c_file, caller, header,
141 lambda c_file: generate_c_printf_expressions(c_file,
142 cast_to, printf_format,
143 expressions)
144 )
145 c_file.close()
David Horstmannddb09e42023-01-18 11:52:56 +0000146
147 compile_c_file(c_name, exe_name, include_path)
Gilles Peskine2adebc82020-12-11 00:30:53 +0100148 if keep_c:
149 sys.stderr.write('List of {} tests kept at {}\n'
150 .format(caller, c_name))
151 else:
152 os.remove(c_name)
153 output = subprocess.check_output([exe_name])
154 return output.decode('ascii').strip().split('\n')
155 finally:
156 remove_file_if_exists(exe_name)