cmd/cloner: add regression test for slice nil/empty semantics

We had a misstep with the semantics when applying an optimization that
showed up in the roll into corp. This test ensures that case and related
cases must be retained.

Updates #9410
Updates #9601
Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker 2023-09-29 18:31:45 -07:00 committed by James Tucker
parent e03f0d5f5c
commit ab810f1f6d
5 changed files with 131 additions and 1 deletions

View File

@ -128,7 +128,9 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
if ptr, isPtr := ft.Elem().(*types.Pointer); isPtr {
if _, isBasic := ptr.Elem().Underlying().(*types.Basic); isBasic {
it.Import("tailscale.com/types/ptr")
writef("if src.%s[i] == nil { dst.%s[i] = nil } else {", fname, fname)
writef("\tdst.%s[i] = ptr.To(*src.%s[i])", fname, fname)
writef("}")
} else {
writef("\tdst.%s[i] = src.%s[i].Clone()", fname, fname)
}

60
cmd/cloner/cloner_test.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"reflect"
"testing"
"tailscale.com/cmd/cloner/clonerex"
)
func TestSliceContainer(t *testing.T) {
num := 5
examples := []struct {
name string
in *clonerex.SliceContianer
}{
{
name: "nil",
in: nil,
},
{
name: "zero",
in: &clonerex.SliceContianer{},
},
{
name: "empty",
in: &clonerex.SliceContianer{
Slice: []*int{},
},
},
{
name: "nils",
in: &clonerex.SliceContianer{
Slice: []*int{nil, nil, nil, nil, nil},
},
},
{
name: "one",
in: &clonerex.SliceContianer{
Slice: []*int{&num},
},
},
{
name: "several",
in: &clonerex.SliceContianer{
Slice: []*int{&num, &num, &num, &num, &num},
},
},
}
for _, ex := range examples {
t.Run(ex.name, func(t *testing.T) {
out := ex.in.Clone()
if !reflect.DeepEqual(ex.in, out) {
t.Errorf("Clone() = %v, want %v", out, ex.in)
}
})
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type SliceContianer
package clonerex
type SliceContianer struct {
Slice []*int
}

View File

@ -0,0 +1,54 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
package clonerex
import (
"tailscale.com/types/ptr"
)
// Clone makes a deep copy of SliceContianer.
// The result aliases no memory with the original.
func (src *SliceContianer) Clone() *SliceContianer {
if src == nil {
return nil
}
dst := new(SliceContianer)
*dst = *src
if src.Slice != nil {
dst.Slice = make([]*int, len(src.Slice))
for i := range dst.Slice {
if src.Slice[i] == nil {
dst.Slice[i] = nil
} else {
dst.Slice[i] = ptr.To(*src.Slice[i])
}
}
}
return dst
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _SliceContianerCloneNeedsRegeneration = SliceContianer(struct {
Slice []*int
}{})
// Clone duplicates src into dst and reports whether it succeeded.
// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,
// where T is one of SliceContianer.
func Clone(dst, src any) bool {
switch src := src.(type) {
case *SliceContianer:
switch dst := dst.(type) {
case *SliceContianer:
*dst = *src.Clone()
return true
case **SliceContianer:
*dst = src.Clone()
return true
}
}
return false
}

View File

@ -157,7 +157,11 @@ func (src *StructWithSlices) Clone() *StructWithSlices {
if src.Ints != nil {
dst.Ints = make([]*int, len(src.Ints))
for i := range dst.Ints {
dst.Ints[i] = ptr.To(*src.Ints[i])
if src.Ints[i] == nil {
dst.Ints[i] = nil
} else {
dst.Ints[i] = ptr.To(*src.Ints[i])
}
}
}
dst.Slice = append(src.Slice[:0:0], src.Slice...)