summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-structr/runtime.go
blob: a4696187ac04de5d1492c5d525fb3fdfafe5e167 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package structr

import (
	"fmt"
	"reflect"
	"unicode"
	"unicode/utf8"
	"unsafe"

	"codeberg.org/gruf/go-mangler"
	"github.com/modern-go/reflect2"
)

// struct_field contains pre-prepared type
// information about a struct's field member,
// including memory offset and hash function.
type struct_field struct {

	// type2 contains the reflect2
	// type information for this field,
	// used in repacking it as eface.
	type2 reflect2.Type

	// offsets defines whereabouts in
	// memory this field is located.
	offsets []next_offset

	// struct field type mangling
	// (i.e. fast serializing) fn.
	mangle mangler.Mangler

	// zero value data, used when
	// nil encountered during ptr
	// offset following.
	zero unsafe.Pointer

	// mangled zero value string,
	// if set this indicates zero
	// values of field not allowed
	zerostr string
}

// next_offset defines a next offset location
// in a struct_field, first by the number of
// derefences required, then by offset from
// that final memory location.
type next_offset struct {
	derefs uint
	offset uintptr
}

// find_field will search for a struct field with given set of names,
// where names is a len > 0 slice of names account for struct nesting.
func find_field(t reflect.Type, names []string) (sfield struct_field) {
	var (
		// is_exported returns whether name is exported
		// from a package; can be func or struct field.
		is_exported = func(name string) bool {
			r, _ := utf8.DecodeRuneInString(name)
			return unicode.IsUpper(r)
		}

		// pop_name pops the next name from
		// the provided slice of field names.
		pop_name = func() string {
			name := names[0]
			names = names[1:]
			if !is_exported(name) {
				panicf("field is not exported: %s", name)
			}
			return name
		}

		// field is the iteratively searched
		// struct field value in below loop.
		field reflect.StructField
	)

	for len(names) > 0 {
		// Pop next name.
		name := pop_name()

		var off next_offset

		// Dereference any ptrs to struct.
		for t.Kind() == reflect.Pointer {
			t = t.Elem()
			off.derefs++
		}

		// Check for valid struct type.
		if t.Kind() != reflect.Struct {
			panicf("field %s is not struct (or ptr-to): %s", t, name)
		}

		var ok bool

		// Look for next field by name.
		field, ok = t.FieldByName(name)
		if !ok {
			panicf("unknown field: %s", name)
		}

		// Set next offset value.
		off.offset = field.Offset
		sfield.offsets = append(sfield.offsets, off)

		// Set the next type.
		t = field.Type
	}

	// Get field type as reflect2.
	sfield.type2 = reflect2.Type2(t)

	// Find mangler for field type.
	sfield.mangle = mangler.Get(t)

	// Set possible zero value and its string.
	sfield.zero = sfield.type2.UnsafeNew()
	i := sfield.type2.UnsafeIndirect(sfield.zero)
	sfield.zerostr = string(sfield.mangle(nil, i))

	return
}

// extract_fields extracts given structfields from the provided value type,
// this is done using predetermined struct field memory offset locations.
func extract_fields(ptr unsafe.Pointer, fields []struct_field) []any {
	// Prepare slice of field ifaces.
	ifaces := make([]any, len(fields))
	for i, field := range fields {

		// loop scope.
		fptr := ptr

		for _, offset := range field.offsets {
			// Dereference any ptrs to offset.
			fptr = deref(fptr, offset.derefs)

			if fptr == nil {
				// Use zero value.
				fptr = field.zero
				break
			}

			// Jump forward by offset to next ptr.
			fptr = unsafe.Pointer(uintptr(fptr) +
				offset.offset)
		}

		// Repack value data ptr as empty interface.
		ifaces[i] = field.type2.UnsafeIndirect(fptr)
	}

	return ifaces
}

// deref will dereference ptr 'n' times (or until nil).
func deref(p unsafe.Pointer, n uint) unsafe.Pointer {
	for ; n > 0; n-- {
		if p == nil {
			return nil
		}
		p = *(*unsafe.Pointer)(p)
	}
	return p
}

// panicf provides a panic with string formatting.
func panicf(format string, args ...any) {
	panic(fmt.Sprintf(format, args...))
}