github.com/goshafaq/sonic@v0.0.0-20231026082336-871835fb94c6/internal/resolver/resolver.go (about) 1 /* 2 * Copyright 2021 ByteDance Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package resolver 18 19 import ( 20 "fmt" 21 "reflect" 22 "strings" 23 "sync" 24 ) 25 26 type FieldOpts int 27 type OffsetType int 28 29 const ( 30 F_omitempty FieldOpts = 1 << iota 31 F_stringize 32 ) 33 34 const ( 35 F_offset OffsetType = iota 36 F_deref 37 ) 38 39 type Offset struct { 40 Size uintptr 41 Kind OffsetType 42 Type reflect.Type 43 } 44 45 type FieldMeta struct { 46 Name string 47 Path []Offset 48 Opts FieldOpts 49 Type reflect.Type 50 } 51 52 func (self *FieldMeta) String() string { 53 var path []string 54 var opts []string 55 56 /* dump the field path */ 57 for _, off := range self.Path { 58 if off.Kind == F_offset { 59 path = append(path, fmt.Sprintf("%d", off.Size)) 60 } else { 61 path = append(path, fmt.Sprintf("%d.(*%s)", off.Size, off.Type)) 62 } 63 } 64 65 /* check for "string" */ 66 if (self.Opts & F_stringize) != 0 { 67 opts = append(opts, "string") 68 } 69 70 /* check for "omitempty" */ 71 if (self.Opts & F_omitempty) != 0 { 72 opts = append(opts, "omitempty") 73 } 74 75 /* format the field */ 76 return fmt.Sprintf( 77 "{Field \"%s\" @ %s, opts=%s, type=%s}", 78 self.Name, 79 strings.Join(path, "."), 80 strings.Join(opts, ","), 81 self.Type, 82 ) 83 } 84 85 func (self *FieldMeta) optimize() { 86 var n int 87 var v uintptr 88 89 /* merge adjacent offsets */ 90 for _, o := range self.Path { 91 if v += o.Size; o.Kind == F_deref { 92 self.Path[n].Size = v 93 self.Path[n].Type, v = o.Type, 0 94 self.Path[n].Kind, n = F_deref, n+1 95 } 96 } 97 98 /* last offset value */ 99 if v != 0 { 100 self.Path[n].Size = v 101 self.Path[n].Type = nil 102 self.Path[n].Kind = F_offset 103 n++ 104 } 105 106 /* must be at least 1 offset */ 107 if n != 0 { 108 self.Path = self.Path[:n] 109 } else { 110 self.Path = []Offset{{Kind: F_offset}} 111 } 112 } 113 114 func resolveFields(vt reflect.Type) []FieldMeta { 115 tfv := typeFields(vt) 116 ret := []FieldMeta(nil) 117 118 /* convert each field */ 119 for _, fv := range tfv.list { 120 item := vt 121 path := []Offset(nil) 122 opts := FieldOpts(0) 123 124 /* check for "string" */ 125 if fv.quoted { 126 opts |= F_stringize 127 } 128 129 /* check for "omitempty" */ 130 if fv.omitEmpty { 131 opts |= F_omitempty 132 } 133 134 /* dump the field path */ 135 for _, i := range fv.index { 136 kind := F_offset 137 fval := item.Field(i) 138 item = fval.Type 139 140 /* deref the pointer if needed */ 141 if item.Kind() == reflect.Ptr { 142 kind = F_deref 143 item = item.Elem() 144 } 145 146 /* add to path */ 147 path = append(path, Offset{ 148 Kind: kind, 149 Type: item, 150 Size: fval.Offset, 151 }) 152 } 153 154 /* get the index to the last offset */ 155 idx := len(path) - 1 156 fvt := path[idx].Type 157 158 /* do not dereference into fields */ 159 if path[idx].Kind == F_deref { 160 fvt = reflect.PtrTo(fvt) 161 path[idx].Kind = F_offset 162 } 163 164 /* add to result */ 165 ret = append(ret, FieldMeta{ 166 Type: fvt, 167 Opts: opts, 168 Path: path, 169 Name: fv.name, 170 }) 171 } 172 173 /* optimize the offsets */ 174 for i := range ret { 175 ret[i].optimize() 176 } 177 178 /* all done */ 179 return ret 180 } 181 182 var ( 183 fieldLock = sync.RWMutex{} 184 fieldCache = map[reflect.Type][]FieldMeta{} 185 ) 186 187 func ResolveStruct(vt reflect.Type) []FieldMeta { 188 var ok bool 189 var fm []FieldMeta 190 191 /* attempt to read from cache */ 192 fieldLock.RLock() 193 fm, ok = fieldCache[vt] 194 fieldLock.RUnlock() 195 196 /* check if it was cached */ 197 if ok { 198 return fm 199 } 200 201 /* otherwise use write-lock */ 202 fieldLock.Lock() 203 defer fieldLock.Unlock() 204 205 /* double check */ 206 if fm, ok = fieldCache[vt]; ok { 207 return fm 208 } 209 210 /* resolve the field */ 211 fm = resolveFields(vt) 212 fieldCache[vt] = fm 213 return fm 214 }