diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index efb7c8a52e..cb4deedec5 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -517,5 +517,31 @@ def analyze(self) -> None: for function in self._functions_parser: function.analyze_content() + self._rebind_functions() + + def _rebind_functions(self) -> None: + """Rebuild the contract function dict with correct canonical names. + + During ``parse_functions`` the functions are registered via + ``add_function`` *before* parameter types are resolved, which + causes ``canonical_name`` (and ``full_name``) to be cached + with empty parameter lists. After ``analyze_content`` has + resolved all types we invalidate the cached names and + re-register every function so the dict keys are correct. + """ + for func_parser in self._functions_parser: + func = func_parser.underlying_function + # Invalidate stale cached names so they are recomputed + # with the now-known parameter types. + func._canonical_name = None + func._full_name = None + + self._contract.set_functions( + { + func_parser.underlying_function.canonical_name: (func_parser.underlying_function) + for func_parser in self._functions_parser + } + ) + def __hash__(self) -> int: return self._contract.id diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c_uint256__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c_uint256__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a_uint256_bool__0.txt similarity index 62% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a_uint256_bool__0.txt index 2f0451a520..3a1730aa3e 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a_uint256_bool__0.txt @@ -8,7 +8,7 @@ EXPRESSION: self.b(x,True) IRs: -INTERNAL_CALL, default_args.b()(x,True)"]; +INTERNAL_CALL, default_args.b(uint256,bool)(x,True)"]; 1->2; 2[label="Node Type: EXPRESSION 2 @@ -16,7 +16,7 @@ EXPRESSION: self.b(1,self.config) IRs: -INTERNAL_CALL, default_args.b()(1,config)"]; +INTERNAL_CALL, default_args.b(uint256,bool)(1,config)"]; 2->3; 3[label="Node Type: EXPRESSION 3 @@ -24,5 +24,5 @@ EXPRESSION: self.b(1,z) IRs: -INTERNAL_CALL, default_args.b()(1,z)"]; +INTERNAL_CALL, default_args.b(uint256,bool)(1,z)"]; } diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b_uint256_bool__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b_uint256_bool__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop_address_3__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop_address_3__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D_uint256_3_uint256__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D_uint256_3_uint256__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute_uint256__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute_uint256__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar_in_Roles__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar_in_Roles__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz_in_Roles__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz_in_Roles__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo_int128__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo_int128__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init___address_address__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init___address_address__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins_uint256__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins_uint256__0.txt diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo_uint256__0.txt similarity index 100% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo_uint256__0.txt diff --git a/tests/unit/slithir/vyper/test_ir_generation.py b/tests/unit/slithir/vyper/test_ir_generation.py index 19a97e680f..8c742690df 100644 --- a/tests/unit/slithir/vyper/test_ir_generation.py +++ b/tests/unit/slithir/vyper/test_ir_generation.py @@ -1,7 +1,7 @@ # -from slither.core.solidity_types import ElementaryType +from slither.core.solidity_types import ArrayType, ElementaryType from slither.slithir.operations import ( Phi, InternalCall, @@ -97,3 +97,43 @@ def b(x: uint256): if isinstance(op, InternalCall) and op.function.name == "c": assert len(op.arguments) == 2 assert op.arguments[1] == Constant("False", ElementaryType("bool")) + + +def test_vyper_function_params_in_canonical_name(slither_from_vyper_source): + """Regression test for #2796: Vyper function parameters missing from IR. + + canonical_name and full_name were cached before parameter types + were resolved, producing signatures like ``f()`` instead of + ``f(bytes[1024])``. + """ + with slither_from_vyper_source( + """ +@external +@view +def f(_x: Bytes[1024]) -> Bytes[1]: + return slice(_x, 0, 1) + +@external +@view +def g(_x: Bytes[1024]) -> Bytes[1]: + return slice(_x, 0, 1) +""" + ) as sl: + contract = sl.contracts[0] + f = contract.get_function_from_signature("f(bytes[1024])") + g = contract.get_function_from_signature("g(bytes[1024])") + assert f is not None, "f(bytes[1024]) not found" + assert g is not None, "g(bytes[1024]) not found" + + # Verify parameters exist and have correct types + assert len(f.parameters) == 1 + assert isinstance(f.parameters[0].type, ArrayType) + assert f.full_name == "f(bytes[1024])" + + assert len(g.parameters) == 1 + assert isinstance(g.parameters[0].type, ArrayType) + assert g.full_name == "g(bytes[1024])" + + # Verify canonical names include parameter types + assert "(bytes[1024])" in f.canonical_name + assert "(bytes[1024])" in g.canonical_name