Skip to content

Commit

Permalink
Merge pull request #424 from crytic/dev-new-value-syntax
Browse files Browse the repository at this point in the history
Add suport for new call syntax: {gas: X, value: Y}
  • Loading branch information
montyly authored Mar 30, 2020
2 parents d77ad1c + 93d4611 commit 75c3dd5
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 33 deletions.
35 changes: 33 additions & 2 deletions slither/core/expressions/call_expression.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from slither.core.expressions.expression import Expression


class CallExpression(Expression):

def __init__(self, called, arguments, type_call):
Expand All @@ -8,6 +9,27 @@ def __init__(self, called, arguments, type_call):
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):
Expand All @@ -22,5 +44,14 @@ def type_call(self):
return self._type_call

def __str__(self):
return str(self._called) + '(' + ','.join([str(a) for a in self._arguments]) + ')'

txt = str(self._called)
if self.call_gas or self.call_value:
gas = f'gas: {self.call_gas}' if self.call_gas else ''
value = f'value: {self.call_value}' if self.call_value else ''
if gas and value:
txt += '{' + f'{gas}, {value}' + '}'
elif gas:
txt += '{' + f'{gas}' + '}'
else:
txt += '{' + f'{value}' + '}'
return txt + '(' + ','.join([str(a) for a in self._arguments]) + ')'
5 changes: 2 additions & 3 deletions slither/core/expressions/member_access.py
Original file line number Diff line number Diff line change
@@ -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__()
Expand All @@ -27,4 +27,3 @@ def type(self):

def __str__(self):
return str(self.expression) + '.' + self.member_name

6 changes: 6 additions & 0 deletions slither/slithir/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
19 changes: 19 additions & 0 deletions slither/slithir/operations/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ def __init__(self, variable_left, variable_right, result):
self._variable_left = variable_left
self._variable_right = variable_right
self._lvalue = result
self._gas = None
self._value = None


@property
def read(self):
Expand All @@ -29,6 +32,22 @@ def variable_left(self):
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)

22 changes: 18 additions & 4 deletions slither/slithir/tmp_operations/tmp_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ def __init__(self, called, nbr_arguments, result, type_call):
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):
Expand All @@ -36,10 +54,6 @@ def call_id(self, c):
def called(self):
return self._called

@property
def read(self):
return [self.called]

@called.setter
def called(self, c):
self._called = c
Expand Down
3 changes: 1 addition & 2 deletions slither/slithir/utils/ssa.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

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
Expand Down
73 changes: 51 additions & 22 deletions slither/solc_parsing/expressions/expression_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

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:
Expand All @@ -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
Expand Down Expand Up @@ -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]

Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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
###################################################################################
###################################################################################
Expand Down Expand Up @@ -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

###################################################################################
Expand All @@ -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:
Expand All @@ -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']]
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -320,6 +343,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):
"""
Expand Down Expand Up @@ -389,9 +413,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
Expand All @@ -407,7 +437,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]
Expand Down Expand Up @@ -478,7 +508,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
Expand All @@ -494,7 +524,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:
Expand Down Expand Up @@ -525,13 +555,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:
Expand Down Expand Up @@ -672,5 +702,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)
Loading

0 comments on commit 75c3dd5

Please sign in to comment.