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