Skip to content

Commit

Permalink
Merge pull request #7503 from gilles-peskine-arm/test-argument-types-…
Browse files Browse the repository at this point in the history
…union-2.28

Backport 2.28: Support larger integer test arguments
  • Loading branch information
gilles-peskine-arm authored May 4, 2023
2 parents d7570a2 + dca0501 commit 5ead738
Show file tree
Hide file tree
Showing 15 changed files with 1,396 additions and 1,102 deletions.
43 changes: 43 additions & 0 deletions tests/include/test/arguments.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* \file arguments.h
*
* \brief Manipulation of test arguments.
*
* Much of the code is in host_test.function, to be migrated here later.
*/

/*
* 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.
*/

#ifndef TEST_ARGUMENTS_H
#define TEST_ARGUMENTS_H

#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif

#include <stdint.h>
#include <stdlib.h>

typedef union {
size_t len;
intmax_t sint;
} mbedtls_test_argument_t;

#endif /* TEST_ARGUMENTS_H */
6 changes: 3 additions & 3 deletions tests/scripts/generate_bignum_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def generate_function_tests(cls) -> Iterator[test_case.TestCase]:
class BignumCmp(BignumOperation):
"""Test cases for bignum value comparison."""
count = 0
test_function = "mbedtls_mpi_cmp_mpi"
test_function = "mpi_cmp_mpi"
test_name = "MPI compare"
input_cases = [
("-2", "-3"),
Expand All @@ -217,7 +217,7 @@ def result(self) -> str:
class BignumCmpAbs(BignumCmp):
"""Test cases for absolute bignum value comparison."""
count = 0
test_function = "mbedtls_mpi_cmp_abs"
test_function = "mpi_cmp_abs"
test_name = "MPI compare (abs)"

def __init__(self, val_a, val_b) -> None:
Expand All @@ -228,7 +228,7 @@ class BignumAdd(BignumOperation):
"""Test cases for bignum value addition."""
count = 0
symbol = "+"
test_function = "mbedtls_mpi_add_mpi"
test_function = "mpi_add_mpi"
test_name = "MPI add"
input_cases = combination_pairs(
[
Expand Down
162 changes: 116 additions & 46 deletions tests/scripts/generate_test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,28 @@
import argparse


# Types recognized as signed integer arguments in test functions.
SIGNED_INTEGER_TYPES = frozenset([
'char',
'short',
'short int',
'int',
'int8_t',
'int16_t',
'int32_t',
'int64_t',
'intmax_t',
'long',
'long int',
'long long int',
'mbedtls_mpi_sint',
'psa_status_t',
])
# Types recognized as string arguments in test functions.
STRING_TYPES = frozenset(['char*', 'const char*', 'char const*'])
# Types recognized as hex data arguments in test functions.
DATA_TYPES = frozenset(['data_t*', 'const data_t*', 'data_t const*'])

BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'

Expand All @@ -192,9 +214,6 @@
CONDITION_OPERATOR_REGEX,
CONDITION_VALUE_REGEX)
TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
INT_CHECK_REGEX = r'int\s+.*'
CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
EXIT_LABEL_REGEX = r'^exit:'

Expand Down Expand Up @@ -303,7 +322,7 @@ def gen_function_wrapper(name, local_vars, args_dispatch):
:param name: Test function name
:param local_vars: Local variables declaration code
:param args_dispatch: List of dispatch arguments.
Ex: ['(char *)params[0]', '*((int *)params[1])']
Ex: ['(char *) params[0]', '*((int *) params[1])']
:return: Test function wrapper.
"""
# Then create the wrapper
Expand Down Expand Up @@ -444,6 +463,49 @@ def parse_function_dependencies(line):
return dependencies


ARGUMENT_DECLARATION_REGEX = re.compile(r'(.+?) ?(?:\bconst\b)? ?(\w+)\Z', re.S)
def parse_function_argument(arg, arg_idx, args, local_vars, args_dispatch):
"""
Parses one test function's argument declaration.
:param arg: argument declaration.
:param arg_idx: current wrapper argument index.
:param args: accumulator of arguments' internal types.
:param local_vars: accumulator of internal variable declarations.
:param args_dispatch: accumulator of argument usage expressions.
:return: the number of new wrapper arguments,
or None if the argument declaration is invalid.
"""
# Normalize whitespace
arg = arg.strip()
arg = re.sub(r'\s*\*\s*', r'*', arg)
arg = re.sub(r'\s+', r' ', arg)
# Extract name and type
m = ARGUMENT_DECLARATION_REGEX.search(arg)
if not m:
# E.g. "int x[42]"
return None
typ, _ = m.groups()
if typ in SIGNED_INTEGER_TYPES:
args.append('int')
args_dispatch.append('((mbedtls_test_argument_t *) params[%d])->sint' % arg_idx)
return 1
if typ in STRING_TYPES:
args.append('char*')
args_dispatch.append('(char *) params[%d]' % arg_idx)
return 1
if typ in DATA_TYPES:
args.append('hex')
# create a structure
pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
len_initializer = '((mbedtls_test_argument_t *) params[%d])->len' % (arg_idx+1)
local_vars.append(' data_t data%d = {%s, %s};\n' %
(arg_idx, pointer_initializer, len_initializer))
args_dispatch.append('&data%d' % arg_idx)
return 2
return None

ARGUMENT_LIST_REGEX = re.compile(r'\((.*?)\)', re.S)
def parse_function_arguments(line):
"""
Parses test function signature for validation and generates
Expand All @@ -455,42 +517,27 @@ def parse_function_arguments(line):
:return: argument list, local variables for
wrapper function and argument dispatch code.
"""
args = []
local_vars = ''
args_dispatch = []
arg_idx = 0
# Remove characters before arguments
line = line[line.find('(') + 1:]
# Process arguments, ex: <type> arg1, <type> arg2 )
# This script assumes that the argument list is terminated by ')'
# i.e. the test functions will not have a function pointer
# argument.
for arg in line[:line.find(')')].split(','):
arg = arg.strip()
if arg == '':
continue
if re.search(INT_CHECK_REGEX, arg.strip()):
args.append('int')
args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
elif re.search(CHAR_CHECK_REGEX, arg.strip()):
args.append('char*')
args_dispatch.append('(char *) params[%d]' % arg_idx)
elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
args.append('hex')
# create a structure
pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
local_vars += """ data_t data%d = {%s, %s};
""" % (arg_idx, pointer_initializer, len_initializer)

args_dispatch.append('&data%d' % arg_idx)
arg_idx += 1
else:
m = ARGUMENT_LIST_REGEX.search(line)
arg_list = m.group(1).strip()
if arg_list in ['', 'void']:
return [], '', []
args = []
local_vars = []
args_dispatch = []
arg_idx = 0
for arg in arg_list.split(','):
indexes = parse_function_argument(arg, arg_idx,
args, local_vars, args_dispatch)
if indexes is None:
raise ValueError("Test function arguments can only be 'int', "
"'char *' or 'data_t'\n%s" % line)
arg_idx += 1
arg_idx += indexes

return args, local_vars, args_dispatch
return args, ''.join(local_vars), args_dispatch


def generate_function_code(name, code, local_vars, args_dispatch,
Expand Down Expand Up @@ -705,7 +752,7 @@ def parse_test_data(data_f):
execution.
:param data_f: file object of the data file.
:return: Generator that yields test name, function name,
:return: Generator that yields line number, test name, function name,
dependency list and function argument list.
"""
__state_read_name = 0
Expand Down Expand Up @@ -748,7 +795,7 @@ def parse_test_data(data_f):
parts = escaped_split(line, ':')
test_function = parts[0]
args = parts[1:]
yield name, test_function, dependencies, args
yield data_f.line_no, name, test_function, dependencies, args
dependencies = []
state = __state_read_name
if state == __state_read_args:
Expand Down Expand Up @@ -846,6 +893,14 @@ def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
return dep_check_code


INT_VAL_REGEX = re.compile(r'-?(\d+|0x[0-9a-f]+)$', re.I)
def val_is_int(val: str) -> bool:
"""Whether val is suitable as an 'int' parameter in the .datax file."""
if not INT_VAL_REGEX.match(val):
return False
# Limit the range to what is guaranteed to get through strtol()
return abs(int(val, 0)) <= 0x7fffffff

def write_parameters(out_data_f, test_args, func_args, unique_expressions):
"""
Writes test parameters to the intermediate data file, replacing
Expand All @@ -864,9 +919,9 @@ def write_parameters(out_data_f, test_args, func_args, unique_expressions):
typ = func_args[i]
val = test_args[i]

# check if val is a non literal int val (i.e. an expression)
if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
val, re.I):
# Pass small integer constants literally. This reduces the size of
# the C code. Register anything else as an expression.
if typ == 'int' and not val_is_int(val):
typ = 'exp'
if val not in unique_expressions:
unique_expressions.append(val)
Expand Down Expand Up @@ -909,6 +964,24 @@ def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
return dep_check_code, expression_code


def get_function_info(func_info, function_name, line_no):
"""Look up information about a test function by name.
Raise an informative expression if function_name is not found.
:param func_info: dictionary mapping function names to their information.
:param function_name: the function name as written in the .function and
.data files.
:param line_no: line number for error messages.
:return Function information (id, args).
"""
test_function_name = 'test_' + function_name
if test_function_name not in func_info:
raise GeneratorInputError("%d: Function %s not found!" %
(line_no, test_function_name))
return func_info[test_function_name]


def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
"""
This function reads test case name, dependencies and test vectors
Expand All @@ -931,7 +1004,7 @@ def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
unique_expressions = []
dep_check_code = ''
expression_code = ''
for test_name, function_name, test_dependencies, test_args in \
for line_no, test_name, function_name, test_dependencies, test_args in \
parse_test_data(data_f):
out_data_f.write(test_name + '\n')

Expand All @@ -940,18 +1013,15 @@ def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
unique_dependencies)

# Write test function name
test_function_name = 'test_' + function_name
if test_function_name not in func_info:
raise GeneratorInputError("Function %s not found!" %
test_function_name)
func_id, func_args = func_info[test_function_name]
func_id, func_args = \
get_function_info(func_info, function_name, line_no)
out_data_f.write(str(func_id))

# Write parameters
if len(test_args) != len(func_args):
raise GeneratorInputError("Invalid number of arguments in test "
raise GeneratorInputError("%d: Invalid number of arguments in test "
"%s. See function %s signature." %
(test_name, function_name))
(line_no, test_name, function_name))
expression_code += write_parameters(out_data_f, test_args, func_args,
unique_expressions)

Expand Down
Loading

0 comments on commit 5ead738

Please sign in to comment.