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