blob: 9b626fe35eb4e695da6e8cd5fed2ddda013aee7d [file] [log] [blame]
#!/usr/bin/env python3
"""Generate library/ssl_debug_helps_generated.c
The code generated by this module includes debug helper functions that can not be
implemented by fixed codes.
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import re
import os
import textwrap
from mbedtls_dev import build_tree
def remove_c_comments(string):
"""
Remove C style comments from input string
"""
string_pattern = r"(?P<string>\".*?\"|\'.*?\')"
comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\r\n]*$)"
pattern = re.compile(string_pattern + r'|' + comment_pattern,
re.MULTILINE|re.DOTALL)
def replacer(match):
if match.lastgroup == 'comment':
return ""
return match.group()
return pattern.sub(replacer, string)
class CondDirectiveNotMatch(Exception):
pass
def preprocesse_c_source_code(source, *classes):
"""
Simple preprocessor for C source code.
Only processs condition directives without expanding them.
Yield object accodring to the classes input. Most match firstly
If there are directive pair does not match, raise CondDirectiveNotMatch.
Assume source code does not include comments and compile pass.
"""
pattern = re.compile(r"^[ \t]*#[ \t]*" +
r"(?P<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" +
r"[ \t]*(?P<param>(.*\\\n)*.*$)",
re.MULTILINE)
stack = []
def _yield_objects(s, d, p, st, end):
"""
Output matched source piece
"""
nonlocal stack
start_line, end_line = '', ''
if stack:
start_line = '#{} {}'.format(d, p)
if d == 'if':
end_line = '#endif /* {} */'.format(p)
elif d == 'ifdef':
end_line = '#endif /* defined({}) */'.format(p)
else:
end_line = '#endif /* !defined({}) */'.format(p)
has_instance = False
for cls in classes:
for instance in cls.extract(s, st, end):
if has_instance is False:
has_instance = True
yield pair_start, start_line
yield instance.span()[0], instance
if has_instance:
yield start, end_line
for match in pattern.finditer(source):
directive = match.groupdict()['directive'].strip()
param = match.groupdict()['param']
start, end = match.span()
if directive in ('if', 'ifndef', 'ifdef'):
stack.append((directive, param, start, end))
continue
if not stack:
raise CondDirectiveNotMatch()
pair_directive, pair_param, pair_start, pair_end = stack.pop()
yield from _yield_objects(source,
pair_directive,
pair_param,
pair_end,
start)
if directive == 'endif':
continue
if pair_directive == 'if':
directive = 'if'
param = "!( {} )".format(pair_param)
elif pair_directive == 'ifdef':
directive = 'ifndef'
param = pair_param
else:
directive = 'ifdef'
param = pair_param
stack.append((directive, param, start, end))
assert not stack, len(stack)
class EnumDefinition:
"""
Generate helper functions around enumeration.
Currently, it generate tranlation function from enum value to string.
Enum definition looks like:
[typedef] enum [prefix name] { [body] } [suffix name];
Known limitation:
- the '}' and ';' SHOULD NOT exist in different macro blocks. Like
```
enum test {
....
#if defined(A)
....
};
#else
....
};
#endif
```
"""
@classmethod
def extract(cls, source_code, start=0, end=-1):
enum_pattern = re.compile(r'enum\s*(?P<prefix_name>\w*)\s*' +
r'{\s*(?P<body>[^}]*)}' +
r'\s*(?P<suffix_name>\w*)\s*;',
re.MULTILINE|re.DOTALL)
for match in enum_pattern.finditer(source_code, start, end):
yield EnumDefinition(source_code,
span=match.span(),
group=match.groupdict())
def __init__(self, source_code, span=None, group=None):
assert isinstance(group, dict)
prefix_name = group.get('prefix_name', None)
suffix_name = group.get('suffix_name', None)
body = group.get('body', None)
assert prefix_name or suffix_name
assert body
assert span
# If suffix_name exists, it is a typedef
self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name
self._name = suffix_name if suffix_name else prefix_name
self._body = body
self._source = source_code
self._span = span
def __repr__(self):
return 'Enum({},{})'.format(self._name, self._span)
def __str__(self):
return repr(self)
def span(self):
return self._span
def generate_tranlation_function(self):
"""
Generate function for translating value to string
"""
translation_table = []
for line in self._body.splitlines():
if line.strip().startswith('#'):
# Preprocess directive, keep it in table
translation_table.append(line.strip())
continue
if not line.strip():
continue
for field in line.strip().split(','):
if not field.strip():
continue
member = field.strip().split()[0]
translation_table.append(
'{space}[{member}] = "{member}",'.format(member=member,
space=' '*8)
)
body = textwrap.dedent('''\
const char *{name}_str( {prototype} in )
{{
const char * in_to_str[]=
{{
{translation_table}
}};
if( in > ( sizeof( in_to_str )/sizeof( in_to_str[0]) - 1 ) ||
in_to_str[ in ] == NULL )
{{
return "UNKOWN_VAULE";
}}
return in_to_str[ in ];
}}
''')
body = body.format(translation_table='\n'.join(translation_table),
name=self._name,
prototype=self._prototype)
prototype = 'const char *{name}_str( {prototype} in );\n'
prototype = prototype.format(name=self._name,
prototype=self._prototype)
return body, prototype
OUTPUT_C_TEMPLATE = '''\
/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
#include "common.h"
#if defined(MBEDTLS_DEBUG_C)
#include "ssl_debug_helpers_generated.h"
{functions}
#endif /* MBEDTLS_DEBUG_C */
/* End of automatically generated file. */
'''
OUTPUT_H_TEMPLATE = '''\
/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
#ifndef MBEDTLS_SSL_DEBUG_HELPERS_H
#define MBEDTLS_SSL_DEBUG_HELPERS_H
#include "common.h"
#if defined(MBEDTLS_DEBUG_C)
#include "mbedtls/ssl.h"
#include "ssl_misc.h"
{functions}
#endif /* MBEDTLS_DEBUG_C */
#endif /* SSL_DEBUG_HELPERS_H */
/* End of automatically generated file. */
'''
def generate_ssl_debug_helpers(target_dir):
"""
Generate functions of debug helps
"""
with open('include/mbedtls/ssl.h') as f:
source_code = remove_c_comments(f.read())
definitions = dict()
prototypes = dict()
for start, instance in preprocesse_c_source_code(source_code, EnumDefinition):
if start in definitions:
continue
if isinstance(instance, EnumDefinition):
definition, prototype = instance.generate_tranlation_function()
else:
definition = instance
prototype = instance
definitions[start] = definition
prototypes[start] = prototype
functions = [str(v) for _, v in sorted(definitions.items())]
with open(os.path.join(target_dir, 'ssl_debug_helpers_generated.c'), 'w') as f:
f.write(OUTPUT_C_TEMPLATE.format(functions='\n'.join(functions)))
functions = [str(v) for _, v in sorted(prototypes.items())]
with open(os.path.join(target_dir, 'ssl_debug_helpers_generated.h'), 'w') as f:
f.write(OUTPUT_H_TEMPLATE.format(functions='\n'.join(functions)))
if __name__ == '__main__':
build_tree.chdir_to_root()
OUTPUT_FILE_DIR = sys.argv[1] if len(sys.argv) == 2 else "library"
generate_ssl_debug_helpers(OUTPUT_FILE_DIR)