Skip to content

Commit

Permalink
core: frontends: cpp: improve control flow analysis (#1984)
Browse files Browse the repository at this point in the history
* core: frontends: cpp: improve control flow analysis

Signed-off-by: David Korczynski <[email protected]>

* nit

Signed-off-by: David Korczynski <[email protected]>

* nit

Signed-off-by: David Korczynski <[email protected]>

---------

Signed-off-by: David Korczynski <[email protected]>
  • Loading branch information
DavidKorczynski authored Jan 16, 2025
1 parent 5775eee commit 94a35b1
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 5 deletions.
48 changes: 43 additions & 5 deletions src/fuzz_introspector/frontends/frontend_cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,10 +378,14 @@ def _process_invoke(self, expr: Node,
target_name = func.text.decode()

# Find the matching function in our project
logger.debug('Matching function %s', target_name)
matched_func = project.find_function_from_approximate_name(
target_name)
if matched_func:
logger.debug('Matched function')
target_name = matched_func.name
else:
logger.debug('Did not find matching function')

# Chained or method calls
elif func.type == 'field_expression':
Expand All @@ -407,11 +411,16 @@ def _process_field_expr_return_type(self, field_expr: Node,
project) -> tuple[Optional[str], str]:
"""Helper for determining the return type of a field expression
in a chained call and its full qualified name."""
logger.debug('Handling field expression: %s', field_expr.text.decode())
ret_type = None
object_type = None

arg = field_expr.child_by_field_name('argument')
name = field_expr.child_by_field_name('field').text.decode()
if field_expr.child_by_field_name('field').type == 'template_method':
name = field_expr.child_by_field_name('field').child_by_field_name(
'name').text.decode()
else:
name = field_expr.child_by_field_name('field').text.decode()
full_name = name

# Chained field access
Expand All @@ -425,6 +434,12 @@ def _process_field_expr_return_type(self, field_expr: Node,
# Named object
elif arg.type in ['identifier', 'qualified_identifier']:
object_type = self.var_map.get(arg.text.decode())
elif arg.type == 'call_expression':
# Bail, we do not support this yet. Examples of code:
# "static_cast<impl::xpath_query_impl*>(_impl)->root->eval_string(c, sd.stack);""
# We give up here.
logger.debug('Cant analyse this.')
return ('', '')

if object_type:
if object_type == 'void':
Expand Down Expand Up @@ -684,8 +699,9 @@ def extract_calltree(self,
else:
logger.debug('Extracting node using lookup table.')
func_node = get_function_node(function, self.all_functions)

if func_node:
logger.debug('Found function node')
logger.debug('Found function node: %s', func_node.name)
func_name = func_node.name
else:
logger.debug('Found no function node')
Expand All @@ -703,15 +719,23 @@ def extract_calltree(self,
line_to_print += str(line_number)

line_to_print += '\n'
if func_node and not source_code:
source_code = func_node.parent_source

if function in visited_functions or not func_node or not source_code:
if function in visited_functions:
logger.debug('Function in visited ')
if not func_node:
logger.debug('Not func_node')
if not source_code:
logger.debug('Not source code')
logger.debug('Function visited or no function node')
return line_to_print

visited_functions.add(function)
logger.debug('Iterating %s callsites', len(func_node.base_callsites))
for cs, line in func_node.base_callsites:
logger.debug('Callsites: %s', cs)
logger.debug('Callsite: %s', cs)
line_to_print += self.extract_calltree(
source_file=source_code.source_file,
function=cs,
Expand Down Expand Up @@ -882,7 +906,7 @@ def capture_source_files_in_tree(directory_tree):
'.inl'
]
exclude_directories = [
'build', 'target', 'tests', 'node_modules', 'aflplusplus', 'honggfuzz',
'build', 'target', 'node_modules', 'aflplusplus', 'honggfuzz',
'inspector', 'libfuzzer', 'fuzztest'
]

Expand Down Expand Up @@ -929,24 +953,38 @@ def get_function_node(
one_layer_only: bool = False) -> Optional[FunctionDefinition]:
"""Helper to retrieve the RustFunction object of a function."""

logger.debug('Finding match for %s', target_name)
for function in function_list:
if target_name == function.name:
logger.debug('Found exact match')
return function

# Exact match
# if target_name in function_map:
# return function_map[target_name]
if '::' not in target_name:
return None

# Avoid all references to std library for the heuristics that are follow.
# This is because we do approximate namespace matching, and functions
# from standard library are definitely not imlemented in any library we
# analyse.
if target_name.startswith('std::'):
return None

# Match any key that ends with target_name, then
# split the target_name by :: and check one by one
if one_layer_only:
name_split = target_name.split('::', 1)
else:
name_split = target_name.split('::')
for count in range(len(name_split)):

for count in range(len(name_split)):
logger.debug('Testing %s', '::'.join(name_split[count:]))
for func in function_list:
if func.name.endswith('::'.join(name_split[count:])):
logger.debug('Found match: %s', func.name)
return func

logger.debug('Found no matching function node')
return None
1 change: 1 addition & 0 deletions src/fuzz_introspector/frontends/oss_fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def process_cpp_project(target_dir: str,
with open(target, 'w', encoding='utf-8') as f:
f.write(f'Call tree\n{calltree}')

logger.info('Complete cpp frontend.')
return project


Expand Down

0 comments on commit 94a35b1

Please sign in to comment.