1// Copyright 2024 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package unique
6
7import (
8	"internal/abi"
9	"internal/stringslite"
10	"unsafe"
11)
12
13// clone makes a copy of value, and may update string values found in value
14// with a cloned version of those strings. The purpose of explicitly cloning
15// strings is to avoid accidentally giving a large string a long lifetime.
16//
17// Note that this will clone strings in structs and arrays found in value,
18// and will clone value if it itself is a string. It will not, however, clone
19// strings if value is of interface or slice type (that is, found via an
20// indirection).
21func clone[T comparable](value T, seq *cloneSeq) T {
22	for _, offset := range seq.stringOffsets {
23		ps := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&value)) + offset))
24		*ps = stringslite.Clone(*ps)
25	}
26	return value
27}
28
29// singleStringClone describes how to clone a single string.
30var singleStringClone = cloneSeq{stringOffsets: []uintptr{0}}
31
32// cloneSeq describes how to clone a value of a particular type.
33type cloneSeq struct {
34	stringOffsets []uintptr
35}
36
37// makeCloneSeq creates a cloneSeq for a type.
38func makeCloneSeq(typ *abi.Type) cloneSeq {
39	if typ == nil {
40		return cloneSeq{}
41	}
42	if typ.Kind() == abi.String {
43		return singleStringClone
44	}
45	var seq cloneSeq
46	switch typ.Kind() {
47	case abi.Struct:
48		buildStructCloneSeq(typ, &seq, 0)
49	case abi.Array:
50		buildArrayCloneSeq(typ, &seq, 0)
51	}
52	return seq
53}
54
55// buildStructCloneSeq populates a cloneSeq for an abi.Type that has Kind abi.Struct.
56func buildStructCloneSeq(typ *abi.Type, seq *cloneSeq, baseOffset uintptr) {
57	styp := typ.StructType()
58	for i := range styp.Fields {
59		f := &styp.Fields[i]
60		switch f.Typ.Kind() {
61		case abi.String:
62			seq.stringOffsets = append(seq.stringOffsets, baseOffset+f.Offset)
63		case abi.Struct:
64			buildStructCloneSeq(f.Typ, seq, baseOffset+f.Offset)
65		case abi.Array:
66			buildArrayCloneSeq(f.Typ, seq, baseOffset+f.Offset)
67		}
68	}
69}
70
71// buildArrayCloneSeq populates a cloneSeq for an abi.Type that has Kind abi.Array.
72func buildArrayCloneSeq(typ *abi.Type, seq *cloneSeq, baseOffset uintptr) {
73	atyp := typ.ArrayType()
74	etyp := atyp.Elem
75	offset := baseOffset
76	for range atyp.Len {
77		switch etyp.Kind() {
78		case abi.String:
79			seq.stringOffsets = append(seq.stringOffsets, offset)
80		case abi.Struct:
81			buildStructCloneSeq(etyp, seq, offset)
82		case abi.Array:
83			buildArrayCloneSeq(etyp, seq, offset)
84		}
85		offset += etyp.Size()
86		align := uintptr(etyp.FieldAlign())
87		offset = (offset + align - 1) &^ (align - 1)
88	}
89}
90