diff --git a/slither/detectors/statements/incorrect_using_for.py b/slither/detectors/statements/incorrect_using_for.py index 4df0701a2e..e2e87e7e0d 100644 --- a/slither/detectors/statements/incorrect_using_for.py +++ b/slither/detectors/statements/incorrect_using_for.py @@ -1,3 +1,5 @@ +from typing import List + from slither.core.declarations import Contract, Structure, Enum from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.solidity_types import ( @@ -9,7 +11,148 @@ ArrayType, ) from slither.core.solidity_types.elementary_type import Uint, Int, Byte -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.utils.output import Output + + +def _is_correctly_used(type_: Type, library: Contract) -> bool: + """ + Checks if a `using library for type_` statement is used correctly (that is, does library contain any function + with type_ as the first argument). + """ + for f in library.functions: + if len(f.parameters) == 0: + continue + if f.parameters[0].type and not _implicitly_convertible_to(type_, f.parameters[0].type): + continue + return True + return False + + +def _implicitly_convertible_to(type1: Type, type2: Type) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + if isinstance(type1, TypeAlias) or isinstance(type2, TypeAlias): + if isinstance(type1, TypeAlias) and isinstance(type2, TypeAlias): + return type1.type == type2.type + return False + + if isinstance(type1, UserDefinedType) and isinstance(type2, UserDefinedType): + if isinstance(type1.type, Contract) and isinstance(type2.type, Contract): + return _implicitly_convertible_to_for_contracts(type1.type, type2.type) + + if isinstance(type1.type, Structure) and isinstance(type2.type, Structure): + return type1.type.canonical_name == type2.type.canonical_name + + if isinstance(type1.type, Enum) and isinstance(type2.type, Enum): + return type1.type.canonical_name == type2.type.canonical_name + + if isinstance(type1, ElementaryType) and isinstance(type2, ElementaryType): + return _implicitly_convertible_to_for_elementary_types(type1, type2) + + if isinstance(type1, MappingType) and isinstance(type2, MappingType): + return _implicitly_convertible_to_for_mappings(type1, type2) + + if isinstance(type1, ArrayType) and isinstance(type2, ArrayType): + return _implicitly_convertible_to_for_arrays(type1, type2) + + return False + + +def _implicitly_convertible_to_for_arrays(type1: ArrayType, type2: ArrayType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + return _implicitly_convertible_to(type1.type, type2.type) + + +def _implicitly_convertible_to_for_mappings(type1: MappingType, type2: MappingType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + return type1.type_from == type2.type_from and type1.type_to == type2.type_to + + +def _implicitly_convertible_to_for_elementary_types( + type1: ElementaryType, type2: ElementaryType +) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + if type1.type == "bool" and type2.type == "bool": + return True + if type1.type == "string" and type2.type == "string": + return True + if type1.type == "bytes" and type2.type == "bytes": + return True + if type1.type == "address" and type2.type == "address": + return _implicitly_convertible_to_for_addresses(type1, type2) + if type1.type in Uint and type2.type in Uint: + return _implicitly_convertible_to_for_uints(type1, type2) + if type1.type in Int and type2.type in Int: + return _implicitly_convertible_to_for_ints(type1, type2) + if ( + type1.type != "bytes" + and type2.type != "bytes" + and type1.type in Byte + and type2.type in Byte + ): + return _implicitly_convertible_to_for_bytes(type1, type2) + return False + + +def _implicitly_convertible_to_for_bytes(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both bytes. + """ + assert type1.type in Byte and type2.type in Byte + assert type1.size is not None + assert type2.size is not None + + return type1.size <= type2.size + + +def _implicitly_convertible_to_for_addresses(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both addresses. + """ + assert type1.type == "address" and type2.type == "address" + # payable attribute to be implemented; for now, always return True + return True + + +def _implicitly_convertible_to_for_ints(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both ints. + """ + assert type1.type in Int and type2.type in Int + assert type1.size is not None + assert type2.size is not None + + return type1.size <= type2.size + + +def _implicitly_convertible_to_for_uints(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both uints. + """ + assert type1.type in Uint and type2.type in Uint + assert type1.size is not None + assert type2.size is not None + + return type1.size <= type2.size + + +def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool: + """ + Returns True if contract1 may be implicitly converted to contract2. + """ + return contract1 == contract2 or contract2 in contract1.inheritance class IncorrectUsingFor(AbstractDetector): @@ -48,150 +191,19 @@ class IncorrectUsingFor(AbstractDetector): "matching a type used in these statements. " ) - @staticmethod - def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool: - """ - Returns True if contract1 may be implicitly converted to contract2. - """ - return contract1 == contract2 or contract2 in contract1.inheritance - - @staticmethod - def _implicitly_convertible_to_for_uints(type1: ElementaryType, type2: ElementaryType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2 assuming they are both uints. - """ - assert type1.type in Uint and type2.type in Uint - - return type1.size <= type2.size - - @staticmethod - def _implicitly_convertible_to_for_ints(type1: ElementaryType, type2: ElementaryType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2 assuming they are both ints. - """ - assert type1.type in Int and type2.type in Int - - return type1.size <= type2.size - - @staticmethod - def _implicitly_convertible_to_for_addresses( - type1: ElementaryType, type2: ElementaryType - ) -> bool: - """ - Returns True if type1 may be implicitly converted to type2 assuming they are both addresses. - """ - assert type1.type == "address" and type2.type == "address" - # payable attribute to be implemented; for now, always return True - return True - - @staticmethod - def _implicitly_convertible_to_for_bytes(type1: ElementaryType, type2: ElementaryType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2 assuming they are both bytes. - """ - assert type1.type in Byte and type2.type in Byte - - return type1.size <= type2.size - - @staticmethod - def _implicitly_convertible_to_for_elementary_types( - type1: ElementaryType, type2: ElementaryType - ) -> bool: - """ - Returns True if type1 may be implicitly converted to type2. - """ - if type1.type == "bool" and type2.type == "bool": - return True - if type1.type == "string" and type2.type == "string": - return True - if type1.type == "bytes" and type2.type == "bytes": - return True - if type1.type == "address" and type2.type == "address": - return IncorrectUsingFor._implicitly_convertible_to_for_addresses(type1, type2) - if type1.type in Uint and type2.type in Uint: - return IncorrectUsingFor._implicitly_convertible_to_for_uints(type1, type2) - if type1.type in Int and type2.type in Int: - return IncorrectUsingFor._implicitly_convertible_to_for_ints(type1, type2) - if ( - type1.type != "bytes" - and type2.type != "bytes" - and type1.type in Byte - and type2.type in Byte - ): - return IncorrectUsingFor._implicitly_convertible_to_for_bytes(type1, type2) - return False - - @staticmethod - def _implicitly_convertible_to_for_mappings(type1: MappingType, type2: MappingType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2. - """ - return type1.type_from == type2.type_from and type1.type_to == type2.type_to - - @staticmethod - def _implicitly_convertible_to_for_arrays(type1: ArrayType, type2: ArrayType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2. - """ - return IncorrectUsingFor._implicitly_convertible_to(type1.type, type2.type) - - @staticmethod - def _implicitly_convertible_to(type1: Type, type2: Type) -> bool: - """ - Returns True if type1 may be implicitly converted to type2. - """ - if isinstance(type1, TypeAlias) or isinstance(type2, TypeAlias): - if isinstance(type1, TypeAlias) and isinstance(type2, TypeAlias): - return type1.type == type2.type - return False - - if isinstance(type1, UserDefinedType) and isinstance(type2, UserDefinedType): - if isinstance(type1.type, Contract) and isinstance(type2.type, Contract): - return IncorrectUsingFor._implicitly_convertible_to_for_contracts( - type1.type, type2.type - ) - - if isinstance(type1.type, Structure) and isinstance(type2.type, Structure): - return type1.type.canonical_name == type2.type.canonical_name - - if isinstance(type1.type, Enum) and isinstance(type2.type, Enum): - return type1.type.canonical_name == type2.type.canonical_name - - if isinstance(type1, ElementaryType) and isinstance(type2, ElementaryType): - return IncorrectUsingFor._implicitly_convertible_to_for_elementary_types(type1, type2) - - if isinstance(type1, MappingType) and isinstance(type2, MappingType): - return IncorrectUsingFor._implicitly_convertible_to_for_mappings(type1, type2) - - if isinstance(type1, ArrayType) and isinstance(type2, ArrayType): - return IncorrectUsingFor._implicitly_convertible_to_for_arrays(type1, type2) - - return False - - @staticmethod - def _is_correctly_used(type_: Type, library: Contract) -> bool: - """ - Checks if a `using library for type_` statement is used correctly (that is, does library contain any function - with type_ as the first argument). - """ - for f in library.functions: - if len(f.parameters) == 0: - continue - if not IncorrectUsingFor._implicitly_convertible_to(type_, f.parameters[0].type): - continue - return True - return False - - def _append_result(self, results: list, uf: UsingForTopLevel, type_: Type, library: Contract): - info = ( - f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in " - f"{library}.\n" - ) + def _append_result( + self, results: List[Output], uf: UsingForTopLevel, type_: Type, library: Contract + ) -> None: + info: DETECTOR_INFO = [ + f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in ", + library, + ".\n", + ] res = self.generate_result(info) results.append(res) - def _detect(self): - results = [] + def _detect(self) -> List[Output]: + results: List[Output] = [] for uf in self.compilation_unit.using_for_top_level: # UsingForTopLevel.using_for is a dict with a single entry, which is mapped to a list of functions/libraries @@ -200,7 +212,12 @@ def _detect(self): for lib_or_fcn in uf.using_for[type_]: # checking for using-for with functions is already performed by the compiler; we only consider libraries if isinstance(lib_or_fcn, UserDefinedType): - if not self._is_correctly_used(type_, lib_or_fcn.type): - self._append_result(results, uf, type_, lib_or_fcn.type) + lib_or_fcn_type = lib_or_fcn.type + if ( + isinstance(type_, Type) + and isinstance(lib_or_fcn_type, Contract) + and not _is_correctly_used(type_, lib_or_fcn_type) + ): + self._append_result(results, uf, type_, lib_or_fcn_type) return results diff --git a/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt index 4a85bca5fc..518fba20d6 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt @@ -1,24 +1,24 @@ -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64).