# # pylint: disable=too-many-lines # import pathlib # from argparse import ArgumentTypeError # from collections import defaultdict # from contextlib import contextmanager # from inspect import getsourcefile # from tempfile import NamedTemporaryFile # from typing import Union, List, Optional, Dict, Callable # import pytest # from solc_select import solc_select # from solc_select.solc_select import valid_version as solc_valid_version # from slither import Slither # from slither.core.cfg.node import Node, NodeType # from slither.core.declarations import Function, Contract # from slither.core.variables.local_variable import LocalVariable # from slither.core.variables.state_variable import StateVariable # from slither.slithir.operations import ( # OperationWithLValue, # Phi, # Assignment, # HighLevelCall, # Return, # Operation, # Binary, # BinaryType, # InternalCall, # Index, # InitArray, # ) # from slither.slithir.utils.ssa import is_used_later # from slither.slithir.variables import ( # Constant, # ReferenceVariable, # LocalIRVariable, # StateIRVariable, # TemporaryVariableSSA, # ) # # Directory of currently executing script. Will be used as basis for temporary file names. # SCRIPT_DIR = pathlib.Path(getsourcefile(lambda: 0)).parent # type:ignore # def valid_version(ver: str) -> bool: # """Wrapper function to check if the solc-version is valid # The solc_select function raises and exception but for checks below, # only a bool is needed. # """ # try: # solc_valid_version(ver) # return True # except ArgumentTypeError: # return False # def have_ssa_if_ir(function: Function) -> None: # """Verifies that all nodes in a function that have IR also have SSA IR""" # for n in function.nodes: # if n.irs: # assert n.irs_ssa # # pylint: disable=too-many-branches, too-many-locals # def ssa_basic_properties(function: Function) -> None: # """Verifies that basic properties of ssa holds # 1. Every name is defined only once # 2. A l-value is never index zero - there is always a zero-value available for each var # 3. Every r-value is at least defined at some point # 4. The number of ssa defs is >= the number of assignments to var # 5. Function parameters SSA are stored in function.parameters_ssa # - if function parameter is_storage it refers to a fake variable # 6. Function returns SSA are stored in function.returns_ssa # - if function return is_storage it refers to a fake variable # """ # ssa_lvalues = set() # ssa_rvalues = set() # lvalue_assignments: Dict[str, int] = {} # for n in function.nodes: # for ir in n.irs: # if isinstance(ir, OperationWithLValue) and ir.lvalue: # name = ir.lvalue.name # if name is None: # continue # if name in lvalue_assignments: # lvalue_assignments[name] += 1 # else: # lvalue_assignments[name] = 1 # for ssa in n.irs_ssa: # if isinstance(ssa, OperationWithLValue): # # 1 # assert ssa.lvalue not in ssa_lvalues # ssa_lvalues.add(ssa.lvalue) # # 2 (if Local/State Var) # ssa_lvalue = ssa.lvalue # if isinstance(ssa_lvalue, (StateIRVariable, LocalIRVariable)): # assert ssa_lvalue.index > 0 # for rvalue in filter( # lambda x: not isinstance(x, (StateIRVariable, Constant)), ssa.read # ): # ssa_rvalues.add(rvalue) # # 3 # # Each var can have one non-defined value, the value initially held. Typically, # # var_0, i_0, state_0 or similar. # undef_vars = set() # for rvalue in ssa_rvalues: # if rvalue not in ssa_lvalues: # assert rvalue.non_ssa_version not in undef_vars # undef_vars.add(rvalue.non_ssa_version) # # 4 # ssa_defs: Dict[str, int] = defaultdict(int) # for v in ssa_lvalues: # if v and v.name: # ssa_defs[v.name] += 1 # for (k, count) in lvalue_assignments.items(): # assert ssa_defs[k] >= count # # Helper 5/6 # def check_property_5_and_6( # variables: List[LocalVariable], ssavars: List[LocalIRVariable] # ) -> None: # for var in filter(lambda x: x.name, variables): # ssa_vars = [x for x in ssavars if x.non_ssa_version == var] # assert len(ssa_vars) == 1 # ssa_var = ssa_vars[0] # assert var.is_storage == ssa_var.is_storage # if ssa_var.is_storage: # assert len(ssa_var.refers_to) == 1 # assert ssa_var.refers_to[0].location == "reference_to_storage" # # 5 # check_property_5_and_6(function.parameters, function.parameters_ssa) # # 6 # check_property_5_and_6(function.returns, function.returns_ssa) # def ssa_phi_node_properties(f: Function) -> None: # """Every phi-function should have as many args as predecessors # This does not apply if the phi-node refers to state variables, # they make use os special phi-nodes for tracking potential values # a state variable can have # """ # for node in f.nodes: # for ssa in node.irs_ssa: # if isinstance(ssa, Phi): # n = len(ssa.read) # if not isinstance(ssa.lvalue, StateIRVariable): # assert len(node.fathers) == n # # TODO (hbrodin): This should probably go into another file, not specific to SSA # def dominance_properties(f: Function) -> None: # """Verifies properties related to dominators holds # 1. Every node have an immediate dominator except entry_node which have none # 2. From every node immediate dominator there is a path via its successors to the node # """ # def find_path(from_node: Node, to: Node) -> bool: # visited = set() # worklist = list(from_node.sons) # while worklist: # first, *worklist = worklist # if first == to: # return True # visited.add(first) # for successor in first.sons: # if successor not in visited: # worklist.append(successor) # return False # for node in f.nodes: # if node is f.entry_point: # assert node.immediate_dominator is None # else: # assert node.immediate_dominator is not None # assert find_path(node.immediate_dominator, node) # def phi_values_inserted(f: Function) -> None: # """Verifies that phi-values are inserted at the right places # For every node that has a dominance frontier, any def (including # phi) should be a phi function in its dominance frontier # """ # def have_phi_for_var( # node: Node, var: Union[StateIRVariable, LocalIRVariable, TemporaryVariableSSA] # ) -> bool: # """Checks if a node has a phi-instruction for var # The ssa version would ideally be checked, but then # more data flow analysis would be needed, for cases # where a new def for var is introduced before reaching # DF # """ # non_ssa = var.non_ssa_version # for ssa in node.irs_ssa: # if isinstance(ssa, Phi): # if non_ssa in map( # lambda ssa_var: ssa_var.non_ssa_version, # [ # r # for r in ssa.read # if isinstance(r, (StateIRVariable, LocalIRVariable, TemporaryVariableSSA)) # ], # ): # return True # return False # for node in filter(lambda n: n.dominance_frontier, f.nodes): # for df in node.dominance_frontier: # for ssa in node.irs_ssa: # if isinstance(ssa, OperationWithLValue): # ssa_lvalue = ssa.lvalue # if isinstance( # ssa_lvalue, (StateIRVariable, LocalIRVariable, TemporaryVariableSSA) # ) and is_used_later(node, ssa_lvalue): # assert have_phi_for_var(df, ssa_lvalue) # @contextmanager # def slither_from_source(source_code: str, use_solc_version, solc_version: str = "latest"): # """Yields a Slither instance using source_code string and solc_version # Creates a temporary file and changes the solc-version temporary to solc_version. # """ # fname = "" # try: # with NamedTemporaryFile(dir=SCRIPT_DIR, mode="w", suffix=".sol", delete=False) as f: # fname = f.name # f.write(source_code) # solc_path = use_solc_version(solc_version) # yield Slither(fname, solc=solc_path) # finally: # pathlib.Path(fname).unlink() # def verify_properties_hold(source_code_or_slither: Union[str, Slither]) -> None: # """Ensures that basic properties of SSA hold true""" # def verify_func(func: Function) -> None: # have_ssa_if_ir(func) # phi_values_inserted(func) # ssa_basic_properties(func) # ssa_phi_node_properties(func) # dominance_properties(func) # def verify(slither: Slither) -> None: # for cu in slither.compilation_units: # for func in cu.functions_and_modifiers: # _dump_function(func) # verify_func(func) # for contract in cu.contracts: # for f in contract.functions: # if f.is_constructor or f.is_constructor_variables: # _dump_function(f) # verify_func(f) # if isinstance(source_code_or_slither, Slither): # verify(source_code_or_slither) # else: # slither: Slither # with slither_from_source(source_code_or_slither) as slither: # verify(slither) # def _dump_function(f: Function) -> None: # """Helper function to print nodes/ssa ir for a function or modifier""" # print(f"---- {f.name} ----") # for n in f.nodes: # print(n) # for ir in n.irs_ssa: # print(f"\t{ir}") # print("") # def _dump_functions(c: Contract) -> None: # """Helper function to print functions and modifiers of a contract""" # for f in c.functions_and_modifiers: # _dump_function(f) # def get_filtered_ssa(f: Union[Function, Node], flt: Callable) -> List[Operation]: # """Returns a list of all ssanodes filtered by filter for all nodes in function f""" # if isinstance(f, Function): # return [ssanode for node in f.nodes for ssanode in node.irs_ssa if flt(ssanode)] # assert isinstance(f, Node) # return [ssanode for ssanode in f.irs_ssa if flt(ssanode)] # def get_ssa_of_type(f: Union[Function, Node], ssatype) -> List[Operation]: # """Returns a list of all ssanodes of a specific type for all nodes in function f""" # return get_filtered_ssa(f, lambda ssanode: isinstance(ssanode, ssatype)) # def test_multi_write() -> None: # contract = """ # pragma solidity ^0.8.11; # contract Test { # function multi_write(uint val) external pure returns(uint) { # val = 1; # val = 2; # val = 3; # } # }""" # verify_properties_hold(contract) # def test_single_branch_phi() -> None: # contract = """ # pragma solidity ^0.8.11; # contract Test { # function single_branch_phi(uint val) external pure returns(uint) { # if (val == 3) { # val = 9; # } # return val; # } # } # """ # verify_properties_hold(contract) # def test_basic_phi() -> None: # contract = """ # pragma solidity ^0.8.11; # contract Test { # function basic_phi(uint val) external pure returns(uint) { # if (val == 3) { # val = 9; # } else { # val = 1; # } # return val; # } # } # """ # verify_properties_hold(contract) # def test_basic_loop_phi() -> None: # contract = """ # pragma solidity ^0.8.11; # contract Test { # function basic_loop_phi(uint val) external pure returns(uint) { # for (uint i=0;i<128;i++) { # val = val + 1; # } # return val; # } # } # """ # verify_properties_hold(contract) # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_phi_propagation_loop(): # contract = """ # pragma solidity ^0.8.11; # contract Test { # function looping(uint v) external pure returns(uint) { # uint val = 0; # for (uint i=0;i i) { # val = i; # } else { # val = 3; # } # } # return val; # } # } # """ # verify_properties_hold(contract) # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_free_function_properties(): # contract = """ # pragma solidity ^0.8.11; # function free_looping(uint v) returns(uint) { # uint val = 0; # for (uint i=0;i i) { # val = i; # } else { # val = 3; # } # } # return val; # } # contract Test {} # """ # verify_properties_hold(contract) # def test_ssa_inter_transactional() -> None: # source = """ # pragma solidity ^0.8.11; # contract A { # uint my_var_A; # uint my_var_B; # function direct_set(uint i) public { # my_var_A = i; # } # function direct_set_plus_one(uint i) public { # my_var_A = i + 1; # } # function indirect_set() public { # my_var_B = my_var_A; # } # } # """ # with slither_from_source(source) as slither: # c = slither.contracts[0] # variables = c.variables_as_dict # funcs = c.available_functions_as_dict() # direct_set = funcs["direct_set(uint256)"] # # Skip entry point and go straight to assignment ir # assign1 = direct_set.nodes[1].irs_ssa[0] # assert isinstance(assign1, Assignment) # assign2 = direct_set.nodes[1].irs_ssa[0] # assert isinstance(assign2, Assignment) # indirect_set = funcs["indirect_set()"] # phi = indirect_set.entry_point.irs_ssa[0] # assert isinstance(phi, Phi) # # phi rvalues come from 1, initial value of my_var_a and 2, assignment in direct_set # assert len(phi.rvalues) == 3 # assert all(x.non_ssa_version == variables["my_var_A"] for x in phi.rvalues) # assert assign1.lvalue in phi.rvalues # assert assign2.lvalue in phi.rvalues # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_ssa_phi_callbacks(): # source = """ # pragma solidity ^0.8.11; # contract A { # uint my_var_A; # uint my_var_B; # function direct_set(uint i) public { # my_var_A = i; # } # function use_a() public { # // Expect a phi-node here # my_var_B = my_var_A; # B b = new B(); # my_var_A = 3; # b.do_stuff(); # // Expect a phi-node here # my_var_B = my_var_A; # } # } # contract B { # function do_stuff() public returns (uint) { # // This could be calling back into A # } # } # """ # with slither_from_source(source) as slither: # c = slither.get_contract_from_name("A")[0] # _dump_functions(c) # f = [x for x in c.functions if x.name == "use_a"][0] # var_a = [x for x in c.variables if x.name == "my_var_A"][0] # entry_phi = [ # x # for x in f.entry_point.irs_ssa # if isinstance(x, Phi) and x.lvalue.non_ssa_version == var_a # ][0] # # The four potential sources are: # # 1. initial value # # 2. my_var_A = i; # # 3. my_var_A = 3; # # 4. phi-value after call to b.do_stuff(), which could be reentrant. # assert len(entry_phi.rvalues) == 4 # # Locate the first high-level call (should be b.do_stuff()) # call_node = [x for y in f.nodes for x in y.irs_ssa if isinstance(x, HighLevelCall)][0] # n = call_node.node # # Get phi-node after call # after_call_phi = n.irs_ssa[n.irs_ssa.index(call_node) + 1] # # The two sources for this phi node is # # 1. my_var_A = i; # # 2. my_var_A = 3; # assert isinstance(after_call_phi, Phi) # assert len(after_call_phi.rvalues) == 2 # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_storage_refers_to(): # """Test the storage aspects of the SSA IR # When declaring a var as being storage, start tracking what storage it refers_to. # When a phi-node is created, ensure refers_to is propagated to the phi-node. # Assignments also propagate refers_to. # Whenever a ReferenceVariable is the destination of an assignment (e.g. s.v = 10) # below, create additional versions of the variables it refers to record that a a # write was made. In the current implementation, this is referenced by phis. # """ # source = """ # contract A{ # struct St{ # int v; # } # St state0; # St state1; # function f() public{ # St storage s = state0; # if(true){ # s = state1; # } # s.v = 10; # } # } # """ # with slither_from_source(source) as slither: # c = slither.contracts[0] # f = c.functions[0] # phinodes = get_ssa_of_type(f, Phi) # # Expect 2 in entrypoint (state0/state1 initial values), 1 at 'ENDIF' and two related to the # # ReferenceVariable write s.v = 10. # assert len(phinodes) == 5 # # Assign s to state0, s to state1, s.v to 10 # assigns = get_ssa_of_type(f, Assignment) # assert len(assigns) == 3 # # The IR variables have is_storage # assert all(x.lvalue.is_storage for x in assigns if isinstance(x, LocalIRVariable)) # # s.v ReferenceVariable points to one of the phi vars... # ref0 = [x.lvalue for x in assigns if isinstance(x.lvalue, ReferenceVariable)][0] # sphis = [x for x in phinodes if x.lvalue == ref0.points_to] # assert len(sphis) == 1 # sphi = sphis[0] # # ...and that phi refers to the two entry phi-values # entryphi = [x for x in phinodes if x.lvalue in sphi.lvalue.refers_to] # assert len(entryphi) == 2 # # The remaining two phis are the ones recording that write through ReferenceVariable occured # for ephi in entryphi: # phinodes.remove(ephi) # phinodes.remove(sphi) # assert len(phinodes) == 2 # # And they are recorded in one of the entry phis # assert phinodes[0].lvalue in entryphi[0].rvalues or entryphi[1].rvalues # assert phinodes[1].lvalue in entryphi[0].rvalues or entryphi[1].rvalues # @pytest.mark.skipif( # not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform" # ) # def test_initial_version_exists_for_locals(): # """ # In solidity you can write statements such as # uint a = a + 1, this test ensures that can be handled for local variables. # """ # src = """ # contract C { # function func() internal { # uint a = a + 1; # } # } # """ # with slither_from_source(src, "0.4.0") as slither: # verify_properties_hold(slither) # c = slither.contracts[0] # f = c.functions[0] # addition = get_ssa_of_type(f, Binary)[0] # assert addition.type == BinaryType.ADDITION # assert isinstance(addition.variable_right, Constant) # a_0 = addition.variable_left # assert a_0.index == 0 # assert a_0.name == "a" # assignment = get_ssa_of_type(f, Assignment)[0] # a_1 = assignment.lvalue # assert a_1.index == 1 # assert a_1.name == "a" # assert assignment.rvalue == addition.lvalue # assert a_0.non_ssa_version == a_1.non_ssa_version # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # @pytest.mark.skipif( # not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform" # ) # def test_initial_version_exists_for_state_variables(): # """ # In solidity you can write statements such as # uint a = a + 1, this test ensures that can be handled for state variables. # """ # src = """ # contract C { # uint a = a + 1; # } # """ # with slither_from_source(src, "0.4.0") as slither: # verify_properties_hold(slither) # c = slither.contracts[0] # f = c.functions[0] # There will be one artificial ctor function for the state vars # addition = get_ssa_of_type(f, Binary)[0] # assert addition.type == BinaryType.ADDITION # assert isinstance(addition.variable_right, Constant) # a_0 = addition.variable_left # assert isinstance(a_0, StateIRVariable) # assert a_0.name == "a" # assignment = get_ssa_of_type(f, Assignment)[0] # a_1 = assignment.lvalue # assert isinstance(a_1, StateIRVariable) # assert a_1.index == a_0.index + 1 # assert a_1.name == "a" # assert assignment.rvalue == addition.lvalue # assert a_0.non_ssa_version == a_1.non_ssa_version # assert isinstance(a_0.non_ssa_version, StateVariable) # # No conditional/other function interaction so no phis # assert len(get_ssa_of_type(f, Phi)) == 0 # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_initial_version_exists_for_state_variables_function_assign(): # """ # In solidity you can write statements such as # uint a = a + 1, this test ensures that can be handled for local variables. # """ # # TODO (hbrodin): Could be a detector that a is not used in f # src = """ # contract C { # uint a = f(); # function f() internal returns(uint) { # return a; # } # } # """ # with slither_from_source(src) as slither: # verify_properties_hold(slither) # c = slither.contracts[0] # f, ctor = c.functions # if f.is_constructor_variables: # f, ctor = ctor, f # # ctor should have a single call to f that assigns to a # # temporary variable, that is then assigned to a # call = get_ssa_of_type(ctor, InternalCall)[0] # assert call.node.function == f # assign = get_ssa_of_type(ctor, Assignment)[0] # assert assign.rvalue == call.lvalue # assert isinstance(assign.lvalue, StateIRVariable) # assert assign.lvalue.name == "a" # # f should have a phi node on entry of a0, a1 and should return # # a2 # phi = get_ssa_of_type(f, Phi)[0] # assert len(phi.rvalues) == 2 # assert assign.lvalue in phi.rvalues # @pytest.mark.skipif( # not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform" # ) # def test_return_local_before_assign(): # src = """ # // this require solidity < 0.5 # // a variable can be returned before declared. Ensure it can be # // handled by Slither. # contract A { # function local(bool my_bool) internal returns(uint){ # if(my_bool){ # return a_local; # } # uint a_local = 10; # } # } # """ # with slither_from_source(src, "0.4.0") as slither: # f = slither.contracts[0].functions[0] # ret = get_ssa_of_type(f, Return)[0] # assert len(ret.values) == 1 # assert ret.values[0].index == 0 # assign = get_ssa_of_type(f, Assignment)[0] # assert assign.lvalue.index == 1 # assert assign.lvalue.non_ssa_version == ret.values[0].non_ssa_version # @pytest.mark.skipif( # not valid_version("0.5.0"), reason="Solidity version 0.5.0 not available on this platform" # ) # def test_shadow_local(): # src = """ # contract A { # // this require solidity 0.5 # function shadowing_local() internal{ # uint local = 0; # { # uint local = 1; # { # uint local = 2; # } # } # } # } # """ # with slither_from_source(src, "0.5.0") as slither: # _dump_functions(slither.contracts[0]) # f = slither.contracts[0].functions[0] # # Ensure all assignments are to a variable of index 1 # # not using the same IR var. # assert all(map(lambda x: x.lvalue.index == 1, get_ssa_of_type(f, Assignment))) # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_multiple_named_args_returns(): # """Verifies that named arguments and return values have correct versions # Each arg/ret have an initial version, version 0, and is written once and should # then have version 1. # """ # src = """ # contract A { # function multi(uint arg1, uint arg2) internal returns (uint ret1, uint ret2) { # arg1 = arg1 + 1; # arg2 = arg2 + 2; # ret1 = arg1 + 3; # ret2 = arg2 + 4; # } # }""" # with slither_from_source(src) as slither: # verify_properties_hold(slither) # f = slither.contracts[0].functions[0] # # Ensure all LocalIRVariables (not TemporaryVariables) have index 1 # assert all( # map( # lambda x: x.lvalue.index == 1 or not isinstance(x.lvalue, LocalIRVariable), # get_ssa_of_type(f, OperationWithLValue), # ) # ) # @pytest.mark.xfail(reason="Tests for wanted state of SSA IR, not current.", strict=True) # def test_memory_array(): # src = """ # contract MemArray { # struct A { # uint val1; # uint val2; # } # function test_array() internal { # A[] memory a= new A[](4); # // Create REF_0 -> a_1[2] # accept_array_entry(a[2]); # // Create REF_1 -> a_1[3] # accept_array_entry(a[3]); # A memory alocal; # accept_array_entry(alocal); # } # // val_1 = ϕ(val_0, REF_0, REF_1, alocal_1) # // val_0 is an unknown external value # function accept_array_entry(A memory val) public returns (uint) { # uint zero = 0; # b(zero); # // Create REF_2 -> val_1.val1 # return b(val.val1); # } # function b(uint arg) public returns (uint){ # // arg_1 = ϕ(arg_0, zero_1, REF_2) # return arg + 1; # } # }""" # with slither_from_source(src) as slither: # c = slither.contracts[0] # ftest_array, faccept, fb = c.functions # # Locate REF_0/REF_1/alocal (they are all args to the call) # accept_args = [x.arguments[0] for x in get_ssa_of_type(ftest_array, InternalCall)] # # Check entrypoint of accept_array_entry, it should contain a phi-node # # of expected rvalues # [phi_entry_accept] = get_ssa_of_type(faccept.entry_point, Phi) # for arg in accept_args: # assert arg in phi_entry_accept.rvalues # # NOTE(hbrodin): There should be an additional val_0 in the phi-node. # # That additional val_0 indicates an external caller of this function. # assert len(phi_entry_accept.rvalues) == len(accept_args) + 1 # # Args used to invoke b # b_args = [x.arguments[0] for x in get_ssa_of_type(faccept, InternalCall)] # # Check entrypoint of B, it should contain a phi-node of expected # # rvalues # [phi_entry_b] = get_ssa_of_type(fb.entry_point, Phi) # for arg in b_args: # assert arg in phi_entry_b.rvalues # # NOTE(hbrodin): There should be an additional arg_0 (see comment about phi_entry_accept). # assert len(phi_entry_b.rvalues) == len(b_args) + 1 # @pytest.mark.xfail(reason="Tests for wanted state of SSA IR, not current.", strict=True) # def test_storage_array(): # src = """ # contract StorageArray { # struct A { # uint val1; # uint val2; # } # // NOTE(hbrodin): a is never written, should only become a_0. Same for astorage (astorage_0). Phi-nodes at entry # // should only add new versions of a state variable if it is actually written. # A[] a; # A astorage; # function test_array() internal { # accept_array_entry(a[2]); # accept_array_entry(a[3]); # accept_array_entry(astorage); # } # function accept_array_entry(A storage val) internal returns (uint) { # // val is either a[2], a[3] or astorage_0. Ideally this could be identified. # uint five = 5; # // NOTE(hbrodin): If the following line is enabled, there would ideally be a phi-node representing writes # // to either a or astorage. # //val.val2 = 4; # b(five); # return b(val.val1); # } # function b(uint value) public returns (uint){ # // Expect a phi-node at the entrypoint # // value_1 = ϕ(value_0, five_0, REF_x), where REF_x is the reference to val.val1 in accept_array_entry. # return value + 1; # } # }""" # with slither_from_source(src) as slither: # c = slither.contracts[0] # _dump_functions(c) # ftest, faccept, fb = c.functions # # None of a/astorage is written so expect that there are no phi-nodes at entrypoint. # assert len(get_ssa_of_type(ftest.entry_point, Phi)) == 0 # # Expect all references to start from index 0 (no writes) # assert all(x.variable_left.index == 0 for x in get_ssa_of_type(ftest, Index)) # [phi_entry_accept] = get_ssa_of_type(faccept.entry_point, Phi) # assert len(phi_entry_accept.rvalues) == 3 # See comment in b above # [phi_entry_b] = get_ssa_of_type(fb.entry_point, Phi) # assert len(phi_entry_b.rvalues) == 3 # See comment in b above # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_issue_468(): # """ # Ensure issue 468 is corrected as per # https://github.com/crytic/slither/issues/468#issuecomment-620974151 # The one difference is that we allow the phi-function at entry of f to # hold exit state which contains init state and state from branch, which # is a bit redundant. This could be further simplified. # """ # source = """ # contract State { # int state = 0; # function f(int a) public returns (int) { # // phi-node here for state # if (a < 1) { # state += 1; # } # // phi-node here for state # return state; # } # } # """ # with slither_from_source(source) as slither: # c = slither.get_contract_from_name("State")[0] # f = [x for x in c.functions if x.name == "f"][0] # # Check that there is an entry point phi values for each later value # # plus one additional which is the initial value # entry_ssa = f.entry_point.irs_ssa # assert len(entry_ssa) == 1 # phi_entry = entry_ssa[0] # assert isinstance(phi_entry, Phi) # # Find the second phi function # endif_node = [x for x in f.nodes if x.type == NodeType.ENDIF][0] # assert len(endif_node.irs_ssa) == 1 # phi_endif = endif_node.irs_ssa[0] # assert isinstance(phi_endif, Phi) # # Ensure second phi-function contains init-phi and one additional # assert len(phi_endif.rvalues) == 2 # assert phi_entry.lvalue in phi_endif.rvalues # # Find return-statement and ensure it returns the phi_endif # return_node = [x for x in f.nodes if x.type == NodeType.RETURN][0] # assert len(return_node.irs_ssa) == 1 # ret = return_node.irs_ssa[0] # assert len(ret.values) == 1 # assert phi_endif.lvalue in ret.values # # Ensure that the phi_endif (which is the end-state for function as well) is in the entry_phi # assert phi_endif.lvalue in phi_entry.rvalues # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_issue_434(): # source = """ # contract Contract { # int public a; # function f() public { # g(); # a += 1; # } # function e() public { # a -= 1; # } # function g() public { # e(); # } # } # """ # with slither_from_source(source) as slither: # c = slither.get_contract_from_name("Contract")[0] # e = [x for x in c.functions if x.name == "e"][0] # f = [x for x in c.functions if x.name == "f"][0] # g = [x for x in c.functions if x.name == "g"][0] # # Ensure there is a phi-node at the beginning of f and e # phi_entry_e = get_ssa_of_type(e.entry_point, Phi)[0] # phi_entry_f = get_ssa_of_type(f.entry_point, Phi)[0] # # But not at g # assert len(get_ssa_of_type(g, Phi)) == 0 # # Ensure that the final states of f and e are in the entry-states # add_f = get_filtered_ssa( # f, lambda x: isinstance(x, Binary) and x.type == BinaryType.ADDITION # )[0] # sub_e = get_filtered_ssa( # e, lambda x: isinstance(x, Binary) and x.type == BinaryType.SUBTRACTION # )[0] # assert add_f.lvalue in phi_entry_f.rvalues # assert add_f.lvalue in phi_entry_e.rvalues # assert sub_e.lvalue in phi_entry_f.rvalues # assert sub_e.lvalue in phi_entry_e.rvalues # # Ensure there is a phi-node after call to g # call = get_ssa_of_type(f, InternalCall)[0] # idx = call.node.irs_ssa.index(call) # aftercall_phi = call.node.irs_ssa[idx + 1] # assert isinstance(aftercall_phi, Phi) # # Ensure that phi node ^ is used in the addition afterwards # assert aftercall_phi.lvalue in (add_f.variable_left, add_f.variable_right) # @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") # def test_issue_473(): # source = """ # contract Contract { # function f() public returns (int) { # int a = 1; # if (a > 0) { # a = 2; # } # if (a == 3) { # a = 6; # } # return a; # } # } # """ # with slither_from_source(source) as slither: # c = slither.get_contract_from_name("Contract")[0] # f = c.functions[0] # phis = get_ssa_of_type(f, Phi) # return_value = get_ssa_of_type(f, Return)[0] # # There shall be two phi functions # assert len(phis) == 2 # first_phi = phis[0] # second_phi = phis[1] # # The second phi is the one being returned, if it's the first swap them (iteration order) # if first_phi.lvalue in return_value.values: # first_phi, second_phi = second_phi, first_phi # # First phi is for [a=1 or a=2] # assert len(first_phi.rvalues) == 2 # # second is for [a=6 or first phi] # assert first_phi.lvalue in second_phi.rvalues # assert len(second_phi.rvalues) == 2 # # return is for second phi # assert len(return_value.values) == 1 # assert second_phi.lvalue in return_value.values # def test_issue_1748(): # source = """ # contract Contract { # uint[] arr; # function foo(uint i) public { # arr = [1]; # } # } # """ # with slither_from_source(source) as slither: # c = slither.get_contract_from_name("Contract")[0] # f = c.functions[0] # operations = f.slithir_operations # assign_op = operations[0] # assert isinstance(assign_op, InitArray)