Add suport for new call syntax: {gas: X, value: Y} (fix #419)

pull/424/head
Josselin 5 years ago
parent 7ac3ff8fe9
commit f9b3af814d
  1. 23
      slither/core/expressions/call_expression.py
  2. 5
      slither/core/expressions/member_access.py
  3. 6
      slither/slithir/convert.py
  4. 19
      slither/slithir/operations/member.py
  5. 22
      slither/slithir/tmp_operations/tmp_call.py
  6. 3
      slither/slithir/utils/ssa.py
  7. 73
      slither/solc_parsing/expressions/expression_parsing.py
  8. 4
      slither/visitors/expression/expression.py
  9. 8
      slither/visitors/slithir/expression_to_slithir.py

@ -1,5 +1,6 @@
from slither.core.expressions.expression import Expression from slither.core.expressions.expression import Expression
class CallExpression(Expression): class CallExpression(Expression):
def __init__(self, called, arguments, type_call): def __init__(self, called, arguments, type_call):
@ -8,6 +9,27 @@ class CallExpression(Expression):
self._called = called self._called = called
self._arguments = arguments self._arguments = arguments
self._type_call = type_call 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 @property
def called(self): def called(self):
@ -23,4 +45,3 @@ class CallExpression(Expression):
def __str__(self): def __str__(self):
return str(self._called) + '(' + ','.join([str(a) for a in self._arguments]) + ')' return str(self._called) + '(' + ','.join([str(a) for a in self._arguments]) + ')'

@ -1,11 +1,11 @@
from slither.core.expressions.expression import Expression from slither.core.expressions.expression import Expression
from slither.core.expressions.expression_typed import ExpressionTyped from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.solidity_types.type import Type
class MemberAccess(ExpressionTyped): class MemberAccess(ExpressionTyped):
def __init__(self, member_name, member_type, expression): 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 # TODO member_type is not always a Type
assert isinstance(expression, Expression) assert isinstance(expression, Expression)
super(MemberAccess, self).__init__() super(MemberAccess, self).__init__()
@ -27,4 +27,3 @@ class MemberAccess(ExpressionTyped):
def __str__(self): def __str__(self):
return str(self.expression) + '.' + self.member_name return str(self.expression) + '.' + self.member_name

@ -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, msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue,
ins.type_call) ins.type_call)
msgcall.call_id = ins.call_id 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) msgcall.set_expression(ins.expression)
return msgcall return msgcall
if isinstance(ins.ori, TmpCall): if isinstance(ins.ori, TmpCall):

@ -16,6 +16,9 @@ class Member(OperationWithLValue):
self._variable_left = variable_left self._variable_left = variable_left
self._variable_right = variable_right self._variable_right = variable_right
self._lvalue = result self._lvalue = result
self._gas = None
self._value = None
@property @property
def read(self): def read(self):
@ -29,6 +32,22 @@ class Member(OperationWithLValue):
def variable_right(self): def variable_right(self):
return self._variable_right 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): def __str__(self):
return '{}({}) -> {}.{}'.format(self.lvalue, self.lvalue.type, self.variable_left, self.variable_right) return '{}({}) -> {}.{}'.format(self.lvalue, self.lvalue.type, self.variable_left, self.variable_right)

@ -19,6 +19,24 @@ class TmpCall(OperationWithLValue):
self._lvalue = result self._lvalue = result
self._ori = None # self._ori = None #
self._callid = 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 @property
def call_id(self): def call_id(self):
@ -36,10 +54,6 @@ class TmpCall(OperationWithLValue):
def called(self): def called(self):
return self._called return self._called
@property
def read(self):
return [self.called]
@called.setter @called.setter
def called(self, c): def called(self, c):
self._called = c self._called = c

@ -2,8 +2,7 @@ import logging
from slither.core.cfg.node import NodeType from slither.core.cfg.node import NodeType
from slither.core.declarations import (Contract, Enum, Function, from slither.core.declarations import (Contract, Enum, Function,
SolidityFunction, SolidityVariable, SolidityFunction, SolidityVariable, Structure)
SolidityVariableComposed, Structure)
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
from slither.core.variables.local_variable import LocalVariable from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable

@ -48,7 +48,7 @@ logger = logging.getLogger("ExpressionParsing")
def get_pointer_name(variable): def get_pointer_name(variable):
curr_type = variable.type curr_type = variable.type
while(isinstance(curr_type, (ArrayType, MappingType))): while (isinstance(curr_type, (ArrayType, MappingType))):
if isinstance(curr_type, ArrayType): if isinstance(curr_type, ArrayType):
curr_type = curr_type.type curr_type = curr_type.type
else: else:
@ -61,7 +61,6 @@ def get_pointer_name(variable):
def find_variable(var_name, caller_context, referenced_declaration=None, is_super=False): def find_variable(var_name, caller_context, referenced_declaration=None, is_super=False):
# variable are looked from the contract declarer # variable are looked from the contract declarer
# functions can be shadowed, but are looked from the contract instance, rather than 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 # 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{ # function test(function(uint) internal returns(bool) t) interna{
# Will have a local variable t which will match the signature # Will have a local variable t which will match the signature
# t(uint256) # 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: if var_name and var_name in func_variables_ptr:
return func_variables_ptr[var_name] 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] return contract_variables[var_name]
# A state variable can be a pointer # 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: if var_name and var_name in conc_variables_ptr:
return conc_variables_ptr[var_name] return conc_variables_ptr[var_name]
if is_super: if is_super:
getter_available = lambda f: f.functions_declared getter_available = lambda f: f.functions_declared
d = {f.canonical_name:f for f in contract.functions} 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()} functions = {f.full_name: f for f in
contract_declarer.available_elements_from_inheritances(d, getter_available).values()}
else: else:
functions = contract.available_functions_as_dict() functions = contract.available_functions_as_dict()
if var_name in functions: if var_name in functions:
@ -128,7 +128,8 @@ def find_variable(var_name, caller_context, referenced_declaration=None, is_supe
if is_super: if is_super:
getter_available = lambda m: m.modifiers_declared getter_available = lambda m: m.modifiers_declared
d = {m.canonical_name: m for m in contract.modifiers} 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: else:
modifiers = contract.available_modifiers_as_dict() modifiers = contract.available_modifiers_as_dict()
if var_name in modifiers: 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)) raise VariableNotFound('Variable not found: {} (context {})'.format(var_name, caller_context))
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -211,14 +213,15 @@ def filter_name(value):
max_idx = len(value) max_idx = len(value)
while counter: while counter:
assert idx < max_idx assert idx < max_idx
idx = idx +1 idx = idx + 1
if value[idx] == '(': if value[idx] == '(':
counter += 1 counter += 1
elif value[idx] == ')': elif value[idx] == ')':
counter -= 1 counter -= 1
value = value[:idx+1] value = value[:idx + 1]
return value return value
# endregion # endregion
################################################################################### ###################################################################################
@ -242,9 +245,7 @@ def parse_call(expression, caller_context):
if type_conversion: if type_conversion:
type_call = parse_type(UnknownType(type_return), caller_context) type_call = parse_type(UnknownType(type_return), caller_context)
if caller_context.is_compact_ast: if caller_context.is_compact_ast:
type_info = expression['expression']
assert len(expression['arguments']) == 1 assert len(expression['arguments']) == 1
expression_to_parse = expression['arguments'][0] expression_to_parse = expression['arguments'][0]
else: else:
@ -264,8 +265,25 @@ def parse_call(expression, caller_context):
t.set_offset(src, caller_context.slither) t.set_offset(src, caller_context.slither)
return t return t
call_gas = None
call_value = None
if caller_context.is_compact_ast: if caller_context.is_compact_ast:
called = parse_expression(expression['expression'], caller_context) 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 = [] arguments = []
if expression['arguments']: if expression['arguments']:
arguments = [parse_expression(a, caller_context) for a in 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::]] arguments = [parse_expression(a, caller_context) for a in children[1::]]
if isinstance(called, SuperCallExpression): if isinstance(called, SuperCallExpression):
sp = SuperCallExpression(called, arguments, type_return) sp = SuperCallExpression(called, arguments, type_return)
sp.set_offset(expression['src'], caller_context.slither) sp.set_offset(expression['src'], caller_context.slither)
return sp return sp
call_expression = CallExpression(called, arguments, type_return) call_expression = CallExpression(called, arguments, type_return)
call_expression.set_offset(src, caller_context.slither) 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 return call_expression
def parse_super_name(expression, is_compact_ast): def parse_super_name(expression, is_compact_ast):
if is_compact_ast: if is_compact_ast:
assert expression['nodeType'] == 'MemberAccess' assert expression['nodeType'] == 'MemberAccess'
attributes = expression
base_name = expression['memberName'] base_name = expression['memberName']
arguments = expression['typeDescriptions']['typeString'] arguments = expression['typeDescriptions']['typeString']
else: else:
@ -302,7 +324,8 @@ def parse_super_name(expression, is_compact_ast):
if ' ' in arguments: if ' ' in arguments:
arguments = arguments[:arguments.find(' ')] arguments = arguments[:arguments.find(' ')]
return base_name+arguments return base_name + arguments
def _parse_elementary_type_name_expression(expression, is_compact_ast, caller_context): def _parse_elementary_type_name_expression(expression, is_compact_ast, caller_context):
# nop exression # 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) e.set_offset(expression['src'], caller_context.slither)
return e return e
def parse_expression(expression, caller_context): def parse_expression(expression, caller_context):
""" """
@ -387,9 +411,15 @@ def parse_expression(expression, caller_context):
binary_op.set_offset(src, caller_context.slither) binary_op.set_offset(src, caller_context.slither)
return binary_op return binary_op
elif name == 'FunctionCall': elif name in 'FunctionCall':
return parse_call(expression, caller_context) 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': elif name == 'TupleExpression':
""" """
For expression like For expression like
@ -405,7 +435,7 @@ def parse_expression(expression, caller_context):
if is_compact_ast: if is_compact_ast:
expressions = [parse_expression(e, caller_context) if e else None for e in expression['components']] expressions = [parse_expression(e, caller_context) if e else None for e in expression['components']]
else: else:
if 'children' not in expression : if 'children' not in expression:
attributes = expression['attributes'] attributes = expression['attributes']
components = attributes['components'] components = attributes['components']
expressions = [parse_expression(c, caller_context) if c else None for c in 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']: if 'subdenomination' in expression and expression['subdenomination']:
subdenomination = expression['subdenomination'] subdenomination = expression['subdenomination']
elif not value and value != "": elif not value and value != "":
value = '0x'+expression['hexValue'] value = '0x' + expression['hexValue']
type = expression['typeDescriptions']['typeString'] type = expression['typeDescriptions']['typeString']
# Length declaration for array was None until solc 0.5.5 # 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 # for literal declared as hex
# see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals
assert 'hexvalue' in expression['attributes'] assert 'hexvalue' in expression['attributes']
value = '0x'+expression['attributes']['hexvalue'] value = '0x' + expression['attributes']['hexvalue']
type = expression['attributes']['type'] type = expression['attributes']['type']
if type is None: if type is None:
@ -523,13 +553,13 @@ def parse_expression(expression, caller_context):
else: else:
value = expression['attributes']['value'] value = expression['attributes']['value']
if 'type' in expression['attributes']: if 'type' in expression['attributes']:
t = expression['attributes']['type'] t = expression['attributes']['type']
if t: if t:
found = re.findall('[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)', t) found = re.findall('[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)', t)
assert len(found) <= 1 assert len(found) <= 1
if found: if found:
value = value+'('+found[0]+')' value = value + '(' + found[0] + ')'
value = filter_name(value) value = filter_name(value)
if 'referencedDeclaration' in expression: if 'referencedDeclaration' in expression:
@ -670,5 +700,4 @@ def parse_expression(expression, caller_context):
call.set_offset(src, caller_context.slither) call.set_offset(src, caller_context.slither)
return call return call
raise ParsingError('Expression not parsed %s'%name) raise ParsingError('Expression not parsed %s' % name)

@ -107,6 +107,10 @@ class ExpressionVisitor:
for arg in expression.arguments: for arg in expression.arguments:
if arg: if arg:
self._visit_expression(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): def _visit_conditional_expression(self, expression):
self._visit_expression(expression.if_expression) self._visit_expression(expression.if_expression)

@ -150,6 +150,14 @@ class ExpressionToSlithIR(ExpressionVisitor):
message_call = TmpCall(called, len(args), val, expression.type_call) message_call = TmpCall(called, len(args), val, expression.type_call)
message_call.set_expression(expression) 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) self._result.append(message_call)
set_val(expression, val) set_val(expression, val)

Loading…
Cancel
Save