Skip to content

Commit 89a6a75

Browse files
author
D. Teuchert
committed
second commit
1 parent b7461e9 commit 89a6a75

19 files changed

+1661
-0
lines changed

CMakeLists.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
3+
project(AUTOMOTIVE-FUZZING-EXAMPLE)
4+
5+
add_library(AUTOMOTIVE-FUZZING-EXAMPLE
6+
modules/crypto_module/src/crypto_module_1.c
7+
modules/crypto_module/src/crypto_module_2.c
8+
modules/time_module/src/time_module_1.c
9+
modules/GPS_module/src/GPS_module_1.c
10+
modules/key_management_module/src/key_management_module_1.c
11+
)
12+
13+
target_include_directories(AUTOMOTIVE-FUZZING-EXAMPLE PRIVATE
14+
modules/crypto_module/src
15+
modules/time_module/src
16+
modules/key_management_module/src
17+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#!/usr/bin/env python3
2+
3+
import copy
4+
import json
5+
import openpyxl
6+
from openpyxl.styles import Color, PatternFill
7+
from openpyxl.styles.differential import DifferentialStyle
8+
from openpyxl.formatting.rule import Rule, FormulaRule
9+
import os
10+
import re
11+
import subprocess
12+
import sys
13+
14+
def eprint(*args, **kwargs):
15+
print(*args, file=sys.stderr, **kwargs)
16+
17+
18+
def parse_function(function):
19+
eprint(function)
20+
# Remove whitespace and trailing semicolon
21+
function = function.strip().strip(';')
22+
# For parsing make sure there is no space before the parenthesis
23+
function = re.sub(r' *\(', '(', function)
24+
# Replace (void) notation with empty parentheses
25+
function = re.sub(r'\( *void *\)', '()', function)
26+
# Parse return type, function name and parameter string
27+
match = re.match(r'^(.*[\* ])(\w+)\((.*)\)$', function)
28+
if not match:
29+
raise Exception(f'Function did not match regex: {function}')
30+
func_data = {}
31+
func_data['name'] = match.group(2).strip()
32+
# Parse modifiers and return type
33+
mod_ret_str = match.group(1).strip()
34+
mod_ret = mod_ret_str.split(' ')
35+
mod_ret = [mr.strip() for mr in mod_ret if mr]
36+
# Fix pointer return type which could be split up
37+
if len(mod_ret) >= 2 and mod_ret[-1] == '*':
38+
mod_ret[-2] += ' *'
39+
del mod_ret[-1]
40+
func_data['return_type'] = mod_ret[-1]
41+
func_data['attributes'] = mod_ret[:-1]
42+
# Parse parameters
43+
func_data['params'] = {}
44+
if match.group(3).strip() != '':
45+
params_str = [param_str.strip() for param_str in match.group(3).split(',')]
46+
for i, param_str in enumerate(params_str):
47+
eprint(param_str)
48+
pmatch = re.fullmatch(r'(.*[\* ])([\w\[\]]+)', param_str)
49+
if pmatch:
50+
func_data['params'][pmatch.group(2).strip()] = pmatch.group(1).strip()
51+
else:
52+
ptmatch = re.fullmatch(r'([\w]+(?: *)?(?:\**)?)', param_str)
53+
if ptmatch:
54+
func_data['params'][f'param{i}'] = ptmatch.group(1).strip()
55+
else:
56+
raise Exception(f'Parameter in function "{function}" did not match regex: {param_str}')
57+
return func_data
58+
59+
# Builds the function declaration for a function
60+
def get_function_declaration(func):
61+
decl_params = ', '.join([f'{p_type} {p_name}' for p_name, p_type in func['params'].items()])
62+
return f'{" ".join(func["attributes"])} {func["return_type"]} {func["name"]}({decl_params})'.strip()
63+
64+
# Extracts functions from source files by calling the 'get_source_context.py' script
65+
def get_source_context(cmd, source_files):
66+
try:
67+
return subprocess.run([sys.executable, 'get_source_context.py', cmd, *source_files], cwd='..', check=True, stdout=subprocess.PIPE).stdout.decode().strip().split('\n')
68+
except subprocess.CalledProcessError:
69+
eprint('Failed to extract functions from source')
70+
sys.exit(1)
71+
72+
# Searches the source files for "#define func_name other_func_name" statements
73+
# and returns them as a dict
74+
RE_DEFINE_LINE = r'^\s*#define\s+(\w+)\s+(\w+)\s*$'
75+
def find_redefinitions(source_files):
76+
redefinitions = {}
77+
for source_file in source_files:
78+
with open(source_file, 'r') as f:
79+
for line in f:
80+
match = re.fullmatch(RE_DEFINE_LINE, line)
81+
if match:
82+
redefinitions[match.group(1)] = match.group(2)
83+
return redefinitions
84+
85+
# Writes functions to an excel sheet
86+
def write_functions_to_excel(filename, funcs, prefill_params=False):
87+
# Create an excel document
88+
wb = openpyxl.Workbook()
89+
sheet_funcs = wb.create_sheet('functions')
90+
sheet_funcs.title = 'functions'
91+
sheet_funcs.column_dimensions['A'].width = 120
92+
for i in range(6):
93+
sheet_funcs.column_dimensions[chr(ord('B')+i)].width = 25
94+
sheet_snips = wb.create_sheet('snippets')
95+
sheet_snips.title = 'snippets'
96+
wb.remove(wb['Sheet'])
97+
# Add highlighting for void/non-void functions
98+
fill_green = PatternFill(start_color='99ff99', fill_type='solid')
99+
fill_orange = PatternFill(start_color='ffd699', fill_type='solid')
100+
#dxf_green = DifferentialStyle(fill=fill_green)
101+
#dxf_orange = DifferentialStyle(fill=fill_orange)
102+
#rule_void = Rule(type='containsText', operator='containsText', text='()', dxf=dxf_green)
103+
#rule_void.formula = ['NOT(ISERROR(SEARCH("()",A1)))']
104+
#rule_params = Rule(type='notContainsText', operator='notContains', text='()', dxf=dxf_orange)
105+
#rule_params.formula = ['ISERROR(SEARCH("()",A1))']
106+
#ws.conditional_formatting.add('A1:A1000', rule_void)
107+
108+
for idx in range(len(funcs)):
109+
# Write function
110+
func = funcs[idx]
111+
sheet_funcs.cell(row=1+idx, column=1).value = get_function_declaration(func)
112+
sheet_funcs.cell(row=1+idx, column=1).fill = fill_green if len(func['params']) == 0 else fill_orange
113+
# Write function parameter names
114+
if prefill_params:
115+
p_col = 2
116+
for p_name in func['params']:
117+
sheet_funcs.cell(row=1+idx, column=p_col).value = f'{p_name}: '
118+
p_col += 1
119+
120+
# Save document
121+
wb.save(filename)
122+
123+
124+
def main():
125+
if len(sys.argv) < 2:
126+
eprint(f'Usage: {sys.argv[0]} <source files>... <mock header files>...')
127+
sys.exit(1)
128+
129+
# Separate arguments into implementation source files and mock header files
130+
source_files = []
131+
header_files = []
132+
for file_arg in sys.argv[1:]:
133+
if not os.path.isfile(file_arg):
134+
eprint(f'{file_arg} not found')
135+
sys.exit(1)
136+
_, ext = os.path.splitext(file_arg)
137+
if ext.startswith('.c'):
138+
source_files.append(file_arg)
139+
elif ext.startswith('.h'):
140+
header_files.append(file_arg)
141+
else:
142+
eprint(f'Unknown file type: {file_arg}')
143+
sys.exit(1)
144+
145+
# Extract all data we need from the provided files, that is:
146+
# - All declarations from header files that contain mock functions (and possibly other ones)
147+
# - All function calls inside the source files. We'll use this to filter the mock declarations.
148+
# - All function definitions (their declarations) in the source files to filter them out of the mock declarations
149+
eprint('Parsing mock declarations...')
150+
mock_decls_str = get_source_context('find_func_decls', header_files)
151+
mock_decls = [parse_function(func) for func in mock_decls_str]
152+
# Remove the 'extern' attribute
153+
for mock_decl in mock_decls:
154+
if 'extern' in mock_decl['attributes']:
155+
mock_decl['attributes'].remove('extern')
156+
eprint('Parsing function definitions...')
157+
func_calls_str = get_source_context('find_func_calls', source_files)
158+
func_defs_str = get_source_context('find_func_defs', source_files)
159+
func_defs = [parse_function(func) for func in func_defs_str]
160+
# Also search preprocessor definitions (we're looking for redefined function names)
161+
redefs = find_redefinitions(header_files)
162+
163+
# Deduplicate mocks, we just take the shorter declaration for now
164+
mock_decls_dict = {}
165+
for mock_decl in mock_decls:
166+
if mock_decl['name'] in mock_decls_dict:
167+
decl_str_1 = get_function_declaration(mock_decls_dict[mock_decl['name']])
168+
decl_str_2 = get_function_declaration(mock_decl)
169+
if decl_str_1 == decl_str_2:
170+
pass
171+
elif len(decl_str_1) <= len(decl_str_2):
172+
eprint(f'Mock conflict: -> {decl_str_1}')
173+
eprint(f'Mock conflict: {decl_str_2}')
174+
else:
175+
eprint(f'Mock conflict: {decl_str_1}')
176+
eprint(f'Mock conflict: -> {decl_str_2}')
177+
mock_decls_dict[mock_decl['name']] = mock_decl
178+
else:
179+
mock_decls_dict[mock_decl['name']] = mock_decl
180+
mock_decls = list(mock_decls_dict.values())
181+
182+
# Copy mock declarations when additional new names for them have been defined
183+
for new_name, old_name in redefs.items():
184+
if old_name in mock_decls_dict and new_name not in mock_decls_dict:
185+
mock_decl = copy.deepcopy(mock_decls_dict[old_name])
186+
mock_decl['name'] = new_name
187+
mock_decls.append(mock_decl)
188+
189+
# Filter the mock declarations to keep called function only and remove functions we have implementations for
190+
func_defs_names = [func_def['name'] for func_def in func_defs]
191+
mock_decls = [mock_decl for mock_decl in mock_decls if mock_decl['name'] in func_calls_str and mock_decl['name'] not in func_defs_names]
192+
193+
# Filter the function implementations to include only public functions
194+
# I.e. remove static functions and functions starting with <module>__ (TODO: Conti specific)
195+
func_defs = [func_def for func_def in func_defs if 'static' not in func_def['attributes'] and not re.match(r'[a-zA-Z0-9]+__', func_def['name'])]
196+
197+
# Sort the lists first by function name, then group function with/without parameters
198+
mock_decls.sort(key=lambda x: x['name'])
199+
mock_decls.sort(key=lambda x: int(len(x['params']) == 0))
200+
func_defs.sort(key=lambda x: x['name'])
201+
func_defs.sort(key=lambda x: int(len(x['params']) == 0))
202+
203+
# Write excel files
204+
write_functions_to_excel('testgen_mocks.xlsx', mock_decls)
205+
write_functions_to_excel('testgen_functions.xlsx', func_defs, prefill_params=True)
206+
207+
208+
if __name__ == '__main__':
209+
main()

0 commit comments

Comments
 (0)