diff --git a/.gitignore b/.gitignore index 083ddebe27..c5a6a7a5ab 100644 --- a/.gitignore +++ b/.gitignore @@ -114,4 +114,4 @@ test_artifacts/ crytic-export/ # slither.db.json -slither.db.json +slither.db.json \ No newline at end of file diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 8951707fff..6e986cd368 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -219,48 +219,55 @@ def _find_function_from_parameter( type_args: List[str] for idx, arg in enumerate(arguments): if isinstance(arg, (list,)): + # If the argument is an array literal (list of IR variables), the type is a fixed array. + # We resolve the complete fixed array type here (e.g., 'struct B[2]') and store it in type_args. type_args = [f"{get_type(arg[0].type)}[{len(arg)}]"] elif isinstance(arg, Function): type_args = [arg.signature_str] else: type_args = [get_type(arg.type)] - arg_type = arg.type - if isinstance( - arg_type, ElementaryType - ) and arg_type.type in ElementaryTypeInt + Uint + Byte + ["string"]: - if isinstance(arg, Constant): - value = arg.value - can_be_uint = True - can_be_int = True - else: - value = MaxValues[arg_type.type] - can_be_uint = False - can_be_int = False - if arg_type.type in ElementaryTypeInt: - can_be_int = True - elif arg_type.type in Uint: + # --- Array Instantiation Guard --- + # If the argument was an array literal (list), its type was definitively resolved above. + # We must skip the subsequent logic, which attempts implicit type conversions + # designed only for single ElementaryTypes (uint/int/bytes). + if not isinstance(arg, (list,)): + arg_type = arg.type + if isinstance( + arg_type, ElementaryType + ) and arg_type.type in ElementaryTypeInt + Uint + Byte + ["string"]: + if isinstance(arg, Constant): + value = arg.value can_be_uint = True + can_be_int = True + else: + value = MaxValues[arg_type.type] + can_be_uint = False + can_be_int = False + if arg_type.type in ElementaryTypeInt: + can_be_int = True + elif arg_type.type in Uint: + can_be_uint = True + + if arg_type.type in ElementaryTypeInt + Uint: + type_args = _fits_under_integer(value, can_be_int, can_be_uint) + elif value is None and arg_type.type in ["bytes", "string"]: + type_args = ["bytes", "string"] + else: + type_args = _fits_under_byte(value) + if arg_type.type == "string": + type_args += ["string"] - if arg_type.type in ElementaryTypeInt + Uint: - type_args = _fits_under_integer(value, can_be_int, can_be_uint) - elif value is None and arg_type.type in ["bytes", "string"]: - type_args = ["bytes", "string"] - else: - type_args = _fits_under_byte(value) - if arg_type.type == "string": - type_args += ["string"] - - not_found = True + found = False candidates_kept: List[Function] = [] for type_arg in type_args: - if not not_found: + if found: break candidates_kept = [] for candidate in candidates: param = get_type(candidate.parameters[idx].type) if param == type_arg: - not_found = False + found = True candidates_kept.append(candidate) if len(candidates_kept) == 1 and not full_comparison: diff --git a/tests/unit/slithir/test_array_instantiation_call.py b/tests/unit/slithir/test_array_instantiation_call.py new file mode 100644 index 0000000000..2abb317562 --- /dev/null +++ b/tests/unit/slithir/test_array_instantiation_call.py @@ -0,0 +1,67 @@ +from pathlib import Path +from slither import Slither +from slither.core.declarations.function_contract import FunctionContract +from slither.slithir.operations import HighLevelCall, NewStructure + +# Assuming this path setup is correct for the test runner environment +# This path points to the directory containing array_instantiation_call.sol +TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" + +def _check_common_assertions(target_func: FunctionContract, expected_description: str): + """Utility to check the base requirement: successful HighLevelCall generation.""" + assert target_func is not None, f"Target function for {expected_description} not found." + + slithir_ops = target_func.slithir_operations + + # 1. Check for the HighLevelCall (the external call to a.f) + # The presence of this call proves the array and argument resolution completed without crashing. + call_to_f = next((op for op in slithir_ops if isinstance(op, HighLevelCall)), None) + assert call_to_f is not None, f"[{expected_description}]: HighLevelCall to 'a.f' should exist." + assert ( + len(slithir_ops) > 0 + ), f"[{expected_description}]: No SlithIR operations found, indicating a crash or severe parsing failure." + + +def test_primitive_array_instantiation_fix(solc_binary_path) -> None: + """ + Tests the fix for array instantiation of simple primitive types (e.g., a.f([0, num])). + This case was previously bugged and must now successfully generate the HighLevelCall. + """ + solc_path = solc_binary_path("0.8.0") + slither = Slither( + Path(TEST_DATA_DIR, "array_instantiation_call.sol").as_posix(), solc=solc_path + ) + contract_c = slither.get_contract_from_name("C")[0] + + target_func: FunctionContract = contract_c.get_function_from_signature( + "test_primitive_array_instantiation(address,uint256)" + ) + + _check_common_assertions(target_func, "Primitive Array Instantiation") + + +def test_struct_array_instantiation_fix(solc_binary_path) -> None: + """ + Tests the fix for complex array instantiation involving structs and arithmetic + (e.g., a.f([B(num), B(num + 1)])). This was a more complex buggy case. + """ + solc_path = solc_binary_path("0.8.0") + slither = Slither( + Path(TEST_DATA_DIR, "array_instantiation_call.sol").as_posix(), solc=solc_path + ) + + contract_c = slither.get_contract_from_name("C")[0] + + target_func: FunctionContract = contract_c.get_function_from_signature( + "test_struct_array_instantiation(address,uint256)" + ) + + _check_common_assertions(target_func, "Struct Array Instantiation") + + slithir_ops = target_func.slithir_operations + + # Check for the NewStructure operation (for B(num) or B(num + 1)) + new_struct_op = next((op for op in slithir_ops if isinstance(op, NewStructure)), None) + assert ( + new_struct_op is not None + ), "[Struct Array Instantiation]: NewStructure operation must be generated for struct B creation." diff --git a/tests/unit/slithir/test_data/array_instantiation_call.sol b/tests/unit/slithir/test_data/array_instantiation_call.sol new file mode 100644 index 0000000000..d669903426 --- /dev/null +++ b/tests/unit/slithir/test_data/array_instantiation_call.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +struct B{ + uint256 x; +} + +contract A { + function f(uint256[2] calldata arr) external {} + function f(B[2] calldata arr) external {} + function f(uint256[4] calldata arr) external {} +} + +contract C { + // TEST CASE 1: Simple array literal of primitives. + // This case was also bugged by the array instantiation issue. + function test_primitive_array_instantiation(A a, uint256 num) public { + a.f([0, num]); + } + + // TEST CASE 2: Complex array literal of structs with arithmetic. + // This is the original complex bug case. + function test_struct_array_instantiation(A a, uint256 num) public { + a.f([B(num), B(num + 1 )]); + } + + +}