Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport 2.28: Support larger integer test arguments #7503

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
366e685
Remove mbedtls_ prefix from bignum test cases
gilles-peskine-arm Apr 26, 2023
f549d9c
Stop supporting non-canonical case in mpi_write_string test data
gilles-peskine-arm Dec 4, 2022
404923e
Remove declarations of the nonstandard function strcasecmp
gilles-peskine-arm Dec 4, 2022
af2fc50
Remove stdint.h substitute for older MSVC
gilles-peskine-arm Dec 4, 2022
13e836a
printf testing: exercise integer parsing in the test framework
gilles-peskine-arm Dec 3, 2022
f122aed
Add line number to a few error messages
gilles-peskine-arm Dec 3, 2022
ab56a69
Factor get_function_info out of gen_from_test_data
gilles-peskine-arm Dec 4, 2022
1780875
Simplify string escapes
gilles-peskine-arm Dec 3, 2022
242b05b
Exercise string parsing in the test framework
gilles-peskine-arm Dec 3, 2022
a299043
Simplify parsing of integers in .datax files
gilles-peskine-arm Dec 3, 2022
9ad7bd3
Support different types in the parameter store
gilles-peskine-arm Dec 4, 2022
3a37f19
parse_function_arguments: make local_vars a list
gilles-peskine-arm Dec 4, 2022
f153c56
parse_function_arguments: extract per-argument function
gilles-peskine-arm Dec 4, 2022
46476e0
Support (void) as an argument list of a test function
gilles-peskine-arm Dec 4, 2022
615be63
parse_function_arguments: stricter type parsing
gilles-peskine-arm Dec 4, 2022
6494d92
Support larger integer test arguments: C part
gilles-peskine-arm Dec 4, 2022
ba72662
Allow more signed integer types in test function arguments
gilles-peskine-arm Dec 4, 2022
f599ddf
Remove string hack for mbedtls_mpi_mod_int testing
gilles-peskine-arm Dec 4, 2022
c627917
Test the line number returned by parse_test_data
gilles-peskine-arm Feb 23, 2023
c2fda4b
Fix typos in test descriptions
gilles-peskine-arm Feb 23, 2023
f089972
Explain the format argument expected by the test functions
gilles-peskine-arm Feb 23, 2023
5be4083
Fix wrong comment
gilles-peskine-arm Feb 23, 2023
97f5cff
Add test cases with a question mark
gilles-peskine-arm Apr 26, 2023
47903b1
typo
gilles-peskine-arm Apr 26, 2023
dca0501
Adjust code style for pointer types and casts
gilles-peskine-arm Apr 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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