blob: d02deb35a4aced8d397c84e794d98ca8d79f2ebd [file] [log] [blame]
Gilles Peskine2adebc82020-12-11 00:30:53 +01001"""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
Bence Szépkúti24c29fe2021-05-10 23:56:29 +020048 obj_name = c_name[:-2] + '.obj'
Gilles Peskine2adebc82020-12-11 00:30:53 +010049 remove_file_if_exists(exe_name)
Bence Szépkúti24c29fe2021-05-10 23:56:29 +020050 remove_file_if_exists(obj_name)
Gilles Peskine2adebc82020-12-11 00:30:53 +010051 c_file = os.fdopen(c_fd, 'w', encoding='ascii')
Bence Szépkúti24c29fe2021-05-10 23:56:29 +020052 return c_file, c_name, exe_name, obj_name
Gilles Peskine2adebc82020-12-11 00:30:53 +010053
54def generate_c_printf_expressions(c_file, cast_to, printf_format, expressions):
55 """Generate C instructions to print the value of ``expressions``.
56
57 Write the code with ``c_file``'s ``write`` method.
58
59 Each expression is cast to the type ``cast_to`` and printed with the
60 printf format ``printf_format``.
61 """
62 for expr in expressions:
63 c_file.write(' printf("{}\\n", ({}) {});\n'
64 .format(printf_format, cast_to, expr))
65
66def generate_c_file(c_file,
67 caller, header,
68 main_generator):
69 """Generate a temporary C source file.
70
71 * ``c_file`` is an open stream on the C source file.
72 * ``caller``: an informational string written in a comment at the top
73 of the file.
74 * ``header``: extra code to insert before any function in the generated
75 C file.
76 * ``main_generator``: a function called with ``c_file`` as its sole argument
77 to generate the body of the ``main()`` function.
78 """
79 c_file.write('/* Generated by {} */'
80 .format(caller))
81 c_file.write('''
82#include <stdio.h>
83''')
84 c_file.write(header)
85 c_file.write('''
86int main(void)
87{
88''')
89 main_generator(c_file)
90 c_file.write(''' return 0;
91}
92''')
93
Bence Szépkútifa6bf1e2021-05-10 20:50:07 +020094_cc_is_msvc = None #pylint: disable=invalid-name
Gilles Peskine2adebc82020-12-11 00:30:53 +010095def get_c_expression_values(
96 cast_to, printf_format,
97 expressions,
98 caller=__name__, file_label='',
99 header='', include_path=None,
100 keep_c=False,
Bence Szépkúticabae3d2021-05-06 15:14:16 +0200101): # pylint: disable=too-many-arguments, too-many-locals
Gilles Peskine2adebc82020-12-11 00:30:53 +0100102 """Generate and run a program to print out numerical values for expressions.
103
104 * ``cast_to``: a C type.
105 * ``printf_format``: a printf format suitable for the type ``cast_to``.
106 * ``header``: extra code to insert before any function in the generated
107 C file.
108 * ``expressions``: a list of C language expressions that have the type
Gilles Peskine2991b5f2021-01-19 21:19:02 +0100109 ``cast_to``.
Gilles Peskine2adebc82020-12-11 00:30:53 +0100110 * ``include_path``: a list of directories containing header files.
111 * ``keep_c``: if true, keep the temporary C file (presumably for debugging
112 purposes).
113
Gilles Peskine9d1edb62021-04-23 22:07:25 +0200114 Use the C compiler specified by the ``CC`` environment variable, defaulting
115 to ``cc``. If ``CC`` looks like MSVC, use its command line syntax,
116 otherwise assume the compiler supports Unix traditional ``-I`` and ``-o``.
117
Bence Szépkúti0671dd32021-05-12 09:49:45 +0200118 NOTE: This function only checks the identity of the compiler referred to by
119 ``CC`` on its first invocation, and caches the result.
120
Gilles Peskine2adebc82020-12-11 00:30:53 +0100121 Return the list of values of the ``expressions``.
122 """
123 if include_path is None:
124 include_path = []
125 c_name = None
126 exe_name = None
127 try:
Bence Szépkúti24c29fe2021-05-10 23:56:29 +0200128 c_file, c_name, exe_name, obj_name = create_c_file(file_label)
Gilles Peskine2adebc82020-12-11 00:30:53 +0100129 generate_c_file(
130 c_file, caller, header,
131 lambda c_file: generate_c_printf_expressions(c_file,
132 cast_to, printf_format,
133 expressions)
134 )
135 c_file.close()
136 cc = os.getenv('CC', 'cc')
Gilles Peskine9d1edb62021-04-23 22:07:25 +0200137 cmd = [cc]
Bence Szépkútifa6bf1e2021-05-10 20:50:07 +0200138
139 global _cc_is_msvc #pylint: disable=global-statement,invalid-name
140 if _cc_is_msvc is None:
141 proc = subprocess.Popen(cmd,
142 stdout=subprocess.DEVNULL,
143 stderr=subprocess.PIPE,
144 universal_newlines=True)
145 _cc_is_msvc = 'Microsoft (R) C/C++ Optimizing Compiler' in \
146 proc.communicate()[1]
147
Gilles Peskine9d1edb62021-04-23 22:07:25 +0200148 cmd += ['-I' + dir for dir in include_path]
Bence Szépkúti24c29fe2021-05-10 23:56:29 +0200149 if _cc_is_msvc:
150 # MSVC has deprecated using -o to specify the output file,
151 # and produces an object file in the working directory by default.
152 cmd += ['-Fe' + exe_name, '-Fo' + obj_name]
153 else:
154 cmd += ['-o' + exe_name]
Gilles Peskine9d1edb62021-04-23 22:07:25 +0200155 subprocess.check_call(cmd + [c_name])
Gilles Peskine2adebc82020-12-11 00:30:53 +0100156 if keep_c:
157 sys.stderr.write('List of {} tests kept at {}\n'
158 .format(caller, c_name))
159 else:
160 os.remove(c_name)
Bence Szépkúti24c29fe2021-05-10 23:56:29 +0200161 if _cc_is_msvc:
162 os.remove(obj_name)
Gilles Peskine2adebc82020-12-11 00:30:53 +0100163 output = subprocess.check_output([exe_name])
164 return output.decode('ascii').strip().split('\n')
165 finally:
166 remove_file_if_exists(exe_name)