-
-
Notifications
You must be signed in to change notification settings - Fork 45
/
parse.go
230 lines (216 loc) · 5.24 KB
/
parse.go
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package struc
import (
"encoding/binary"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
)
// struc:"int32,big,sizeof=Data,skip,sizefrom=Len"
type strucTag struct {
Type string
Order binary.ByteOrder
Sizeof string
Skip bool
Sizefrom string
}
func parseStrucTag(tag reflect.StructTag) *strucTag {
t := &strucTag{
Order: binary.BigEndian,
}
tagStr := tag.Get("struc")
if tagStr == "" {
// someone's going to typo this (I already did once)
// sorry if you made a module actually using this tag
// and you're mad at me now
tagStr = tag.Get("struct")
}
for _, s := range strings.Split(tagStr, ",") {
if strings.HasPrefix(s, "sizeof=") {
tmp := strings.SplitN(s, "=", 2)
t.Sizeof = tmp[1]
} else if strings.HasPrefix(s, "sizefrom=") {
tmp := strings.SplitN(s, "=", 2)
t.Sizefrom = tmp[1]
} else if s == "big" {
t.Order = binary.BigEndian
} else if s == "little" {
t.Order = binary.LittleEndian
} else if s == "skip" {
t.Skip = true
} else {
t.Type = s
}
}
return t
}
var typeLenRe = regexp.MustCompile(`^\[(\d*)\]`)
func parseField(f reflect.StructField) (fd *Field, tag *strucTag, err error) {
tag = parseStrucTag(f.Tag)
var ok bool
fd = &Field{
Name: f.Name,
Len: 1,
Order: tag.Order,
Slice: false,
kind: f.Type.Kind(),
}
switch fd.kind {
case reflect.Array:
fd.Slice = true
fd.Array = true
fd.Len = f.Type.Len()
fd.kind = f.Type.Elem().Kind()
case reflect.Slice:
fd.Slice = true
fd.Len = -1
fd.kind = f.Type.Elem().Kind()
case reflect.Ptr:
fd.Ptr = true
fd.kind = f.Type.Elem().Kind()
}
// check for custom types
tmp := reflect.New(f.Type)
if _, ok := tmp.Interface().(Custom); ok {
fd.Type = CustomType
return
}
var defTypeOk bool
fd.defType, defTypeOk = reflectTypeMap[fd.kind]
// find a type in the struct tag
pureType := typeLenRe.ReplaceAllLiteralString(tag.Type, "")
if fd.Type, ok = typeLookup[pureType]; ok {
fd.Len = 1
match := typeLenRe.FindAllStringSubmatch(tag.Type, -1)
if len(match) > 0 && len(match[0]) > 1 {
fd.Slice = true
first := match[0][1]
// Field.Len = -1 indicates a []slice
if first == "" {
fd.Len = -1
} else {
fd.Len, err = strconv.Atoi(first)
}
}
return
}
// the user didn't specify a type
switch f.Type {
case reflect.TypeOf(Size_t(0)):
fd.Type = SizeType
case reflect.TypeOf(Off_t(0)):
fd.Type = OffType
default:
if defTypeOk {
fd.Type = fd.defType
} else {
err = errors.New(fmt.Sprintf("struc: Could not resolve field '%v' type '%v'.", f.Name, f.Type))
}
}
return
}
func parseFieldsLocked(v reflect.Value) (Fields, error) {
// we need to repeat this logic because parseFields() below can't be recursively called due to locking
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
t := v.Type()
if v.NumField() < 1 {
return nil, errors.New("struc: Struct has no fields.")
}
sizeofMap := make(map[string][]int)
fields := make(Fields, v.NumField())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
f, tag, err := parseField(field)
if tag.Skip {
continue
}
if err != nil {
return nil, err
}
if !v.Field(i).CanSet() {
continue
}
f.Index = i
if tag.Sizeof != "" {
target, ok := t.FieldByName(tag.Sizeof)
if !ok {
return nil, fmt.Errorf("struc: `sizeof=%s` field does not exist", tag.Sizeof)
}
f.Sizeof = target.Index
sizeofMap[tag.Sizeof] = field.Index
}
if sizefrom, ok := sizeofMap[field.Name]; ok {
f.Sizefrom = sizefrom
}
if tag.Sizefrom != "" {
source, ok := t.FieldByName(tag.Sizefrom)
if !ok {
return nil, fmt.Errorf("struc: `sizefrom=%s` field does not exist", tag.Sizefrom)
}
f.Sizefrom = source.Index
}
if f.Len == -1 && f.Sizefrom == nil {
return nil, fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name)
}
// recurse into nested structs
// TODO: handle loops (probably by indirecting the []Field and putting pointer in cache)
if f.Type == Struct {
typ := field.Type
if f.Ptr {
typ = typ.Elem()
}
if f.Slice {
typ = typ.Elem()
}
f.Fields, err = parseFieldsLocked(reflect.New(typ))
if err != nil {
return nil, err
}
}
fields[i] = f
}
return fields, nil
}
var fieldCache = make(map[reflect.Type]Fields)
var fieldCacheLock sync.RWMutex
var parseLock sync.Mutex
func fieldCacheLookup(t reflect.Type) Fields {
fieldCacheLock.RLock()
defer fieldCacheLock.RUnlock()
if cached, ok := fieldCache[t]; ok {
return cached
}
return nil
}
func parseFields(v reflect.Value) (Fields, error) {
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
t := v.Type()
// fast path: hopefully the field parsing is already cached
if cached := fieldCacheLookup(t); cached != nil {
return cached, nil
}
// hold a global lock so multiple goroutines can't parse (the same) fields at once
parseLock.Lock()
defer parseLock.Unlock()
// check cache a second time, in case parseLock was just released by
// another thread who filled the cache for us
if cached := fieldCacheLookup(t); cached != nil {
return cached, nil
}
// no luck, time to parse and fill the cache ourselves
fields, err := parseFieldsLocked(v)
if err != nil {
return nil, err
}
fieldCacheLock.Lock()
fieldCache[t] = fields
fieldCacheLock.Unlock()
return fields, nil
}