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.
This commit is contained in:
Evgenii Stratonikov 2020-08-05 09:33:18 +03:00
parent d54c60ded3
commit f2107bfbc4
2 changed files with 50 additions and 0 deletions

View file

@ -793,6 +793,18 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// Handle the arguments // Handle the arguments
for _, arg := range args { for _, arg := range args {
ast.Walk(c, arg) 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. // Do not swap for builtin functions.
if !isBuiltin { if !isBuiltin {

View file

@ -28,3 +28,41 @@ func TestPointerDereference(t *testing.T) {
func setA(s Foo, a int) { s.A = a }` func setA(s Foo, a int) { s.A = a }`
eval(t, src, big.NewInt(4)) 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))
})
}