mirror of https://github.com/crytic/slither
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 frontierpull/117/head^2
commit
fb1d907850
@ -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) |
||||
|
||||
|
@ -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 |
@ -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 |
||||
|
||||
|
@ -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) |
||||
|
@ -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) |
@ -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]) |
@ -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 |
@ -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) |
@ -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) |
@ -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 |
Loading…
Reference in new issue