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
26 changes: 26 additions & 0 deletions slither/vyper_parsing/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ 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

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

EXPRESSION:
self.b(1,z)

IRs:
INTERNAL_CALL, default_args.b()(1,z)"];
INTERNAL_CALL, default_args.b(uint256,bool)(1,z)"];
}
42 changes: 41 additions & 1 deletion tests/unit/slithir/vyper/test_ir_generation.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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