Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,4 @@ test_artifacts/
crytic-export/

# slither.db.json
slither.db.json
slither.db.json
61 changes: 34 additions & 27 deletions slither/slithir/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
67 changes: 67 additions & 0 deletions tests/unit/slithir/test_array_instantiation_call.py
Original file line number Diff line number Diff line change
@@ -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."
28 changes: 28 additions & 0 deletions tests/unit/slithir/test_data/array_instantiation_call.sol
Original file line number Diff line number Diff line change
@@ -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 )]);
}


}