Commit changes

pull/827/head
Josselin 4 years ago
parent 047ca4071d
commit 8e4d93e9ff
  1. 1
      slither/core/declarations/__init__.py
  2. 25
      slither/core/declarations/contract.py
  3. 12
      slither/core/solidity_types/elementary_type.py
  4. 233
      slither/slithir/convert.py
  5. 8
      slither/slithir/operations/library_call.py
  6. 3
      tests/ast-parsing/library_implicit_conversion-0.4.0.sol

@ -13,3 +13,4 @@ from .solidity_variables import (
from .structure import Structure from .structure import Structure
from .enum_contract import EnumContract from .enum_contract import EnumContract
from .structure_contract import StructureContract from .structure_contract import StructureContract
from .function_contract import FunctionContract

@ -25,7 +25,14 @@ from slither.utils.tests_pattern import is_test_contract
# pylint: disable=too-many-lines,too-many-instance-attributes,import-outside-toplevel,too-many-nested-blocks # pylint: disable=too-many-lines,too-many-instance-attributes,import-outside-toplevel,too-many-nested-blocks
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import Enum, Event, Modifier, EnumContract, StructureContract from slither.core.declarations import (
Enum,
Event,
Modifier,
EnumContract,
StructureContract,
FunctionContract,
)
from slither.slithir.variables.variable import SlithIRVariable from slither.slithir.variables.variable import SlithIRVariable
from slither.core.variables.variable import Variable from slither.core.variables.variable import Variable
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
@ -56,7 +63,7 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public-
self._variables: Dict[str, "StateVariable"] = {} self._variables: Dict[str, "StateVariable"] = {}
self._variables_ordered: List["StateVariable"] = [] self._variables_ordered: List["StateVariable"] = []
self._modifiers: Dict[str, "Modifier"] = {} self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "Function"] = {} self._functions: Dict[str, "FunctionContract"] = {}
self._linearizedBaseContracts = List[int] self._linearizedBaseContracts = List[int]
# The only str is "*" # The only str is "*"
@ -387,23 +394,23 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public-
return self._signatures_declared return self._signatures_declared
@property @property
def functions(self) -> List["Function"]: def functions(self) -> List["FunctionContract"]:
""" """
list(Function): List of the functions list(Function): List of the functions
""" """
return list(self._functions.values()) return list(self._functions.values())
def available_functions_as_dict(self) -> Dict[str, "Function"]: def available_functions_as_dict(self) -> Dict[str, "FunctionContract"]:
if self._available_functions_as_dict is None: if self._available_functions_as_dict is None:
self._available_functions_as_dict = { self._available_functions_as_dict = {
f.full_name: f for f in self._functions.values() if not f.is_shadowed f.full_name: f for f in self._functions.values() if not f.is_shadowed
} }
return self._available_functions_as_dict return self._available_functions_as_dict
def add_function(self, func: "Function"): def add_function(self, func: "FunctionContract"):
self._functions[func.canonical_name] = func self._functions[func.canonical_name] = func
def set_functions(self, functions: Dict[str, "Function"]): def set_functions(self, functions: Dict[str, "FunctionContract"]):
""" """
Set the functions Set the functions
@ -413,21 +420,21 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public-
self._functions = functions self._functions = functions
@property @property
def functions_inherited(self) -> List["Function"]: def functions_inherited(self) -> List["FunctionContract"]:
""" """
list(Function): List of the inherited functions list(Function): List of the inherited functions
""" """
return [f for f in self.functions if f.contract_declarer != self] return [f for f in self.functions if f.contract_declarer != self]
@property @property
def functions_declared(self) -> List["Function"]: def functions_declared(self) -> List["FunctionContract"]:
""" """
list(Function): List of the functions defined within the contract (not inherited) list(Function): List of the functions defined within the contract (not inherited)
""" """
return [f for f in self.functions if f.contract_declarer == self] return [f for f in self.functions if f.contract_declarer == self]
@property @property
def functions_entry_points(self) -> List["Function"]: def functions_entry_points(self) -> List["FunctionContract"]:
""" """
list(Functions): List of public and external functions list(Functions): List of public and external functions
""" """

@ -85,8 +85,6 @@ Uint = [
Max_Uint = {k: 2 ** (8 * i) - 1 if i > 0 else 2 ** 256 - 1 for i, k in enumerate(Uint)} Max_Uint = {k: 2 ** (8 * i) - 1 if i > 0 else 2 ** 256 - 1 for i, k in enumerate(Uint)}
Min_Uint = {k: 0 for k in Uint} Min_Uint = {k: 0 for k in Uint}
MaxValues = dict(Max_Int, **Max_Uint)
MinValues = dict(Min_Int, **Min_Uint)
Byte = [ Byte = [
"byte", "byte",
@ -125,6 +123,16 @@ Byte = [
"bytes32", "bytes32",
] ]
Max_Byte = {k: 2 ** (8 * (i + 1)) - 1 for i, k in enumerate(Byte[2:])}
Max_Byte["bytes"] = None
Max_Byte["byte"] = 255
Min_Byte = {k: 1 << (4 + 8 * i) for i, k in enumerate(Byte[2:])}
Min_Byte["bytes"] = 0x0
Min_Byte["byte"] = 0x10
MaxValues = dict(dict(Max_Int, **Max_Uint), **Max_Byte)
MinValues = dict(dict(Min_Int, **Min_Uint), **Min_Byte)
# https://solidity.readthedocs.io/en/v0.4.24/types.html#fixed-point-numbers # https://solidity.readthedocs.io/en/v0.4.24/types.html#fixed-point-numbers
M = list(range(8, 257, 8)) M = list(range(8, 257, 8))
N = list(range(0, 81)) N = list(range(0, 81))

@ -1,5 +1,5 @@
import logging import logging
from typing import List, TYPE_CHECKING from typing import List, TYPE_CHECKING, Union, Optional
# pylint: disable= too-many-lines,import-outside-toplevel,too-many-branches,too-many-statements,too-many-nested-blocks # pylint: disable= too-many-lines,import-outside-toplevel,too-many-branches,too-many-statements,too-many-nested-blocks
from slither.core.declarations import ( from slither.core.declarations import (
@ -22,7 +22,12 @@ from slither.core.solidity_types import (
UserDefinedType, UserDefinedType,
TypeInformation, TypeInformation,
) )
from slither.core.solidity_types.elementary_type import Int as ElementaryTypeInt from slither.core.solidity_types.elementary_type import (
Int as ElementaryTypeInt,
Uint,
Byte,
MaxValues,
)
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
from slither.core.variables.function_type_variable import FunctionTypeVariable from slither.core.variables.function_type_variable import FunctionTypeVariable
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
@ -60,6 +65,7 @@ from slither.slithir.operations import (
Unary, Unary,
Unpack, Unpack,
Nop, Nop,
Operation,
) )
from slither.slithir.operations.codesize import CodeSize from slither.slithir.operations.codesize import CodeSize
from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.argument import Argument, ArgumentType
@ -148,64 +154,111 @@ def is_gas(ins):
return False return False
def get_sig(ir, name): def _fits_under_integer(val: int, can_be_int: bool, can_be_uint) -> List[str]:
"""
Return a list of potential signature
It is a list, as Constant variables can be converted to int256
Args:
ir (slithIR.operation)
Returns:
list(str)
""" """
sig = "{}({})" Return the list of uint/int that can contain val
# list of list of arguments :param val:
argss = convert_arguments(ir.arguments) :return:
return [sig.format(name, ",".join(args)) for args in argss] """
ret: List[str] = []
n = 8
assert can_be_int | can_be_uint
while n <= 256:
if can_be_uint:
if val <= 2 ** n - 1:
ret.append(f"uint{n}")
if can_be_int:
if val <= (2 ** n) / 2 - 1:
ret.append(f"int{n}")
n = n + 8
return ret
def get_canonical_names(ir, function_name, contract_name): def _fits_under_byte(val: Union[int, str]) -> List[str]:
""" """
Return a list of potential signature Return the list of byte that can contain val
It is a list, as Constant variables can be converted to int256
Args: :param val:
ir (slithIR.operation) :return:
Returns:
list(str)
""" """
sig = "{}({})"
# list of list of arguments # If the value is written as an int, it can only be saved in one byte size
argss = convert_arguments(ir.arguments) # If its a string, it can be fitted under multiple values
return [sig.format(f"{contract_name}.{function_name}", ",".join(args)) for args in argss]
if isinstance(val, int):
hex_val = hex(val)[2:]
size = len(hex_val) // 2
return [f"byte{size}"]
# val is a str
length = len(val.encode("utf-8"))
return [f"byte{f}" for f in range(length, 32)]
def convert_arguments(arguments):
argss = [[]] def _find_function_from_parameter(ir: Call, candidates: List[Function]) -> Optional[Function]:
for arg in arguments: """
Look for a function in candidates that can be the target of the ir's call
Try the implicit type conversion for uint/int/bytes. Constant values can be both uint/int
While variables stick to their base type, but can changed the size
:param ir:
:param candidates:
:return:
"""
arguments = ir.arguments
type_args: List[str]
for idx, arg in enumerate(arguments):
if isinstance(arg, (list,)): if isinstance(arg, (list,)):
type_arg = "{}[{}]".format(get_type(arg[0].type), len(arg)) type_args = ["{}[{}]".format(get_type(arg[0].type), len(arg))]
elif isinstance(arg, Function): elif isinstance(arg, Function):
type_arg = arg.signature_str type_args = [arg.signature_str]
else: else:
type_arg = get_type(arg.type) type_args = [get_type(arg.type)]
if isinstance(arg, Constant) and arg.type == ElementaryType("uint256"):
# If it is a constant if (
# We dupplicate the existing list isinstance(arg.type, ElementaryType)
# And we add uint256 and int256 cases and arg.type.type in ElementaryTypeInt + Uint + Byte
# There is no potential collision, as the compiler ):
# Prevent it with a if isinstance(arg, Constant):
# "not unique after argument-dependent loopkup" issue value = arg.value
argss_new = [list(args) for args in argss] can_be_uint = True
for args in argss: can_be_int = True
args.append(str(ElementaryType("uint256"))) else:
for args in argss_new: value = MaxValues[arg.type.type]
args.append(str(ElementaryType("int256"))) can_be_uint = False
argss = argss + argss_new can_be_int = False
if arg.type.type in ElementaryTypeInt:
can_be_int = True
elif arg.type.type in Uint:
can_be_uint = True
if arg.type.type in ElementaryTypeInt + Uint:
type_args = _fits_under_integer(value, can_be_int, can_be_uint)
else: else:
for args in argss: print(value)
args.append(type_arg) type_args = _fits_under_byte(value)
return argss
not_found = True
candidates_kept = []
for type_arg in type_args:
if not not_found:
break
candidates_kept = []
for candidate in candidates:
param = str(candidate.parameters[idx].type)
if param == type_arg:
not_found = False
candidates_kept.append(candidate)
if len(candidates_kept) == 1:
return candidates_kept[0]
candidates = candidates_kept
if len(candidates) == 1:
return candidates[0]
return None
def is_temporary(ins): def is_temporary(ins):
@ -1205,17 +1258,34 @@ def get_type(t):
return str(t) return str(t)
def convert_type_library_call(ir, lib_contract): def _can_be_implicitly_converted(source: str, target: str) -> bool:
sigs = get_sig(ir, ir.function_name) if source in ElementaryTypeInt and target in ElementaryTypeInt:
return int(source[3:]) <= int(target[3:])
if source in Uint and target in Uint:
return int(source[4:]) <= int(target[4:])
return source == target
def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract):
func = None func = None
for sig in sigs: candidates = [
func = lib_contract.get_function_from_signature(sig) f
if not func: for f in lib_contract.functions
if f.name == ir.function_name
and not f.is_shadowed
and len(f.parameters) == len(ir.arguments)
]
if len(candidates) == 1:
func = candidates[0]
if func is None:
# TODO: handle collision with multiple state variables/functions
func = lib_contract.get_state_variable_from_name(ir.function_name) func = lib_contract.get_state_variable_from_name(ir.function_name)
if func: if func is None and candidates:
# stop to explore if func is found (prevent dupplicate issue) func = _find_function_from_parameter(ir, candidates)
break
# In case of multiple binding to the same type # In case of multiple binding to the same type
# TODO: this part might not be needed with _find_function_from_parameter
if not func: if not func:
# specific lookup when the compiler does implicit conversion # specific lookup when the compiler does implicit conversion
# for example # for example
@ -1282,36 +1352,41 @@ def _convert_to_structure_to_list(return_type: Type) -> List[Type]:
return [return_type.type] return [return_type.type]
def convert_type_of_high_and_internal_level_call(ir, contract): def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Contract):
func = None func = None
if isinstance(ir, InternalCall): if isinstance(ir, InternalCall):
sigs = get_canonical_names(ir, ir.function_name, ir.contract_name) candidates = [
for sig in sigs: f
func = contract.get_function_from_canonical_name(sig) for f in contract.functions
if func: if f.name == ir.function_name
# stop to explore if func is found (prevent dupplicate issue) and f.contract_declarer.name == ir.contract_name
break and not f.is_shadowed
and len(f.parameters) == len(ir.arguments)
]
func = _find_function_from_parameter(ir, candidates)
if not func: if not func:
func = contract.get_state_variable_from_name(ir.function_name) func = contract.get_state_variable_from_name(ir.function_name)
else: else:
assert isinstance(ir, HighLevelCall) assert isinstance(ir, HighLevelCall)
sigs = get_sig(ir, ir.function_name)
for sig in sigs: candidates = [
func = contract.get_function_from_signature(sig) f
if func: for f in contract.functions
# stop to explore if func is found (prevent dupplicate issue) if f.name == ir.function_name
break and not f.is_shadowed
if not func: and len(f.parameters) == len(ir.arguments)
]
if len(candidates) == 1:
func = candidates[0]
if func is None:
# TODO: handle collision with multiple state variables/functions
func = contract.get_state_variable_from_name(ir.function_name) func = contract.get_state_variable_from_name(ir.function_name)
if not func: if func is None and candidates:
# specific lookup when the compiler does implicit conversion func = _find_function_from_parameter(ir, candidates)
# for example
# myFunc(uint)
# can be called with an uint8
for function in contract.functions:
if function.name == ir.function_name and len(function.parameters) == len(ir.arguments):
func = function
break
# lowlelvel lookup needs to be done at last step # lowlelvel lookup needs to be done at last step
if not func: if not func:
if can_be_low_level(ir): if can_be_low_level(ir):
@ -1319,7 +1394,7 @@ def convert_type_of_high_and_internal_level_call(ir, contract):
if can_be_solidity_func(ir): if can_be_solidity_func(ir):
return convert_to_solidity_func(ir) return convert_to_solidity_func(ir)
if not func: if not func:
to_log = "Function not found {}".format(sig) to_log = "Function not found {}".format(ir.function_name)
logger.error(to_log) logger.error(to_log)
ir.function = func ir.function = func
if isinstance(func, Function): if isinstance(func, Function):

@ -1,3 +1,4 @@
from slither.core.declarations import Function
from slither.slithir.operations.high_level_call import HighLevelCall from slither.slithir.operations.high_level_call import HighLevelCall
from slither.core.declarations.contract import Contract from slither.core.declarations.contract import Contract
@ -37,10 +38,15 @@ class LibraryCall(HighLevelCall):
else: else:
lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type) lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type)
txt = "{}LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}" txt = "{}LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}"
function_name = self.function_name
if self.function:
if isinstance(self.function, Function):
function_name = self.function.canonical_name
return txt.format( return txt.format(
lvalue, lvalue,
self.destination, self.destination,
self.function_name, function_name,
[str(x) for x in arguments], [str(x) for x in arguments],
gas, gas,
) )

@ -1 +1,4 @@
// test only above 0.5 // test only above 0.5
contract C{
}

Loading…
Cancel
Save