From eabcc49b6a765cdb253c4c017376696e16d129d3 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 24 Nov 2020 14:57:55 +0100 Subject: [PATCH 1/4] Memoize core.function/contract properties --- slither/core/declarations/contract.py | 25 +++++++----- slither/core/declarations/function.py | 56 ++++++++++++++++++--------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index aba7ffdf8..e94e2a185 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -77,6 +77,9 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- self._is_incorrectly_parsed: bool = False + self._available_functions_as_dict: Optional[Dict[str, "Function"]] = None + self._all_functions_called: Optional[List["InternalCallType"]] = None + ################################################################################### ################################################################################### # region General's properties @@ -392,7 +395,9 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- return list(self._functions.values()) def available_functions_as_dict(self) -> Dict[str, "Function"]: - return {f.full_name: f for f in self._functions.values() if not f.is_shadowed} + if self._available_functions_as_dict is None: + self._available_functions_as_dict = {f.full_name: f for f in self._functions.values() if not f.is_shadowed} + return self._available_functions_as_dict def add_function(self, func: "Function"): self._functions[func.canonical_name] = func @@ -731,17 +736,19 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- list(Function): List of functions reachable from the contract Includes super, and private/internal functions not shadowed """ - all_functions = [f for f in self.functions + self.modifiers if not f.is_shadowed] # type: ignore - all_callss = [f.all_internal_calls() for f in all_functions] + [list(all_functions)] - all_calls = [item for sublist in all_callss for item in sublist] - all_calls = list(set(all_calls)) + if self._all_functions_called is None: + all_functions = [f for f in self.functions + self.modifiers if not f.is_shadowed] # type: ignore + all_callss = [f.all_internal_calls() for f in all_functions] + [list(all_functions)] + all_calls = [item for sublist in all_callss for item in sublist] + all_calls = list(set(all_calls)) - all_constructors = [c.constructor for c in self.inheritance if c.constructor] - all_constructors = list(set(all_constructors)) + all_constructors = [c.constructor for c in self.inheritance if c.constructor] + all_constructors = list(set(all_constructors)) - set_all_calls = set(all_calls + list(all_constructors)) + set_all_calls = set(all_calls + list(all_constructors)) - return [c for c in set_all_calls if isinstance(c, Function)] + self._all_functions_called = [c for c in set_all_calls if isinstance(c, Function)] + return self._all_functions_called @property def all_state_variables_written(self) -> List["StateVariable"]: diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 6b6fce056..d876eb77e 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -197,6 +197,14 @@ class Function( self._counter_nodes = 0 + # Memoize parameters: + # TODO: identify all the memoize parameters and add a way to undo the memoization + self._full_name: Optional[str] = None + self._signature: Optional[Tuple[str, List[str], List[str]]] = None + self._solidity_signature: Optional[str] = None + self._signature_str: Optional[str] = None + self._canonical_name: Optional[str] = None + ################################################################################### ################################################################################### # region General properties @@ -244,8 +252,11 @@ class Function( str: func_name(type1,type2) Return the function signature without the return values """ - name, parameters, _ = self.signature - return ".".join(self._scope + [name]) + "(" + ",".join(parameters) + ")" + if self._full_name is None: + name, parameters, _ = self.signature + full_name = ".".join(self._scope + [name]) + "(" + ",".join(parameters) + ")" + self._full_name = full_name + return self._full_name @property def canonical_name(self) -> str: @@ -253,13 +264,15 @@ class Function( str: contract.func_name(type1,type2) Return the function signature without the return values """ - name, parameters, _ = self.signature - return ( - ".".join([self.contract_declarer.name] + self._scope + [name]) - + "(" - + ",".join(parameters) - + ")" - ) + if self._canonical_name is None: + name, parameters, _ = self.signature + self._canonical_name = ( + ".".join([self.contract_declarer.name] + self._scope + [name]) + + "(" + + ",".join(parameters) + + ")" + ) + return self._canonical_name @property def contains_assembly(self) -> bool: @@ -921,8 +934,10 @@ class Function( Contract and converted into address :return: the solidity signature """ - parameters = [self._convert_type_for_solidity_signature(x.type) for x in self.parameters] - return self.name + "(" + ",".join(parameters) + ")" + if self._solidity_signature is None: + parameters = [self._convert_type_for_solidity_signature(x.type) for x in self.parameters] + self._solidity_signature = self.name + "(" + ",".join(parameters) + ")" + return self._solidity_signature @property def signature(self) -> Tuple[str, List[str], List[str]]: @@ -930,11 +945,14 @@ class Function( (str, list(str), list(str)): Function signature as (name, list parameters type, list return values type) """ - return ( - self.name, - [str(x.type) for x in self.parameters], - [str(x.type) for x in self.returns], - ) + if self._signature is None: + signature = ( + self.name, + [str(x.type) for x in self.parameters], + [str(x.type) for x in self.returns], + ) + self._signature = signature + return self._signature @property def signature_str(self) -> str: @@ -942,8 +960,10 @@ class Function( str: func_name(type1,type2) returns (type3) Return the function signature as a str (contains the return values) """ - name, parameters, returnVars = self.signature - return name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" + if self._signature_str is None: + name, parameters, returnVars = self.signature + self._signature_str = name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" + return self._signature_str # endregion ################################################################################### From 3368b52a243504dcff84e50fb257ee937674c03f Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 24 Nov 2020 14:59:00 +0100 Subject: [PATCH 2/4] Minor --- slither/core/source_mapping/source_mapping.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index 72bc5c0ff..943b82c83 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -9,16 +9,16 @@ class SourceMapping(Context): super().__init__() # TODO create a namedtuple for the source mapping rather than a dict self._source_mapping: Optional[Dict] = None - self._start: Optional[int] = None - self._length: Optional[int] = None - self._filename_used: Optional[str] = None - self._filename_relative: Optional[str] = None - self._filename_absolute: Optional[str] = None - self._filename_short: Optional[str] = None - self._is_dependency: Optional[bool] = None - self._lines: Optional[List[int]] = None - self._starting_column: Optional[int] = None - self._ending_column: Optional[int] = None + # self._start: Optional[int] = None + # self._length: Optional[int] = None + # self._filename_used: Optional[str] = None + # self._filename_relative: Optional[str] = None + # self._filename_absolute: Optional[str] = None + # self._filename_short: Optional[str] = None + # self._is_dependency: Optional[bool] = None + # self._lines: Optional[List[int]] = None + # self._starting_column: Optional[int] = None + # self._ending_column: Optional[int] = None @property def source_mapping(self) -> Optional[Dict]: From 19ad2e94508fc0846771a789f061ae831383567f Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 24 Nov 2020 15:25:22 +0100 Subject: [PATCH 3/4] Additional memoization --- slither/core/declarations/contract.py | 14 +------------- slither/core/declarations/function.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index e94e2a185..f3ec9ffdf 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -71,7 +71,7 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- self._is_upgradeable: Optional[bool] = None self._is_upgradeable_proxy: Optional[bool] = None - self._is_top_level = False + self.is_top_level = False # heavily used, so no @property self._initial_state_variables: List["StateVariable"] = [] # ssa @@ -1207,18 +1207,6 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- for func in self.functions + self.modifiers: func.fix_phi(last_state_variables_instances, initial_state_variables_instances) - @property - def is_top_level(self) -> bool: - """ - The "TopLevel" contract is used to hold structures and enums defined at the top level - ie. structures and enums that are represented outside of any contract - :return: - """ - return self._is_top_level - - @is_top_level.setter - def is_top_level(self, t: bool): - self._is_top_level = t # endregion ################################################################################### diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index d876eb77e..e871d60f4 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -204,6 +204,7 @@ class Function( self._solidity_signature: Optional[str] = None self._signature_str: Optional[str] = None self._canonical_name: Optional[str] = None + self._is_protected: Optional[bool] = None ################################################################################### ################################################################################### @@ -1427,11 +1428,14 @@ class Function( (bool) """ - if self.is_constructor: - return True - conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False) - args_vars = self.all_solidity_variables_used_as_args() - return SolidityVariableComposed("msg.sender") in conditional_vars + args_vars + if self._is_protected is None: + if self.is_constructor: + self._is_protected = True + return True + conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False) + args_vars = self.all_solidity_variables_used_as_args() + self._is_protected = SolidityVariableComposed("msg.sender") in conditional_vars + args_vars + return self._is_protected # endregion ################################################################################### From 8d943dc764bba9ad91e0657b08e973701d583869 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 24 Nov 2020 16:16:54 +0100 Subject: [PATCH 4/4] black --- slither/core/declarations/contract.py | 7 ++++--- slither/core/declarations/function.py | 16 +++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index f3ec9ffdf..2b15e29f5 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -71,7 +71,7 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- self._is_upgradeable: Optional[bool] = None self._is_upgradeable_proxy: Optional[bool] = None - self.is_top_level = False # heavily used, so no @property + self.is_top_level = False # heavily used, so no @property self._initial_state_variables: List["StateVariable"] = [] # ssa @@ -396,7 +396,9 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- def available_functions_as_dict(self) -> Dict[str, "Function"]: if self._available_functions_as_dict is None: - self._available_functions_as_dict = {f.full_name: f for f in self._functions.values() if not f.is_shadowed} + self._available_functions_as_dict = { + f.full_name: f for f in self._functions.values() if not f.is_shadowed + } return self._available_functions_as_dict def add_function(self, func: "Function"): @@ -1207,7 +1209,6 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- for func in self.functions + self.modifiers: func.fix_phi(last_state_variables_instances, initial_state_variables_instances) - # endregion ################################################################################### ################################################################################### diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index e871d60f4..f71899a2b 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -936,8 +936,10 @@ class Function( :return: the solidity signature """ if self._solidity_signature is None: - parameters = [self._convert_type_for_solidity_signature(x.type) for x in self.parameters] - self._solidity_signature = self.name + "(" + ",".join(parameters) + ")" + parameters = [ + self._convert_type_for_solidity_signature(x.type) for x in self.parameters + ] + self._solidity_signature = self.name + "(" + ",".join(parameters) + ")" return self._solidity_signature @property @@ -947,7 +949,7 @@ class Function( (name, list parameters type, list return values type) """ if self._signature is None: - signature = ( + signature = ( self.name, [str(x.type) for x in self.parameters], [str(x.type) for x in self.returns], @@ -963,7 +965,9 @@ class Function( """ if self._signature_str is None: name, parameters, returnVars = self.signature - self._signature_str = name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" + self._signature_str = ( + name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" + ) return self._signature_str # endregion @@ -1434,7 +1438,9 @@ class Function( return True conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False) args_vars = self.all_solidity_variables_used_as_args() - self._is_protected = SolidityVariableComposed("msg.sender") in conditional_vars + args_vars + self._is_protected = ( + SolidityVariableComposed("msg.sender") in conditional_vars + args_vars + ) return self._is_protected # endregion