From b4add6eb055d205b3ab3c040357ed918cf9e8359 Mon Sep 17 00:00:00 2001 From: Simone Date: Tue, 18 Jul 2023 13:53:17 +0200 Subject: [PATCH 1/2] Fix enum.max/min when enum in other contract --- .../visitors/slithir/expression_to_slithir.py | 25 ++++++++++-- tests/e2e/solc_parsing/test_ast_parsing.py | 1 + .../enum-max-min.sol-0.8.19-compact.zip | Bin 0 -> 3319 bytes .../solc_parsing/test_data/enum-max-min.sol | 37 ++++++++++++++++++ .../enum-max-min.sol-0.8.19-compact.json | 12 ++++++ 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 tests/e2e/solc_parsing/test_data/compile/enum-max-min.sol-0.8.19-compact.zip create mode 100644 tests/e2e/solc_parsing/test_data/enum-max-min.sol create mode 100644 tests/e2e/solc_parsing/test_data/expected/enum-max-min.sol-0.8.19-compact.json diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 005ad81a44..0e265f909c 100644 --- 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) diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py index 307e6736ff..d6cc1b8b4c 100644 --- a/tests/e2e/solc_parsing/test_ast_parsing.py +++ b/tests/e2e/solc_parsing/test_ast_parsing.py @@ -459,6 +459,7 @@ def make_version(minor: int, patch_min: int, patch_max: int) -> List[str]: ["0.6.9", "0.7.6", "0.8.16"], ), Test("user_defined_operators-0.8.19.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 0000000000000000000000000000000000000000..f29ad0bdb623661cfecebcc9e63e18e268196b83 GIT binary patch literal 3319 zcma)Q@U0XzVZ0aWde^=SIm$}~s-fF2J3fE@q;SUI@a^VwT? z^4Z%u@Vhv^H?#6YIXb(VSvVtYY~8K+?OYrkfOz--69B*;0FVj^u@<@!a>}0g(I82h z-s@ody!}Z~B#c3tW!9yYR^UutI%8>6lnAGx|)K;iT#XY(F_kx$x2al&e#Q z17X`pk5kb5oa)4KoH+*R082@SZJKemSUzUvxGCwa?EDeGUqxT=-X1M5i3*<;kWDtR zB_kW(BG(=zp>n$6r4&#E$lx|#vm|*7-%;_c>UM!q8Hoel2rFeb)S6sKU4KyF%BxU6zodzr2yq9U92)BE@rj zq2dOM2+pT^w~|N|n^ze1VUi1iebpR7Pa$Q1xV4XKOZGaY8W1V35t|y8Up2Pu+yH1g zH95U$KzCVRO`LDfzSgIEIN0@F4f9WyOyr<-;38MTcn}-M4Fyb_Tj#B4uP%GjDg849 zpIA6u6N&0UEvZ2;?`V@9V;rg>udc2b;@>Y)e%+x7T7yVrX5`M#yOn@#zm_ z{0Hi;sIJWclhy4wi|~?R$b^v7gsA;{xPp!*@DzTG5p1meN8-mGZP716y#|9p?^~-neGLC(8UvUVivBk}lBFufVI7 zzhIz?X16~3ZHD`8*k-)opAxF0-+WeXeS$&~oPjuihtAOlLvv5MH1wTQkS^J^3 z%MaY|DkKtp!%)^RswybgR#saIUqglpvCx#sv_8FD4aG?#oBX>Onwv+=JqOrpV%wbd zkKd1pjuHxO2*&+Ru z4`3W)8C&fx?PhGOREzp{4jwzG({rv3Wql-jR_B7$)UppQ3bly6BQnI@S31qyjg#LU z{Ge{#`l0<{M2Ay z=%)FmTBZ1F!-2;BvESFtj+gtvH|S6wIV?qc`|K zxkVI`i9^_fx)Jxsmq|>2hHnNrS-Rzb&1?yb!e^OS9mP2E|4P*_|?38785< zhxto?L@7GzX>ctvQx!dX5G4xQBoXr~G}grVmlQC`uz8waKynSC!+~UYosUp}Z>6*l z?RESg?o=5on+9nI=9b$AT}!1*F}k3yG#;Evke@4E4ErA=FnZ<~UrB&M&X#UWh#|)k z>x)G08}rbPk$zp(26t=QuFD{l%&|g5+Mzm@(37L=WV(7XERguHn( zdAAn?k(6Rw{^UOKxj~nrUSC7EmJ1hRjB=(&`j_|k%;csqwbbj(BgfOZm z2}jimjAt*J=MWT3^uk}ZWCQ5(8|+%Dhnr+MS{OJ<6v0;I>dP$y`8b=iaa%S4Bt8BmiwVyH!FVlMZ&W>H;-vggHYaUhz=6+>w)Ee^StoxAU z^G{dZGGDI4F5SflFb7^H=h7o$UK5OQ#WuA&Cgf|J(~MtkqBImNTRV8>omgGU%3ASn z6}SL>-d(1exjPtrwm8Cig(Z~`tQ78w!-(LQe{Kmeszrvgh8+6=(lhSv>_dC%+Z1~& zz^1{gNy74KZ3Jr({9cQ|>ojN?XQefE!A^~A6X2KHXmo-Stz|a+C?(7tVVBJX4Qeja zj8FGLwB)eRh1%``20*Hc&r`BHwr7Ov-MgZT+a{HE&UO{YbA~(t~}h7AB{@X!O}`Eqt1#HDysEsPRM+`!AablVq-`T$tHc%cR%AZd3k-Si-Ik*>v<&LG8uwRo*WhsrL zxh8RM=U>jz{KM)^Uf7)Rhgp8H9wBE$@XIz;?;LPgpZ$o+*U^N7hhP1t{wjR*7LC7{&)n9w@RXOOp=3UE9d6W=cjz!sd}^@K zpGC$KwCMhW@cTE6t;_ZW6YZGrw-(svTZIVxk0e9F8tY}O7P0iku}QvEYvkTo@uZpo zkqgWm5e{kEQ_cDd^a^Fa_<@~;&ug*lkUY$i-FTYE=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 From ddd9d6561f5583f01415930d753efc9f2acd55a4 Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 8 Sep 2023 13:20:03 +0200 Subject: [PATCH 2/2] Add IR test --- tests/unit/slithir/test_data/enum_max_min.sol | 37 ++++++++++ tests/unit/slithir/test_enum.py | 67 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tests/unit/slithir/test_data/enum_max_min.sol create mode 100644 tests/unit/slithir/test_enum.py 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 + )