aboutsummaryrefslogtreecommitdiff
path: root/src/unique/clone.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/unique/clone.go')
-rw-r--r--src/unique/clone.go100
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))
+}