Repository: crytic/slither

Issue:
[Bug]: IR generation failing 
### Describe the issue:

IR generation failing.

I've identified the issue lies with the following line of code:
`uint256 totalRegions = uint256(type(BosonTypes.PausableRegion).max);`
It appears that during the Slither IR generation process, it's being transformed into:
`totalRegions = uint256(type()(BosonTypes.PausableRegion).max)`

This seems to be a bug that needs addressing.

### Code example to reproduce the issue:

Clone https://github.com/bosonprotocol/boson-protocol-contracts
Go to commit https://github.com/bosonprotocol/boson-protocol-contracts/pull/644/commits/c9ee232050034e9f12b0b17ef8c56466f0e5ba11
run `slither . --checklist`

### Version:

0.9.5

### Relevant log output:

```shell
'npx hardhat clean' running (wd: /Users/anajulia/dev/code/boson-protocol-contracts)
'npx hardhat clean --global' running (wd: /Users/anajulia/dev/code/boson-protocol-contracts)
'npx hardhat compile --force' running (wd: /Users/anajulia/dev/code/boson-protocol-contracts)
ERROR:SlitherSolcParsing:
Failed to generate IR for PauseHandlerFacet.getPausedRegions. Please open an issue https://github.com/crytic/slither/issues.
PauseHandlerFacet.getPausedRegions (contracts/protocol/facets/PauseHandlerFacet.sol#72-102):
        status = protocolStatus()
        totalRegions = uint256(type()(BosonTypes.PausableRegion).max)
        regions = new BosonTypes.PausableRegion[](totalRegions)
        status.pauseScenario == ALL_REGIONS_MASK
        i = 0
        i < totalRegions
        regions[i] = BosonTypes.PausableRegion(i)
        i ++
        regions
        count = 0
        i_scope_0 = 0
        i_scope_0 < totalRegions
        (status.pauseScenario & (1 << i_scope_0)) != 0
        regions[count] = BosonTypes.PausableRegion(i_scope_0)
        count ++
        i_scope_0 ++
        mstore(uint256,uint256)(regions,count)
Traceback (most recent call last):
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/__main__.py", line 814, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/__main__.py", line 102, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/__main__.py", line 80, in process_single
    slither = Slither(target, ast_format=ast, **vars(args))
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/slither.py", line 135, in __init__
    self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/slither.py", line 155, in _init_parsing_and_analyses
    raise e
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/slither.py", line 151, in _init_parsing_and_analyses
    parser.analyze_contracts()
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 541, in analyze_contracts
    self._convert_to_slithir()
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 767, in _convert_to_slithir
    raise e
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 752, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/core/declarations/function.py", line 1760, in generate_slithir_and_analyze
    node.slithir_generation()
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/core/cfg/node.py", line 706, in slithir_generation
    self._irs = convert_expression(expression, self)  # type:ignore
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/slithir/convert.py", line 115, in convert_expression
    visitor = ExpressionToSlithIR(expression, node)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 162, in __init__
    self._visit_expression(self.expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 43, in _visit_expression
    self._visit_assignement_operation(expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 99, in _visit_assignement_operation
    self._visit_expression(expression.expression_right)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 82, in _visit_expression
    self._visit_type_conversion(expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 155, in _visit_type_conversion
    self._visit_expression(expression.expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 93, in _visit_expression
    self._post_visit(expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 291, in _post_visit
    self._post_member_access(expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 465, in _post_member_access
    assert isinstance(type_expression_found, Identifier)
AssertionError
ERROR:root:Error in .
ERROR:root:Traceback (most recent call last):
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/__main__.py", line 814, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/__main__.py", line 102, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/__main__.py", line 80, in process_single
    slither = Slither(target, ast_format=ast, **vars(args))
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/slither.py", line 135, in __init__
    self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/slither.py", line 155, in _init_parsing_and_analyses
    raise e
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/slither.py", line 151, in _init_parsing_and_analyses
    parser.analyze_contracts()
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 541, in analyze_contracts
    self._convert_to_slithir()
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 767, in _convert_to_slithir
    raise e
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 752, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/core/declarations/function.py", line 1760, in generate_slithir_and_analyze
    node.slithir_generation()
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/core/cfg/node.py", line 706, in slithir_generation
    self._irs = convert_expression(expression, self)  # type:ignore
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/slithir/convert.py", line 115, in convert_expression
    visitor = ExpressionToSlithIR(expression, node)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 162, in __init__
    self._visit_expression(self.expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 43, in _visit_expression
    self._visit_assignement_operation(expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 99, in _visit_assignement_operation
    self._visit_expression(expression.expression_right)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 82, in _visit_expression
    self._visit_type_conversion(expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 155, in _visit_type_conversion
    self._visit_expression(expression.expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 93, in _visit_expression
    self._post_visit(expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/expression/expression.py", line 291, in _post_visit
    self._post_member_access(expression)
  File "/Users/anajulia/Library/Python/3.9/lib/python/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 465, in _post_member_access
    assert isinstance(type_expression_found, Identifier)
AssertionError
```



Discussion:
Same issue as #1602 

Resolving patch:
diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py
--- a/slither/visitors/slithir/expression_to_slithir.py
+++ b/slither/visitors/slithir/expression_to_slithir.py
@@ -443,6 +443,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",
@@ -462,10 +463,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
@@ -523,6 +536,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)


Test patch:
diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py
--- 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
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
--- /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
--- /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
--- /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
--- /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
+    )