diff --git a/gno.land/cmd/gnoland/testdata/append.txtar b/gno.land/cmd/gnoland/testdata/append.txtar new file mode 100644 index 00000000000..792d71882e5 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/append.txtar @@ -0,0 +1,129 @@ +# start a new node +gnoland start + +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/append -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call Append 1 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '1' -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func AppendNil -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call Append 2 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '2' -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call Append 3 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '3' -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("1-2-3-" string)' +stdout OK! + +# Call Pop +gnokey maketx call -pkgpath gno.land/r/append -func Pop -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-" string)' +stdout OK! + +# Call Append 42 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '42' -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-42-" string)' +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func CopyAppend -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func PopB -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-42-" string)' +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func AppendMoreAndC -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func ReassignC -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-42-70-100-" string)' +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args 'd' -broadcast -chainid=tendermint_test test1 +stdout '("1-" string)' +stdout OK! + +-- append.gno -- +package append + +import ( + "gno.land/p/demo/ufmt" +) + +type T struct{ i int } + +var a, b, d []T +var c = []T{{i: 100}} + + +func init() { + a = make([]T, 0, 1) +} + +func Pop() { + a = append(a[:0], a[1:]...) +} + +func Append(i int) { + a = append(a, T{i: i}) +} + +func CopyAppend() { + b = append(a, T{i: 50}, T{i: 60}) +} + +func PopB() { + b = append(b[:0], b[1:]...) +} + +func AppendMoreAndC() { + // Fill to capacity + a = append(a, T{i: 70}) + // Above capacity; make new array + a = append(a, c...) +} + +func ReassignC() { + c[0] = T{i: 200} +} + +func AppendNil() { + d = append(d, a...) +} + +func Render(path string) string { + source := a + if path == "d" { + source = d + } + + var s string + for i:=0;i *Game + +var counter byte + +func New(s string) string { + // Bug shows if Moves has a cap > 0 when initialised. + el := &Game{Position: Position{Moves: make([]Move, 0, 2)}} + games.Set(s, el) + return values(el.Position) +} + +func Delta(s string) string { + v, _ := games.Get(s) + g, ok := v.(*Game) + if !ok { + panic("invalid game") + } + n := g.Position.update() + g.Position = n + ret := values(n) + return ret +} + +func Render(s string) string { + v, _ := games.Get(s) + g, ok := v.(*Game) + if !ok { + panic("invalid game") + } + return values(g.Position) +} + +func values(x Position) string { + s := "" + for _, val := range x.Moves { + s += strconv.Itoa(int(val.N1)) + "," + strconv.Itoa(int(val.N2)) + "," + strconv.Itoa(int(val.N3)) + ";" + } + return s +} diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index d10a9280301..519b183ad3a 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -151,6 +151,7 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { // Updates to .newCreated/.newEscaped /.newDeleted made here. (first gen) // More appends happen during FinalizeRealmTransactions(). (second+ gen) rlm.MarkDirty(po) + if co != nil { co.IncRefCount() if co.GetRefCount() > 1 { @@ -166,6 +167,7 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { rlm.MarkNewReal(co) } } + if xo != nil { xo.DecRefCount() if xo.GetRefCount() == 0 { diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 57f8f6d393d..041f1557e61 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -210,9 +210,9 @@ func UverseNode() *PackageNode { // append(nil, *SliceValue) new list --------- list := make([]TypedValue, argsl) if 0 < argsl { - copy( - list[:argsl], - argsb.List[argso:argso+argsl]) + for i := 0; i < argsl; i++ { + list[i] = argsb.List[argso+i].unrefCopy(m.Alloc, m.Store) + } } m.PushValue(TypedValue{ T: xt, @@ -294,14 +294,25 @@ func UverseNode() *PackageNode { // append(*SliceValue.List, *SliceValue) --------- list := xvb.List if argsb.Data == nil { - copy( - list[xvo+xvl:xvo+xvl+argsl], - argsb.List[argso:argso+argsl]) + for i := 0; i < argsl; i++ { + oldElem := list[xvo+xvl+i] + // unrefCopy will resolve references and copy their values + // to copy by value rather than by reference. + newElem := argsb.List[argso+i].unrefCopy(m.Alloc, m.Store) + list[xvo+xvl+i] = newElem + + m.Realm.DidUpdate( + xvb, + oldElem.GetFirstObject(m.Store), + newElem.GetFirstObject(m.Store), + ) + } } else { copyDataToList( list[xvo+xvl:xvo+xvl+argsl], argsb.Data[argso:argso+argsl], xt.Elem()) + m.Realm.DidUpdate(xvb, nil, nil) } } else { // append(*SliceValue.Data, *SliceValue) --------- @@ -310,6 +321,7 @@ func UverseNode() *PackageNode { copyListToData( data[xvo+xvl:xvo+xvl+argsl], argsb.List[argso:argso+argsl]) + m.Realm.DidUpdate(xvb, nil, nil) } else { copy( data[xvo+xvl:xvo+xvl+argsl], @@ -363,9 +375,9 @@ func UverseNode() *PackageNode { list := make([]TypedValue, xvl+argsl) if 0 < xvl { if xvb.Data == nil { - copy( - list[:xvl], - xvb.List[xvo:xvo+xvl]) + for i := 0; i < xvl; i++ { + list[i] = xvb.List[xvo+i].unrefCopy(m.Alloc, m.Store) + } } else { panic("should not happen") /* @@ -379,9 +391,9 @@ func UverseNode() *PackageNode { } if 0 < argsl { if argsb.Data == nil { - copy( - list[xvl:xvl+argsl], - argsb.List[argso:argso+argsl]) + for i := 0; i < argsl; i++ { + list[xvl+i] = argsb.List[argso+i].unrefCopy(m.Alloc, m.Store) + } } else { copyDataToList( list[xvl:xvl+argsl], @@ -457,11 +469,12 @@ func UverseNode() *PackageNode { return } else { // append(*SliceValue, *NativeValue) new list -------- - list := make([]TypedValue, xvl+argsl) + listLen := xvl + argsl + list := make([]TypedValue, listLen) if 0 < xvl { - copy( - list[:xvl], - xvb.List[xvo:xvo+xvl]) + for i := 0; i < listLen; i++ { + list[i] = xvb.List[xvo+i].unrefCopy(m.Alloc, m.Store) + } } if 0 < argsl { copyNativeToList( diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 3607ccc4669..53d482613a1 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -1027,6 +1027,28 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) { return } +// unrefCopy makes a copy of the underlying value in the case of reference values. +// It copies other values as expected using the normal Copy method. +func (tv TypedValue) unrefCopy(alloc *Allocator, store Store) (cp TypedValue) { + switch tv.V.(type) { + case RefValue: + cp.T = tv.T + refObject := tv.GetFirstObject(store) + switch refObjectValue := refObject.(type) { + case *ArrayValue: + cp.V = refObjectValue.Copy(alloc) + case *StructValue: + cp.V = refObjectValue.Copy(alloc) + default: + cp = tv + } + default: + cp = tv.Copy(alloc) + } + + return +} + // Returns encoded bytes for primitive values. // These bytes are used for both value hashes as well // as hash key bytes. diff --git a/gnovm/tests/files/a47.gno b/gnovm/tests/files/a47.gno new file mode 100644 index 00000000000..2775547a3e8 --- /dev/null +++ b/gnovm/tests/files/a47.gno @@ -0,0 +1,32 @@ +package main + +type S struct { + i int +} + +func main() { + sArr := make([]S, 0, 4) + sArr = append(sArr, S{1}, S{2}, S{3}) + println(sArr[0].i, sArr[1].i, sArr[2].i) + + newArr := append(sArr[:0], sArr[1:]...) + + // The append modified the underlying array because it was within capacity. + println(len(sArr) == 3) + println(sArr[0].i, sArr[1].i, sArr[2].i) + + // It generated a new slice that references the same array. + println(len(newArr) == 2) + println(newArr[0].i, newArr[1].i) + + // The struct should have been copied, not referenced. + println(&sArr[2] != &newArr[1]) +} + +// Output: +// 1 2 3 +// true +// 2 3 3 +// true +// 2 3 +// true