blob: 767c3871f5409d3a5044b931f3fae580039fdfbe [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package copy
import (
"reflect"
)
// DeepCopyValue produces a deep copy of the given value, where the result
// ideally shares no mutable memory with the given value.
//
// There are some limitations on what's possible, however:
// - This package can't write into an unexported field of a struct, so those
// will be ignored entirely and thus left as their zero values in the
// result.
// - If the given structure contains function pointers then their closures
// might refer to shared memory that this function cannot copy. If they
// refer to memory that's also included in the data structure outside of
// the function pointer then the two will be disconnected in the result.
// - It isn't really meaningful to "copy" a channel since it's a
// synchronization primitive rather than a data structure, so the result
// will share the same channels as the input.
// - Copying other library-based synchronization primitives like [sync.Mutex]
// doesn't really make sense either, although this function doesn't
// understand what they are and so the result is undefined. If your
// synchronization primitive has a "ready to use" zero value then it _might_
// be acceptable to store it in an unexported field and thus have it be
// zeroed in the result, but at that point you're probably better off
// writing a specialized deepcopy function so that you can actually use
// the synchronization primitive to prevent data races during copying.
// - The uintptr and [unsafe.Pointer] types might well refer to some shared
// memory, but don't give any information about how to copy that memory
// and so those are just preserved verbatim, making the result point to
// the same memory as the input.
// - Broadly, this function needs special handling for each different kind
// of value in Go, and so if a later version of Go has introduced a new kind
// of value then this function might not support it yet. That might cause
// this function to panic, or to ignore part of the structure, or otherwise
// misbehave.
//
// This is intended as a relatively simple utility for straightforward cases,
// primarily for use in contrived situations like unit tests.
//
// It intentionally does not offer any customization; if you need to do
// something special then it's better to just write your own simple direct code
// than to pull in all of this reflection trickery. Even if you don't need to do
// something special it's probably still better to just write some
// straightforward code that directly describes the behavior you're intending,
// so that the Go compiler can help you and so you don't force future
// maintainers to understand all of this metaprogramming if something goes
// wrong. Seriously... don't use this function.
func DeepCopyValue[T any](v T) T {
// We use type parameters in the signature to make usage more convenient
// for the caller (no type assertions required) but we actually do all
// our internal work in the realm of package reflect.
input := reflect.ValueOf(&v).Elem() // if T is an interface type then input is the interface value, not the value inside it
ty := reflect.TypeFor[T]() // likewise, if T is interface type then this is the static interface type, not the dynamic type
result := deepCopyValue(input, ty)
return result.Interface().(T)
}
func deepCopyValue(v reflect.Value, ty reflect.Type) reflect.Value {
switch ty.Kind() {
// A large subset of the kinds don't point to mutable sharable memory,
// or don't refer to something we can possibly copy, and so we can just
// return them directly without any extra work.
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String, reflect.UnsafePointer, reflect.Func, reflect.Chan:
return v
case reflect.Array:
return deepCopyArray(v, ty)
case reflect.Interface:
return deepCopyInterface(v, ty)
case reflect.Map:
return deepCopyMap(v, ty)
case reflect.Pointer:
return deepCopyPointer(v, ty)
case reflect.Slice:
return deepCopySlice(v, ty)
case reflect.Struct:
return deepCopyStruct(v, ty)
default:
panic("unsupported type kind " + ty.Kind().String())
}
}
func deepCopyArray(v reflect.Value, ty reflect.Type) reflect.Value {
// Copying an array really means allocating a new array and then
// copying each of the elements from the source.
ret := reflect.New(ty).Elem()
for i := range ret.Len() {
newElemV := deepCopyValue(v.Index(i), ty.Elem())
ret.Index(i).Set(newElemV)
}
return ret
}
func deepCopyInterface(v reflect.Value, ty reflect.Type) reflect.Value {
if v.IsNil() {
return v
}
// An interface value is not directly mutable itself, but the value
// inside it might be and so we'll copy that and then wrap the result
// in a new interface value of the same type.
ret := reflect.New(ty).Elem()
dynV := deepCopyValue(v.Elem(), v.Elem().Type())
ret.Set(dynV)
return ret
}
func deepCopyMap(v reflect.Value, ty reflect.Type) reflect.Value {
if v.IsNil() {
return v
}
ret := reflect.MakeMap(ty)
for iter := v.MapRange(); iter.Next(); {
// We don't copy the key because Go does not allow any mutably-aliasable
// types as map keys. (That would make it very easy to corrupt the
// internals of the map, after all!)
k := iter.Key()
v := deepCopyValue(iter.Value(), ty.Elem())
ret.SetMapIndex(k, v)
}
return ret
}
func deepCopyPointer(v reflect.Value, ty reflect.Type) reflect.Value {
if v.IsNil() {
return v
}
// We copy a pointer by copying what it refers to and then returning
// a pointer to that copy.
newTarget := deepCopyValue(v.Elem(), ty.Elem())
return newTarget.Addr()
}
func deepCopySlice(v reflect.Value, ty reflect.Type) reflect.Value {
if v.IsNil() {
return v
}
// Copying a slice really means copying the part of its backing array
// that in could potentially observe. In particular, it's possible to
// expand the view of the backing array up to the slice's capacity,
// so we need to copy the entire capacity even if the length is
// currently shorter to ensure that the result is truly equivalent.
length := v.Len()
capacity := v.Cap()
// This exposes any elements that are between length and capacity.
fullView := v.Slice3(0, capacity, capacity)
// Making a slice also allocates a new backing array for it.
ret := reflect.MakeSlice(ty, capacity, capacity)
for i := range capacity {
ret.Index(i).Set(fullView.Index(i))
}
// We must restore the original length before we return.
return ret.Slice(0, length)
}
func deepCopyStruct(v reflect.Value, ty reflect.Type) reflect.Value {
// To copy a struct we must copy each exported field one by one.
// We can't assign to unexported fields and so we just leave those
// unset in the new value.
ret := reflect.New(ty).Elem()
for i := range ty.NumField() {
fieldRet := ret.Field(i)
if !fieldRet.CanSet() {
// Presumably it's an unexported field, so we can't do anything
// with it and must leave it zeroed.
continue
}
newVal := deepCopyValue(v.Field(i), ty.Field(i).Type)
fieldRet.Set(newVal)
}
return ret
}