-
-
Notifications
You must be signed in to change notification settings - Fork 887
right-to-left evaluation in certain cases #4019
Description
Version Information
- vyper Version (output of
vyper --versionOR linkable commit hash vyperlang/vyper@): b43ffac
Issue Description
The order of evaluation in Vyper is specified as left-to-right; in the
same order as the expressions appear in the the source code.
-
Due to the way IR nodes that match directly EVM opcodes are
converted to assembly, for some built-ins and operators, it is known
that it might not be the case as described in
GHSA-g2xh-c426-v8mf. -
The compiler uses a pattern
cache_when_complexto cache the result
of a complex expression and avoid double evaluation. However, this
pattern appears to be problematic regarding the order of evaluation.- Given an IR node that has multiple children that should be
evaluated in a specific order, it might be that not all
children are cached. A cached complex child would hence be
evaluated before any non-cached children independently of
their order in the source code. This behavior is notably seen
in assignments given thatmake_setter()cachessrcbut not
dst. - A more subtle issue is that the compiler might cache all
children of an IR node, however, one or multiple nodes are not
considered as complex and their evaluation is inlined.
although in that case, a non-complex node cannot have side
effects, its evaluation can still read (e.g aSLOAD) side
effects from a complex node that was cached and evaluated
first. This case appears to be problematic for theslice()
andextract32()built-ins.
- Given an IR node that has multiple children that should be
-
Another issue regarding the order of evaluation is that the order of
evaluation of the kwargs passed to built-ins and call expressions do
not follow the order of the source code.
POC
The following examples show multiple cases where the order of evaluation
is right-to-left.
i:uint256
@internal
def change_i() -> uint256:
self.i += 1
return 12
@external
def foo() -> DynArray[uint256,2]:
x:DynArray[uint256,2] = [1,2]
x[self.i] += self.change_i()
return x # returns [13,2]boo:Bytes[32]
@internal
def bar() -> uint256:
self.boo = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
return 0
@internal
def baz() -> Bytes[32]:
return self.boo
@external
def slice() -> (Bytes[32], Bytes[32]):
self.boo = b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
s: Bytes[1] = slice(self.boo, self.bar(), 1)
self.boo = b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
t: Bytes[1] = slice(self.baz(), self.bar(), 1)
return s,t # (b'a', b'b')
@external
def extract32() -> (bytes32, bytes32):
self.boo = b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
s: bytes32 = extract32(self.boo, self.bar())
self.boo = b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
t: bytes32 = extract32(self.baz(), self.bar())
return s,t #(b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')@external
@payable
def bar(): return
interface Bar:
def bar(): payable
x: uint256
@internal
def gas() -> uint256:
self.x = 2
return 100000
@internal
def value() -> uint256:
self.x = 1
return 0
@external
@payable
def foo():
extcall Bar(self).bar(gas=self.gas(), value=self.value())
temp: uint256 = self.x
extcall Bar(self).bar(value=self.value(), gas=self.gas())
assert self.x == temp # passes