diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 534b157c34..06f5ba54cd 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -455,6 +455,7 @@ def _post_member_access(self, expression: MemberAccess) -> None: # Look for type(X).max / min # Because we looked at the AST structure, we need to look into the nested expression # Hopefully this is always on a direct sub field, and there is no weird construction + # pylint: disable=too-many-nested-blocks if isinstance(expression.expression, CallExpression) and expression.member_name in [ "min", "max", @@ -474,10 +475,22 @@ def _post_member_access(self, expression: MemberAccess) -> None: constant_type = type_found else: # type(enum).max/min - assert isinstance(type_expression_found, Identifier) - type_found_in_expression = type_expression_found.value - assert isinstance(type_found_in_expression, (EnumContract, EnumTopLevel)) - type_found = UserDefinedType(type_found_in_expression) + # Case when enum is in another contract e.g. type(C.E).max + if isinstance(type_expression_found, MemberAccess): + contract = type_expression_found.expression.value + assert isinstance(contract, Contract) + for enum in contract.enums: + if enum.name == type_expression_found.member_name: + type_found_in_expression = enum + type_found = UserDefinedType(enum) + break + else: + assert isinstance(type_expression_found, Identifier) + type_found_in_expression = type_expression_found.value + assert isinstance( + type_found_in_expression, (EnumContract, EnumTopLevel) + ) + type_found = UserDefinedType(type_found_in_expression) constant_type = None min_value = type_found_in_expression.min max_value = type_found_in_expression.max @@ -535,6 +548,10 @@ def _post_member_access(self, expression: MemberAccess) -> None: if expression.member_name in expr.custom_errors_as_dict: set_val(expression, expr.custom_errors_as_dict[expression.member_name]) return + # Lookup enums when in a different contract e.g. C.E + if str(expression) in expr.enums_as_dict: + set_val(expression, expr.enums_as_dict[str(expression)]) + return val_ref = ReferenceVariable(self._node) member = Member(expr, Constant(expression.member_name), val_ref) diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py index 28ac79986d..2da4cf1e26 100644 --- a/tests/e2e/solc_parsing/test_ast_parsing.py +++ b/tests/e2e/solc_parsing/test_ast_parsing.py @@ -460,6 +460,7 @@ def make_version(minor: int, patch_min: int, patch_max: int) -> List[str]: ), Test("user_defined_operators-0.8.19.sol", ["0.8.19"]), Test("type-aliases.sol", ["0.8.19"]), + Test("enum-max-min.sol", ["0.8.19"]), ] # create the output folder if needed try: diff --git a/tests/e2e/solc_parsing/test_data/compile/enum-max-min.sol-0.8.19-compact.zip b/tests/e2e/solc_parsing/test_data/compile/enum-max-min.sol-0.8.19-compact.zip new file mode 100644 index 0000000000..f29ad0bdb6 Binary files /dev/null and b/tests/e2e/solc_parsing/test_data/compile/enum-max-min.sol-0.8.19-compact.zip differ diff --git a/tests/e2e/solc_parsing/test_data/enum-max-min.sol b/tests/e2e/solc_parsing/test_data/enum-max-min.sol new file mode 100644 index 0000000000..5f5ecc342f --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/enum-max-min.sol @@ -0,0 +1,37 @@ + +library Q { + enum E {a} +} + +contract Z { + enum E {a,b} +} + +contract D { + enum E {a,b,c} + + function a() public returns(uint){ + return uint(type(E).max); + } + + function b() public returns(uint){ + return uint(type(Q.E).max); + } + + function c() public returns(uint){ + return uint(type(Z.E).max); + } + + function d() public returns(uint){ + return uint(type(E).min); + } + + function e() public returns(uint){ + return uint(type(Q.E).min); + } + + function f() public returns(uint){ + return uint(type(Z.E).min); + } + +} diff --git a/tests/e2e/solc_parsing/test_data/expected/enum-max-min.sol-0.8.19-compact.json b/tests/e2e/solc_parsing/test_data/expected/enum-max-min.sol-0.8.19-compact.json new file mode 100644 index 0000000000..db0ebc88be --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/expected/enum-max-min.sol-0.8.19-compact.json @@ -0,0 +1,12 @@ +{ + "Q": {}, + "Z": {}, + "D": { + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "b()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "c()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "d()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "e()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "f()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/unit/slithir/test_data/enum_max_min.sol b/tests/unit/slithir/test_data/enum_max_min.sol new file mode 100644 index 0000000000..5f5ecc342f --- /dev/null +++ b/tests/unit/slithir/test_data/enum_max_min.sol @@ -0,0 +1,37 @@ + +library Q { + enum E {a} +} + +contract Z { + enum E {a,b} +} + +contract D { + enum E {a,b,c} + + function a() public returns(uint){ + return uint(type(E).max); + } + + function b() public returns(uint){ + return uint(type(Q.E).max); + } + + function c() public returns(uint){ + return uint(type(Z.E).max); + } + + function d() public returns(uint){ + return uint(type(E).min); + } + + function e() public returns(uint){ + return uint(type(Q.E).min); + } + + function f() public returns(uint){ + return uint(type(Z.E).min); + } + +} diff --git a/tests/unit/slithir/test_enum.py b/tests/unit/slithir/test_enum.py new file mode 100644 index 0000000000..4f1fc4e595 --- /dev/null +++ b/tests/unit/slithir/test_enum.py @@ -0,0 +1,67 @@ +from pathlib import Path +from slither import Slither +from slither.slithir.operations import Assignment +from slither.slithir.variables import Constant + +TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" + + +def test_enum_max_min(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.19") + slither = Slither(Path(TEST_DATA_DIR, "enum_max_min.sol").as_posix(), solc=solc_path) + + contract = slither.get_contract_from_name("D")[0] + + f = contract.get_function_from_full_name("a()") + # TMP_1(uint256) := 2(uint256) + assignment = f.slithir_operations[1] + assert ( + isinstance(assignment, Assignment) + and isinstance(assignment.rvalue, Constant) + and assignment.rvalue.value == 2 + ) + + f = contract.get_function_from_full_name("b()") + # TMP_4(uint256) := 0(uint256) + assignment = f.slithir_operations[1] + assert ( + isinstance(assignment, Assignment) + and isinstance(assignment.rvalue, Constant) + and assignment.rvalue.value == 0 + ) + + f = contract.get_function_from_full_name("c()") + # TMP_7(uint256) := 1(uint256) + assignment = f.slithir_operations[1] + assert ( + isinstance(assignment, Assignment) + and isinstance(assignment.rvalue, Constant) + and assignment.rvalue.value == 1 + ) + + f = contract.get_function_from_full_name("d()") + # TMP_10(uint256) := 0(uint256) + assignment = f.slithir_operations[1] + assert ( + isinstance(assignment, Assignment) + and isinstance(assignment.rvalue, Constant) + and assignment.rvalue.value == 0 + ) + + f = contract.get_function_from_full_name("e()") + # TMP_13(uint256) := 0(uint256) + assignment = f.slithir_operations[1] + assert ( + isinstance(assignment, Assignment) + and isinstance(assignment.rvalue, Constant) + and assignment.rvalue.value == 0 + ) + + f = contract.get_function_from_full_name("f()") + # TMP_16(uint256) := 0(uint256) + assignment = f.slithir_operations[1] + assert ( + isinstance(assignment, Assignment) + and isinstance(assignment.rvalue, Constant) + and assignment.rvalue.value == 0 + )