blob: 9b626fe35eb4e695da6e8cd5fed2ddda013aee7d [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
47def preprocesse_c_source_code(source, *classes):
48 """
49 Simple preprocessor for C source code.
50
51 Only processs condition directives without expanding them.
52 Yield object accodring to the classes input. Most match firstly
53
54 If there are directive pair does not match, raise CondDirectiveNotMatch.
55
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
132 Currently, it generate tranlation function from enum value to string.
133 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()
284 for start, instance in preprocesse_c_source_code(source_code, EnumDefinition):
285 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)