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 @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs data dependency and this PR disables computing data dependency for the echidna printer. call_a_parameter is not used in echidna.

"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