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>
This commit is contained in:
Brad Fitzpatrick
2026-04-05 22:56:53 +00:00
committed by Brad Fitzpatrick
parent 5a899e406d
commit 86f42ea87b
10 changed files with 440 additions and 6 deletions
+59 -1
View File
@@ -12,7 +12,7 @@ import (
"tailscale.com/types/views"
)
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded,GenericIntStruct,GenericNoPtrsStruct,GenericCloneableStruct,StructWithContainers,StructWithTypeAliasFields,GenericTypeAliasStruct,StructWithMapOfViews --clone-only-type=OnlyGetClone
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded,GenericIntStruct,GenericNoPtrsStruct,GenericCloneableStruct,StructWithContainers,StructWithTypeAliasFields,GenericTypeAliasStruct,StructWithMapOfViews,StructWithNamedMap,StructWithNamedSlice --clone-only-type=OnlyGetClone
type StructWithoutPtrs struct {
Int int
@@ -241,3 +241,61 @@ type GenericTypeAliasStruct[T integer, T2 views.ViewCloner[T2, V2], V2 views.Str
type StructWithMapOfViews struct {
MapOfViews map[string]StructWithoutPtrsView
}
// NamedMap is a named map type with its own Clone and View methods.
// This tests that the viewer calls View() on named map types rather
// than trying to generate a view of the underlying map[string]any.
type NamedMap map[string]any
func (m NamedMap) Clone() NamedMap {
if m == nil {
return nil
}
m2 := make(NamedMap, len(m))
for k, v := range m {
m2[k] = v
}
return m2
}
// NamedMapView is a read-only view of NamedMap.
type NamedMapView struct {
ж NamedMap
}
func (m NamedMap) View() NamedMapView { return NamedMapView{m} }
func (v NamedMapView) Get(k string) (any, bool) { val, ok := v.ж[k]; return val, ok }
func (v NamedMapView) Len() int { return len(v.ж) }
type StructWithNamedMap struct {
Attrs NamedMap
}
// NamedSlice is a named slice type with its own Clone and View methods.
// This tests that the viewer calls View() on named slice types rather
// than trying to generate a view of the underlying []any.
type NamedSlice []any
func (s NamedSlice) Clone() NamedSlice {
if s == nil {
return nil
}
s2 := make(NamedSlice, len(s))
copy(s2, s)
return s2
}
// NamedSliceView is a read-only view of NamedSlice.
type NamedSliceView struct {
ж NamedSlice
}
func (s NamedSlice) View() NamedSliceView { return NamedSliceView{s} }
func (v NamedSliceView) At(i int) any { return v.ж[i] }
func (v NamedSliceView) Len() int { return len(v.ж) }
type StructWithNamedSlice struct {
Items NamedSlice
}