From f9b3af814d896b6b743773ef29fb1cb49ee0a394 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 30 Mar 2020 15:30:57 +0200 Subject: [PATCH] Add suport for new call syntax: {gas: X, value: Y} (fix #419) --- slither/core/expressions/call_expression.py | 23 +++++- slither/core/expressions/member_access.py | 5 +- slither/slithir/convert.py | 6 ++ slither/slithir/operations/member.py | 19 +++++ slither/slithir/tmp_operations/tmp_call.py | 22 +++++- slither/slithir/utils/ssa.py | 3 +- .../expressions/expression_parsing.py | 73 +++++++++++++------ slither/visitors/expression/expression.py | 4 + .../visitors/slithir/expression_to_slithir.py | 8 ++ 9 files changed, 131 insertions(+), 32 deletions(-) diff --git a/slither/core/expressions/call_expression.py b/slither/core/expressions/call_expression.py index 937a67a58..42b1a9207 100644 --- a/slither/core/expressions/call_expression.py +++ b/slither/core/expressions/call_expression.py @@ -1,5 +1,6 @@ from slither.core.expressions.expression import Expression + class CallExpression(Expression): def __init__(self, called, arguments, type_call): @@ -8,6 +9,27 @@ class CallExpression(Expression): self._called = called self._arguments = arguments self._type_call = type_call + # gas and value are only available if the syntax is {gas: , value: } + # For the .gas().value(), the member are considered as function call + # And converted later to the correct info (convert.py) + self._gas = None + self._value = None + + @property + def call_value(self): + return self._value + + @call_value.setter + def call_value(self, v): + self._value = v + + @property + def call_gas(self): + return self._gas + + @call_gas.setter + def call_gas(self, gas): + self._gas = gas @property def called(self): @@ -23,4 +45,3 @@ class CallExpression(Expression): def __str__(self): return str(self._called) + '(' + ','.join([str(a) for a in self._arguments]) + ')' - diff --git a/slither/core/expressions/member_access.py b/slither/core/expressions/member_access.py index 72d1238b4..d93583142 100644 --- a/slither/core/expressions/member_access.py +++ b/slither/core/expressions/member_access.py @@ -1,11 +1,11 @@ from slither.core.expressions.expression import Expression from slither.core.expressions.expression_typed import ExpressionTyped -from slither.core.solidity_types.type import Type + class MemberAccess(ExpressionTyped): def __init__(self, member_name, member_type, expression): - #assert isinstance(member_type, Type) + # assert isinstance(member_type, Type) # TODO member_type is not always a Type assert isinstance(expression, Expression) super(MemberAccess, self).__init__() @@ -27,4 +27,3 @@ class MemberAccess(ExpressionTyped): def __str__(self): return str(self.expression) + '.' + self.member_name - diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 03875c5b2..3ad69e606 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -620,7 +620,13 @@ def extract_tmp_call(ins, contract): msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) msgcall.call_id = ins.call_id + + if ins.call_gas: + msgcall.call_gas = ins.call_gas + if ins.call_value: + msgcall.call_value = ins.call_value msgcall.set_expression(ins.expression) + return msgcall if isinstance(ins.ori, TmpCall): diff --git a/slither/slithir/operations/member.py b/slither/slithir/operations/member.py index 858aa7ada..a8d66fb03 100644 --- a/slither/slithir/operations/member.py +++ b/slither/slithir/operations/member.py @@ -16,6 +16,9 @@ class Member(OperationWithLValue): self._variable_left = variable_left self._variable_right = variable_right self._lvalue = result + self._gas = None + self._value = None + @property def read(self): @@ -29,6 +32,22 @@ class Member(OperationWithLValue): def variable_right(self): return self._variable_right + @property + def call_value(self): + return self._value + + @call_value.setter + def call_value(self, v): + self._value = v + + @property + def call_gas(self): + return self._gas + + @call_gas.setter + def call_gas(self, gas): + self._gas = gas + def __str__(self): return '{}({}) -> {}.{}'.format(self.lvalue, self.lvalue.type, self.variable_left, self.variable_right) diff --git a/slither/slithir/tmp_operations/tmp_call.py b/slither/slithir/tmp_operations/tmp_call.py index 3c8faec3c..f64c3718b 100644 --- a/slither/slithir/tmp_operations/tmp_call.py +++ b/slither/slithir/tmp_operations/tmp_call.py @@ -19,6 +19,24 @@ class TmpCall(OperationWithLValue): self._lvalue = result self._ori = None # self._callid = None + self._gas = None + self._value = None + + @property + def call_value(self): + return self._value + + @call_value.setter + def call_value(self, v): + self._value = v + + @property + def call_gas(self): + return self._gas + + @call_gas.setter + def call_gas(self, gas): + self._gas = gas @property def call_id(self): @@ -36,10 +54,6 @@ class TmpCall(OperationWithLValue): def called(self): return self._called - @property - def read(self): - return [self.called] - @called.setter def called(self, c): self._called = c diff --git a/slither/slithir/utils/ssa.py b/slither/slithir/utils/ssa.py index 4b3da9eb5..044f71a9c 100644 --- a/slither/slithir/utils/ssa.py +++ b/slither/slithir/utils/ssa.py @@ -2,8 +2,7 @@ import logging from slither.core.cfg.node import NodeType from slither.core.declarations import (Contract, Enum, Function, - SolidityFunction, SolidityVariable, - SolidityVariableComposed, Structure) + SolidityFunction, SolidityVariable, Structure) from slither.core.solidity_types.type import Type from slither.core.variables.local_variable import LocalVariable from slither.core.variables.state_variable import StateVariable diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index a3d48c7f2..6cf12b1fe 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -48,7 +48,7 @@ logger = logging.getLogger("ExpressionParsing") def get_pointer_name(variable): curr_type = variable.type - while(isinstance(curr_type, (ArrayType, MappingType))): + while (isinstance(curr_type, (ArrayType, MappingType))): if isinstance(curr_type, ArrayType): curr_type = curr_type.type else: @@ -61,7 +61,6 @@ def get_pointer_name(variable): def find_variable(var_name, caller_context, referenced_declaration=None, is_super=False): - # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access @@ -102,7 +101,7 @@ def find_variable(var_name, caller_context, referenced_declaration=None, is_supe # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) - func_variables_ptr = {get_pointer_name(f) : f for f in function.variables} + func_variables_ptr = {get_pointer_name(f): f for f in function.variables} if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] @@ -112,14 +111,15 @@ def find_variable(var_name, caller_context, referenced_declaration=None, is_supe return contract_variables[var_name] # A state variable can be a pointer - conc_variables_ptr = {get_pointer_name(f) : f for f in contract_declarer.variables} + conc_variables_ptr = {get_pointer_name(f): f for f in contract_declarer.variables} if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] if is_super: getter_available = lambda f: f.functions_declared - d = {f.canonical_name:f for f in contract.functions} - functions = {f.full_name:f for f in contract_declarer.available_elements_from_inheritances(d, getter_available).values()} + d = {f.canonical_name: f for f in contract.functions} + functions = {f.full_name: f for f in + contract_declarer.available_elements_from_inheritances(d, getter_available).values()} else: functions = contract.available_functions_as_dict() if var_name in functions: @@ -128,7 +128,8 @@ def find_variable(var_name, caller_context, referenced_declaration=None, is_supe if is_super: getter_available = lambda m: m.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} - modifiers = {m.full_name: m for m in contract_declarer.available_elements_from_inheritances(d, getter_available).values()} + modifiers = {m.full_name: m for m in + contract_declarer.available_elements_from_inheritances(d, getter_available).values()} else: modifiers = contract.available_modifiers_as_dict() if var_name in modifiers: @@ -178,6 +179,7 @@ def find_variable(var_name, caller_context, referenced_declaration=None, is_supe raise VariableNotFound('Variable not found: {} (context {})'.format(var_name, caller_context)) + # endregion ################################################################################### ################################################################################### @@ -211,14 +213,15 @@ def filter_name(value): max_idx = len(value) while counter: assert idx < max_idx - idx = idx +1 + idx = idx + 1 if value[idx] == '(': counter += 1 elif value[idx] == ')': counter -= 1 - value = value[:idx+1] + value = value[:idx + 1] return value + # endregion ################################################################################### @@ -242,9 +245,7 @@ def parse_call(expression, caller_context): if type_conversion: type_call = parse_type(UnknownType(type_return), caller_context) - if caller_context.is_compact_ast: - type_info = expression['expression'] assert len(expression['arguments']) == 1 expression_to_parse = expression['arguments'][0] else: @@ -264,8 +265,25 @@ def parse_call(expression, caller_context): t.set_offset(src, caller_context.slither) return t + call_gas = None + call_value = None if caller_context.is_compact_ast: called = parse_expression(expression['expression'], caller_context) + + # If the next expression is a FunctionCallOptions + # We can here the gas/value information + # This is only available if the syntax is {gas: , value: } + # For the .gas().value(), the member are considered as function call + # And converted later to the correct info (convert.py) + if expression['expression'][caller_context.get_key()] == 'FunctionCallOptions': + call_with_options = expression['expression'] + for idx, name in enumerate(call_with_options.get('names', [])): + option = parse_expression(call_with_options['options'][idx], caller_context) + if name == 'value': + call_value = option + if name == 'gas': + call_gas = option + arguments = [] if expression['arguments']: arguments = [parse_expression(a, caller_context) for a in expression['arguments']] @@ -275,17 +293,21 @@ def parse_call(expression, caller_context): arguments = [parse_expression(a, caller_context) for a in children[1::]] if isinstance(called, SuperCallExpression): - sp = SuperCallExpression(called, arguments, type_return) + sp = SuperCallExpression(called, arguments, type_return) sp.set_offset(expression['src'], caller_context.slither) return sp call_expression = CallExpression(called, arguments, type_return) call_expression.set_offset(src, caller_context.slither) + + # Only available if the syntax {gas:, value:} was used + call_expression.call_gas = call_gas + call_expression.call_value = call_value return call_expression + def parse_super_name(expression, is_compact_ast): if is_compact_ast: assert expression['nodeType'] == 'MemberAccess' - attributes = expression base_name = expression['memberName'] arguments = expression['typeDescriptions']['typeString'] else: @@ -302,7 +324,8 @@ def parse_super_name(expression, is_compact_ast): if ' ' in arguments: arguments = arguments[:arguments.find(' ')] - return base_name+arguments + return base_name + arguments + def _parse_elementary_type_name_expression(expression, is_compact_ast, caller_context): # nop exression @@ -318,6 +341,7 @@ def _parse_elementary_type_name_expression(expression, is_compact_ast, caller_co e.set_offset(expression['src'], caller_context.slither) return e + def parse_expression(expression, caller_context): """ @@ -387,9 +411,15 @@ def parse_expression(expression, caller_context): binary_op.set_offset(src, caller_context.slither) return binary_op - elif name == 'FunctionCall': + elif name in 'FunctionCall': return parse_call(expression, caller_context) + elif name == 'FunctionCallOptions': + # call/gas info are handled in parse_call + called = parse_expression(expression['expression'], caller_context) + assert isinstance(called, MemberAccess) + return called + elif name == 'TupleExpression': """ For expression like @@ -405,7 +435,7 @@ def parse_expression(expression, caller_context): if is_compact_ast: expressions = [parse_expression(e, caller_context) if e else None for e in expression['components']] else: - if 'children' not in expression : + if 'children' not in expression: attributes = expression['attributes'] components = attributes['components'] expressions = [parse_expression(c, caller_context) if c else None for c in components] @@ -476,7 +506,7 @@ def parse_expression(expression, caller_context): if 'subdenomination' in expression and expression['subdenomination']: subdenomination = expression['subdenomination'] elif not value and value != "": - value = '0x'+expression['hexValue'] + value = '0x' + expression['hexValue'] type = expression['typeDescriptions']['typeString'] # Length declaration for array was None until solc 0.5.5 @@ -492,7 +522,7 @@ def parse_expression(expression, caller_context): # for literal declared as hex # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals assert 'hexvalue' in expression['attributes'] - value = '0x'+expression['attributes']['hexvalue'] + value = '0x' + expression['attributes']['hexvalue'] type = expression['attributes']['type'] if type is None: @@ -523,13 +553,13 @@ def parse_expression(expression, caller_context): else: value = expression['attributes']['value'] if 'type' in expression['attributes']: - t = expression['attributes']['type'] + t = expression['attributes']['type'] if t: found = re.findall('[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)', t) assert len(found) <= 1 if found: - value = value+'('+found[0]+')' + value = value + '(' + found[0] + ')' value = filter_name(value) if 'referencedDeclaration' in expression: @@ -670,5 +700,4 @@ def parse_expression(expression, caller_context): call.set_offset(src, caller_context.slither) return call - raise ParsingError('Expression not parsed %s'%name) - + raise ParsingError('Expression not parsed %s' % name) diff --git a/slither/visitors/expression/expression.py b/slither/visitors/expression/expression.py index e78ad17ce..2666fac46 100644 --- a/slither/visitors/expression/expression.py +++ b/slither/visitors/expression/expression.py @@ -107,6 +107,10 @@ class ExpressionVisitor: for arg in expression.arguments: if arg: self._visit_expression(arg) + if expression.call_value: + self._visit_expression(expression.call_value) + if expression.call_gas: + self._visit_expression(expression.call_gas) def _visit_conditional_expression(self, expression): self._visit_expression(expression.if_expression) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index ed53f9ef7..386693a7c 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -150,6 +150,14 @@ class ExpressionToSlithIR(ExpressionVisitor): message_call = TmpCall(called, len(args), val, expression.type_call) message_call.set_expression(expression) + # Gas/value are only accessible here if the syntax {gas: , value: } + # Is used over .gas().value() + if expression.call_gas: + call_gas = get(expression.call_gas) + message_call.call_gas = call_gas + if expression.call_value: + call_value = get(expression.call_value) + message_call.call_value = call_value self._result.append(message_call) set_val(expression, val)