From 97bd577c3fe5b53648ce651851ace57433c2257d Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 22 Mar 2020 14:58:36 +0100 Subject: [PATCH 1/2] Add support for receive() method --- slither/core/declarations/function.py | 7 ++-- slither/solc_parsing/declarations/function.py | 35 ++++++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index c9778dcc7..7c1532ca4 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -54,8 +54,9 @@ class FunctionType(Enum): NORMAL = 0 CONSTRUCTOR = 1 FALLBACK = 2 - CONSTRUCTOR_VARIABLES = 3 # Fake function to hold variable declaration statements - CONSTRUCTOR_CONSTANT_VARIABLES = 4 # Fake function to hold variable declaration statements + RECEIVE = 3 + CONSTRUCTOR_VARIABLES = 10 # Fake function to hold variable declaration statements + CONSTRUCTOR_CONSTANT_VARIABLES = 11 # Fake function to hold variable declaration statements class Function(ChildContract, ChildInheritance, SourceMapping): """ @@ -156,6 +157,8 @@ class Function(ChildContract, ChildInheritance, SourceMapping): return 'constructor' elif self._function_type == FunctionType.FALLBACK: return 'fallback' + elif self._function_type == FunctionType.RECEIVE: + return 'receive' elif self._function_type == FunctionType.CONSTRUCTOR_VARIABLES: return 'slitherConstructorVariables' elif self._function_type == FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES: diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 78177de81..8a74bac73 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -56,6 +56,8 @@ class FunctionSolc(Function): # which is only possible with solc > 0.5 self._variables_renamed = {} + self._analyze_type() + ################################################################################### ################################################################################### # region AST format @@ -113,6 +115,31 @@ class FunctionSolc(Function): ################################################################################### ################################################################################### + def _analyze_type(self): + """ + Analyz the type of the function + Myst be called in the constructor as the name might change according to the function's type + For example both the fallback and the receiver will have an empty name + :return: + """ + if self.is_compact_ast: + attributes = self._functionNotParsed + else: + attributes = self._functionNotParsed['attributes'] + + if self._name == '': + self._function_type = FunctionType.FALLBACK + # 0.6.x introduced the receiver function + # It has also an empty name, so we need to check the kind attribute + if 'kind' in attributes: + if attributes['kind'] == 'receive': + self._function_type = FunctionType.RECEIVE + else: + self._function_type = FunctionType.NORMAL + + if self._name == self.contract_declarer.name: + self._function_type = FunctionType.CONSTRUCTOR + def _analyze_attributes(self): if self.is_compact_ast: attributes = self._functionNotParsed @@ -133,14 +160,6 @@ class FunctionSolc(Function): if 'constant' in attributes: self._view = attributes['constant'] - if self._name == '': - self._function_type = FunctionType.FALLBACK - else: - self._function_type = FunctionType.NORMAL - - if self._name == self.contract_declarer.name: - self._function_type = FunctionType.CONSTRUCTOR - if 'isConstructor' in attributes and attributes['isConstructor']: self._function_type = FunctionType.CONSTRUCTOR From 73ece898ea5a263b7d5220568ea0015fc7bd028d Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 22 Mar 2020 16:38:51 +0100 Subject: [PATCH 2/2] Add support for try/catch statements --- slither/core/cfg/node.py | 35 ++++--- slither/solc_parsing/declarations/contract.py | 33 ++++--- slither/solc_parsing/declarations/function.py | 97 ++++++++++++++----- 3 files changed, 115 insertions(+), 50 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 76b8f6915..034879997 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -62,6 +62,9 @@ class NodeType: # Only modifier node PLACEHOLDER = 0x40 + TRY = 0x41 + CATCH = 0x42 + # Node not related to the CFG # Use for state variable declaration OTHER_ENTRYPOINT = 0x50 @@ -69,33 +72,37 @@ class NodeType: # @staticmethod def str(t): - if t == 0x0: + if t == NodeType.ENTRYPOINT: return 'ENTRY_POINT' - if t == 0x10: + if t == NodeType.EXPRESSION: return 'EXPRESSION' - if t == 0x11: + if t == NodeType.RETURN: return 'RETURN' - if t == 0x12: + if t == NodeType.IF: return 'IF' - if t == 0x13: + if t == NodeType.VARIABLE: return 'NEW VARIABLE' - if t == 0x14: + if t == NodeType.ASSEMBLY: return 'INLINE ASM' - if t == 0x15: + if t == NodeType.IFLOOP: return 'IF_LOOP' - if t == 0x20: + if t == NodeType.THROW: return 'THROW' - if t == 0x31: + if t == NodeType.BREAK: return 'BREAK' - if t == 0x32: + if t == NodeType.CONTINUE: return 'CONTINUE' - if t == 0x40: + if t == NodeType.PLACEHOLDER: return '_' - if t == 0x50: + if t == NodeType.TRY: + return 'TRY' + if t == NodeType.CATCH: + return 'CATCH' + if t == NodeType.ENDIF: return 'END_IF' - if t == 0x51: + if t == NodeType.STARTLOOP: return 'BEGIN_LOOP' - if t == 0x52: + if t == NodeType.ENDLOOP: return 'END_LOOP' return 'Unknown type {}'.format(hex(t)) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 0bbfb0347..5599e9775 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -280,7 +280,8 @@ class ContractSolc04(Contract): try: for modifier in self.modifiers: modifier.analyze_content() - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() return @@ -288,7 +289,8 @@ class ContractSolc04(Contract): try: for function in self.functions: function.analyze_content() - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() return @@ -300,7 +302,8 @@ class ContractSolc04(Contract): getter_available = lambda f: f.modifiers_declared Cls = ModifierSolc self._modifiers = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls) - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() self._modifiers_no_params = [] @@ -313,7 +316,8 @@ class ContractSolc04(Contract): getter_available = lambda f: f.functions_declared Cls = FunctionSolc self._functions = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls) - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() self._functions_no_params = [] return @@ -364,7 +368,8 @@ class ContractSolc04(Contract): if accessible_elements[element.full_name] != all_elements[element.canonical_name]: element.is_shadowed = True accessible_elements[element.full_name].shadows = True - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() return all_elements @@ -374,7 +379,8 @@ class ContractSolc04(Contract): # cant parse constant expression based on function calls try: var.analyze(self) - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) pass return @@ -451,7 +457,8 @@ class ContractSolc04(Contract): for var in self.variables: var.analyze(self) return - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() def analyze_using_for(self): @@ -483,7 +490,8 @@ class ContractSolc04(Contract): self.using_for[old] = [] self._using_for[old].append(new) self._usingForNotParsed = [] - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() def analyze_enums(self): @@ -496,7 +504,8 @@ class ContractSolc04(Contract): # at the same time self._analyze_enum(enum) self._enumsNotParsed = None - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() def _analyze_enum(self, enum): @@ -530,7 +539,8 @@ class ContractSolc04(Contract): try: for struct in self.structures: self._analyze_struct(struct) - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() def analyze_events(self): @@ -544,7 +554,8 @@ class ContractSolc04(Contract): event.set_contract(self) event.set_offset(event_to_parse['src'], self.slither) self._events[event.full_name] = event - except (VariableNotFound, KeyError): + except (VariableNotFound, KeyError) as e: + logger.error(e) self.log_incorrect_parsing() self._eventsNotParsed = None diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 8a74bac73..31f87a60b 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -494,6 +494,39 @@ class FunctionSolc(Function): link_nodes(node_condition, node_endDoWhile) return node_endDoWhile + def _parse_try_catch(self, statement, node): + externalCall = statement.get('externalCall', None) + + if externalCall is None: + raise ParsingError('Try/Catch not correctly parsed by Slither %s' % statement) + + new_node = self._new_node(NodeType.TRY, statement['src']) + new_node.add_unparsed_expression(externalCall) + link_nodes(node, new_node) + node = new_node + + for clause in statement.get('clauses', []): + self._parse_catch(clause, node) + return node + + def _parse_catch(self, statement, node): + block = statement.get('block', None) + if block is None: + raise ParsingError('Catch not correctly parsed by Slither %s' % statement) + try_node = self._new_node(NodeType.CATCH, statement['src']) + link_nodes(node, try_node) + + if self.is_compact_ast: + params = statement['parameters'] + else: + params = statement[self.get_children('children')] + + for param in params.get('parameters', []): + assert param[self.get_key()] == 'VariableDeclaration' + self._add_param(param) + + return self._parse_statement(block, try_node) + def _parse_variable_definition(self, statement, node): try: local_var = LocalVariableSolc(statement) @@ -753,8 +786,12 @@ class FunctionSolc(Function): new_node.add_unparsed_expression(expression) link_nodes(node, new_node) node = new_node + elif name == 'TryStatement': + node = self._parse_try_catch(statement, node) + # elif name == 'TryCatchClause': + # self._parse_catch(statement, node) else: - raise ParsingError('Statement not parsed %s'%name) + raise ParsingError('Statement not parsed %s' % name) return node @@ -865,6 +902,35 @@ class FunctionSolc(Function): node.set_sons([start_node]) start_node.add_father(node) + def _fix_try(self, node): + end_node = next((son for son in node.sons if son.type != NodeType.CATCH), None) + if end_node: + for son in node.sons: + if son.type == NodeType.CATCH: + self._fix_catch(son, end_node) + + def _fix_catch(self, node, end_node): + if not node.sons: + link_nodes(node, end_node) + else: + for son in node.sons: + self._fix_catch(son, end_node) + + def _add_param(self, param): + local_var = LocalVariableSolc(param) + + local_var.set_function(self) + local_var.set_offset(param['src'], self.contract.slither) + local_var.analyze(self) + + # see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location + if local_var.location == 'default': + local_var.set_location('memory') + + self._add_local_variable(local_var) + return local_var + + def _parse_params(self, params): assert params[self.get_key()] == 'ParameterList' @@ -878,20 +944,10 @@ class FunctionSolc(Function): for param in params: assert param[self.get_key()] == 'VariableDeclaration' - - local_var = LocalVariableSolc(param) - - local_var.set_function(self) - local_var.set_offset(param['src'], self.contract.slither) - local_var.analyze(self) - - # see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location - if local_var.location == 'default': - local_var.set_location('memory') - - self._add_local_variable(local_var) + local_var = self._add_param(param) self._parameters.append(local_var) + def _parse_returns(self, returns): assert returns[self.get_key()] == 'ParameterList' @@ -906,18 +962,7 @@ class FunctionSolc(Function): for ret in returns: assert ret[self.get_key()] == 'VariableDeclaration' - - local_var = LocalVariableSolc(ret) - - local_var.set_function(self) - local_var.set_offset(ret['src'], self.contract.slither) - local_var.analyze(self) - - # see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location - if local_var.location == 'default': - local_var.set_location('memory') - - self._add_local_variable(local_var) + local_var = self._add_param(ret) self._returns.append(local_var) @@ -973,6 +1018,8 @@ class FunctionSolc(Function): self._fix_break_node(node) if node.type in [NodeType.CONTINUE]: self._fix_continue_node(node) + if node.type in [NodeType.TRY]: + self._fix_try(node) def _remove_alone_endif(self): """