diff options
Diffstat (limited to 'src/unique/clone.go')
-rw-r--r-- | src/unique/clone.go | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/src/unique/clone.go b/src/unique/clone.go new file mode 100644 index 0000000000..b30d44e393 --- /dev/null +++ b/src/unique/clone.go @@ -0,0 +1,100 @@ +// 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" + "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 = cloneString(*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) + } +} + +// cloneString is a copy of strings.Clone, because we can't depend on the strings +// package here. Several packages that might make use of unique, like net, explicitly +// forbid depending on unicode, which strings depends on. +func cloneString(s string) string { + if len(s) == 0 { + return "" + } + b := make([]byte, len(s)) + copy(b, s) + return unsafe.String(&b[0], len(b)) +} |