mirror of https://github.com/crytic/slither
commit
3974f01b89
@ -0,0 +1,34 @@ |
||||
library Library { |
||||
function library_func() { |
||||
} |
||||
} |
||||
|
||||
contract ContractA { |
||||
uint256 public val = 0; |
||||
|
||||
function my_func_a() { |
||||
keccak256(0); |
||||
Library.library_func(); |
||||
} |
||||
} |
||||
|
||||
contract ContractB { |
||||
ContractA a; |
||||
|
||||
constructor() { |
||||
a = new ContractA(); |
||||
} |
||||
|
||||
function my_func_b() { |
||||
a.my_func_a(); |
||||
my_second_func_b(); |
||||
} |
||||
|
||||
function my_func_a() { |
||||
my_second_func_b(); |
||||
} |
||||
|
||||
function my_second_func_b(){ |
||||
a.val(); |
||||
} |
||||
} |
@ -0,0 +1,28 @@ |
||||
strict digraph { |
||||
subgraph cluster_5_Library { |
||||
label = "Library" |
||||
"5_library_func" [label="library_func"] |
||||
} |
||||
subgraph cluster_22_ContractA { |
||||
label = "ContractA" |
||||
"22_my_func_a" [label="my_func_a"] |
||||
"22_val" [label="val"] |
||||
} |
||||
subgraph cluster_63_ContractB { |
||||
label = "ContractB" |
||||
"63_my_second_func_b" [label="my_second_func_b"] |
||||
"63_my_func_a" [label="my_func_a"] |
||||
"63_constructor" [label="constructor"] |
||||
"63_my_func_b" [label="my_func_b"] |
||||
"63_my_func_b" -> "63_my_second_func_b" |
||||
"63_my_func_a" -> "63_my_second_func_b" |
||||
} |
||||
subgraph cluster_solidity { |
||||
label = "[Solidity]" |
||||
"keccak256()" |
||||
"22_my_func_a" -> "keccak256()" |
||||
} |
||||
"22_my_func_a" -> "5_library_func" |
||||
"63_my_func_b" -> "22_my_func_a" |
||||
"63_my_second_func_b" -> "22_val" |
||||
} |
After Width: | Height: | Size: 35 KiB |
@ -0,0 +1,155 @@ |
||||
""" |
||||
Module printing the call graph |
||||
|
||||
The call graph shows for each function, |
||||
what are the contracts/functions called. |
||||
The output is a dot file named filename.dot |
||||
""" |
||||
|
||||
from slither.printers.abstract_printer import AbstractPrinter |
||||
from slither.core.declarations.solidity_variables import SolidityFunction |
||||
from slither.core.declarations.function import Function |
||||
from slither.core.declarations.contract import Contract |
||||
from slither.core.expressions.member_access import MemberAccess |
||||
from slither.core.expressions.identifier import Identifier |
||||
from slither.core.variables.variable import Variable |
||||
from slither.core.solidity_types.user_defined_type import UserDefinedType |
||||
|
||||
# return unique id for contract to use as subgraph name |
||||
def _contract_subgraph(contract): |
||||
return f'cluster_{contract.id}_{contract.name}' |
||||
|
||||
# return unique id for contract function to use as node name |
||||
def _function_node(contract, function): |
||||
return f'{contract.id}_{function.name}' |
||||
|
||||
# return unique id for solidity function to use as node name |
||||
def _solidity_function_node(solidity_function): |
||||
return f'{solidity_function.name}' |
||||
|
||||
# return dot language string to add graph edge |
||||
def _edge(from_node, to_node): |
||||
return f'"{from_node}" -> "{to_node}"' |
||||
|
||||
# return dot language string to add graph node (with optional label) |
||||
def _node(node, label=None): |
||||
return ' '.join(( |
||||
f'"{node}"', |
||||
f'[label="{label}"]' if label is not None else '', |
||||
)) |
||||
|
||||
class PrinterCallGraph(AbstractPrinter): |
||||
ARGUMENT = 'call-graph' |
||||
HELP = 'the call graph' |
||||
|
||||
def __init__(self, slither, logger): |
||||
super(PrinterCallGraph, self).__init__(slither, logger) |
||||
|
||||
self.contract_functions = {} # contract -> contract functions nodes |
||||
self.contract_calls = {} # contract -> contract calls edges |
||||
|
||||
for contract in slither.contracts: |
||||
self.contract_functions[contract] = set() |
||||
self.contract_calls[contract] = set() |
||||
|
||||
self.solidity_functions = set() # solidity function nodes |
||||
self.solidity_calls = set() # solidity calls edges |
||||
|
||||
self.external_calls = set() # external calls edges |
||||
|
||||
self._process_contracts(slither.contracts) |
||||
|
||||
def _process_contracts(self, contracts): |
||||
for contract in contracts: |
||||
for function in contract.functions: |
||||
self._process_function(contract, function) |
||||
|
||||
def _process_function(self, contract, function): |
||||
self.contract_functions[contract].add( |
||||
_node(_function_node(contract, function), function.name), |
||||
) |
||||
|
||||
for internal_call in function.internal_calls: |
||||
self._process_internal_call(contract, function, internal_call) |
||||
for external_call in function.high_level_calls: |
||||
self._process_external_call(contract, function, external_call) |
||||
|
||||
def _process_internal_call(self, contract, function, internal_call): |
||||
if isinstance(internal_call, (Function)): |
||||
self.contract_calls[contract].add(_edge( |
||||
_function_node(contract, function), |
||||
_function_node(contract, internal_call), |
||||
)) |
||||
elif isinstance(internal_call, (SolidityFunction)): |
||||
self.solidity_functions.add( |
||||
_node(_solidity_function_node(internal_call)), |
||||
) |
||||
self.solidity_calls.add(_edge( |
||||
_function_node(contract, function), |
||||
_solidity_function_node(internal_call), |
||||
)) |
||||
|
||||
def _process_external_call(self, contract, function, external_call): |
||||
external_contract, external_function = external_call |
||||
|
||||
# add variable as node to respective contract |
||||
if isinstance(external_function, (Variable)): |
||||
self.contract_functions[external_contract].add(_node( |
||||
_function_node(external_contract, external_function), |
||||
external_function.name |
||||
)) |
||||
|
||||
self.external_calls.add(_edge( |
||||
_function_node(contract, function), |
||||
_function_node(external_contract, external_function), |
||||
)) |
||||
|
||||
def _render_internal_calls(self): |
||||
lines = [] |
||||
|
||||
for contract in self.contract_functions: |
||||
lines.append(f'subgraph {_contract_subgraph(contract)} {{') |
||||
lines.append(f'label = "{contract.name}"') |
||||
|
||||
lines.extend(self.contract_functions[contract]) |
||||
lines.extend(self.contract_calls[contract]) |
||||
|
||||
lines.append('}') |
||||
|
||||
return '\n'.join(lines) |
||||
|
||||
def _render_solidity_calls(self): |
||||
lines = [] |
||||
|
||||
lines.append('subgraph cluster_solidity {') |
||||
lines.append('label = "[Solidity]"') |
||||
|
||||
lines.extend(self.solidity_functions) |
||||
lines.extend(self.solidity_calls) |
||||
|
||||
lines.append('}') |
||||
|
||||
return '\n'.join(lines) |
||||
|
||||
def _render_external_calls(self): |
||||
return '\n'.join(self.external_calls) |
||||
|
||||
def output(self, filename): |
||||
""" |
||||
Output the graph in filename |
||||
Args: |
||||
filename(string) |
||||
""" |
||||
if not filename.endswith('.dot'): |
||||
filename += '.dot' |
||||
|
||||
self.info(f'Call Graph: {filename}') |
||||
|
||||
with open(filename, 'w') as f: |
||||
f.write('\n'.join([ |
||||
'strict digraph {', |
||||
self._render_internal_calls(), |
||||
self._render_solidity_calls(), |
||||
self._render_external_calls(), |
||||
'}', |
||||
])) |
Loading…
Reference in new issue