diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 4c88150d2b..ea60a986b3 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -959,6 +959,11 @@ def solidity_signature(self) -> str: """ Return a signature following the Solidity Standard Contract and converted into address + + It might still keep internal types (ex: structure name) for internal functions. + The reason is that internal functions allows recursive structure definition, which + can't be converted following the Solidity stand ard + :return: the solidity signature """ if self._solidity_signature is None: diff --git a/slither/utils/type.py b/slither/utils/type.py index 1ce5fc158f..5c7fcee344 100644 --- a/slither/utils/type.py +++ b/slither/utils/type.py @@ -1,12 +1,14 @@ import math -from typing import List, Union +from typing import List, Union, Set from slither.core.solidity_types import ArrayType, MappingType, ElementaryType, UserDefinedType from slither.core.solidity_types.type import Type from slither.core.variables.variable import Variable -def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type]]) -> str: +def _convert_type_for_solidity_signature_to_string( + types: Union[Type, List[Type]], seen: Set[Type] +) -> str: if isinstance(types, Type): # Array might be struct, so we need to go again through the conversion here # We could have this logic in convert_type_for_solidity_signature @@ -15,8 +17,10 @@ def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type] # Which is currently not supported. This comes down to (uint, uint)[] not being possible in Solidity # While having an array of a struct of two uint leads to a (uint, uint)[] signature if isinstance(types, ArrayType): - underlying_type = convert_type_for_solidity_signature(types.type) - underlying_type_str = _convert_type_for_solidity_signature_to_string(underlying_type) + underlying_type = convert_type_for_solidity_signature(types.type, seen) + underlying_type_str = _convert_type_for_solidity_signature_to_string( + underlying_type, seen + ) return underlying_type_str + "[]" return str(types) @@ -26,9 +30,9 @@ def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type] ret = "(" for underlying_type in types: if first_item: - ret += _convert_type_for_solidity_signature_to_string(underlying_type) + ret += _convert_type_for_solidity_signature_to_string(underlying_type, seen) else: - ret += "," + _convert_type_for_solidity_signature_to_string(underlying_type) + ret += "," + _convert_type_for_solidity_signature_to_string(underlying_type, seen) first_item = False ret += ")" @@ -36,14 +40,33 @@ def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type] def convert_type_for_solidity_signature_to_string(t: Type) -> str: - types = convert_type_for_solidity_signature(t) - return _convert_type_for_solidity_signature_to_string(types) + seen: Set[Type] = set() + types = convert_type_for_solidity_signature(t, seen) + return _convert_type_for_solidity_signature_to_string(types, seen) -def convert_type_for_solidity_signature(t: Type) -> Union[Type, List[Type]]: +def convert_type_for_solidity_signature(t: Type, seen: Set[Type]) -> Union[Type, List[Type]]: # pylint: disable=import-outside-toplevel from slither.core.declarations import Contract, Enum, Structure + # Solidity allows recursive type for structure definition if its not used in public /external + # When this happens we can reach an infinite loop. If we detect a loop, we just stop converting the underlying type + # This is ok, because it wont happen for public/external function + # + # contract A{ + # + # struct St{ + # St[] a; + # uint b; + # } + # + # function f(St memory s) internal{} + # + # } + if t in seen: + return t + seen.add(t) + if isinstance(t, UserDefinedType): underlying_type = t.type if isinstance(underlying_type, Contract): @@ -61,21 +84,24 @@ def convert_type_for_solidity_signature(t: Type) -> Union[Type, List[Type]]: if isinstance(underlying_type, Structure): # We can't have recursive types for structure, so recursion is ok here types = [ - convert_type_for_solidity_signature(x.type) for x in underlying_type.elems_ordered + convert_type_for_solidity_signature(x.type, seen) + for x in underlying_type.elems_ordered ] return types return t -def _export_nested_types_from_variable(current_type: Type, ret: List[Type]) -> None: +def _export_nested_types_from_variable( + current_type: Type, ret: List[Type], seen: Set[Type] +) -> None: """ Export the list of nested types (mapping/array) :param variable: :return: list(Type) """ if isinstance(current_type, MappingType): - underlying_type = convert_type_for_solidity_signature(current_type.type_from) + underlying_type = convert_type_for_solidity_signature(current_type.type_from, seen) if isinstance(underlying_type, list): ret.extend(underlying_type) else: @@ -87,7 +113,7 @@ def _export_nested_types_from_variable(current_type: Type, ret: List[Type]) -> N next_type = current_type.type else: return - _export_nested_types_from_variable(next_type, ret) + _export_nested_types_from_variable(next_type, ret, seen) def export_nested_types_from_variable(variable: Variable) -> List[Type]: @@ -97,7 +123,8 @@ def export_nested_types_from_variable(variable: Variable) -> List[Type]: :return: list(Type) """ l: List[Type] = [] - _export_nested_types_from_variable(variable.type, l) + seen: Set[Type] = set() + _export_nested_types_from_variable(variable.type, l, seen) return l diff --git a/tests/function_ids/rec_struct-0.8.sol b/tests/function_ids/rec_struct-0.8.sol new file mode 100644 index 0000000000..7440a5a33d --- /dev/null +++ b/tests/function_ids/rec_struct-0.8.sol @@ -0,0 +1,12 @@ +contract A{ + + struct St{ + St[] a; + uint b; + } + + function f(St memory s) internal{ + f(s); + } + +} diff --git a/tests/test_features.py b/tests/test_features.py index 88751e3243..c06ee96ce8 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -2,6 +2,7 @@ from crytic_compile import CryticCompile from crytic_compile.platform.solc_standard_json import SolcStandardJson +from solc_select import solc_select from slither import Slither from slither.detectors import all_detectors @@ -41,3 +42,11 @@ def test_collision(): def test_cycle(): slither = Slither("./tests/test_cyclic_import/a.sol") _run_all_detectors(slither) + + +def test_funcion_id_rec_structure(): + solc_select.switch_global_version("0.8.0", always_install=True) + slither = Slither("./tests/function_ids/rec_struct-0.8.sol") + for compilation_unit in slither.compilation_units: + for function in compilation_unit.functions: + assert function.solidity_signature