cmd/cloner: preserve nil-valued entries when cloning map (#19749)

The codegen path for map-of-slice-of-pointer fields, skipped
nil-valued entries. That dropped the key from the map.

This broke how dns.Config.Routes uses nil values sentinels.

Fixes #19730
Fixes #19732
Fixes #19746
Fixes #19744

Change-Id: Ic6400227f4ab21b3ca0e8c0eeecf9b83d145a9ab

Signed-off-by: Fernando Serboncini <fserb@tailscale.com>
This commit is contained in:
Fernando Serboncini
2026-05-14 10:30:59 -04:00
committed by GitHub
parent 48919f708b
commit 2a06fb66d0
7 changed files with 105 additions and 0 deletions
+1
View File
@@ -176,6 +176,7 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
if codegen.ContainsPointers(sliceType.Elem()) {
writef("\tfor k, sv := range src.%s {", fname)
writef("\t\tif sv == nil {")
writef("\t\t\tdst.%s[k] = nil", fname)
writef("\t\t\tcontinue")
writef("\t\t}")
writef("\t\tdst.%s[k] = make([]%s, len(sv))", fname, n)
+15
View File
@@ -7,6 +7,7 @@ import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"tailscale.com/cmd/cloner/clonerex"
)
@@ -208,6 +209,20 @@ func TestMapSlicePointerContainer(t *testing.T) {
}
}
func TestMapSlicePointerContainerNilValue(t *testing.T) {
num := 7
orig := &clonerex.MapSlicePointerContainer{
Routes: map[string][]*clonerex.SliceContainer{
"nil-value": nil,
"non-nil": {{Slice: []*int{&num}}},
},
}
cloned := orig.Clone()
if diff := cmp.Diff(orig.Routes, cloned.Routes); diff != "" {
t.Errorf("Clone() Routes mismatch (-orig +cloned):\n%s", diff)
}
}
func TestDeeplyNestedMap(t *testing.T) {
num := 123
orig := &clonerex.DeeplyNestedMap{
+1
View File
@@ -188,6 +188,7 @@ func (src *MapSlicePointerContainer) Clone() *MapSlicePointerContainer {
dst.Routes = map[string][]*SliceContainer{}
for k, sv := range src.Routes {
if sv == nil {
dst.Routes[k] = nil
continue
}
dst.Routes[k] = make([]*SliceContainer, len(sv))
+6
View File
@@ -98,6 +98,7 @@ func (src *Map) Clone() *Map {
dst.SlicesWithPtrs = map[string][]*StructWithPtrs{}
for k, sv := range src.SlicesWithPtrs {
if sv == nil {
dst.SlicesWithPtrs[k] = nil
continue
}
dst.SlicesWithPtrs[k] = make([]*StructWithPtrs, len(sv))
@@ -114,6 +115,7 @@ func (src *Map) Clone() *Map {
dst.SlicesWithoutPtrs = map[string][]*StructWithoutPtrs{}
for k, sv := range src.SlicesWithoutPtrs {
if sv == nil {
dst.SlicesWithoutPtrs[k] = nil
continue
}
dst.SlicesWithoutPtrs[k] = make([]*StructWithoutPtrs, len(sv))
@@ -137,6 +139,7 @@ func (src *Map) Clone() *Map {
dst.SliceIntPtr = map[string][]*int{}
for k, sv := range src.SliceIntPtr {
if sv == nil {
dst.SliceIntPtr[k] = nil
continue
}
dst.SliceIntPtr[k] = make([]*int, len(sv))
@@ -431,6 +434,7 @@ func (src *GenericCloneableStruct[T, V]) Clone() *GenericCloneableStruct[T, V] {
dst.SliceMap = map[string][]T{}
for k, sv := range src.SliceMap {
if sv == nil {
dst.SliceMap[k] = nil
continue
}
dst.SliceMap[k] = make([]T, len(sv))
@@ -538,6 +542,7 @@ func (src *StructWithTypeAliasFields) Clone() *StructWithTypeAliasFields {
dst.MapOfSlicesWithPtrs = map[string][]*StructWithPtrsAlias{}
for k, sv := range src.MapOfSlicesWithPtrs {
if sv == nil {
dst.MapOfSlicesWithPtrs[k] = nil
continue
}
dst.MapOfSlicesWithPtrs[k] = make([]*StructWithPtrsAlias, len(sv))
@@ -554,6 +559,7 @@ func (src *StructWithTypeAliasFields) Clone() *StructWithTypeAliasFields {
dst.MapOfSlicesWithoutPtrs = map[string][]*StructWithoutPtrsAlias{}
for k, sv := range src.MapOfSlicesWithoutPtrs {
if sv == nil {
dst.MapOfSlicesWithoutPtrs[k] = nil
continue
}
dst.MapOfSlicesWithoutPtrs[k] = make([]*StructWithoutPtrsAlias, len(sv))