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
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]) + ')'

@ -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

@ -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):

@ -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)

@ -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

@ -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

@ -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)

@ -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)

@ -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)

Loading…
Cancel
Save