Skip to content

Commit

Permalink
fix: prevent reassignment of mutable parameters that are passed by re…
Browse files Browse the repository at this point in the history
…ference

BREAKING CHANGE:
Re-assigment to a parameter that is mutable is now disallowed.
  • Loading branch information
achidlow committed Mar 26, 2024
1 parent f857181 commit a9c7600
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 2 deletions.
6 changes: 6 additions & 0 deletions src/puya/ir/builder/assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ def handle_assignment(
) -> Sequence[Value]:
match target:
case awst_nodes.VarExpression(name=var_name, source_location=var_loc):
if var_name in (p.name for p in context.subroutine.parameters if p.implicit_return):
raise CodeError(
f"Cannot reassign mutable parameter {var_name!r}"
" which is being passed by reference",
assignment_location,
)
return assign(
context,
source=value,
Expand Down
48 changes: 46 additions & 2 deletions tests/test_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import algosdk.transaction
import attrs
import prettytable
import puya.errors
import pytest
from algokit_utils import (
Account,
Expand Down Expand Up @@ -1333,7 +1334,50 @@ def clear_state_program(self) -> bool:
return True

# expected error message here is just an example, update test once a better one is available
import puya.errors

with pytest.raises(puya.errors.CodeError, match="Invalid use of a type union"):
harness.deploy_from_closure(test)


def test_mutable_ref_param_reassignment_fails(harness: _TestHarness) -> None:
def test() -> None:
from puyapy import arc4

class Baddie(arc4.ARC4Contract):
@arc4.abimethod
def okay(self, arr: arc4.DynamicBytes) -> None:
arr = arc4.DynamicBytes.from_bytes(arr.bytes)
self.not_okay(arr)

@arc4.abimethod()
def not_okay(self, arr2: arc4.DynamicBytes) -> None:
arr2 = arc4.DynamicBytes.from_bytes(arr2.bytes)
arr2.append(arc4.Byte(1))

with pytest.raises(
puya.errors.CodeError,
match="Cannot reassign mutable parameter 'arr2' which is being passed by reference",
):
harness.deploy_from_closure(test)


def test_mutable_ref_param_reassignment_in_tuple_fails(harness: _TestHarness) -> None:
def test() -> None:
from puyapy import arc4

class Baddie(arc4.ARC4Contract):
@arc4.abimethod
def okay(self, arr: arc4.DynamicBytes) -> None:
arr = arc4.DynamicBytes.from_bytes(arr.bytes)
self.not_okay(arr)

@arc4.abimethod()
def not_okay(self, arr2: arc4.DynamicBytes) -> None:
(arr2, foo) = (arc4.DynamicBytes.from_bytes(arr2.bytes), arc4.UInt64(1))
arr2.append(arc4.Byte(1))
assert foo

with pytest.raises(
puya.errors.CodeError,
match="Cannot reassign mutable parameter 'arr2' which is being passed by reference",
):
harness.deploy_from_closure(test)

0 comments on commit a9c7600

Please sign in to comment.