blob: 711bfd39895de360103524403f3ae6210675f962 [file] [log] [blame]
Jerry Yue78ee992021-09-22 15:42:14 +08001#!/usr/bin/env python3
2
3"""Generate library/ssl_debug_helps_generated.c
4
5The code generated by this module includes debug helper functions that can not be
6implemented by fixed codes.
7
8"""
9
10# Copyright The Mbed TLS Contributors
11# SPDX-License-Identifier: Apache-2.0
12#
13# Licensed under the Apache License, Version 2.0 (the "License"); you may
14# not use this file except in compliance with the License.
15# You may obtain a copy of the License at
16#
17# http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
21# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22# See the License for the specific language governing permissions and
23# limitations under the License.
24import sys
25import re
26import os
27import textwrap
28from mbedtls_dev import build_tree
29
30def remove_c_comments(string):
31 """
32 Remove C style comments from input string
33 """
34 string_pattern = r"(?P<string>\".*?\"|\'.*?\')"
35 comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\r\n]*$)"
36 pattern = re.compile(string_pattern + r'|' + comment_pattern,
37 re.MULTILINE|re.DOTALL)
38 def replacer(match):
39 if match.lastgroup == 'comment':
40 return ""
41 return match.group()
42 return pattern.sub(replacer, string)
43
44class CondDirectiveNotMatch(Exception):
45 pass
46
Jerry Yue988f0f2021-11-11 13:22:20 +080047def preprocess_c_source_code(source, *classes):
Jerry Yue78ee992021-09-22 15:42:14 +080048 """
49 Simple preprocessor for C source code.
50
Jerry Yu6389b252021-12-02 10:28:40 +080051 Only processses condition directives without expanding them.
52 Yield object according to the classes input. Most match firstly
Jerry Yue78ee992021-09-22 15:42:14 +080053
Jerry Yu6389b252021-12-02 10:28:40 +080054 If the directive pair does not match , raise CondDirectiveNotMatch.
Jerry Yue78ee992021-09-22 15:42:14 +080055
56 Assume source code does not include comments and compile pass.
57
58 """
59
60 pattern = re.compile(r"^[ \t]*#[ \t]*" +
61 r"(?P<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" +
62 r"[ \t]*(?P<param>(.*\\\n)*.*$)",
63 re.MULTILINE)
64 stack = []
65
66 def _yield_objects(s, d, p, st, end):
67 """
68 Output matched source piece
69 """
70 nonlocal stack
71 start_line, end_line = '', ''
72 if stack:
73 start_line = '#{} {}'.format(d, p)
74 if d == 'if':
75 end_line = '#endif /* {} */'.format(p)
76 elif d == 'ifdef':
77 end_line = '#endif /* defined({}) */'.format(p)
78 else:
79 end_line = '#endif /* !defined({}) */'.format(p)
80 has_instance = False
81 for cls in classes:
82 for instance in cls.extract(s, st, end):
83 if has_instance is False:
84 has_instance = True
85 yield pair_start, start_line
86 yield instance.span()[0], instance
87 if has_instance:
88 yield start, end_line
89
90 for match in pattern.finditer(source):
91
92 directive = match.groupdict()['directive'].strip()
93 param = match.groupdict()['param']
94 start, end = match.span()
95
96 if directive in ('if', 'ifndef', 'ifdef'):
97 stack.append((directive, param, start, end))
98 continue
99
100 if not stack:
101 raise CondDirectiveNotMatch()
102
103 pair_directive, pair_param, pair_start, pair_end = stack.pop()
104 yield from _yield_objects(source,
105 pair_directive,
106 pair_param,
107 pair_end,
108 start)
109
110 if directive == 'endif':
111 continue
112
113 if pair_directive == 'if':
114 directive = 'if'
115 param = "!( {} )".format(pair_param)
116 elif pair_directive == 'ifdef':
117 directive = 'ifndef'
118 param = pair_param
119 else:
120 directive = 'ifdef'
121 param = pair_param
122
123 stack.append((directive, param, start, end))
124 assert not stack, len(stack)
125
126
127
128class EnumDefinition:
129 """
130 Generate helper functions around enumeration.
131
Jerry Yu6389b252021-12-02 10:28:40 +0800132 Currently, it generate translation function from enum value to string.
Jerry Yue78ee992021-09-22 15:42:14 +0800133 Enum definition looks like:
134 [typedef] enum [prefix name] { [body] } [suffix name];
135
136 Known limitation:
137 - the '}' and ';' SHOULD NOT exist in different macro blocks. Like
138 ```
139 enum test {
140 ....
141 #if defined(A)
142 ....
143 };
144 #else
145 ....
146 };
147 #endif
148 ```
149 """
150
151 @classmethod
152 def extract(cls, source_code, start=0, end=-1):
153 enum_pattern = re.compile(r'enum\s*(?P<prefix_name>\w*)\s*' +
154 r'{\s*(?P<body>[^}]*)}' +
155 r'\s*(?P<suffix_name>\w*)\s*;',
156 re.MULTILINE|re.DOTALL)
157
158 for match in enum_pattern.finditer(source_code, start, end):
159 yield EnumDefinition(source_code,
160 span=match.span(),
161 group=match.groupdict())
162
163 def __init__(self, source_code, span=None, group=None):
164 assert isinstance(group, dict)
165 prefix_name = group.get('prefix_name', None)
166 suffix_name = group.get('suffix_name', None)
167 body = group.get('body', None)
168 assert prefix_name or suffix_name
169 assert body
170 assert span
171 # If suffix_name exists, it is a typedef
172 self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name
173 self._name = suffix_name if suffix_name else prefix_name
174 self._body = body
175 self._source = source_code
176 self._span = span
177
178 def __repr__(self):
179 return 'Enum({},{})'.format(self._name, self._span)
180
181 def __str__(self):
182 return repr(self)
183
184 def span(self):
185 return self._span
186
187 def generate_tranlation_function(self):
188 """
189 Generate function for translating value to string
190 """
191 translation_table = []
192
193 for line in self._body.splitlines():
194
195 if line.strip().startswith('#'):
196 # Preprocess directive, keep it in table
197 translation_table.append(line.strip())
198 continue
199
200 if not line.strip():
201 continue
202
203 for field in line.strip().split(','):
204 if not field.strip():
205 continue
206 member = field.strip().split()[0]
207 translation_table.append(
208 '{space}[{member}] = "{member}",'.format(member=member,
209 space=' '*8)
210 )
211
212 body = textwrap.dedent('''\
213 const char *{name}_str( {prototype} in )
214 {{
215 const char * in_to_str[]=
216 {{
217 {translation_table}
218 }};
219
220 if( in > ( sizeof( in_to_str )/sizeof( in_to_str[0]) - 1 ) ||
221 in_to_str[ in ] == NULL )
222 {{
223 return "UNKOWN_VAULE";
224 }}
225 return in_to_str[ in ];
226 }}
227 ''')
228 body = body.format(translation_table='\n'.join(translation_table),
229 name=self._name,
230 prototype=self._prototype)
231 prototype = 'const char *{name}_str( {prototype} in );\n'
232 prototype = prototype.format(name=self._name,
233 prototype=self._prototype)
234 return body, prototype
235
236OUTPUT_C_TEMPLATE = '''\
237/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
238
239#include "common.h"
240
241#if defined(MBEDTLS_DEBUG_C)
242
243#include "ssl_debug_helpers_generated.h"
244
245{functions}
246
247#endif /* MBEDTLS_DEBUG_C */
248/* End of automatically generated file. */
249
250'''
251
252OUTPUT_H_TEMPLATE = '''\
253/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
254#ifndef MBEDTLS_SSL_DEBUG_HELPERS_H
255#define MBEDTLS_SSL_DEBUG_HELPERS_H
256
257#include "common.h"
258
259#if defined(MBEDTLS_DEBUG_C)
260
261#include "mbedtls/ssl.h"
262#include "ssl_misc.h"
263
264{functions}
265
266#endif /* MBEDTLS_DEBUG_C */
267
268#endif /* SSL_DEBUG_HELPERS_H */
269
270/* End of automatically generated file. */
271
272'''
273
274
275def generate_ssl_debug_helpers(target_dir):
276 """
277 Generate functions of debug helps
278 """
279 with open('include/mbedtls/ssl.h') as f:
280 source_code = remove_c_comments(f.read())
281
282 definitions = dict()
283 prototypes = dict()
Jerry Yue988f0f2021-11-11 13:22:20 +0800284 for start, instance in preprocess_c_source_code(source_code, EnumDefinition):
Jerry Yue78ee992021-09-22 15:42:14 +0800285 if start in definitions:
286 continue
287 if isinstance(instance, EnumDefinition):
288 definition, prototype = instance.generate_tranlation_function()
289 else:
290 definition = instance
291 prototype = instance
292 definitions[start] = definition
293 prototypes[start] = prototype
294
295 functions = [str(v) for _, v in sorted(definitions.items())]
296 with open(os.path.join(target_dir, 'ssl_debug_helpers_generated.c'), 'w') as f:
297 f.write(OUTPUT_C_TEMPLATE.format(functions='\n'.join(functions)))
298
299 functions = [str(v) for _, v in sorted(prototypes.items())]
300 with open(os.path.join(target_dir, 'ssl_debug_helpers_generated.h'), 'w') as f:
301 f.write(OUTPUT_H_TEMPLATE.format(functions='\n'.join(functions)))
302
303
304
305
306
307if __name__ == '__main__':
308 build_tree.chdir_to_root()
309 OUTPUT_FILE_DIR = sys.argv[1] if len(sys.argv) == 2 else "library"
310 generate_ssl_debug_helpers(OUTPUT_FILE_DIR)