-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
Copy pathclone.go
135 lines (115 loc) · 4.05 KB
/
clone.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright 2016 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package protoutil
import (
"reflect"
"github.com/cockroachdb/cockroach/pkg/util/syncutil"
"github.com/cockroachdb/errors"
"github.com/gogo/protobuf/proto"
)
var verbotenKinds = [...]reflect.Kind{
reflect.Array,
}
type typeKey struct {
typ reflect.Type
verboten reflect.Kind
}
var types struct {
syncutil.Mutex
known map[typeKey]reflect.Type
}
func init() {
types.known = make(map[typeKey]reflect.Type)
}
// RegisterUnclonableType registers a type as not being allowed for cloning.
// This is an added hack on top of the hack to allow clients of this package to
// disallow cloning of certain types which are not recursed into due to how
// oneof is implemented. In particular it may be the case that one of the
// implementations of an interface is unclonable. In this case, due to the type
// (rather than value) traversal, we'd not discover this fact.
//
// See the comment on Clone.
func RegisterUnclonableType(typ reflect.Type, verbotenKind reflect.Kind) {
types.Lock()
defer types.Unlock()
types.known[typeKey{typ: typ, verboten: verbotenKind}] = typ
}
func uncloneable(pb Message) (reflect.Type, bool) {
for _, verbotenKind := range verbotenKinds {
if t := typeIsOrContainsVerboten(reflect.TypeOf(pb), verbotenKind); t != nil {
return t, true
}
}
return nil, false
}
// Clone uses proto.Clone to return a deep copy of pb. It panics if pb
// recursively contains any instances of types which are known to be
// unsupported by proto.Clone.
//
// This function and its associated lint (see build/style_test.go) exist to
// ensure we do not attempt to proto.Clone types which are not supported by
// proto.Clone. This hackery is necessary because proto.Clone gives no direct
// indication that it has incompletely cloned a type; it merely logs to standard
// output (see
// https://github.com/golang/protobuf/blob/89238a3/proto/clone.go#L204).
//
// The concrete case against which this is currently guarding may be resolved
// upstream, see https://github.com/gogo/protobuf/issues/147.
func Clone(pb Message) Message {
if t, ok := uncloneable(pb); ok {
panic(errors.AssertionFailedf("attempt to clone %T, which contains uncloneable field of type %s", pb, t))
}
return proto.Clone(pb).(Message)
}
func typeIsOrContainsVerboten(t reflect.Type, verboten reflect.Kind) reflect.Type {
types.Lock()
defer types.Unlock()
return typeIsOrContainsVerbotenLocked(t, verboten)
}
func typeIsOrContainsVerbotenLocked(t reflect.Type, verboten reflect.Kind) reflect.Type {
key := typeKey{t, verboten}
knownTypeIsOrContainsVerboten, ok := types.known[key]
if !ok {
// To prevent infinite recursion on recursive proto types, put a
// placeholder in here and immediately overwite it after
// typeIsOrContainsVerbotenImpl returns.
types.known[key] = nil
knownTypeIsOrContainsVerboten = typeIsOrContainsVerbotenImpl(t, verboten)
types.known[key] = knownTypeIsOrContainsVerboten
}
return knownTypeIsOrContainsVerboten
}
func typeIsOrContainsVerbotenImpl(t reflect.Type, verboten reflect.Kind) reflect.Type {
switch t.Kind() {
case verboten:
return t
case reflect.Map:
if key := typeIsOrContainsVerbotenLocked(t.Key(), verboten); key != nil {
return key
}
if value := typeIsOrContainsVerbotenLocked(t.Elem(), verboten); value != nil {
return value
}
case reflect.Array, reflect.Ptr, reflect.Slice:
if value := typeIsOrContainsVerbotenLocked(t.Elem(), verboten); value != nil {
return value
}
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
if field := typeIsOrContainsVerbotenLocked(t.Field(i).Type, verboten); field != nil {
return field
}
}
case reflect.Chan, reflect.Func:
// Not strictly correct, but cloning these kinds is not allowed.
return t
}
return nil
}