From 73ece898ea5a263b7d5220568ea0015fc7bd028d Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 22 Mar 2020 16:38:51 +0100 Subject: [PATCH] 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): """