From fbd1ddb5fc7199aaff0a6f1dd73440b1d29f046b Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 6 May 2019 14:59:15 +0100 Subject: [PATCH] - Improve literals parsing and Constant conversion: - add bool type to constant - convert integer using 'e' (1e10) - convert constant uint -> int when possible - Api added: - Constant.original_value: str version of the constant expression - Structure.elems_ordered: keep original structure declaration order --- slither/core/declarations/function.py | 3 +- slither/core/declarations/structure.py | 9 +++ slither/core/expressions/literal.py | 7 +- slither/core/solidity_types/array_type.py | 4 +- slither/core/variables/state_variable.py | 1 - slither/slithir/convert.py | 74 ++++++++++++++++++- slither/slithir/variables/constant.py | 48 ++++++++++-- .../solc_parsing/declarations/structure.py | 2 + .../expressions/expression_parsing.py | 16 +++- .../solidity_types/type_parsing.py | 4 +- slither/utils/type.py | 31 ++++++++ .../visitors/expression/constants_folding.py | 8 +- .../visitors/slithir/expression_to_slithir.py | 3 +- 13 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 slither/utils/type.py diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 5c8849880..1a99f90e3 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -86,6 +86,7 @@ class Function(ChildContract, SourceMapping): self._reachable_from_nodes = set() self._reachable_from_functions = set() + ################################################################################### ################################################################################### # region General properties @@ -1070,4 +1071,4 @@ class Function(ChildContract, SourceMapping): def __str__(self): return self._name - # endregion + # endregion \ No newline at end of file diff --git a/slither/core/declarations/structure.py b/slither/core/declarations/structure.py index b11fb7e35..6584cbe19 100644 --- a/slither/core/declarations/structure.py +++ b/slither/core/declarations/structure.py @@ -10,6 +10,8 @@ class Structure(ChildContract, SourceMapping): self._name = None self._canonical_name = None self._elems = None + # Name of the elements in the order of declaration + self._elems_ordered = None @property def canonical_name(self): @@ -23,5 +25,12 @@ class Structure(ChildContract, SourceMapping): def elems(self): return self._elems + @property + def elems_ordered(self): + ret = [] + for e in self._elems_ordered: + ret.append(self._elems[e]) + return ret + def __str__(self): return self.name diff --git a/slither/core/expressions/literal.py b/slither/core/expressions/literal.py index e0c9ce6b0..c1923eff4 100644 --- a/slither/core/expressions/literal.py +++ b/slither/core/expressions/literal.py @@ -2,14 +2,19 @@ from slither.core.expressions.expression import Expression class Literal(Expression): - def __init__(self, value): + def __init__(self, value, type): super(Literal, self).__init__() self._value = value + self._type = type @property def value(self): return self._value + @property + def type(self): + return self._type + def __str__(self): # be sure to handle any character return str(self._value) diff --git a/slither/core/solidity_types/array_type.py b/slither/core/solidity_types/array_type.py index 918ed9355..ad8061b74 100644 --- a/slither/core/solidity_types/array_type.py +++ b/slither/core/solidity_types/array_type.py @@ -10,7 +10,7 @@ class ArrayType(Type): assert isinstance(t, Type) if length: if isinstance(length, int): - length = Literal(length) + length = Literal(length, 'uint256') assert isinstance(length, Expression) super(ArrayType, self).__init__() self._type = t @@ -18,7 +18,7 @@ class ArrayType(Type): if length: if not isinstance(length, Literal): - cf = ConstantFolding(length) + cf = ConstantFolding(length, "uint256") length = cf.result() self._length_value = length else: diff --git a/slither/core/variables/state_variable.py b/slither/core/variables/state_variable.py index adca10c19..b8f46482e 100644 --- a/slither/core/variables/state_variable.py +++ b/slither/core/variables/state_variable.py @@ -3,7 +3,6 @@ from slither.core.children.child_contract import ChildContract class StateVariable(ChildContract, Variable): - @property def canonical_name(self): return '{}:{}'.format(self.contract.name, self.name) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 7b9607df0..4534ea607 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -7,7 +7,9 @@ from slither.core.expressions import Identifier, Literal from slither.core.solidity_types import (ArrayType, ElementaryType, FunctionType, MappingType, UserDefinedType) +from slither.core.solidity_types.elementary_type import Int as ElementaryTypeInt from slither.core.variables.variable import Variable +from slither.core.variables.state_variable import StateVariable from slither.slithir.operations import (Assignment, Balance, Binary, BinaryType, Call, Condition, Delete, EventCall, HighLevelCall, Index, @@ -30,6 +32,7 @@ from slither.slithir.variables import (Constant, ReferenceVariable, TemporaryVariable) from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR from slither.utils.function import get_function_id +from slither.utils.type import export_nested_types_from_variable logger = logging.getLogger('ConvertToIR') @@ -39,7 +42,8 @@ def convert_expression(expression, node): from slither.core.cfg.node import NodeType if isinstance(expression, Literal) and node.type in [NodeType.IF, NodeType.IFLOOP]: - result = [Condition(Constant(expression.value))] + cst = Constant(expression.value, expression.type) + result = [Condition(cst)] return result if isinstance(expression, Identifier) and node.type in [NodeType.IF, NodeType.IFLOOP]: result = [Condition(expression.value)] @@ -599,7 +603,7 @@ def convert_to_push(ir, node): ir = Push(ir.destination, val) - length = Literal(len(operation.init_values)) + length = Literal(len(operation.init_values), 'uint256') t = operation.init_values[0].type ir.lvalue.set_type(ArrayType(t, length)) @@ -822,6 +826,71 @@ def remove_unused(result): result = [i for i in result if not i in to_remove] return result +# endregion +################################################################################### +################################################################################### +# region Constant type conversioh +################################################################################### +################################################################################### + +def convert_constant_types(irs): + """ + late conversion of uint -> type for constant (Literal) + :param irs: + :return: + """ + # TODO: implement instances lookup for events, NewContract + was_changed = True + while was_changed: + was_changed = False + for ir in irs: + if isinstance(ir, Assignment): + if isinstance(ir.lvalue.type, ElementaryType): + if ir.lvalue.type.type in ElementaryTypeInt: + if ir.rvalue.type.type != 'int256': + ir.rvalue.set_type(ElementaryType('int256')) + was_changed = True + if isinstance(ir, Binary): + if isinstance(ir.lvalue.type, ElementaryType): + if ir.lvalue.type.type in ElementaryTypeInt: + for r in ir.read: + if r.type.type != 'int256': + r.set_type(ElementaryType('int256')) + was_changed = True + if isinstance(ir, (HighLevelCall, InternalCall)): + func = ir.function + if isinstance(func, StateVariable): + types = export_nested_types_from_variable(func) + else: + types = [p.type for p in func.parameters] + for idx, arg in enumerate(ir.arguments): + t = types[idx] + if isinstance(t, ElementaryType): + if t.type in ElementaryTypeInt: + if arg.type.type != 'int256': + arg.set_type(ElementaryType('int256')) + was_changed = True + if isinstance(ir, NewStructure): + st = ir.structure + for idx, arg in enumerate(ir.arguments): + e = st.elems_ordered[idx] + if isinstance(e.type, ElementaryType): + if e.type.type in ElementaryTypeInt: + if arg.type.type != 'int256': + arg.set_type(ElementaryType('int256')) + was_changed = True + if isinstance(ir, InitArray): + if isinstance(ir.lvalue.type, ArrayType): + if isinstance(ir.lvalue.type.type, ElementaryType): + if ir.lvalue.type.type.type in ElementaryTypeInt: + for r in ir.read: + if r.type.type != 'int256': + r.set_type(ElementaryType('int256')) + was_changed = True + + + + # endregion ################################################################################### ################################################################################### @@ -839,6 +908,7 @@ def apply_ir_heuristics(irs, node): irs = propagate_type_and_convert_call(irs, node) irs = remove_unused(irs) find_references_origin(irs) + convert_constant_types(irs) return irs diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index e7fa55716..ba19279e1 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -1,17 +1,41 @@ from .variable import SlithIRVariable -from slither.core.solidity_types.elementary_type import ElementaryType +from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint + class Constant(SlithIRVariable): - def __init__(self, val): + def __init__(self, val, type=None): super(Constant, self).__init__() assert isinstance(val, str) - if val.isdigit(): - self._type = ElementaryType('uint256') - self._val = int(val) + + self._original_value = val + + if type: + assert isinstance(type, ElementaryType) + self._type = type + if type.type in Int + Uint: + if val.startswith('0x'): + self._val = int(val, 16) + else: + if 'e' in val: + base, expo = val.split('e') + self._val = int(float(base)* (10 ** int(expo))) + elif 'E' in val: + base, expo = val.split('E') + self._val = int(float(base) * (10 ** int(expo))) + else: + self._val = int(val) + elif type.type == 'bool': + self._val = val == 'true' + else: + self._val = val else: - self._type = ElementaryType('string') - self._val = val + if val.isdigit(): + self._type = ElementaryType('uint256') + self._val = int(val) + else: + self._type = ElementaryType('string') + self._val = val @property def value(self): @@ -20,10 +44,18 @@ class Constant(SlithIRVariable): If the expression was an hexadecimal delcared as hex'...' return a str Returns: - (str, int) + (str | int | bool) ''' return self._val + @property + def original_value(self): + ''' + Return the string representation of the value + :return: str + ''' + return self._original_value + def __str__(self): return str(self.value) diff --git a/slither/solc_parsing/declarations/structure.py b/slither/solc_parsing/declarations/structure.py index 0026f451e..825e85c98 100644 --- a/slither/solc_parsing/declarations/structure.py +++ b/slither/solc_parsing/declarations/structure.py @@ -16,6 +16,7 @@ class StructureSolc(Structure): self._name = name self._canonical_name = canonicalName self._elems = {} + self._elems_ordered = [] self._elemsNotParsed = elems @@ -28,5 +29,6 @@ class StructureSolc(Structure): elem.analyze(self.contract) self._elems[elem.name] = elem + self._elems_ordered.append(elem.name) self._elemsNotParsed = [] diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 39b2334d6..968ae446a 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -479,6 +479,12 @@ def parse_expression(expression, caller_context): value = str(convert_subdenomination(value, expression['subdenomination'])) elif not value and value != "": value = '0x'+expression['hexValue'] + type = expression['typeDescriptions']['typeString'] + + # Length declaration for array was None until solc 0.5.5 + if type is None: + if expression['kind'] == 'number': + type = 'int_const' else: value = expression['attributes']['value'] if value: @@ -489,7 +495,15 @@ def parse_expression(expression, caller_context): # 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'] - literal = Literal(value) + type = expression['attributes']['type'] + + if type.startswith('int_const '): + type = ElementaryType('uint256') + elif type.startswith('bool'): + type = ElementaryType('bool') + else: + type = ElementaryType('string') + literal = Literal(value, type) return literal elif name == 'Identifier': diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index 2b5057dda..22e8954ab 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -32,7 +32,7 @@ def _find_from_type_name(name, contract, contracts, structures, enums): if name_elementary in ElementaryTypeName: depth = name.count('[') if depth: - return ArrayType(ElementaryType(name_elementary), Literal(depth)) + return ArrayType(ElementaryType(name_elementary), Literal(depth, 'uint256')) else: return ElementaryType(name_elementary) # We first look for contract @@ -78,7 +78,7 @@ def _find_from_type_name(name, contract, contracts, structures, enums): depth+=1 var_type = next((st for st in all_structures if st.contract.name+"."+st.name == name_struct), None) if var_type: - return ArrayType(UserDefinedType(var_type), Literal(depth)) + return ArrayType(UserDefinedType(var_type), Literal(depth, 'uint256')) if not var_type: var_type = next((f for f in contract.functions if f.name == name), None) diff --git a/slither/utils/type.py b/slither/utils/type.py new file mode 100644 index 000000000..d5bca3720 --- /dev/null +++ b/slither/utils/type.py @@ -0,0 +1,31 @@ +from slither.core.solidity_types import (ArrayType, MappingType, ElementaryType) + +def _add_mapping_parameter(t, l): + while isinstance(t, MappingType): + l.append(t.type_from) + t = t.type_to + _add_array_parameter(t, l) + +def _add_array_parameter(t, l): + while isinstance(t, ArrayType): + l.append(ElementaryType('uint256')) + t = t.type + +def export_nested_types_from_variable(variable): + """ + Export the list of nested types (mapping/array) + :param variable: + :return: list(Type) + """ + l = [] + if isinstance(variable.type, MappingType): + t = variable.type + _add_mapping_parameter(t, l) + + if isinstance(variable.type, ArrayType): + v = variable + _add_array_parameter(v.type, l) + + return l + + diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index bca1bfe04..26ce38906 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -20,8 +20,12 @@ def set_val(expression, val): class ConstantFolding(ExpressionVisitor): + def __init__(self, expression, type): + super(ConstantFolding, self).__init__(expression) + self._type = type + def result(self): - return Literal(int(get_val(self._expression))) + return Literal(int(get_val(self._expression)), self._type) def _post_identifier(self, expression): if not expression.value.is_constant: @@ -29,7 +33,7 @@ class ConstantFolding(ExpressionVisitor): expr = expression.value.expression # assumption that we won't have infinite loop if not isinstance(expr, Literal): - cf = ConstantFolding(expr) + cf = ConstantFolding(expr, self._type) expr = cf.result() set_val(expression, int(expr.value)) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index a1c1c5e5d..c5f275749 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -173,7 +173,8 @@ class ExpressionToSlithIR(ExpressionVisitor): set_val(expression, val) def _post_literal(self, expression): - set_val(expression, Constant(expression.value)) + cst = Constant(expression.value, expression.type) + set_val(expression, cst) def _post_member_access(self, expression): expr = get(expression.expression)