diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index ad4a07029..4b24d39f3 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -793,6 +793,18 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // Handle the arguments for _, arg := range args { ast.Walk(c, arg) + typ := c.typeOf(arg) + _, ok := typ.Underlying().(*types.Struct) + if ok && !isInteropPath(typ.String()) { + // To clone struct fields we create a new array and append struct to it. + // This way even non-pointer struct fields will be copied. + emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY0) + emit.Opcode(c.prog.BinWriter, opcode.DUP) + emit.Opcode(c.prog.BinWriter, opcode.ROT) + emit.Opcode(c.prog.BinWriter, opcode.APPEND) + emit.Opcode(c.prog.BinWriter, opcode.PUSH0) + emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) + } } // Do not swap for builtin functions. if !isBuiltin { diff --git a/pkg/compiler/pointer_test.go b/pkg/compiler/pointer_test.go index 7b9e8c2dd..eb86e2276 100644 --- a/pkg/compiler/pointer_test.go +++ b/pkg/compiler/pointer_test.go @@ -28,3 +28,41 @@ func TestPointerDereference(t *testing.T) { func setA(s Foo, a int) { s.A = a }` eval(t, src, big.NewInt(4)) } + +func TestStructArgCopy(t *testing.T) { + t.Run("Simple", func(t *testing.T) { + src := `package foo + type Foo struct { A int } + func Main() int { + f := Foo{A: 4} + setA(f, 3) + return f.A + } + func setA(s Foo, a int) { s.A = a }` + eval(t, src, big.NewInt(4)) + }) + t.Run("StructField", func(t *testing.T) { + src := `package foo + type Bar struct { A int } + type Foo struct { B Bar } + func Main() int { + f := Foo{B: Bar{A: 4}} + setA(f, 3) + return f.B.A + } + func setA(s Foo, a int) { s.B.A = a }` + eval(t, src, big.NewInt(4)) + }) + t.Run("StructPointerField", func(t *testing.T) { + src := `package foo + type Bar struct { A int } + type Foo struct { B *Bar } + func Main() int { + f := Foo{B: &Bar{A: 4}} + setA(f, 3) + return f.B.A + } + func setA(s Foo, a int) { s.B.A = a }` + eval(t, src, big.NewInt(3)) + }) +}