golang/src/unique/clone.go

90 lines
2.6 KiB
Go

// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unique
import (
"internal/abi"
"internal/stringslite"
"unsafe"
)
// clone makes a copy of value, and may update string values found in value
// with a cloned version of those strings. The purpose of explicitly cloning
// strings is to avoid accidentally giving a large string a long lifetime.
//
// Note that this will clone strings in structs and arrays found in value,
// and will clone value if it itself is a string. It will not, however, clone
// strings if value is of interface or slice type (that is, found via an
// indirection).
func clone[T comparable](value T, seq *cloneSeq) T {
for _, offset := range seq.stringOffsets {
ps := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&value)) + offset))
*ps = stringslite.Clone(*ps)
}
return value
}
// singleStringClone describes how to clone a single string.
var singleStringClone = cloneSeq{stringOffsets: []uintptr{0}}
// cloneSeq describes how to clone a value of a particular type.
type cloneSeq struct {
stringOffsets []uintptr
}
// makeCloneSeq creates a cloneSeq for a type.
func makeCloneSeq(typ *abi.Type) cloneSeq {
if typ == nil {
return cloneSeq{}
}
if typ.Kind() == abi.String {
return singleStringClone
}
var seq cloneSeq
switch typ.Kind() {
case abi.Struct:
buildStructCloneSeq(typ, &seq, 0)
case abi.Array:
buildArrayCloneSeq(typ, &seq, 0)
}
return seq
}
// buildStructCloneSeq populates a cloneSeq for an abi.Type that has Kind abi.Struct.
func buildStructCloneSeq(typ *abi.Type, seq *cloneSeq, baseOffset uintptr) {
styp := typ.StructType()
for i := range styp.Fields {
f := &styp.Fields[i]
switch f.Typ.Kind() {
case abi.String:
seq.stringOffsets = append(seq.stringOffsets, baseOffset+f.Offset)
case abi.Struct:
buildStructCloneSeq(f.Typ, seq, baseOffset+f.Offset)
case abi.Array:
buildArrayCloneSeq(f.Typ, seq, baseOffset+f.Offset)
}
}
}
// buildArrayCloneSeq populates a cloneSeq for an abi.Type that has Kind abi.Array.
func buildArrayCloneSeq(typ *abi.Type, seq *cloneSeq, baseOffset uintptr) {
atyp := typ.ArrayType()
etyp := atyp.Elem
offset := baseOffset
for range atyp.Len {
switch etyp.Kind() {
case abi.String:
seq.stringOffsets = append(seq.stringOffsets, offset)
case abi.Struct:
buildStructCloneSeq(etyp, seq, offset)
case abi.Array:
buildArrayCloneSeq(etyp, seq, offset)
}
offset += etyp.Size()
align := uintptr(etyp.FieldAlign())
offset = (offset + align - 1) &^ (align - 1)
}
}