@ -4,6 +4,7 @@
import logging
from collections import namedtuple
from itertools import groupby
from enum import Enum
from slither . core . children . child_contract import ChildContract
from slither . core . children . child_inheritance import ChildInheritance
@ -13,8 +14,9 @@ from slither.core.declarations.solidity_variables import (SolidityFunction,
from slither . core . expressions import ( Identifier , IndexAccess , MemberAccess ,
UnaryOperation )
from slither . core . source_mapping . source_mapping import SourceMapping
from slither . core . variables . state_variable import StateVariable
from slither . core . variables . state_variable import StateVariable
from slither . utils . utils import unroll
logger = logging . getLogger ( " Function " )
@ -22,6 +24,12 @@ ReacheableNode = namedtuple('ReacheableNode', ['node', 'ir'])
ModifierStatements = namedtuple ( ' Modifier ' , [ ' modifier ' , ' node ' ] )
class FunctionType ( Enum ) :
NORMAL = 0
CONSTRUCTOR = 1
FALLBACK = 2
CONSTRUCTOR_VARIABLES = 3 # Fake function to hold variable declaration statements
class Function ( ChildContract , ChildInheritance , SourceMapping ) :
"""
Function class
@ -34,7 +42,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
self . _pure = None
self . _payable = None
self . _visibility = None
self . _is_constructor = None
self . _is_implemented = None
self . _is_empty = None
self . _entry_point = None
@ -94,6 +102,9 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
self . _reachable_from_nodes = set ( )
self . _reachable_from_functions = set ( )
# Constructor, fallback, State variable constructor
self . _function_type = None
self . _is_constructor = None
###################################################################################
###################################################################################
@ -106,11 +117,12 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
"""
str : function name
"""
if self . _name == ' ' :
if self . is_constructor :
if self . _function_type == FunctionType . CONSTRUCTOR :
return ' constructor '
else :
elif self . _function_type == FunctionType . FALLBACK :
return ' fallback '
elif self . _function_type == FunctionType . CONSTRUCTOR_VARIABLES :
return ' slither_constructor_variables '
return self . _name
@property
@ -131,12 +143,6 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
name , parameters , _ = self . signature
return self . contract_declarer . name + ' . ' + name + ' ( ' + ' , ' . join ( parameters ) + ' ) '
@property
def is_constructor ( self ) :
"""
bool : True if the function is the constructor
"""
return self . _is_constructor or self . _name == self . contract_declarer . name
@property
def contains_assembly ( self ) :
@ -154,6 +160,41 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
"""
return self . contract_declarer == contract
# endregion
###################################################################################
###################################################################################
# region Type (FunctionType)
###################################################################################
###################################################################################
def set_function_type ( self , t ) :
assert isinstance ( t , FunctionType )
self . _function_type = t
@property
def is_constructor ( self ) :
"""
bool : True if the function is the constructor
"""
return self . _function_type == FunctionType . CONSTRUCTOR
@property
def is_constructor_variables ( self ) :
"""
bool : True if the function is the constructor of the variables
Slither has a inbuilt function to hold the state variables initialization
"""
return self . _function_type == FunctionType . CONSTRUCTOR_VARIABLES
@property
def is_fallback ( self ) :
"""
Determine if the function is the fallback function for the contract
Returns
( bool )
"""
return self . _function_type == FunctionType . FALLBACK
# endregion
###################################################################################
###################################################################################
@ -182,6 +223,9 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
"""
return self . _visibility
def set_visibility ( self , v ) :
self . _visibility = v
@property
def view ( self ) :
"""
@ -248,6 +292,11 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
"""
return self . _entry_point
def add_node ( self , node ) :
if not self . _entry_point :
self . _entry_point = node
self . _nodes . append ( node )
# endregion
###################################################################################
###################################################################################
@ -1005,13 +1054,6 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
args_vars = self . all_solidity_variables_used_as_args ( )
return SolidityVariableComposed ( ' msg.sender ' ) in conditional_vars + args_vars
def is_fallback ( self ) :
"""
Determine if the function is the fallback function for the contract
Returns
( bool )
"""
return self . _name == " " and not self . is_constructor
# endregion
###################################################################################
@ -1119,6 +1161,133 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
# endregion
###################################################################################
###################################################################################
# region SlithIr and SSA
###################################################################################
###################################################################################
def get_last_ssa_state_variables_instances ( self ) :
from slither . slithir . variables import ReferenceVariable
from slither . slithir . operations import OperationWithLValue
from slither . core . cfg . node import NodeType
if not self . is_implemented :
return 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 : : ]
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
# 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
for son in node . sons :
to_explore . append ( ( son , dict ( values ) ) )
return ret
@staticmethod
def _unchange_phi ( ir ) :
from slither . slithir . operations import ( Phi , PhiCallback )
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 ) :
from slither . slithir . operations import ( InternalCall , PhiCallback )
from slither . slithir . variables import ( Constant , StateIRVariable )
for node in self . nodes :
for ir in node . irs_ssa :
if node == self . entry_point :
if isinstance ( ir . lvalue , StateIRVariable ) :
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 ) )
# function parameter
else :
# find index of the parameter
idx = self . parameters . index ( ir . lvalue . non_ssa_version )
# find non ssa version of that index
additional = [ n . ir . arguments [ idx ] for n in self . reachable_from_nodes ]
additional = unroll ( additional )
additional = [ a for a in additional if not isinstance ( a , Constant ) ]
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 ( )
for modifier_statement in self . modifiers_statements :
modifier_statement . node . slithir_generation ( )
for modifier_statement in self . explicit_base_constructor_calls_statements :
modifier_statement . node . slithir_generation ( )
self . _analyze_read_write ( )
self . _analyze_calls ( )
def generate_slithir_ssa ( self , all_ssa_state_variables_instances ) :
from slither . slithir . utils . ssa import add_ssa_ir , transform_slithir_vars_to_ssa
from slither . core . dominators . utils import ( compute_dominance_frontier ,
compute_dominators )
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 ( )
###################################################################################
###################################################################################
# region Built in definitions