mirror of https://github.com/crytic/slither
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.
1070 lines
36 KiB
1070 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, solc_binary_path, 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 = solc_binary_path(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)
|
|
|