Merge pull request #87 from trailofbits/dev-ssa

Add SSA to SlithIR:
- Add Phi operators 
- Use LocalIRVariable and StateIRVariable to add an index
- Follow Cooper, Harvey, Kennedy to compute minimal SSA
- Add additional Phi operators at functions entrance and after external calls to handle state variable

Add Alias Analysis to track storage reference
- Integrate alias analysis info into the SSA engine for precise SSA construction
- Limitation: alias analysis is not yet interprocedural (no support for function returning a storage reference)

Add dominators information
- List of node dominators
- Dominator tree
- Dominance frontier
pull/117/head^2
Feist Josselin 6 years ago committed by GitHub
commit fb1d907850
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      examples/scripts/export_dominator_tree_to_dot.py
  2. 3
      slither/__main__.py
  3. 0
      slither/analyses/data_depencency/__init__.py
  4. 105
      slither/analyses/data_depencency/data_depency.py
  5. 210
      slither/core/cfg/node.py
  6. 25
      slither/core/declarations/contract.py
  7. 44
      slither/core/declarations/function.py
  8. 0
      slither/core/dominators/__init__.py
  9. 37
      slither/core/dominators/node_dominator_tree.py
  10. 68
      slither/core/dominators/utils.py
  11. 8
      slither/core/variables/local_variable.py
  12. 7
      slither/core/variables/state_variable.py
  13. 10
      slither/core/variables/variable.py
  14. 1
      slither/detectors/reentrancy/reentrancy.py
  15. 4
      slither/printers/summary/slithir.py
  16. 44
      slither/printers/summary/slithir_ssa.py
  17. 4
      slither/slithir/convert.py
  18. 2
      slither/slithir/operations/__init__.py
  19. 7
      slither/slithir/operations/assignment.py
  20. 4
      slither/slithir/operations/delete.py
  21. 4
      slither/slithir/operations/high_level_call.py
  22. 4
      slither/slithir/operations/index.py
  23. 2
      slither/slithir/operations/internal_dynamic_call.py
  24. 40
      slither/slithir/operations/phi.py
  25. 47
      slither/slithir/operations/phi_callback.py
  26. 4
      slither/slithir/operations/unary.py
  27. 570
      slither/slithir/utils/ssa.py
  28. 23
      slither/slithir/utils/variable_number.py
  29. 2
      slither/slithir/variables/__init__.py
  30. 7
      slither/slithir/variables/constant.py
  31. 64
      slither/slithir/variables/local_variable.py
  32. 19
      slither/slithir/variables/reference.py
  33. 36
      slither/slithir/variables/state_variable.py
  34. 11
      slither/slithir/variables/temporary.py
  35. 4
      slither/slithir/variables/tuple.py
  36. 14
      slither/slithir/variables/variable.py
  37. 44
      slither/solc_parsing/declarations/contract.py
  38. 125
      slither/solc_parsing/declarations/function.py
  39. 12
      slither/solc_parsing/slitherSolc.py
  40. 9
      slither/visitors/slithir/expression_to_slithir.py

@ -0,0 +1,18 @@
import sys
from slither.slither import Slither
if len(sys.argv) != 2:
print('python export_dominator_tree_to_dot.py contract.sol')
exit(-1)
# Init slither
slither = Slither(sys.argv[1])
for contract in slither.contracts:
for function in contract.functions + contract.modifiers:
filename = "{}-{}-{}_dom.dot".format(sys.argv[1], contract.name, function.full_name)
print('Export {}'.format(filename))
function.dominator_tree_to_dot(filename)

@ -167,6 +167,7 @@ def get_detectors_and_printers():
from slither.printers.call.call_graph import PrinterCallGraph
from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization
from slither.printers.summary.slithir import PrinterSlithIR
from slither.printers.summary.slithir_ssa import PrinterSlithIRSSA
from slither.printers.summary.human_summary import PrinterHumanSummary
from slither.printers.functions.cfg import CFG
@ -177,6 +178,7 @@ def get_detectors_and_printers():
PrinterCallGraph,
PrinterWrittenVariablesAndAuthorization,
PrinterSlithIR,
PrinterSlithIRSSA,
PrinterHumanSummary,
CFG]
@ -225,6 +227,7 @@ def main_impl(all_detector_classes, all_printer_classes):
('FunctionSolc', default_log),
('ExpressionParsing', default_log),
('TypeParsing', default_log),
('SSA_Conversion', default_log),
('Printers', default_log)]:
l = logging.getLogger(l_name)
l.setLevel(l_level)

@ -0,0 +1,105 @@
"""
Compute the data depenency between all the SSA variables
"""
from slither.slithir.operations import Index, Member, OperationWithLValue
from slither.slithir.variables import ReferenceVariable, Constant
from slither.slithir.variables import (Constant, LocalIRVariable, StateIRVariable,
ReferenceVariable, TemporaryVariable,
TupleVariable)
KEY = "DATA_DEPENDENCY_SSA"
KEY_NON_SSA = "DATA_DEPENDENCY"
def compute_dependency(slither):
for contract in slither.contracts:
compute_dependency_contract(contract)
def compute_dependency_contract(contract):
if KEY in contract.context:
return
contract.context[KEY] = dict()
for function in contract.all_functions_called:
compute_dependency_function(function)
data_depencencies = function.context[KEY]
for (key, values) in data_depencencies.items():
if not key in contract.context[KEY]:
contract.context[KEY][key] = set(values)
else:
contract.context[KEY][key].union(values)
# transitive closure
changed = True
while changed:
changed = False
# Need to create new set() as its changed during iteration
data_depencencies = {k: set([v for v in values]) for k, values in contract.context[KEY].items()}
for key, items in data_depencencies.items():
for item in items:
if item in data_depencencies:
additional_items = contract.context[KEY][item]
for additional_item in additional_items:
if not additional_item in items and additional_item != key:
changed = True
contract.context[KEY][key].add(additional_item)
contract.context[KEY_NON_SSA] = convert_to_non_ssa(contract.context[KEY])
def compute_dependency_function(function):
if KEY in function.context:
return function.context[KEY]
function.context[KEY] = dict()
for node in function.nodes:
for ir in node.irs_ssa:
if isinstance(ir, OperationWithLValue) and ir.lvalue:
lvalue = ir.lvalue
# if isinstance(ir.lvalue, ReferenceVariable):
# lvalue = lvalue.points_to_origin
# # TODO fix incorrect points_to for BALANCE
# if not lvalue:
# continue
if not lvalue in function.context[KEY]:
function.context[KEY][lvalue] = set()
if isinstance(ir, Index):
read = [ir.variable_left]
else:
read = ir.read
[function.context[KEY][lvalue].add(v) for v in read if not isinstance(v, Constant)]
function.context[KEY_NON_SSA] = convert_to_non_ssa(function.context[KEY])
def valid_non_ssa(v):
if isinstance(v, (TemporaryVariable,
ReferenceVariable,
TupleVariable)):
return False
return True
def convert_variable_to_non_ssa(v):
if isinstance(v, (LocalIRVariable, StateIRVariable)):
if isinstance(v, LocalIRVariable):
function = v.function
return function.get_local_variable_from_name(v.name)
else:
contract = v.contract
return contract.get_state_variable_from_name(v.name)
return v
def convert_to_non_ssa(data_depencies):
# Need to create new set() as its changed during iteration
ret = dict()
for (k, values) in data_depencies.items():
if not valid_non_ssa(k):
continue
var = convert_variable_to_non_ssa(k)
if not var in ret:
ret[var] = set()
ret[var] = ret[var].union(set([convert_variable_to_non_ssa(v) for v in
values if valid_non_ssa(v)]))
return ret

@ -9,14 +9,15 @@ from slither.core.declarations.solidity_variables import (SolidityFunction,
SolidityVariable)
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.variable import Variable
from slither.slithir.convert import convert_expression
from slither.slithir.operations import (Balance, HighLevelCall, Index,
InternalCall, Length, LibraryCall,
LowLevelCall, Member,
OperationWithLValue, SolidityCall)
OperationWithLValue, SolidityCall, Phi, PhiCallback)
from slither.slithir.variables import (Constant, ReferenceVariable,
TemporaryVariable, TupleVariable)
TemporaryVariable, TupleVariable, StateIRVariable, LocalIRVariable)
from slither.visitors.expression.expression_printer import ExpressionPrinter
from slither.visitors.expression.read_var import ReadVar
from slither.visitors.expression.write_var import WriteVar
@ -36,6 +37,12 @@ class NodeType:
ASSEMBLY = 0x14
IFLOOP = 0x15
# Merging nodes
# Can have phi IR operation
ENDIF = 0x50
STARTLOOP = 0x51
ENDLOOP = 0x52
# Below the nodes have no expression
# But are used to expression CFG structure
@ -49,11 +56,6 @@ class NodeType:
# Only modifier node
PLACEHOLDER = 0x40
# Merging nodes
# Unclear if they will be necessary
ENDIF = 0x50
STARTLOOP = 0x51
ENDLOOP = 0x52
# @staticmethod
def str(t):
@ -100,28 +102,124 @@ class Node(SourceMapping, ChildFunction):
def __init__(self, node_type, node_id):
super(Node, self).__init__()
self._node_type = node_type
# TODO: rename to explicit CFG
self._sons = []
self._fathers = []
## Dominators info
# Dominators nodes
self._dominators = set()
self._immediate_dominator = None
## Nodes of the dominators tree
#self._dom_predecessors = set()
self._dom_successors = set()
# Dominance frontier
self._dominance_frontier = set()
# Phi origin
# key are variable name
# values are list of Node
self._phi_origins_state_variables = {}
self._phi_origins_local_variables = {}
self._expression = None
self._variable_declaration = None
self._node_id = node_id
self._vars_written = []
self._vars_read = []
self._ssa_vars_written = []
self._ssa_vars_read = []
self._internal_calls = []
self._solidity_calls = []
self._high_level_calls = []
self._low_level_calls = []
self._external_calls_as_expressions = []
self._irs = []
self._irs_ssa = []
self._state_vars_written = []
self._state_vars_read = []
self._solidity_vars_read = []
self._ssa_state_vars_written = []
self._ssa_state_vars_read = []
self._local_vars_read = []
self._local_vars_written = []
self._ssa_local_vars_read = []
self._ssa_local_vars_written = []
self._expression_vars_written = []
self._expression_vars_read = []
self._expression_calls = []
@property
def dominators(self):
'''
Returns:
set(Node)
'''
return self._dominators
@property
def immediate_dominator(self):
'''
Returns:
Node or None
'''
return self._immediate_dominator
@property
def dominance_frontier(self):
'''
Returns:
set(Node)
'''
return self._dominance_frontier
@property
def dominator_successors(self):
return self._dom_successors
@dominators.setter
def dominators(self, dom):
self._dominators = dom
@immediate_dominator.setter
def immediate_dominator(self, idom):
self._immediate_dominator = idom
@dominance_frontier.setter
def dominance_frontier(self, dom):
self._dominance_frontier = dom
@property
def phi_origins_local_variables(self):
return self._phi_origins_local_variables
@property
def phi_origins_state_variables(self):
return self._phi_origins_state_variables
def add_phi_origin_local_variable(self, variable, node):
if variable.name not in self._phi_origins_local_variables:
self._phi_origins_local_variables[variable.name] = (variable, set())
(v, nodes) = self._phi_origins_local_variables[variable.name]
assert v == variable
nodes.add(node)
def add_phi_origin_state_variable(self, variable, node):
if variable.canonical_name not in self._phi_origins_state_variables:
self._phi_origins_state_variables[variable.canonical_name] = (variable, set())
(v, nodes) = self._phi_origins_state_variables[variable.canonical_name]
assert v == variable
nodes.add(node)
@property
def slither(self):
return self.function.slither
@ -156,6 +254,13 @@ class Node(SourceMapping, ChildFunction):
"""
return list(self._state_vars_read)
@property
def local_variables_read(self):
"""
list(LocalVariable): Local variables read
"""
return list(self._local_vars_read)
@property
def solidity_variables_read(self):
"""
@ -181,6 +286,13 @@ class Node(SourceMapping, ChildFunction):
"""
return list(self._state_vars_written)
@property
def local_variables_written(self):
"""
list(LocalVariable): Local variables written
"""
return list(self._local_vars_written)
@property
def variables_written_as_expression(self):
return self._expression_vars_written
@ -246,6 +358,7 @@ class Node(SourceMapping, ChildFunction):
self._variable_declaration = var
if var.expression:
self._vars_written += [var]
self._local_vars_written += [var]
@property
def variable_declaration(self):
@ -360,6 +473,25 @@ class Node(SourceMapping, ChildFunction):
"""
return self._irs
@property
def irs_ssa(self):
""" Returns the slithIR representation with SSA
return
list(slithIR.Operation)
"""
return self._irs_ssa
@irs_ssa.setter
def irs_ssa(self, irs):
self._irs_ssa = irs
def add_ssa_ir(self, ir):
'''
Use to place phi operation
'''
self._irs_ssa.append(ir)
def slithir_generation(self):
if self.expression:
expression = self.expression
@ -367,22 +499,22 @@ class Node(SourceMapping, ChildFunction):
self._find_read_write_call()
@staticmethod
def _is_slithir_var(var):
return isinstance(var, (Constant, ReferenceVariable, TemporaryVariable, TupleVariable))
def _find_read_write_call(self):
def is_slithir_var(var):
return isinstance(var, (Constant, ReferenceVariable, TemporaryVariable, TupleVariable))
for ir in self.irs:
self._vars_read += [v for v in ir.read if not is_slithir_var(v)]
self._vars_read += [v for v in ir.read if not self._is_slithir_var(v)]
if isinstance(ir, OperationWithLValue):
if isinstance(ir, (Index, Member, Length, Balance)):
continue # Don't consider Member and Index operations -> ReferenceVariable
var = ir.lvalue
# If its a reference, we loop until finding the origin
if isinstance(var, (ReferenceVariable)):
while isinstance(var, ReferenceVariable):
var = var.points_to
var = var.points_to_origin
# Only store non-slithIR variables
if not is_slithir_var(var) and var:
if not self._is_slithir_var(var) and var:
self._vars_written.append(var)
if isinstance(ir, InternalCall):
@ -405,11 +537,61 @@ class Node(SourceMapping, ChildFunction):
self._vars_read = list(set(self._vars_read))
self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)]
self._local_vars_read = [v for v in self._vars_read if isinstance(v, LocalVariable)]
self._solidity_vars_read = [v for v in self._vars_read if isinstance(v, SolidityVariable)]
self._vars_written = list(set(self._vars_written))
self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)]
self._local_vars_written = [v for v in self._vars_written if isinstance(v, LocalVariable)]
self._internal_calls = list(set(self._internal_calls))
self._solidity_calls = list(set(self._solidity_calls))
self._high_level_calls = list(set(self._high_level_calls))
self._low_level_calls = list(set(self._low_level_calls))
@staticmethod
def _convert_ssa(v):
if isinstance(v, StateIRVariable):
contract = v.contract
non_ssa_var = contract.get_state_variable_from_name(v.name)
return non_ssa_var
assert isinstance(v, LocalIRVariable)
function = v.function
non_ssa_var = function.get_local_variable_from_name(v.name)
return non_ssa_var
def update_read_write_using_ssa(self):
if not self.expression:
return
for ir in self.irs_ssa:
self._ssa_vars_read += [v for v in ir.read if isinstance(v,
(StateIRVariable,
LocalIRVariable))]
if isinstance(ir, OperationWithLValue):
if isinstance(ir, (Index, Member, Length, Balance)):
continue # Don't consider Member and Index operations -> ReferenceVariable
var = ir.lvalue
if isinstance(var, (ReferenceVariable)):
var = var.points_to_origin
# Only store non-slithIR variables
if var and isinstance(var, (StateIRVariable, LocalIRVariable)):
if isinstance(ir, (PhiCallback)):
continue
self._ssa_vars_written.append(var)
self._ssa_vars_read = list(set(self._ssa_vars_read))
self._ssa_state_vars_read = [v for v in self._ssa_vars_read if isinstance(v, StateVariable)]
self._ssa_local_vars_read = [v for v in self._ssa_vars_read if isinstance(v, LocalVariable)]
self._ssa_vars_written = list(set(self._ssa_vars_written))
self._ssa_state_vars_written = [v for v in self._ssa_vars_written if isinstance(v, StateVariable)]
self._ssa_local_vars_written = [v for v in self._ssa_vars_written if isinstance(v, LocalVariable)]
vars_read = [self._convert_ssa(x) for x in self._ssa_vars_read]
vars_written = [self._convert_ssa(x) for x in self._ssa_vars_written]
self._vars_read += [v for v in vars_read if v not in self._vars_read]
self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)]
self._local_vars_read = [v for v in self._vars_read if isinstance(v, LocalVariable)]
self._vars_written += [v for v in vars_written if v not in self._vars_written]
self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)]
self._local_vars_written = [v for v in self._vars_written if isinstance(v, LocalVariable)]

@ -31,6 +31,8 @@ class Contract(ChildSlither, SourceMapping):
self._kind = None
self._initial_state_variables = [] # ssa
def __eq__(self, other):
if isinstance(other, str):
return other == self.name
@ -189,7 +191,7 @@ class Contract(ChildSlither, SourceMapping):
'''
list(Function): List of functions reachable from the contract (include super)
'''
all_calls = (f.all_internal_calls() for f in self.functions)
all_calls = [f.all_internal_calls() for f in self.functions + self.modifiers]
all_calls = [item for sublist in all_calls for item in sublist] + self.functions
all_calls = list(set(all_calls))
@ -200,6 +202,23 @@ class Contract(ChildSlither, SourceMapping):
return [c for c in all_calls if isinstance(c, Function)]
@property
def all_state_variables_written(self):
'''
list(StateVariable): List all of the state variables written
'''
all_state_variables_written = [f.all_state_variables_written() for f in self.functions + self.modifiers]
all_state_variables_written = [item for sublist in all_state_variables_written for item in sublist]
return list(set(all_state_variables_written))
@property
def all_state_variables_read(self):
'''
list(StateVariable): List all of the state variables read
'''
all_state_variables_read = [f.all_state_variables_read() for f in self.functions + self.modifiers]
all_state_variables_read = [item for sublist in all_state_variables_read for item in sublist]
return list(set(all_state_variables_read))
def functions_as_dict(self):
return self._functions
@ -381,6 +400,10 @@ class Contract(ChildSlither, SourceMapping):
'transferFrom(address,address,uint256)' in full_names and\
'approve(address,uint256)' in full_names
def update_read_write_using_ssa(self):
for function in self.functions + self.modifiers:
function.update_read_write_using_ssa()
def get_summary(self):
""" Return the function summary

@ -63,7 +63,7 @@ class Function(ChildContract, SourceMapping):
@property
def return_type(self):
"""
Return the list of return type
Return the list of return type
If no return, return None
"""
returns = self.returns
@ -71,6 +71,14 @@ class Function(ChildContract, SourceMapping):
return [r.type for r in returns]
return None
@property
def type(self):
"""
Return the list of return type
If no return, return None
"""
return self.return_type
@property
def name(self):
"""
@ -618,9 +626,10 @@ class Function(ChildContract, SourceMapping):
with open(filename, 'w') as f:
f.write('digraph{\n')
for node in self.nodes:
label = 'Node Type: {}\n'.format(NodeType.str(node.type))
label = 'Node Type: {} {}\n'.format(NodeType.str(node.type), node.node_id)
if node.expression:
label += '\nEXPRESSION:\n{}\n'.format(node.expression)
if node.irs:
label += '\nIRs:\n' + '\n'.join([str(ir) for ir in node.irs])
f.write('{}[label="{}"];\n'.format(node.node_id, label))
for son in node.sons:
@ -628,6 +637,27 @@ class Function(ChildContract, SourceMapping):
f.write("}\n")
def dominator_tree_to_dot(self, filename):
"""
Export the dominator tree of the function to a dot file
Args:
filename (str)
"""
def description(node):
desc ='{}\n'.format(node)
desc += 'id: {}'.format(node.node_id)
if node.dominance_frontier:
desc += '\ndominance frontier: {}'.format([n.node_id for n in node.dominance_frontier])
return desc
with open(filename, 'w') as f:
f.write('digraph{\n')
for node in self.nodes:
f.write('{}[label="{}"];\n'.format(node.node_id, description(node)))
if node.immediate_dominator:
f.write('{}->{};\n'.format(node.immediate_dominator.node_id, node.node_id))
f.write("}\n")
def get_summary(self):
"""
Return the function summary
@ -659,3 +689,13 @@ class Function(ChildContract, SourceMapping):
conditional_vars = self.all_conditional_solidity_variables_read()
args_vars = self.all_solidity_variables_used_as_args()
return SolidityVariableComposed('msg.sender') in conditional_vars + args_vars
def get_local_variable_from_name(self, variable_name):
"""
Return a local variable from a name
Args:
varible_name (str): name of the variable
Returns:
LocalVariable
"""
return next((v for v in self.variables if v.name == variable_name), None)

@ -0,0 +1,37 @@
'''
Nodes of the dominator tree
'''
from slither.core.children.child_function import ChildFunction
class DominatorNode(object):
def __init__(self):
self._succ = set()
self._nodes = []
def add_node(self, node):
self._nodes.append(node)
def add_successor(self, succ):
self._succ.add(succ)
@property
def cfg_nodes(self):
return self._nodes
@property
def sucessors(self):
'''
Returns:
dict(Node)
'''
return self._succ
class DominatorTree(ChildFunction):
def __init__(self, entry_point):
super(DominatorTree, self).__init__()

@ -0,0 +1,68 @@
from slither.core.cfg.node import NodeType
def intersection_predecessor(node):
if not node.fathers:
return set()
ret = node.fathers[0].dominators
for pred in node.fathers[1:]:
ret = ret.intersection(pred.dominators)
return ret
def compute_dominators(nodes):
'''
Naive implementation of Cooper, Harvey, Kennedy algo
See 'A Simple,Fast Dominance Algorithm'
Compute strict domniators
'''
changed = True
for n in nodes:
n.dominators = set(nodes)
while changed:
changed = False
for node in nodes:
new_set = intersection_predecessor(node).union({node})
if new_set != node.dominators:
node.dominators = new_set
changed = True
# compute immediate dominator
for node in nodes:
idom_candidates = set(node.dominators)
idom_candidates.remove(node)
for dominator in node.dominators:
if dominator != node:
[idom_candidates.remove(d) for d in dominator.dominators if d in idom_candidates and d!=dominator]
assert len(idom_candidates)<=1
if idom_candidates:
idom = idom_candidates.pop()
node.immediate_dominator = idom
idom.dominator_successors.add(node)
def compute_dominance_frontier(nodes):
'''
Naive implementation of Cooper, Harvey, Kennedy algo
See 'A Simple,Fast Dominance Algorithm'
Compute dominance frontier
'''
for node in nodes:
if len(node.fathers) >= 2:
for father in node.fathers:
runner = father
# Corner case: if there is a if without else
# we need to add update the conditional node
if runner == node.immediate_dominator and runner.type == NodeType.IF and node.type == NodeType.ENDIF:
runner.dominance_frontier = runner.dominance_frontier.union({node})
while runner != node.immediate_dominator:
runner.dominance_frontier = runner.dominance_frontier.union({node})
runner = runner.immediate_dominator

@ -2,6 +2,7 @@ from .variable import Variable
from slither.core.children.child_function import ChildFunction
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.mapping_type import MappingType
from slither.core.declarations.structure import Structure
@ -12,7 +13,6 @@ class LocalVariable(ChildFunction, Variable):
super(LocalVariable, self).__init__()
self._location = None
def set_location(self, loc):
self._location = loc
@ -36,13 +36,17 @@ class LocalVariable(ChildFunction, Variable):
"""
if self.location == 'memory':
return False
# Use by slithIR SSA
if self.location == 'reference_to_storage':
return False
if self.location == 'storage':
return True
if isinstance(self.type, ArrayType):
if isinstance(self.type, (ArrayType, MappingType)):
return True
if isinstance(self.type, UserDefinedType):
return isinstance(self.type.type, Structure)
return False

@ -1,4 +1,9 @@
from .variable import Variable
from slither.core.children.child_contract import ChildContract
class StateVariable(ChildContract, Variable): pass
class StateVariable(ChildContract, Variable):
@property
def canonical_name(self):
return '{}:{}'.format(self.contract.name, self.name)

@ -11,11 +11,6 @@ class Variable(SourceMapping):
def __init__(self):
super(Variable, self).__init__()
self._name = None
self._typeName = None
self._arrayDepth = None
self._isMapping = None
self._mappingFrom = None
self._mappingTo = None
self._initial_expression = None
self._type = None
self._initialized = None
@ -58,6 +53,10 @@ class Variable(SourceMapping):
'''
return self._name
@name.setter
def name(self, name):
self._name = name
@property
def type(self):
return self._type
@ -82,3 +81,4 @@ class Variable(SourceMapping):
def __str__(self):
return self._name

@ -10,7 +10,6 @@ from slither.core.declarations import Function, SolidityFunction
from slither.core.expressions import UnaryOperation, UnaryOperationType
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.visitors.expression.export_values import ExportValues
from slither.slithir.operations import (HighLevelCall, LowLevelCall,
LibraryCall,
Send, Transfer)

@ -29,6 +29,10 @@ class PrinterSlithIR(AbstractPrinter):
print('\t\tIRs:')
for ir in node.irs:
print('\t\t\t{}'.format(ir))
elif node.irs:
print('\t\tIRs:')
for ir in node.irs:
print('\t\t\t{}'.format(ir))
for modifier in contract.modifiers:
if modifier.contract == contract:
print('\tModifier {}'.format(modifier.full_name))

@ -0,0 +1,44 @@
"""
Module printing summary of the contract
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.colors import blue, green, magenta
class PrinterSlithIRSSA(AbstractPrinter):
ARGUMENT = 'slithir-ssa'
HELP = 'Print the slithIR representation of the functions'
def output(self, _filename):
"""
_filename is not used
Args:
_filename(string)
"""
txt = ""
for contract in self.contracts:
print('Contract {}'.format(contract.name))
for function in contract.functions:
if function.contract == contract:
print('\tFunction {}'.format(function.full_name))
for node in function.nodes:
if node.expression:
print('\t\tExpression: {}'.format(node.expression))
if node.irs_ssa:
print('\t\tIRs:')
for ir in node.irs_ssa:
print('\t\t\t{}'.format(ir))
for modifier in contract.modifiers:
if modifier.contract == contract:
print('\tModifier {}'.format(modifier.full_name))
for node in modifier.nodes:
print(node)
if node.expression:
print('\t\tExpression: {}'.format(node.expression))
if node.irs_ssa:
print('\t\tIRs:')
for ir in node.irs_ssa:
print('\t\t\t{}'.format(ir))
self.info(txt)

@ -600,13 +600,9 @@ def apply_ir_heuristics(irs, node):
irs = integrate_value_gas(irs)
irs = propage_type_and_convert_call(irs, node)
# irs = remove_temporary(irs)
# irs = replace_calls(irs)
irs = remove_unused(irs)
find_references_origin(irs)
#reset_variable_number(irs)
return irs

@ -28,3 +28,5 @@ from .unary import Unary, UnaryType
from .unpack import Unpack
from .length import Length
from .balance import Balance
from .phi import Phi
from .phi_callback import PhiCallback

@ -2,7 +2,7 @@ import logging
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.core.variables.variable import Variable
from slither.slithir.variables import TupleVariable
from slither.slithir.variables import TupleVariable, ReferenceVariable
from slither.core.declarations.function import Function
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
@ -36,4 +36,9 @@ class Assignment(OperationWithLValue):
return self._rvalue
def __str__(self):
if isinstance(self.lvalue, ReferenceVariable):
points = self.lvalue.points_to
while isinstance(points, ReferenceVariable):
points = points.points_to
return '{} (->{}) := {}({})'.format(self.lvalue, points, self.rvalue, self.rvalue.type)
return '{}({}) := {}({})'.format(self.lvalue, self.lvalue.type, self.rvalue, self.rvalue.type)

@ -9,11 +9,11 @@ class Delete(OperationWithLValue):
of its operand
"""
def __init__(self, variable):
def __init__(self, lvalue, variable):
assert is_valid_lvalue(variable)
super(Delete, self).__init__()
self._variable = variable
self._lvalue = variable
self._lvalue = lvalue
@property
def read(self):

@ -13,7 +13,7 @@ class HighLevelCall(Call, OperationWithLValue):
def __init__(self, destination, function_name, nbr_arguments, result, type_call):
assert isinstance(function_name, Constant)
assert is_valid_lvalue(result)
assert is_valid_lvalue(result) or result is None
self._check_destination(destination)
super(HighLevelCall, self).__init__()
self._destination = destination
@ -60,7 +60,7 @@ class HighLevelCall(Call, OperationWithLValue):
def read(self):
all_read = [self.destination, self.call_gas, self.call_value] + self._unroll(self.arguments)
# remove None
return [x for x in all_read if x]
return [x for x in all_read if x] + [self.destination]
@property
def destination(self):

@ -33,5 +33,9 @@ class Index(OperationWithLValue):
def variable_right(self):
return self._variables[1]
@property
def index_type(self):
return self._type
def __str__(self):
return "{}({}) -> {}[{}]".format(self.lvalue, self.lvalue.type, self.variable_left, self.variable_right)

@ -11,7 +11,7 @@ class InternalDynamicCall(Call, OperationWithLValue):
def __init__(self, lvalue, function, function_type):
assert isinstance(function_type, FunctionType)
assert isinstance(function, Variable)
assert is_valid_lvalue(lvalue)
assert is_valid_lvalue(lvalue) or lvalue is None
super(InternalDynamicCall, self).__init__()
self._function = function
self._function_type = function_type

@ -0,0 +1,40 @@
import logging
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.core.variables.variable import Variable
from slither.slithir.variables import TupleVariable
from slither.core.declarations.function import Function
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
class Phi(OperationWithLValue):
def __init__(self, left_variable, nodes):
# When Phi operations are created the
# correct indexes of the variables are not yet computed
# We store the nodes where the variables are written
# so we can update the rvalues of the Phi operation
# after its instantiation
assert is_valid_lvalue(left_variable)
assert isinstance(nodes, set)
super(Phi, self).__init__()
self._lvalue = left_variable
self._rvalues = []
self._nodes = nodes
@property
def read(self):
return self.rvalues
@property
def rvalues(self):
return self._rvalues
@rvalues.setter
def rvalues(self, vals):
self._rvalues = vals
@property
def nodes(self):
return self._nodes
def __str__(self):
return '{}({}) := \u03D5({})'.format(self.lvalue, self.lvalue.type, [str(v) for v in self._rvalues])

@ -0,0 +1,47 @@
import logging
from slither.core.variables.variable import Variable
from slither.slithir.variables import TupleVariable
from slither.core.declarations.function import Function
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
from .phi import Phi
class PhiCallback(Phi):
def __init__(self, left_variable, nodes, call_ir, rvalue):
assert is_valid_lvalue(left_variable)
assert isinstance(nodes, set)
super(PhiCallback, self).__init__(left_variable, nodes)
self._call_ir = call_ir
self._rvalues = [rvalue]
self._rvalue_no_callback = rvalue
@property
def callee_ir(self):
return self._call_ir
@property
def read(self):
return self.rvalues
@property
def rvalues(self):
return self._rvalues
@property
def rvalue_no_callback(self):
'''
rvalue if callback are not considered
'''
return self._rvalue_no_callback
@rvalues.setter
def rvalues(self, vals):
self._rvalues = vals
@property
def nodes(self):
return self._nodes
def __str__(self):
return '{}({}) := \u03D5({})'.format(self.lvalue, self.lvalue.type, [v.ssa_name for v in self._rvalues])

@ -48,6 +48,10 @@ class Unary(OperationWithLValue):
def rvalue(self):
return self._variable
@property
def type(self):
return self._type
@property
def type_str(self):
return UnaryType.str(self._type)

@ -0,0 +1,570 @@
import logging
from slither.core.cfg.node import NodeType
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
from slither.slithir.operations import (Assignment, Balance, Binary,
BinaryType, Condition, Delete,
EventCall, HighLevelCall, Index,
InitArray, InternalCall,
InternalDynamicCall, Length,
LibraryCall, LowLevelCall, Member,
NewArray, NewContract,
NewElementaryType, NewStructure,
OperationWithLValue, Phi, PhiCallback, Push, Return,
Send, SolidityCall, Transfer,
TypeConversion, Unary, Unpack)
from slither.slithir.variables import (Constant, LocalIRVariable, StateIRVariable,
ReferenceVariable, TemporaryVariable,
TupleVariable)
logger = logging.getLogger('SSA_Conversion')
def transform_slithir_vars_to_ssa(function):
"""
Transform slithIR vars to SSA
"""
variables = []
for node in function.nodes:
for ir in node.irs_ssa:
if isinstance(ir, OperationWithLValue) and not ir.lvalue in variables:
variables += [ir.lvalue]
tmp_variables = [v for v in variables if isinstance(v, TemporaryVariable)]
for idx in range(len(tmp_variables)):
tmp_variables[idx].index = idx
ref_variables = [v for v in variables if isinstance(v, ReferenceVariable)]
for idx in range(len(ref_variables)):
ref_variables[idx].index = idx
tuple_variables = [v for v in variables if isinstance(v, TupleVariable)]
for idx in range(len(tuple_variables)):
tuple_variables[idx].index = idx
def add_ssa_ir(function, all_state_variables_instances):
'''
Add SSA version of the IR
Args:
function
all_state_variables_instances
'''
if not function.is_implemented:
return
init_definition = dict()
for v in function.parameters+function.returns:
if v.name:
init_definition[v.name] = (v, function.entry_point)
# We only add phi function for state variable at entry node if
# The state variable is used
# And if the state variables is written in another function (otherwise its stay at index 0)
for (_, variable_instance) in all_state_variables_instances.items():
if is_used_later(function.entry_point, variable_instance):
# rvalues are fixed in solc_parsing.declaration.function
function.entry_point.add_ssa_ir(Phi(StateIRVariable(variable_instance), set()))
add_phi_origins(function.entry_point, init_definition, dict())
for node in function.nodes:
for (variable, nodes) in node.phi_origins_local_variables.values():
if len(nodes)<2:
continue
if not is_used_later(node, variable):
continue
node.add_ssa_ir(Phi(LocalIRVariable(variable), nodes))
for (variable, nodes) in node.phi_origins_state_variables.values():
if len(nodes)<2:
continue
#if not is_used_later(node, variable.name, []):
# continue
node.add_ssa_ir(Phi(StateIRVariable(variable), nodes))
init_local_variables_instances = dict()
for v in function.parameters+function.returns:
if v.name:
new_var = LocalIRVariable(v)
if new_var.is_storage:
fake_variable = LocalIRVariable(v)
fake_variable.name = 'STORAGE_'+fake_variable.name
fake_variable.set_location('reference_to_storage')
new_var.refers_to = {fake_variable}
init_local_variables_instances[fake_variable.name] = fake_variable
init_local_variables_instances[v.name] = new_var
all_init_local_variables_instances = dict(init_local_variables_instances)
init_state_variables_instances = dict(all_state_variables_instances)
generate_ssa_irs(function.entry_point,
dict(init_local_variables_instances),
all_init_local_variables_instances,
dict(init_state_variables_instances),
all_state_variables_instances,
init_local_variables_instances,
[])
fix_phi_rvalues_and_storage_ref(function.entry_point,
dict(init_local_variables_instances),
all_init_local_variables_instances,
dict(init_state_variables_instances),
all_state_variables_instances,
init_local_variables_instances)
def last_name(n, var, init_vars):
candidates = []
# Todo optimize by creating a variables_ssa_written attribute
for ir_ssa in n.irs_ssa:
if isinstance(ir_ssa, OperationWithLValue):
lvalue = ir_ssa.lvalue
while isinstance(lvalue, ReferenceVariable):
lvalue = lvalue.points_to
if lvalue and lvalue.name == var.name:
candidates.append(lvalue)
if n.variable_declaration and n.variable_declaration.name == var.name:
candidates.append(LocalIRVariable(n.variable_declaration))
if n.type == NodeType.ENTRYPOINT:
if var.name in init_vars:
candidates.append(init_vars[var.name])
assert candidates
return max(candidates, key=lambda v: v.index)
def update_lvalue(new_ir, node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances):
if isinstance(new_ir, OperationWithLValue):
lvalue = new_ir.lvalue
update_through_ref = False
if isinstance(new_ir, Assignment):
if isinstance(lvalue, ReferenceVariable):
update_through_ref = True
while isinstance(lvalue, ReferenceVariable):
lvalue = lvalue.points_to
if isinstance(lvalue, (LocalIRVariable, StateIRVariable)):
if isinstance(lvalue, LocalIRVariable):
new_var = LocalIRVariable(lvalue)
new_var.index = all_local_variables_instances[lvalue.name].index + 1
all_local_variables_instances[lvalue.name] = new_var
local_variables_instances[lvalue.name] = new_var
else:
new_var = StateIRVariable(lvalue)
new_var.index = all_state_variables_instances[lvalue.canonical_name].index + 1
all_state_variables_instances[lvalue.canonical_name] = new_var
state_variables_instances[lvalue.canonical_name] = new_var
if update_through_ref:
phi_operation = Phi(new_var, {node})
phi_operation.rvalues = [lvalue]
node.add_ssa_ir(phi_operation)
if not isinstance(new_ir.lvalue, ReferenceVariable):
new_ir.lvalue = new_var
else:
to_update = new_ir.lvalue
while isinstance(to_update.points_to, ReferenceVariable):
to_update = to_update.points_to
to_update.points_to = new_var
def is_used_later(initial_node, variable):
# TODO: does not handle the case where its read and written in the declaration node
# It can be problematic if this happens in a loop/if structure
# Ex:
# for(;true;){
# if(true){
# uint a = a;
# }
# ..
to_explore = {initial_node}
explored = set()
while to_explore:
node = to_explore.pop()
explored.add(node)
if isinstance(variable, LocalVariable):
if any(v.name == variable.name for v in node.local_variables_read):
return True
if any(v.name == variable.name for v in node.local_variables_written):
return False
if isinstance(variable, StateVariable):
if any(v.name == variable.name and v.contract == variable.contract for v in node.state_variables_read):
return True
if any(v.name == variable.name and v.contract == variable.contract for v in node.state_variables_written):
return False
for son in node.sons:
if not son in explored:
to_explore.add(son)
return False
def generate_ssa_irs(node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances, init_local_variables_instances, visited):
if node in visited:
return
if node.type in [NodeType.ENDIF, NodeType.ENDLOOP] and any(not father in visited for father in node.fathers):
return
# visited is shared
visited.append(node)
if node.variable_declaration:
new_var = LocalIRVariable(node.variable_declaration)
if new_var.name in all_local_variables_instances:
new_var.index = all_local_variables_instances[new_var.name].index + 1
local_variables_instances[node.variable_declaration.name] = new_var
all_local_variables_instances[node.variable_declaration.name] = new_var
for ir in node.irs_ssa:
assert isinstance(ir, Phi)
update_lvalue(ir, node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances)
# these variables are lived only during the liveness of the block
# They dont need phi function
temporary_variables_instances = dict()
reference_variables_instances = dict()
for ir in node.irs:
new_ir = copy_ir(ir,
local_variables_instances,
state_variables_instances,
temporary_variables_instances,
reference_variables_instances,
all_local_variables_instances)
update_lvalue(new_ir,
node,
local_variables_instances,
all_local_variables_instances,
state_variables_instances,
all_state_variables_instances)
if new_ir:
node.add_ssa_ir(new_ir)
if isinstance(ir, (InternalCall, HighLevelCall, InternalDynamicCall, LowLevelCall)):
if isinstance(ir, LibraryCall):
continue
for variable in all_state_variables_instances.values():
if not is_used_later(node, variable):
continue
new_var = StateIRVariable(variable)
new_var.index = all_state_variables_instances[variable.canonical_name].index + 1
all_state_variables_instances[variable.canonical_name] = new_var
state_variables_instances[variable.canonical_name] = new_var
phi_ir = PhiCallback(new_var, {node}, new_ir, variable)
# rvalues are fixed in solc_parsing.declaration.function
node.add_ssa_ir(phi_ir)
if isinstance(new_ir, Assignment):
if isinstance(new_ir.lvalue, LocalIRVariable):
if new_ir.lvalue.is_storage:
if isinstance(new_ir.rvalue, ReferenceVariable):
refers_to = new_ir.rvalue.points_to_origin
new_ir.lvalue.add_refers_to(refers_to)
else:
new_ir.lvalue.add_refers_to(new_ir.rvalue)
for succ in node.dominator_successors:
generate_ssa_irs(succ,
dict(local_variables_instances),
all_local_variables_instances,
dict(state_variables_instances),
all_state_variables_instances,
init_local_variables_instances,
visited)
for dominated in node.dominance_frontier:
generate_ssa_irs(dominated,
dict(local_variables_instances),
all_local_variables_instances,
dict(state_variables_instances),
all_state_variables_instances,
init_local_variables_instances,
visited)
def fix_phi_rvalues_and_storage_ref(node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances, init_local_variables_instances):
for ir in node.irs_ssa:
if isinstance(ir, (Phi)) and not ir.rvalues:
variables = [last_name(dst, ir.lvalue, init_local_variables_instances) for dst in ir.nodes]
ir.rvalues = variables
if isinstance(ir, (Phi, PhiCallback)):
if isinstance(ir.lvalue, LocalIRVariable):
if ir.lvalue.is_storage:
l = [v.refers_to for v in ir.rvalues]
l = [item for sublist in l for item in sublist]
ir.lvalue.refers_to = set(l)
if isinstance(ir, Assignment):
if isinstance(ir.lvalue, ReferenceVariable):
origin = ir.lvalue.points_to_origin
if isinstance(origin, LocalIRVariable):
if origin.is_storage:
for refers_to in origin.refers_to:
phi_ir = Phi(refers_to, {node})
phi_ir.rvalues = [origin]
node.add_ssa_ir(phi_ir)
update_lvalue(phi_ir, node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances)
for succ in node.dominator_successors:
fix_phi_rvalues_and_storage_ref(succ, dict(local_variables_instances), all_local_variables_instances, dict(state_variables_instances), all_state_variables_instances, init_local_variables_instances)
def add_phi_origins(node, local_variables_definition, state_variables_definition):
# Add new key to local_variables_definition
# The key is the variable_name
# The value is (variable_instance, the node where its written)
# We keep the instance as we want to avoid to add __hash__ on v.name in Variable
# That might work for this used, but could create collision for other uses
local_variables_definition = dict(local_variables_definition,
**{v.name: (v, node) for v in node.local_variables_written})
state_variables_definition = dict(state_variables_definition,
**{v.canonical_name: (v, node) for v in node.state_variables_written})
# For unini variable declaration
if node.variable_declaration and\
not node.variable_declaration.name in local_variables_definition:
local_variables_definition[node.variable_declaration.name] = (node.variable_declaration, node)
# filter length of successors because we have node with one successor
# while most of the ssa textbook would represent following nodes as one
if node.dominance_frontier and len(node.dominator_successors) != 1:
for phi_node in node.dominance_frontier:
for _, (variable, n) in local_variables_definition.items():
phi_node.add_phi_origin_local_variable(variable, n)
for _, (variable, n) in state_variables_definition.items():
phi_node.add_phi_origin_state_variable(variable, n)
if not node.dominator_successors:
return
for succ in node.dominator_successors:
add_phi_origins(succ, local_variables_definition, state_variables_definition)
def copy_ir(ir, local_variables_instances, state_variables_instances, temporary_variables_instances, reference_variables_instances, all_local_variables_instances):
'''
Args:
ir (Operation)
local_variables_instances(dict(str -> LocalVariable))
state_variables_instances(dict(str -> StateVariable))
temporary_variables_instances(dict(int -> Variable))
reference_variables_instances(dict(int -> Variable))
Note: temporary and reference can be indexed by int, as they dont need phi functions
'''
def get(variable):
if isinstance(variable, LocalVariable):
if variable.name in local_variables_instances:
return local_variables_instances[variable.name]
new_var = LocalIRVariable(variable)
local_variables_instances[variable.name] = new_var
all_local_variables_instances[variable.name] = new_var
return new_var
if isinstance(variable, StateVariable) and variable.canonical_name in state_variables_instances:
return state_variables_instances[variable.canonical_name]
elif isinstance(variable, ReferenceVariable):
if not variable.index in reference_variables_instances:
new_variable = ReferenceVariable(variable.node, index=variable.index)
if variable.points_to:
new_variable.points_to = get(variable.points_to)
new_variable.set_type(variable.type)
reference_variables_instances[variable.index] = new_variable
return reference_variables_instances[variable.index]
elif isinstance(variable, TemporaryVariable):
if not variable.index in temporary_variables_instances:
new_variable = TemporaryVariable(variable.node, index=variable.index)
new_variable.set_type(variable.type)
temporary_variables_instances[variable.index] = new_variable
return temporary_variables_instances[variable.index]
return variable
def get_variable(ir, f):
variable = f(ir)
variable = get(variable)
return variable
def get_arguments(ir):
arguments = []
for arg in ir.arguments:
arg = get(arg)
arguments.append(arg)
return arguments
def get_rec_values(ir, f):
# Use by InitArray and NewArray
# Potential recursive array(s)
ori_init_values = f(ir)
def traversal(values):
ret = []
for v in values:
if isinstance(v, list):
v = traversal(v)
else:
v = get(v)
ret.append(v)
return ret
return traversal(ori_init_values)
if isinstance(ir, Assignment):
lvalue = get_variable(ir, lambda x: ir.lvalue)
rvalue = get_variable(ir, lambda x: ir.rvalue)
variable_return_type = ir.variable_return_type
return Assignment(lvalue, rvalue, variable_return_type)
elif isinstance(ir, Balance):
lvalue = get_variable(ir, lambda x: ir.lvalue)
value = get_variable(ir, lambda x: ir.value)
return Balance(value, lvalue)
elif isinstance(ir, Binary):
lvalue = get_variable(ir, lambda x: ir.lvalue)
variable_left = get_variable(ir, lambda x: ir.variable_left)
variable_right = get_variable(ir, lambda x: ir.variable_right)
operation_type = ir.type
return Binary(lvalue, variable_left, variable_right, operation_type)
elif isinstance(ir, Condition):
val = get_variable(ir, lambda x: ir.value)
return Condition(val)
elif isinstance(ir, Delete):
lvalue = get_variable(ir, lambda x: ir.lvalue)
variable = get_variable(ir, lambda x: ir.variable)
return Delete(lvalue, variable)
elif isinstance(ir, EventCall):
name = ir.name
return EventCall(name)
elif isinstance(ir, HighLevelCall): # include LibraryCall
destination = get_variable(ir, lambda x: ir.destination)
function_name = ir.function_name
nbr_arguments = ir.nbr_arguments
lvalue = get_variable(ir, lambda x: ir.lvalue)
type_call = ir.type_call
if isinstance(ir, LibraryCall):
new_ir = LibraryCall(destination, function_name, nbr_arguments, lvalue, type_call)
else:
new_ir = HighLevelCall(destination, function_name, nbr_arguments, lvalue, type_call)
new_ir.call_id = ir.call_id
new_ir.call_value = get_variable(ir, lambda x: ir.call_value)
new_ir.call_gas = get_variable(ir, lambda x: ir.call_gas)
new_ir.arguments = get_arguments(ir)
new_ir.function_instance = ir.function
return new_ir
elif isinstance(ir, Index):
lvalue = get_variable(ir, lambda x: ir.lvalue)
variable_left = get_variable(ir, lambda x: ir.variable_left)
variable_right = get_variable(ir, lambda x: ir.variable_right)
index_type = ir.index_type
return Index(lvalue, variable_left, variable_right, index_type)
elif isinstance(ir, InitArray):
lvalue = get_variable(ir, lambda x: ir.lvalue)
init_values = get_rec_values(ir, lambda x: ir.init_values)
return InitArray(init_values, lvalue)
elif isinstance(ir, InternalCall):
function = ir.function
nbr_arguments = ir.nbr_arguments
lvalue = get_variable(ir, lambda x: ir.lvalue)
type_call = ir.type_call
new_ir = InternalCall(function, nbr_arguments, lvalue, type_call)
new_ir.arguments = get_arguments(ir)
return new_ir
elif isinstance(ir, InternalDynamicCall):
lvalue = get_variable(ir, lambda x: ir.lvalue)
function = ir.function
function_type = ir.function_type
new_ir = InternalDynamicCall(lvalue, function, function_type)
new_ir.arguments = get_arguments(ir)
return new_ir
elif isinstance(ir, LowLevelCall):
destination = get_variable(ir, lambda x: x.destination)
function_name = ir.function_name
nbr_arguments = ir.nbr_arguments
lvalue = get_variable(ir, lambda x: ir.lvalue)
type_call = ir.type_call
new_ir = LowLevelCall(destination, function_name, nbr_arguments, lvalue, type_call)
new_ir.call_id = ir.call_id
new_ir.call_value = get_variable(ir, lambda x: ir.call_value)
new_ir.call_gas = get_variable(ir, lambda x: ir.call_gas)
new_ir.arguments = get_arguments(ir)
return new_ir
elif isinstance(ir, Member):
lvalue = get_variable(ir, lambda x: ir.lvalue)
variable_left = get_variable(ir, lambda x: ir.variable_left)
variable_right = get_variable(ir, lambda x: ir.variable_right)
return Member(variable_left, variable_right, lvalue)
elif isinstance(ir, NewArray):
depth = ir.depth
array_type = ir.array_type
lvalue = get_variable(ir, lambda x: ir.lvalue)
new_ir = NewArray(depth, array_type, lvalue)
new_ir.arguments = get_rec_values(ir, lambda x: ir.arguments)
return new_ir
elif isinstance(ir, NewElementaryType):
new_type = ir.type
lvalue = get_variable(ir, lambda x: ir.lvalue)
new_ir = NewElementaryType(new_type, lvalue)
new_ir.arguments = get_arguments(ir)
return new_ir
elif isinstance(ir, NewContract):
contract_name = ir.contract_name
lvalue = get_variable(ir, lambda x: ir.lvalue)
new_ir = NewContract(contract_name, lvalue)
new_ir.arguments = get_arguments(ir)
return new_ir
elif isinstance(ir, NewStructure):
structure = ir.structure
lvalue = get_variable(ir, lambda x: ir.lvalue)
new_ir = NewStructure(structure, lvalue)
new_ir.arguments = get_arguments(ir)
return new_ir
elif isinstance(ir, Push):
array = get_variable(ir, lambda x: ir.array)
lvalue = get_variable(ir, lambda x: ir.lvalue)
return Push(array, lvalue)
elif isinstance(ir, Return):
value = get_variable(ir, lambda x: ir.values)
return Return(value)
elif isinstance(ir, Send):
destination = get_variable(ir, lambda x: ir.destination)
value = get_variable(ir, lambda x: ir.call_value)
lvalue = get_variable(ir, lambda x: ir.lvalue)
return Send(destination, value, lvalue)
elif isinstance(ir, SolidityCall):
function = ir.function
nbr_arguments = ir.nbr_arguments
lvalue = get_variable(ir, lambda x: ir.lvalue)
type_call = ir.type_call
new_ir = SolidityCall(function, nbr_arguments, lvalue, type_call)
new_ir.arguments = get_arguments(ir)
return new_ir
elif isinstance(ir, Transfer):
destination = get_variable(ir, lambda x: ir.destination)
value = get_variable(ir, lambda x: ir.call_value)
return Transfer(destination, value)
elif isinstance(ir, TypeConversion):
lvalue = get_variable(ir, lambda x: ir.lvalue)
variable = get_variable(ir, lambda x: ir.variable)
variable_type = ir.type
return TypeConversion(lvalue, variable, variable_type)
elif isinstance(ir, Unary):
lvalue = get_variable(ir, lambda x: ir.lvalue)
rvalue = get_variable(ir, lambda x: ir.rvalue)
operation_type = ir.type
return Unary(lvalue, rvalue, operation_type)
elif isinstance(ir, Unpack):
lvalue = get_variable(ir, lambda x: ir.lvalue)
tuple_var = ir.tuple
idx = ir.index
return Unpack(lvalue, tuple_var, idx)
elif isinstance(ir, Length):
lvalue = get_variable(ir, lambda x: ir.lvalue)
value = get_variable(ir, lambda x: ir.value)
return Length(value, lvalue)
logger.error('Impossible ir copy on {} ({})'.format(ir, type(ir)))
exit(-1)

@ -1,23 +0,0 @@
from slither.slithir.variables import (Constant, ReferenceVariable,
TemporaryVariable, TupleVariable)
from slither.slithir.operations import OperationWithLValue
def transform_slithir_vars_to_ssa(function):
"""
Transform slithIR vars to SSA
"""
variables = []
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, OperationWithLValue) and not ir.lvalue in variables:
variables += [ir.lvalue]
tmp_variables = [v for v in variables if isinstance(v, TemporaryVariable)]
for idx in range(len(tmp_variables)):
tmp_variables[idx].index = idx
ref_variables = [v for v in variables if isinstance(v, ReferenceVariable)]
for idx in range(len(ref_variables)):
ref_variables[idx].index = idx
tuple_variables = [v for v in variables if isinstance(v, TupleVariable)]
for idx in range(len(tuple_variables)):
tuple_variables[idx].index = idx

@ -2,3 +2,5 @@ from .constant import Constant
from .reference import ReferenceVariable
from .temporary import TemporaryVariable
from .tuple import TupleVariable
from .local_variable import LocalIRVariable
from .state_variable import StateIRVariable

@ -1,7 +1,7 @@
from slither.core.variables.variable import Variable
from .variable import SlithIRVariable
from slither.core.solidity_types.elementary_type import ElementaryType
class Constant(Variable):
class Constant(SlithIRVariable):
def __init__(self, val):
super(Constant, self).__init__()
@ -27,6 +27,9 @@ class Constant(Variable):
def __str__(self):
return str(self.value)
@property
def name(self):
return str(self)
def __eq__(self, other):
return self.value == other

@ -0,0 +1,64 @@
from .variable import SlithIRVariable
from .temporary import TemporaryVariable
from slither.core.variables.local_variable import LocalVariable
from slither.core.children.child_node import ChildNode
class LocalIRVariable(LocalVariable, SlithIRVariable):
def __init__(self, local_variable):
assert isinstance(local_variable, LocalVariable)
super(LocalIRVariable, self).__init__()
# initiate ChildContract
self.set_function(local_variable.function)
# initiate Variable
self._name = local_variable.name
self._initial_expression = local_variable.expression
self._type = local_variable.type
self._initialized = local_variable.initialized
self._visibility = local_variable.visibility
self._is_constant = local_variable.is_constant
# initiate LocalVariable
self._location = self.location
self._index = 0
# Additional field
# points to state variables
self._refers_to = set()
@property
def index(self):
return self._index
@index.setter
def index(self, idx):
self._index = idx
@property
def refers_to(self):
if self.is_storage:
return self._refers_to
return set()
@refers_to.setter
def refers_to(self, variables):
self._refers_to = variables
def add_refers_to(self, variable):
# It is a temporaryVariable if its the return of a new ..
# ex: string[] memory dynargs = new string[](1);
assert isinstance(variable, (SlithIRVariable, TemporaryVariable))
self._refers_to.add(variable)
@property
def ssa_name(self):
if self.is_storage:
return '{}_{} (-> {})'.format(self._name,
self.index,
[v.name for v in self.refers_to])
return '{}_{}'.format(self._name, self.index)

@ -1,17 +1,21 @@
from .variable import SlithIRVariable
from slither.core.children.child_node import ChildNode
from slither.core.declarations import Contract, Enum, SolidityVariable
from slither.core.variables.variable import Variable
from slither.core.declarations import Contract, Enum, SolidityVariable
class ReferenceVariable(ChildNode, Variable):
COUNTER = 0
def __init__(self, node):
def __init__(self, node, index=None):
super(ReferenceVariable, self).__init__()
self._index = ReferenceVariable.COUNTER
ReferenceVariable.COUNTER += 1
if index is None:
self._index = ReferenceVariable.COUNTER
ReferenceVariable.COUNTER += 1
else:
self._index = index
self._points_to = None
self._node = node
@ -31,6 +35,13 @@ class ReferenceVariable(ChildNode, Variable):
"""
return self._points_to
@property
def points_to_origin(self):
points = self.points_to
while isinstance(points, ReferenceVariable):
points = points.points_to
return points
@points_to.setter
def points_to(self, points_to):
# Can only be a rvalue of

@ -0,0 +1,36 @@
from .variable import SlithIRVariable
from slither.core.variables.state_variable import StateVariable
from slither.core.children.child_node import ChildNode
class StateIRVariable(StateVariable, SlithIRVariable):
def __init__(self, state_variable):
assert isinstance(state_variable, StateVariable)
super(StateVariable, self).__init__()
# initiate ChildContract
self.set_contract(state_variable.contract)
# initiate Variable
self._name = state_variable.name
self._initial_expression = state_variable.expression
self._type = state_variable.type
self._initialized = state_variable.initialized
self._visibility = state_variable.visibility
self._is_constant = state_variable.is_constant
self._index = 0
@property
def index(self):
return self._index
@index.setter
def index(self, idx):
self._index = idx
@property
def ssa_name(self):
return '{}_{}'.format(self._name, self.index)

@ -1,4 +1,5 @@
from .variable import SlithIRVariable
from slither.core.variables.variable import Variable
from slither.core.children.child_node import ChildNode
@ -6,12 +7,16 @@ class TemporaryVariable(ChildNode, Variable):
COUNTER = 0
def __init__(self, node):
def __init__(self, node, index=None):
super(TemporaryVariable, self).__init__()
self._index = TemporaryVariable.COUNTER
TemporaryVariable.COUNTER += 1
if index is None:
self._index = TemporaryVariable.COUNTER
TemporaryVariable.COUNTER += 1
else:
self._index = index
self._node = node
@property
def index(self):
return self._index

@ -1,8 +1,8 @@
from .variable import SlithIRVariable
from slither.core.variables.variable import Variable
from slither.core.solidity_types.type import Type
class TupleVariable(Variable):
class TupleVariable(SlithIRVariable):
COUNTER = 0

@ -0,0 +1,14 @@
from slither.core.variables.variable import Variable
class SlithIRVariable(Variable):
def __init__(self):
super(SlithIRVariable, self).__init__()
self._index = 0
@property
def ssa_name(self):
return self.name
def __str__(self):
return self.ssa_name

@ -9,9 +9,10 @@ from slither.solc_parsing.declarations.modifier import ModifierSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.variables.state_variable import StateVariableSolc
from slither.solc_parsing.solidity_types.type_parsing import parse_type
from slither.slithir.variables import StateIRVariable
logger = logging.getLogger("ContractSolcParsing")
class ContractSolc04(Contract):
@ -347,5 +348,46 @@ class ContractSolc04(Contract):
function.analyze_content()
return
def convert_expression_to_slithir(self):
for func in self.functions + self.modifiers:
if func.contract == self:
func.generate_slithir_and_analyze()
all_ssa_state_variables_instances = dict()
for contract in self.inheritance:
for v in contract.variables:
if v.contract == contract:
new_var = StateIRVariable(v)
all_ssa_state_variables_instances[v.canonical_name] = new_var
self._initial_state_variables.append(new_var)
for v in self.variables:
if v.contract == self:
new_var = StateIRVariable(v)
all_ssa_state_variables_instances[v.canonical_name] = new_var
self._initial_state_variables.append(new_var)
for func in self.functions + self.modifiers:
if func.contract == self:
func.generate_slithir_ssa(all_ssa_state_variables_instances)
def fix_phi(self):
last_state_variables_instances = dict()
initial_state_variables_instances = dict()
for v in self._initial_state_variables:
last_state_variables_instances[v.canonical_name] = []
initial_state_variables_instances[v.canonical_name] = v
for func in self.functions + self.modifiers:
result = func.get_last_ssa_state_variables_instances()
for variable_name, instances in result.items():
last_state_variables_instances[variable_name] += instances
for func in self.functions + self.modifiers:
func.fix_phi(last_state_variables_instances, initial_state_variables_instances)
def __hash__(self):
return self._id

@ -2,26 +2,31 @@
Event module
"""
import logging
from slither.core.cfg.node import NodeType, link_nodes
from slither.core.declarations.function import Function
from slither.core.cfg.node import NodeType
from slither.core.dominators.utils import (compute_dominance_frontier,
compute_dominators)
from slither.core.expressions import AssignmentOperation
from slither.core.variables.state_variable import StateVariable
from slither.slithir.operations import (Assignment, HighLevelCall,
InternalCall, InternalDynamicCall,
LowLevelCall, OperationWithLValue, Phi,
PhiCallback, LibraryCall)
from slither.slithir.utils.ssa import add_ssa_ir, transform_slithir_vars_to_ssa
from slither.slithir.variables import LocalIRVariable, ReferenceVariable
from slither.solc_parsing.cfg.node import NodeSolc
from slither.core.cfg.node import NodeType
from slither.core.cfg.node import link_nodes
from slither.solc_parsing.expressions.expression_parsing import \
parse_expression
from slither.solc_parsing.variables.local_variable import LocalVariableSolc
from slither.solc_parsing.variables.local_variable_init_from_tuple import LocalVariableInitFromTupleSolc
from slither.solc_parsing.variables.variable_declaration import MultipleVariablesDeclaration
from slither.solc_parsing.expressions.expression_parsing import parse_expression
from slither.core.expressions import AssignmentOperation
from slither.solc_parsing.variables.local_variable_init_from_tuple import \
LocalVariableInitFromTupleSolc
from slither.solc_parsing.variables.variable_declaration import \
MultipleVariablesDeclaration
from slither.utils.expression_manipulations import SplitTernaryExpression
from slither.visitors.expression.export_values import ExportValues
from slither.visitors.expression.has_conditional import HasConditional
from slither.utils.expression_manipulations import SplitTernaryExpression
from slither.slithir.utils.variable_number import transform_slithir_vars_to_ssa
logger = logging.getLogger("FunctionSolc")
class FunctionSolc(Function):
@ -840,14 +845,100 @@ class FunctionSolc(Function):
break
self._remove_alone_endif()
def get_last_ssa_state_variables_instances(self):
if not self.is_implemented:
return dict()
last_instances = dict()
# node, values
to_explore = [(self._entry_point, dict())]
# node -> values
explored = dict()
# name -> instances
ret = dict()
while to_explore:
node, values = to_explore[0]
to_explore = to_explore[1::]
# Return condition
if not node.sons and node.type != NodeType.THROW:
for name, instances in values.items():
if name not in ret:
ret[name] = set()
ret[name] |= instances
if node.type != NodeType.ENTRYPOINT:
for ir_ssa in node.irs_ssa:
if isinstance(ir_ssa, OperationWithLValue):
lvalue = ir_ssa.lvalue
if isinstance(lvalue, ReferenceVariable):
lvalue = lvalue.points_to_origin
if isinstance(lvalue, StateVariable):
values[lvalue.canonical_name] = {lvalue}
# Check for fixpoint
if node in explored:
if values == explored[node]:
continue
for k, instances in values.items():
if not k in explored[node]:
explored[node][k] = set()
explored[node][k] |= instances
values = explored[node]
else:
explored[node] = values
def convert_expression_to_slithir(self):
for son in node.sons:
to_explore.append((son, dict(values)))
return ret
@staticmethod
def _unchange_phi(ir):
if not isinstance(ir, (Phi, PhiCallback)) or len(ir.rvalues) > 1:
return False
if not ir.rvalues:
return True
return ir.rvalues[0] == ir.lvalue
def fix_phi(self, last_state_variables_instances, initial_state_variables_instances):
for node in self.nodes:
for ir in node.irs_ssa:
if node == self.entry_point:
additional = [initial_state_variables_instances[ir.lvalue.canonical_name]]
additional += last_state_variables_instances[ir.lvalue.canonical_name]
ir.rvalues = list(set(additional + ir.rvalues))
if isinstance(ir, PhiCallback):
callee_ir = ir.callee_ir
if isinstance(callee_ir, InternalCall):
last_ssa = callee_ir.function.get_last_ssa_state_variables_instances()
if ir.lvalue.canonical_name in last_ssa:
ir.rvalues = list(last_ssa[ir.lvalue.canonical_name])
else:
ir.rvalues = [ir.lvalue]
else:
additional = last_state_variables_instances[ir.lvalue.canonical_name]
ir.rvalues = list(set(additional + ir.rvalues))
node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)]
def generate_slithir_and_analyze(self):
for node in self.nodes:
node.slithir_generation()
transform_slithir_vars_to_ssa(self)
self._analyze_read_write()
self._analyze_calls()
def generate_slithir_ssa(self, all_ssa_state_variables_instances):
compute_dominators(self.nodes)
compute_dominance_frontier(self.nodes)
transform_slithir_vars_to_ssa(self)
add_ssa_ir(self, all_ssa_state_variables_instances)
def update_read_write_using_ssa(self):
for node in self.nodes:
node.update_read_write_using_ssa()
self._analyze_read_write()
def split_ternary_node(self, node, condition, true_expr, false_expr):
condition_node = self._new_node(NodeType.IF, node.source_mapping)

@ -9,6 +9,7 @@ from slither.solc_parsing.declarations.contract import ContractSolc04
from slither.core.slither_core import Slither
from slither.core.declarations.pragma_directive import Pragma
from slither.core.declarations.import_directive import Import
from slither.analyses.data_depencency.data_depency import compute_dependency
from slither.utils.colors import red
@ -199,6 +200,8 @@ class SlitherSolc(Slither):
self._convert_to_slithir()
compute_dependency(self)
# TODO refactor the following functions, and use a lambda function
@property
@ -321,6 +324,9 @@ class SlitherSolc(Slither):
def _convert_to_slithir(self):
for contract in self.contracts:
for func in contract.functions + contract.modifiers:
if func.contract == contract:
func.convert_expression_to_slithir()
contract.convert_expression_to_slithir()
for contract in self.contracts:
contract.fix_phi()
contract.update_read_write_using_ssa()

@ -1,9 +1,8 @@
import logging
from slither.core.declarations import Function, Structure
from slither.core.declarations import Function
from slither.core.expressions import (AssignmentOperationType,
UnaryOperationType)
from slither.core.solidity_types.array_type import ArrayType
from slither.slithir.operations import (Assignment, Binary, BinaryType, Delete,
Index, InitArray, InternalCall, Member,
NewArray, NewContract, NewStructure,
@ -14,11 +13,13 @@ from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray
from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract
from slither.slithir.tmp_operations.tmp_new_elementary_type import \
TmpNewElementaryType
from slither.slithir.tmp_operations.tmp_new_structure import TmpNewStructure
from slither.slithir.variables import (Constant, ReferenceVariable,
TemporaryVariable, TupleVariable)
from slither.visitors.expression.expression import ExpressionVisitor
#from slither.slithir.variables.state_variable import StateIRVariable
#from slither.slithir.variables.local_variable import LocalIRVariable
logger = logging.getLogger("VISTIOR:ExpressionToSlithIR")
key = 'expressionToSlithIR'
@ -215,7 +216,7 @@ class ExpressionToSlithIR(ExpressionVisitor):
self._result.append(operation)
set_val(expression, lvalue)
elif expression.type in [UnaryOperationType.DELETE]:
operation = Delete(value)
operation = Delete(value, value)
self._result.append(operation)
set_val(expression, value)
elif expression.type in [UnaryOperationType.PLUSPLUS_PRE]:

Loading…
Cancel
Save