Static Analyzer for Solidity
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
slither/tests/unit/slithir/test_ssa_generation.py

1072 lines
36 KiB

# # 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<v;i++) {
# if (val > 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<v;i++) {
# if (val > 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)