From f2107bfbc45c3fece16e604b507d7108be434d2c Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 5 Aug 2020 09:33:18 +0300 Subject: [PATCH] compiler: copy structs when passing as arguments In Go structs must be copied when used as arguments. To do so we must clone struct on VM level. This is done by appending this struct to an intermediate array. --- pkg/compiler/codegen.go | 12 ++++++++++++ pkg/compiler/pointer_test.go | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) 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)) + }) +}