Merge pull request #56 from evgeniuz/master

implementation of call graph printer
pull/60/head
Feist Josselin 6 years ago committed by GitHub
commit 3974f01b89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      README.md
  2. 34
      examples/printers/call_graph.sol
  3. 28
      examples/printers/call_graph.sol.dot
  4. BIN
      examples/printers/call_graph.sol.dot.png
  5. 2
      slither/__main__.py
  6. 0
      slither/printers/call/__init__.py
  7. 155
      slither/printers/call/call_graph.py

@ -40,6 +40,7 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct
* `--printer-quick-summary`: Print a quick summary of the contracts
* `--printer-inheritance`: Print the inheritance relations
* `--printer-inheritance-graph`: Print the inheritance graph in a file
* `--printer-call-graph`: Print the call graph in a file
* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function
## Checks available

@ -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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@ -157,6 +157,7 @@ def main():
from slither.printers.summary.contract import ContractSummary
from slither.printers.inheritance.inheritance import PrinterInheritance
from slither.printers.inheritance.inheritance_graph import PrinterInheritanceGraph
from slither.printers.call.call_graph import PrinterCallGraph
from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization
from slither.printers.summary.slithir import PrinterSlithIR
@ -164,6 +165,7 @@ def main():
ContractSummary,
PrinterInheritance,
PrinterInheritanceGraph,
PrinterCallGraph,
PrinterWrittenVariablesAndAuthorization,
PrinterSlithIR]

@ -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…
Cancel
Save