angr/tests/knowledge_plugins/functions/test_function.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

137 lines
4.6 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
from __future__ import annotations
from unittest import TestCase, main
import archinfo
import networkx
from angr.knowledge_plugins.functions import Function
from angr.sim_type import parse_defns
def makeFunction(function_manager, function_address, function_name):
# Fill some value that are not relevant for the tests, but help circumvent a lot of mocking.
f = Function(
function_manager,
function_address,
name=function_name,
syscall=False,
is_simprocedure=False,
is_plt=False,
binary_name="rpaulson.bin",
returning=True,
)
function_manager._function_map[function_address] = f
return f
2023-01-12 16:07:58 -07:00
class MockFunctionManager:
def __init__(self):
self.callgraph = networkx.MultiDiGraph()
self._function_map = {}
2023-01-12 16:07:58 -07:00
def function(self, address):
return self._function_map[address]
FunctionManager: Spill to external storage. (#5976) * FunctionManager: Spill to external storage. * Remove atexit registration. Reduce map size. * Introduce FuncNode in function graphs; Fix multiple issues with SpillingFunctionDict. * Implement Function.dirty. * A bit more optimization. * FunctionManager loads only meta data for functions when graphs are not accessed; Save .info for functions. * Only load meta data for Functions in more places. * Remove FunctionManager.block_map because it's never really used. * Retire blockaddr_to_function and replace it with blockaddr_to_funcaddr. * More optimizations and fixes. * More refactor and fixes. * More optimizations. * Fix the bug in lmdb spilling after raising MapFullError. * Introduce RuntimeDb in KB. Migrate SpillingFunctionsDict to use RuntimeDb. * Prioritize the basedir of the main executable for the runtime db path. * Cache non-returning function addrs, unknown-returning function addrs, and function block count in FunctionManager. * Bug fixes. * Fix a bug in FunctionParser. * CFGBase.make_functions: Copy over function metadata when creating functions in the first place. * Update MockFunctionManager. * Type check codenode.py and fix an RDA test case. * Fix serialization tests; Introduce KnowledgeBasePlugin.set_kb(); SpillingFunctionDict now derives from UserDict; Fix KB.name stored in KB._plugins; Fix FunctionDict.__setstate__ swapping Function objects and function addresses. * Update FactCollector to support FuncNode. * FunctionInfo: Update Function.dirty and perform type checks on keys and values. * Make function cache limit configurable. * Adjust FunctionInfo type check. * More fixes. * More updates to account for FuncNode in function graphs. * Update a test case. * Fix another test case (do not use the size of FuncNodes). * Fix Reassembler. * CFGBase.make_functions: Add a missing insertion to _updated_nonreturning_functions. * Update FunctionManager.rebuild_callgraph. * FunctionParser: Call destinations must be FuncNodes. * Serialize Function.is_default_name. * Minor fixes. * Fix CFunctionCall._is_target_ambiguous. * Mark evicted Function instances as evicted. * HashLookupAPIDeobfuscator: Take a list of function addresses instead of Function instances as arg. * CC_NAMES: Fix the bug of missing SimCCCdecl. * FunctionParser: Fix missing syscall function nodes. * HookNode: Take a SimProcedure instance instead of the class as the sim_procedure argument. * FunctionParser: Consider return-type edges when deserializing. * SimTypeCppFunction: Fix to_json() serialization crash. * FunctionParser: Fix missing return sites. * Function.is_{syscall,simprocedure,alignment,plt} settings should mark the function dirty. * RDA: Do not create blocks for FuncNodes or HookNodes. * Update a test case. * Fix issues with SimCppClass.to_json. * HookNode: Fix HookNode.__eq__. * Fix SootFunction. * Lint function_manager.py. * Lint and type check. * More docs; Spill Function.ran_cca. * RuntimeDb: Support specifying base dir using an environment variable. * Lint and fix test cases.
2026-01-12 19:58:18 -07:00
def noop(self, *args, **kwargs):
pass
def contains_addr(self, addr):
return addr in self._function_map
def get_by_addr(self, addr):
return self._function_map[addr]
set_function_returning = noop
2023-01-12 16:07:58 -07:00
class TestFunction(TestCase):
def setUp(self):
self.function_manager = MockFunctionManager()
def test_functions_called_returns_all_functions_that_can_be_reached_from_the_function(self):
A = makeFunction(self.function_manager, 0x40, "A")
B = makeFunction(self.function_manager, 0x41, "B")
function = makeFunction(self.function_manager, 0x42, "function")
C = makeFunction(self.function_manager, 0x43, "C")
D = makeFunction(self.function_manager, 0x44, "D")
E = makeFunction(self.function_manager, 0x45, "E")
# A -> B
# function -> C -> D
# function -> E
self.function_manager.callgraph.add_edges_from(
[
(A.addr, B.addr),
(function.addr, C.addr),
(function.addr, E.addr),
(C.addr, D.addr),
]
)
self.assertEqual(function.functions_reachable(), {C, D, E})
def test_functions_called_with_recursive_function(self):
recursive_function = makeFunction(self.function_manager, 0x40, "recursive_function")
B = makeFunction(self.function_manager, 0x41, "B")
# recursive_function -> B
# recursive_function -> recursive_function
self.function_manager.callgraph.add_edges_from(
[
(recursive_function.addr, B.addr),
(recursive_function.addr, recursive_function.addr),
]
)
self.assertEqual(recursive_function.functions_reachable(), {recursive_function, B})
def test_functions_called_with_cyclic_dependencies(self):
function = makeFunction(self.function_manager, 0x42, "function")
C = makeFunction(self.function_manager, 0x43, "C")
# function -> C -> function
self.function_manager.callgraph.add_edges_from(
[
(function.addr, C.addr),
(C.addr, function.addr),
]
)
self.assertEqual(function.functions_reachable(), {function, C})
def test_function_set_prototype_without_parameter_names(self):
function = makeFunction(self.function_manager, 0x42, "function")
parsed_proto = parse_defns("int func(int, char*);")["func"]
function.prototype = parsed_proto.with_arch(archinfo.arch_from_id("AMD64"))
assert len(function.prototype.args) == 2
assert len(function.prototype.arg_names) == 2
# default function argument names apply
assert function.prototype.arg_names[0] == "a0"
assert function.prototype.arg_names[1] == "a1"
def test_function_set_prototype_missing_a_parameter_name(self):
function = makeFunction(self.function_manager, 0x42, "function")
parsed_proto = parse_defns("int func(int, char*);")["func"]
parsed_proto.arg_names = ["", "a3"]
function.prototype = parsed_proto.with_arch(archinfo.arch_from_id("AMD64"))
assert len(function.prototype.args) == 2
assert len(function.prototype.arg_names) == 2
# default function argument names apply
assert function.prototype.arg_names[0] == "a0"
# the original argument name should be kept
assert function.prototype.arg_names[1] == "a3"
def test_function_set_prototype_none(self):
# you can set Function.prototype to None to clear it
function = makeFunction(self.function_manager, 0x42, "function")
function.prototype = None
assert function.prototype is None
if __name__ == "__main__":
main()