Skip to content

Commit

Permalink
Merge pull request #2064 from crytic/dev-echidna
Browse files Browse the repository at this point in the history
Improve echidna printer perf
  • Loading branch information
montyly authored Oct 13, 2023
2 parents 2c76c94 + d9805c0 commit 7f6edbc
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 45 deletions.
2 changes: 2 additions & 0 deletions slither/core/slither_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ def __init__(self) -> None:
# If true, partial analysis is allowed
self.no_fail = False

self.skip_data_dependency = False

@property
def compilation_units(self) -> List[SlitherCompilationUnit]:
return list(self._compilation_units)
Expand Down
84 changes: 42 additions & 42 deletions slither/printers/guidance/echidna.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.cfg.node import Node
from slither.core.declarations import Enum, Function
from slither.core.declarations import Enum, Function, Contract
from slither.core.declarations.solidity_variables import (
SolidityVariableComposed,
SolidityFunction,
Expand Down Expand Up @@ -43,20 +43,20 @@ def _get_name(f: Union[Function, Variable]) -> str:
return f.solidity_signature


def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_payable(contracts: List[Contract]) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
payable_functions = [_get_name(f) for f in contract.functions_entry_points if f.payable]
if payable_functions:
ret[contract.name] = payable_functions
return ret


def _extract_solidity_variable_usage(
slither: SlitherCore, sol_var: SolidityVariable
contracts: List[Contract], sol_var: SolidityVariable
) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
functions_using_sol_var = []
for f in contract.functions_entry_points:
for v in f.all_solidity_variables_read():
Expand Down Expand Up @@ -114,9 +114,9 @@ def _is_constant(f: Function) -> bool: # pylint: disable=too-many-branches
return True


def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_constant_functions(contracts: List[Contract]) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)]
cst_functions += [
v.solidity_signature for v in contract.state_variables if v.visibility in ["public"]
Expand All @@ -126,18 +126,18 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
return ret


def _extract_assert(slither: SlitherCore) -> Dict[str, Dict[str, List[Dict]]]:
def _extract_assert(contracts: List[Contract]) -> Dict[str, Dict[str, List[Dict]]]:
"""
Return the list of contract -> function name -> List(source mapping of the assert))
Args:
slither:
contracts: list of contracts
Returns:
"""
ret: Dict[str, Dict[str, List[Dict]]] = {}
for contract in slither.contracts:
for contract in contracts:
functions_using_assert: Dict[str, List[Dict]] = defaultdict(list)
for f in contract.functions_entry_points:
for node in f.all_nodes():
Expand Down Expand Up @@ -238,13 +238,13 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n


def _extract_constants(
slither: SlitherCore,
contracts: List[Contract],
) -> Tuple[Dict[str, Dict[str, List]], Dict[str, Dict[str, Dict]]]:
# contract -> function -> [ {"value": value, "type": type} ]
ret_cst_used: Dict[str, Dict[str, List[ConstantValue]]] = defaultdict(dict)
# contract -> function -> binary_operand -> [ {"value": value, "type": type ]
ret_cst_used_in_binary: Dict[str, Dict[str, Dict[str, List[ConstantValue]]]] = defaultdict(dict)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
all_cst_used: List = []
all_cst_used_in_binary: Dict = defaultdict(list)
Expand All @@ -270,11 +270,11 @@ def _extract_constants(


def _extract_function_relations(
slither: SlitherCore,
contracts: List[Contract],
) -> Dict[str, Dict[str, Dict[str, List[str]]]]:
# contract -> function -> [functions]
ret: Dict[str, Dict[str, Dict[str, List[str]]]] = defaultdict(dict)
for contract in slither.contracts:
for contract in contracts:
ret[contract.name] = defaultdict(dict)
written = {
_get_name(function): function.all_state_variables_written()
Expand All @@ -298,14 +298,14 @@ def _extract_function_relations(
return ret


def _have_external_calls(slither: SlitherCore) -> Dict[str, List[str]]:
def _have_external_calls(contracts: List[Contract]) -> Dict[str, List[str]]:
"""
Detect the functions with external calls
:param slither:
:return:
"""
ret: Dict[str, List[str]] = defaultdict(list)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.all_high_level_calls() or function.all_low_level_calls():
ret[contract.name].append(_get_name(function))
Expand All @@ -314,14 +314,14 @@ def _have_external_calls(slither: SlitherCore) -> Dict[str, List[str]]:
return ret


def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
def _use_balance(contracts: List[Contract]) -> Dict[str, List[str]]:
"""
Detect the functions with external calls
:param slither:
:return:
"""
ret: Dict[str, List[str]] = defaultdict(list)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
for ir in function.all_slithir_operations():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
Expand All @@ -333,33 +333,33 @@ def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
return ret


def _with_fallback(slither: SlitherCore) -> Set[str]:
def _with_fallback(contracts: List[Contract]) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.is_fallback:
ret.add(contract.name)
return ret


def _with_receive(slither: SlitherCore) -> Set[str]:
def _with_receive(contracts: List[Contract]) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.is_receive:
ret.add(contract.name)
return ret


def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]:
def _call_a_parameter(slither: SlitherCore, contracts: List[Contract]) -> Dict[str, List[Dict]]:
"""
Detect the functions with external calls
:param slither:
:return:
"""
# contract -> [ (function, idx, interface_called) ]
ret: Dict[str, List[Dict]] = defaultdict(list)
for contract in slither.contracts: # pylint: disable=too-many-nested-blocks
for contract in contracts: # pylint: disable=too-many-nested-blocks
for function in contract.functions_entry_points:
try:
for ir in function.all_slithir_operations():
Expand Down Expand Up @@ -405,40 +405,40 @@ def output(self, filename: str) -> Output: # pylint: disable=too-many-locals
_filename(string)
"""

payable = _extract_payable(self.slither)
contracts = self.slither.contracts

payable = _extract_payable(contracts)
timestamp = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("block.timestamp")
contracts, SolidityVariableComposed("block.timestamp")
)
block_number = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("block.number")
contracts, SolidityVariableComposed("block.number")
)
msg_sender = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.sender")
)
msg_gas = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.gas")
contracts, SolidityVariableComposed("msg.sender")
)
assert_usage = _extract_assert(self.slither)
cst_functions = _extract_constant_functions(self.slither)
(cst_used, cst_used_in_binary) = _extract_constants(self.slither)
msg_gas = _extract_solidity_variable_usage(contracts, SolidityVariableComposed("msg.gas"))
assert_usage = _extract_assert(contracts)
cst_functions = _extract_constant_functions(contracts)
(cst_used, cst_used_in_binary) = _extract_constants(contracts)

functions_relations = _extract_function_relations(self.slither)
functions_relations = _extract_function_relations(contracts)

constructors = {
contract.name: contract.constructor.full_name
for contract in self.slither.contracts
for contract in contracts
if contract.constructor
}

external_calls = _have_external_calls(self.slither)
external_calls = _have_external_calls(contracts)

call_parameters = _call_a_parameter(self.slither)
# call_parameters = _call_a_parameter(self.slither, contracts)

use_balance = _use_balance(self.slither)
use_balance = _use_balance(contracts)

with_fallback = list(_with_fallback(self.slither))
with_fallback = list(_with_fallback(contracts))

with_receive = list(_with_receive(self.slither))
with_receive = list(_with_receive(contracts))

d = {
"payable": payable,
Expand All @@ -453,7 +453,7 @@ def output(self, filename: str) -> Output: # pylint: disable=too-many-locals
"functions_relations": functions_relations,
"constructors": constructors,
"have_external_calls": external_calls,
"call_a_parameter": call_parameters,
# "call_a_parameter": call_parameters,
"use_balance": use_balance,
"solc_versions": [unit.solc_version for unit in self.slither.compilation_units],
"with_fallback": with_fallback,
Expand Down
9 changes: 8 additions & 1 deletion slither/slither.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def _update_file_scopes(candidates: ValuesView[FileScope]):
learned_something = False


class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
class Slither(
SlitherCore
): # pylint: disable=too-many-instance-attributes,too-many-locals,too-many-statements
def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None:
"""
Args:
Expand Down Expand Up @@ -134,6 +136,11 @@ def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None:

triage_mode = kwargs.get("triage_mode", False)
self._triage_mode = triage_mode

printers_to_run = kwargs.get("printers_to_run", "")
if printers_to_run == "echidna":
self.skip_data_dependency = True

self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))

def _init_parsing_and_analyses(self, skip_analyze: bool) -> None:
Expand Down
4 changes: 2 additions & 2 deletions slither/solc_parsing/slither_compilation_unit_solc.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,8 +537,8 @@ def analyze_contracts(self) -> None: # pylint: disable=too-many-statements,too-
if not self._parsed:
raise SlitherException("Parse the contract before running analyses")
self._convert_to_slithir()

compute_dependency(self._compilation_unit)
if not self._compilation_unit.core.skip_data_dependency:
compute_dependency(self._compilation_unit)
self._compilation_unit.compute_storage_layout()
self._analyzed = True

Expand Down

0 comments on commit 7f6edbc

Please sign in to comment.