tailscale/cmd/cloner/cloner_test.go
Brad Fitzpatrick 86f42ea87b cmd/cloner, cmd/viewer: handle named map/slice types with Clone/View methods
The cloner and viewer code generators didn't handle named types
with basic underlying types (map/slice) that have their own Clone
or View methods. For example, a type like:

    type Map map[string]any
    func (m Map) Clone() Map { ... }
    func (m Map) View() MapView { ... }

When used as a struct field, the cloner would descend into the
underlying map[string]any and fail because it can't clone the any
(interface{}) value type. Similarly, the viewer would try to create
a MapFnOf view and fail.

Fix the cloner to check for a Clone method on the named type
before falling through to the underlying type handling.

Fix the viewer to check for a View method on named map/slice types,
so the type author can provide a purpose-built safe view that
doesn't leak raw any values. Named map/slice types without a View
method fall through to normal handling, which correctly rejects
types like map[string]any as unsupported.

Updates tailscale/corp#39502 (needed by tailscale/corp#39594)

Change-Id: Iaef0192a221e02b4b8e409c99ef8398090327744
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-05 20:20:32 -07:00

245 lines
5.8 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// 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.SliceContainer
}{
{
name: "nil",
in: nil,
},
{
name: "zero",
in: &clonerex.SliceContainer{},
},
{
name: "empty",
in: &clonerex.SliceContainer{
Slice: []*int{},
},
},
{
name: "nils",
in: &clonerex.SliceContainer{
Slice: []*int{nil, nil, nil, nil, nil},
},
},
{
name: "one",
in: &clonerex.SliceContainer{
Slice: []*int{&num},
},
},
{
name: "several",
in: &clonerex.SliceContainer{
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)
}
})
}
}
func TestInterfaceContainer(t *testing.T) {
examples := []struct {
name string
in *clonerex.InterfaceContainer
}{
{
name: "nil",
in: nil,
},
{
name: "zero",
in: &clonerex.InterfaceContainer{},
},
{
name: "with_interface",
in: &clonerex.InterfaceContainer{
Interface: &clonerex.CloneableImpl{Value: 42},
},
},
{
name: "with_nil_interface",
in: &clonerex.InterfaceContainer{
Interface: nil,
},
},
}
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)
}
// Verify no aliasing: modifying the clone should not affect the original
if ex.in != nil && ex.in.Interface != nil {
if impl, ok := out.Interface.(*clonerex.CloneableImpl); ok {
impl.Value = 999
if origImpl, ok := ex.in.Interface.(*clonerex.CloneableImpl); ok {
if origImpl.Value == 999 {
t.Errorf("Clone() aliased memory with original")
}
}
}
}
})
}
}
func TestMapWithPointers(t *testing.T) {
num1, num2 := 42, 100
orig := &clonerex.MapWithPointers{
Nested: map[string]*int{
"foo": &num1,
"bar": &num2,
},
WithCloneMethod: map[string]*clonerex.SliceContainer{
"container1": {Slice: []*int{&num1, &num2}},
"container2": {Slice: []*int{&num1}},
},
CloneInterface: map[string]clonerex.Cloneable{
"impl1": &clonerex.CloneableImpl{Value: 123},
"impl2": &clonerex.CloneableImpl{Value: 456},
},
}
cloned := orig.Clone()
if !reflect.DeepEqual(orig, cloned) {
t.Errorf("Clone() = %v, want %v", cloned, orig)
}
// Mutate cloned.Nested pointer values
*cloned.Nested["foo"] = 999
if *orig.Nested["foo"] == 999 {
t.Errorf("Clone() aliased memory in Nested: original was modified")
}
// Mutate cloned.WithCloneMethod slice values
*cloned.WithCloneMethod["container1"].Slice[0] = 888
if *orig.WithCloneMethod["container1"].Slice[0] == 888 {
t.Errorf("Clone() aliased memory in WithCloneMethod: original was modified")
}
// Mutate cloned.CloneInterface values
if impl, ok := cloned.CloneInterface["impl1"].(*clonerex.CloneableImpl); ok {
impl.Value = 777
if origImpl, ok := orig.CloneInterface["impl1"].(*clonerex.CloneableImpl); ok {
if origImpl.Value == 777 {
t.Errorf("Clone() aliased memory in CloneInterface: original was modified")
}
}
}
}
func TestNamedMapContainer(t *testing.T) {
orig := &clonerex.NamedMapContainer{
Attrs: clonerex.NamedMap{
"str": "hello",
"num": int64(42),
"bool": true,
},
}
cloned := orig.Clone()
if !reflect.DeepEqual(orig, cloned) {
t.Errorf("Clone() = %v, want %v", cloned, orig)
}
// Mutate the cloned map to verify no aliasing.
cloned.Attrs["str"] = "modified"
if orig.Attrs["str"] == "modified" {
t.Errorf("Clone() aliased memory in Attrs: original was modified")
}
// Verify nil handling.
nilContainer := &clonerex.NamedMapContainer{}
nilClone := nilContainer.Clone()
if !reflect.DeepEqual(nilContainer, nilClone) {
t.Errorf("Clone() of nil Attrs = %v, want %v", nilClone, nilContainer)
}
}
func TestDeeplyNestedMap(t *testing.T) {
num := 123
orig := &clonerex.DeeplyNestedMap{
ThreeLevels: map[string]map[string]map[string]int{
"a": {
"b": {"c": 1, "d": 2},
"e": {"f": 3},
},
"g": {
"h": {"i": 4},
},
},
FourLevels: map[string]map[string]map[string]map[string]*clonerex.SliceContainer{
"l1a": {
"l2a": {
"l3a": {
"l4a": {Slice: []*int{&num}},
"l4b": {Slice: []*int{&num, &num}},
},
},
},
},
}
cloned := orig.Clone()
if !reflect.DeepEqual(orig, cloned) {
t.Errorf("Clone() = %v, want %v", cloned, orig)
}
// Mutate the clone's ThreeLevels map
cloned.ThreeLevels["a"]["b"]["c"] = 777
if orig.ThreeLevels["a"]["b"]["c"] == 777 {
t.Errorf("Clone() aliased memory in ThreeLevels: original was modified")
}
// Mutate the clone's FourLevels map at the deepest pointer level
*cloned.FourLevels["l1a"]["l2a"]["l3a"]["l4a"].Slice[0] = 666
if *orig.FourLevels["l1a"]["l2a"]["l3a"]["l4a"].Slice[0] == 666 {
t.Errorf("Clone() aliased memory in FourLevels: original was modified")
}
// Add a new top-level key to the clone's FourLevels map
newNum := 999
cloned.FourLevels["l1b"] = map[string]map[string]map[string]*clonerex.SliceContainer{
"l2b": {
"l3b": {
"l4c": {Slice: []*int{&newNum}},
},
},
}
if _, exists := orig.FourLevels["l1b"]; exists {
t.Errorf("Clone() aliased FourLevels map: new top-level key appeared in original")
}
// Add a new nested key to the clone's FourLevels map
cloned.FourLevels["l1a"]["l2a"]["l3a"]["l4c"] = &clonerex.SliceContainer{Slice: []*int{&newNum}}
if _, exists := orig.FourLevels["l1a"]["l2a"]["l3a"]["l4c"]; exists {
t.Errorf("Clone() aliased FourLevels map: new nested key appeared in original")
}
}