Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve echidna printer perf #2064

Merged
merged 11 commits into from
Oct 13, 2023
2 changes: 2 additions & 0 deletions slither/core/slither_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,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
80 changes: 42 additions & 38 deletions slither/printers/guidance/echidna.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import re
from collections import defaultdict
from typing import Dict, List, Set, Tuple, NamedTuple, Union

Expand Down Expand Up @@ -43,20 +44,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) -> 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, 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 +115,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) -> 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,9 +127,9 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
return ret


def _extract_assert(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_assert(contracts) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
functions_using_assert = []
for f in contract.functions_entry_points:
for v in f.all_solidity_calls():
Expand Down Expand Up @@ -223,13 +224,13 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n


def _extract_constants(
slither: SlitherCore,
contracts,
) -> 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 @@ -255,11 +256,11 @@ def _extract_constants(


def _extract_function_relations(
slither: SlitherCore,
contracts,
) -> 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 @@ -283,14 +284,14 @@ def _extract_function_relations(
return ret


def _have_external_calls(slither: SlitherCore) -> Dict[str, List[str]]:
def _have_external_calls(contracts) -> 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 @@ -299,14 +300,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) -> 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 @@ -318,33 +319,33 @@ def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
return ret


def _with_fallback(slither: SlitherCore) -> Set[str]:
def _with_fallback(contracts) -> 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) -> 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) -> 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 @@ -390,40 +391,43 @@ def output(self, filename: str) -> Output: # pylint: disable=too-many-locals
_filename(string)
"""

payable = _extract_payable(self.slither)
filter = r"mock(s)?|test(s)?"
contracts = [c for c in self.slither.contracts if not re.search(filter, c.file_scope.filename.absolute, re.IGNORECASE)]
smonicas marked this conversation as resolved.
Show resolved Hide resolved

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")
contracts, SolidityVariableComposed("msg.sender")
)
msg_gas = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.gas")
contracts, SolidityVariableComposed("msg.gas")
)
assert_usage = _extract_assert(self.slither)
cst_functions = _extract_constant_functions(self.slither)
(cst_used, cst_used_in_binary) = _extract_constants(self.slither)
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 @@ -438,7 +442,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
7 changes: 6 additions & 1 deletion slither/slither.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None:

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

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 @@ -526,8 +526,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