propagate return type on call to state variable with interface type

pull/2099/head
alpharush 1 year ago
parent e80238d8e9
commit 239369c853
  1. 24
      slither/vyper_parsing/expressions/expression_parsing.py
  2. 56
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_baz__0.txt
  3. 12
      tests/e2e/vyper_parsing/test_data/interface_conversion.vy

@ -5,6 +5,7 @@ from slither.core.declarations.solidity_variables import (
SolidityVariableComposed,
)
from slither.core.declarations import SolidityFunction, Function
from slither.core.variables.state_variable import StateVariable
from slither.core.expressions import (
CallExpression,
ElementaryTypeNameExpression,
@ -63,6 +64,7 @@ if TYPE_CHECKING:
def parse_expression(expression: Dict, caller_context) -> "Expression": # pylint
print("parse_expression", expression)
if isinstance(expression, Int):
literal = Literal(str(expression.value), ElementaryType("uint256"))
@ -178,8 +180,8 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylin
arguments = [parse_expression(a, caller_context) for a in expression.args]
rets = None
# Since the AST lacks the type of the return values, we recover it.
if isinstance(called, Identifier):
# Since the AST lacks the type of the return values, we recover it.
if isinstance(called.value, Function):
rets = called.value.returns
# Default arguments are not represented in the AST, so we recover them as well.
@ -264,7 +266,25 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylin
# (recover_type_1) This may be a call to an interface and we don't have the return types,
# so we see if there's a function identifier with `member_name` and propagate the type to
# its enclosing `CallExpression`
if isinstance(expr, TypeConversion) and isinstance(expr.type, UserDefinedType):
if isinstance(expr, Identifier) and isinstance(expr.value, StateVariable) and isinstance(expr.value.type, UserDefinedType) and isinstance(expr.value.type.type, Contract):
# If we access a member of an interface, needs to be interface instead of self namespace
var = find_variable(member_name, expr.value.type.type)
if isinstance(var, Function):
rets = var.returns
def get_type_str(x):
if isinstance(x, str):
return x
return str(x.type)
type_str = (
get_type_str(rets[0])
if len(rets) == 1
else f"tuple({','.join(map(get_type_str, rets))})"
)
member_name_ret_type = type_str
if isinstance(expr, TypeConversion) and isinstance(expr.type, UserDefinedType) and isinstance(expr.type.type, Contract):
# If we access a member of an interface, needs to be interface instead of self namespace
var = find_variable(member_name, expr.type.type)
if isinstance(var, Function):

@ -0,0 +1,56 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
a = 0
IRs:
a(int128) := 0(uint256)"];
1->2;
2[label="Node Type: NEW VARIABLE 2
EXPRESSION:
b = 0
IRs:
b(int128) := 0(uint256)"];
2->3;
3[label="Node Type: EXPRESSION 3
EXPRESSION:
(a,b) = self.foo()
IRs:
TUPLE_2(int128,int128) = INTERNAL_CALL, interface_conversion.foo()()
a(int128)= UNPACK TUPLE_2 index: 0
b(int128)= UNPACK TUPLE_2 index: 1 "];
3->4;
4[label="Node Type: NEW VARIABLE 4
EXPRESSION:
x = 0x0000000000000000000000000000000000000000
IRs:
x(address) := 0(address)"];
4->5;
5[label="Node Type: NEW VARIABLE 5
EXPRESSION:
c = 0
IRs:
c(uint256) := 0(uint256)"];
5->6;
6[label="Node Type: EXPRESSION 6
EXPRESSION:
(a,c) = self.tester.foo()
IRs:
TUPLE_3(int128,uint256) = HIGH_LEVEL_CALL, dest:tester(Test), function:foo, arguments:[]
a(int128)= UNPACK TUPLE_3 index: 0
c(uint256)= UNPACK TUPLE_3 index: 1 "];
}

@ -1,6 +1,8 @@
interface Test:
def foo() -> (int128, uint256): nonpayable
tester: Test
@internal
def foo() -> (int128, int128):
return 2, 3
@ -15,3 +17,13 @@ def bar():
c: uint256 = 0
a, c = Test(x).foo()
@external
def baz():
a: int128 = 0
b: int128 = 0
(a, b) = self.foo()
x: address = 0x0000000000000000000000000000000000000000
c: uint256 = 0
a, c = self.tester.foo()

Loading…
Cancel
Save