From de311b5ab57e7affec6d262c4db457a144ed903e Mon Sep 17 00:00:00 2001 From: Simone Date: Thu, 6 Apr 2023 18:50:59 +0200 Subject: [PATCH 1/8] Improve echidna printer perf --- slither/core/slither_core.py | 2 + slither/printers/guidance/echidna.py | 80 ++++++++++--------- slither/slither.py | 7 +- .../slither_compilation_unit_solc.py | 4 +- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 798008707f..29db9f23a9 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -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) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index acbf5b0158..60fcd6b0ff 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -1,4 +1,5 @@ import json +import re from collections import defaultdict from typing import Dict, List, Set, Tuple, NamedTuple, Union @@ -43,9 +44,9 @@ 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 @@ -53,10 +54,10 @@ def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]: 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(): @@ -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"] @@ -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(): @@ -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) @@ -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() @@ -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)) @@ -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( @@ -318,25 +319,25 @@ 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: @@ -344,7 +345,7 @@ def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]: """ # 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(): @@ -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)] + + 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, @@ -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, diff --git a/slither/slither.py b/slither/slither.py index 85f852e1d5..ca0df5f712 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -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: diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index b1c2387f0c..bdbd0bb94d 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -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 From 27f56466f2f9f52eb4ecaca5d5dc05b1e65e130b Mon Sep 17 00:00:00 2001 From: Simone Date: Tue, 25 Jul 2023 11:03:36 +0200 Subject: [PATCH 2/8] Use is_test_contract util --- slither/printers/guidance/echidna.py | 17 ++++++----------- slither/slither.py | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 60fcd6b0ff..92d031fddb 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -1,5 +1,4 @@ import json -import re from collections import defaultdict from typing import Dict, List, Set, Tuple, NamedTuple, Union @@ -33,6 +32,7 @@ from slither.slithir.operations.binary import Binary from slither.slithir.variables import Constant from slither.utils.output import Output +from slither.utils.tests_pattern import is_test_contract from slither.visitors.expression.constants_folding import ConstantFolding @@ -53,9 +53,7 @@ def _extract_payable(contracts) -> Dict[str, List[str]]: return ret -def _extract_solidity_variable_usage( - contracts, sol_var: SolidityVariable -) -> Dict[str, List[str]]: +def _extract_solidity_variable_usage(contracts, sol_var: SolidityVariable) -> Dict[str, List[str]]: ret: Dict[str, List[str]] = {} for contract in contracts: functions_using_sol_var = [] @@ -391,8 +389,7 @@ def output(self, filename: str) -> Output: # pylint: disable=too-many-locals _filename(string) """ - 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)] + contracts = [c for c in self.slither.contracts if not is_test_contract(c)] payable = _extract_payable(contracts) timestamp = _extract_solidity_variable_usage( @@ -404,9 +401,7 @@ def output(self, filename: str) -> Output: # pylint: disable=too-many-locals msg_sender = _extract_solidity_variable_usage( contracts, SolidityVariableComposed("msg.sender") ) - msg_gas = _extract_solidity_variable_usage( - contracts, SolidityVariableComposed("msg.gas") - ) + 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) @@ -421,7 +416,7 @@ def output(self, filename: str) -> Output: # pylint: disable=too-many-locals external_calls = _have_external_calls(contracts) - #call_parameters = _call_a_parameter(self.slither, contracts) + # call_parameters = _call_a_parameter(self.slither, contracts) use_balance = _use_balance(contracts) @@ -442,7 +437,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, diff --git a/slither/slither.py b/slither/slither.py index ca0df5f712..046fc54faf 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -137,7 +137,7 @@ def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None: if printers_to_run == "echidna": self.skip_data_dependency = True - self._init_parsing_and_analyses(kwargs.get("skip_analyze", False)) + self._init_parsing_and_analyses(kwargs.get("skip_analyze", False)) def _init_parsing_and_analyses(self, skip_analyze: bool) -> None: From c23b8fe8318f5ec287545f6aad79fe18635be31f Mon Sep 17 00:00:00 2001 From: Simone Date: Tue, 25 Jul 2023 11:31:43 +0200 Subject: [PATCH 3/8] Lint --- slither/printers/guidance/echidna.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index ae89c25802..7255534edb 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -33,7 +33,7 @@ from slither.slithir.variables import Constant from slither.utils.output import Output from slither.utils.tests_pattern import is_test_contract -from slither.visitors.expression.constants_folding import ConstantFolding +from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant def _get_name(f: Union[Function, Variable]) -> str: From b2650895c47563e8541ced3ff232e29e79712d35 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 13 Oct 2023 11:27:10 +0200 Subject: [PATCH 4/8] fix pylint --- slither/slither.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slither/slither.py b/slither/slither.py index 773ab9f742..747d2207ef 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -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: From dea0483e82599e088680f72cd468f7e363057cf2 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 13 Oct 2023 11:32:38 +0200 Subject: [PATCH 5/8] Fix merge --- slither/printers/guidance/echidna.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 67745c6ba3..7a03329491 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -125,18 +125,18 @@ def _extract_constant_functions(contracts) -> 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(): From 1437b01f4b92c6ed50ea9fc647e1f2653760a533 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 13 Oct 2023 11:38:55 +0200 Subject: [PATCH 6/8] Add missing type import --- slither/printers/guidance/echidna.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 7a03329491..1acd4051f2 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -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, From 0362e0023e4278014cba0d3363596f34db11de46 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 13 Oct 2023 12:01:20 +0200 Subject: [PATCH 7/8] Keep tests contract in the echidna printer Echidna might need constant from the tests themselves ;) --- slither/printers/guidance/echidna.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 1acd4051f2..2d437dd395 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -404,7 +404,7 @@ def output(self, filename: str) -> Output: # pylint: disable=too-many-locals _filename(string) """ - contracts = [c for c in self.slither.contracts if not is_test_contract(c)] + contracts = self.slither.contracts payable = _extract_payable(contracts) timestamp = _extract_solidity_variable_usage( From d9805c0e63aec426535241fa33149723a9b7b179 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 13 Oct 2023 12:07:32 +0200 Subject: [PATCH 8/8] Add more types, fix pylint --- slither/printers/guidance/echidna.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 2d437dd395..c729779ce1 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -32,7 +32,6 @@ from slither.slithir.operations.binary import Binary from slither.slithir.variables import Constant, ReferenceVariable from slither.utils.output import Output -from slither.utils.tests_pattern import is_test_contract from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant @@ -44,7 +43,7 @@ def _get_name(f: Union[Function, Variable]) -> str: return f.solidity_signature -def _extract_payable(contracts) -> Dict[str, List[str]]: +def _extract_payable(contracts: List[Contract]) -> Dict[str, List[str]]: ret: Dict[str, List[str]] = {} for contract in contracts: payable_functions = [_get_name(f) for f in contract.functions_entry_points if f.payable] @@ -53,7 +52,9 @@ def _extract_payable(contracts) -> Dict[str, List[str]]: return ret -def _extract_solidity_variable_usage(contracts, sol_var: SolidityVariable) -> Dict[str, List[str]]: +def _extract_solidity_variable_usage( + contracts: List[Contract], sol_var: SolidityVariable +) -> Dict[str, List[str]]: ret: Dict[str, List[str]] = {} for contract in contracts: functions_using_sol_var = [] @@ -113,7 +114,7 @@ def _is_constant(f: Function) -> bool: # pylint: disable=too-many-branches return True -def _extract_constant_functions(contracts) -> Dict[str, List[str]]: +def _extract_constant_functions(contracts: List[Contract]) -> Dict[str, List[str]]: ret: Dict[str, List[str]] = {} for contract in contracts: cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)] @@ -237,7 +238,7 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n def _extract_constants( - contracts, + 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) @@ -269,7 +270,7 @@ def _extract_constants( def _extract_function_relations( - contracts, + 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) @@ -297,7 +298,7 @@ def _extract_function_relations( return ret -def _have_external_calls(contracts) -> Dict[str, List[str]]: +def _have_external_calls(contracts: List[Contract]) -> Dict[str, List[str]]: """ Detect the functions with external calls :param slither: @@ -313,7 +314,7 @@ def _have_external_calls(contracts) -> Dict[str, List[str]]: return ret -def _use_balance(contracts) -> Dict[str, List[str]]: +def _use_balance(contracts: List[Contract]) -> Dict[str, List[str]]: """ Detect the functions with external calls :param slither: @@ -332,7 +333,7 @@ def _use_balance(contracts) -> Dict[str, List[str]]: return ret -def _with_fallback(contracts) -> Set[str]: +def _with_fallback(contracts: List[Contract]) -> Set[str]: ret: Set[str] = set() for contract in contracts: for function in contract.functions_entry_points: @@ -341,7 +342,7 @@ def _with_fallback(contracts) -> Set[str]: return ret -def _with_receive(contracts) -> Set[str]: +def _with_receive(contracts: List[Contract]) -> Set[str]: ret: Set[str] = set() for contract in contracts: for function in contract.functions_entry_points: @@ -350,7 +351,7 @@ def _with_receive(contracts) -> Set[str]: return ret -def _call_a_parameter(slither: SlitherCore, contracts) -> 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: