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  }