github.com/wI2L/jettison@v0.7.5-0.20230106001914-c70014c6417a/instruction.go (about) 1 package jettison 2 3 import ( 4 "fmt" 5 "reflect" 6 "sync" 7 "sync/atomic" 8 "unsafe" 9 ) 10 11 var ( 12 instrCachePtr unsafe.Pointer // *instrCache 13 structInstrCache sync.Map // map[string]instruction 14 ) 15 16 // An instruction appends the JSON representation 17 // of a value pointed by the unsafe.Pointer p to 18 // dst and returns the extended buffer. 19 type instruction func(unsafe.Pointer, []byte, encOpts) ([]byte, error) 20 21 // instrCache is an eventually consistent cache that 22 // maps Go type definitions to dynamically generated 23 // instructions. The key is unsafe.Pointer instead of 24 // reflect.Type to improve lookup performance. 25 type instrCache map[unsafe.Pointer]instruction 26 27 func typeID(t reflect.Type) unsafe.Pointer { 28 return unpackEface(t).word 29 } 30 31 // cachedInstr returns an instruction to encode the 32 // given type from a cache, or create one on the fly. 33 func cachedInstr(t reflect.Type) instruction { 34 id := typeID(t) 35 36 if instr, ok := loadInstr(id); ok { 37 return instr 38 } 39 canAddr := t.Kind() == reflect.Ptr 40 41 // canAddr indicates if the input value is addressable. 42 // At this point, we only need to know if the value is 43 // a pointer, the others instructions will handle that 44 // themselves for their type, or pass-by the value. 45 instr := newInstruction(t, canAddr, false) 46 if isInlined(t) { 47 instr = wrapInlineInstr(instr) 48 } 49 storeInstr(id, instr, loadCache()) 50 51 return instr 52 } 53 54 func loadCache() instrCache { 55 p := atomic.LoadPointer(&instrCachePtr) 56 return *(*instrCache)(unsafe.Pointer(&p)) 57 } 58 59 func loadInstr(id unsafe.Pointer) (instruction, bool) { 60 cache := loadCache() 61 instr, ok := cache[id] 62 return instr, ok 63 } 64 65 func storeInstr(key unsafe.Pointer, instr instruction, cache instrCache) { 66 newCache := make(instrCache, len(cache)+1) 67 68 // Clone the current cache and add the 69 // new instruction. 70 for k, v := range cache { 71 newCache[k] = v 72 } 73 newCache[key] = instr 74 75 atomic.StorePointer( 76 &instrCachePtr, 77 *(*unsafe.Pointer)(unsafe.Pointer(&newCache)), 78 ) 79 } 80 81 // newInstruction returns an instruction to encode t. 82 // canAddr and quoted respectively indicates if the 83 // value to encode is addressable and must be enclosed 84 // with double-quote character in the output. 85 func newInstruction(t reflect.Type, canAddr, quoted bool) instruction { 86 // Go types must be checked first, because a Duration 87 // is an int64, json.Number is a string, and both would 88 // be interpreted as a basic type. Also, the time.Time 89 // type implements the TextMarshaler interface, but we 90 // want to use a special instruction instead. 91 if ins := newGoTypeInstr(t); ins != nil { 92 return ins 93 } 94 if ins := newMarshalerTypeInstr(t, canAddr); ins != nil { 95 return ins 96 } 97 if ins := newBasicTypeInstr(t, quoted); ins != nil { 98 return ins 99 } 100 switch t.Kind() { 101 case reflect.Interface: 102 return encodeInterface 103 case reflect.Struct: 104 return newStructInstr(t, canAddr) 105 case reflect.Map: 106 return newMapInstr(t) 107 case reflect.Slice: 108 return newSliceInstr(t) 109 case reflect.Array: 110 return newArrayInstr(t, canAddr) 111 case reflect.Ptr: 112 return newPtrInstr(t, quoted) 113 } 114 return newUnsupportedTypeInstr(t) 115 } 116 117 func newGoTypeInstr(t reflect.Type) instruction { 118 switch t { 119 case syncMapType: 120 return encodeSyncMap 121 case timeTimeType: 122 return encodeTime 123 case timeDurationType: 124 return encodeDuration 125 case jsonNumberType: 126 return encodeNumber 127 case jsonRawMessageType: 128 return encodeRawMessage 129 default: 130 return nil 131 } 132 } 133 134 // newMarshalerTypeInstr returns an instruction to handle 135 // a type that implement one of the Marshaler, MarshalerCtx, 136 // json.Marshal, encoding.TextMarshaler interfaces. 137 func newMarshalerTypeInstr(t reflect.Type, canAddr bool) instruction { 138 isPtr := t.Kind() == reflect.Ptr 139 ptrTo := reflect.PtrTo(t) 140 141 switch { 142 case t.Implements(appendMarshalerCtxType): 143 return newAppendMarshalerCtxInstr(t, false) 144 case !isPtr && canAddr && ptrTo.Implements(appendMarshalerCtxType): 145 return newAppendMarshalerCtxInstr(t, true) 146 case t.Implements(appendMarshalerType): 147 return newAppendMarshalerInstr(t, false) 148 case !isPtr && canAddr && ptrTo.Implements(appendMarshalerType): 149 return newAppendMarshalerInstr(t, true) 150 case t.Implements(jsonMarshalerType): 151 return newJSONMarshalerInstr(t, false) 152 case !isPtr && canAddr && ptrTo.Implements(jsonMarshalerType): 153 return newJSONMarshalerInstr(t, true) 154 case t.Implements(textMarshalerType): 155 return newTextMarshalerInstr(t, false) 156 case !isPtr && canAddr && ptrTo.Implements(textMarshalerType): 157 return newTextMarshalerInstr(t, true) 158 default: 159 return nil 160 } 161 } 162 163 func newBasicTypeInstr(t reflect.Type, quoted bool) instruction { 164 var ins instruction 165 166 switch t.Kind() { 167 case reflect.Bool: 168 ins = encodeBool 169 case reflect.String: 170 return newStringInstr(quoted) 171 case reflect.Int: 172 ins = encodeInt 173 case reflect.Int8: 174 ins = encodeInt8 175 case reflect.Int16: 176 ins = encodeInt16 177 case reflect.Int32: 178 ins = encodeInt32 179 case reflect.Int64: 180 ins = encodeInt64 181 case reflect.Uint: 182 ins = encodeUint 183 case reflect.Uint8: 184 ins = encodeUint8 185 case reflect.Uint16: 186 ins = encodeUint16 187 case reflect.Uint32: 188 ins = encodeUint32 189 case reflect.Uint64: 190 ins = encodeUint64 191 case reflect.Uintptr: 192 ins = encodeUintptr 193 case reflect.Float32: 194 ins = encodeFloat32 195 case reflect.Float64: 196 ins = encodeFloat64 197 default: 198 return nil 199 } 200 if quoted { 201 return wrapQuotedInstr(ins) 202 } 203 return ins 204 } 205 206 func newStringInstr(quoted bool) instruction { 207 if quoted { 208 return encodeQuotedString 209 } 210 return encodeString 211 } 212 213 func newUnsupportedTypeInstr(t reflect.Type) instruction { 214 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 215 return dst, &UnsupportedTypeError{t} 216 } 217 } 218 219 func newPtrInstr(t reflect.Type, quoted bool) instruction { 220 e := t.Elem() 221 i := newInstruction(e, true, quoted) 222 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 223 return encodePointer(p, dst, opts, i) 224 } 225 } 226 227 func newAppendMarshalerCtxInstr(t reflect.Type, hasPtr bool) instruction { 228 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 229 return encodeMarshaler(p, dst, opts, t, hasPtr, encodeAppendMarshalerCtx) 230 } 231 } 232 233 func newAppendMarshalerInstr(t reflect.Type, hasPtr bool) instruction { 234 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 235 return encodeMarshaler(p, dst, opts, t, hasPtr, encodeAppendMarshaler) 236 } 237 } 238 239 func newJSONMarshalerInstr(t reflect.Type, hasPtr bool) instruction { 240 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 241 return encodeMarshaler(p, dst, opts, t, hasPtr, encodeJSONMarshaler) 242 } 243 } 244 245 func newTextMarshalerInstr(t reflect.Type, hasPtr bool) instruction { 246 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 247 return encodeMarshaler(p, dst, opts, t, hasPtr, encodeTextMarshaler) 248 } 249 } 250 251 func newStructInstr(t reflect.Type, canAddr bool) instruction { 252 id := fmt.Sprintf("%p-%t", typeID(t), canAddr) 253 254 if instr, ok := structInstrCache.Load(id); ok { 255 return instr.(instruction) 256 } 257 // To deal with recursive types, populate the 258 // instructions cache with an indirect func 259 // before we build it. This type waits on the 260 // real instruction (ins) to be ready and then 261 // calls it. This indirect function is only 262 // used for recursive types. 263 var ( 264 wg sync.WaitGroup 265 ins instruction 266 ) 267 wg.Add(1) 268 i, loaded := structInstrCache.LoadOrStore(id, 269 instruction(func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 270 wg.Wait() // few ns/op overhead 271 return ins(p, dst, opts) 272 }), 273 ) 274 if loaded { 275 return i.(instruction) 276 } 277 // Generate the real instruction and replace 278 // the indirect func with it. 279 ins = newStructFieldsInstr(t, canAddr) 280 wg.Done() 281 structInstrCache.Store(id, ins) 282 283 return ins 284 } 285 286 func newStructFieldsInstr(t reflect.Type, canAddr bool) instruction { 287 if t.NumField() == 0 { 288 // Fast path for empty struct. 289 return func(_ unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 290 return append(dst, "{}"...), nil 291 } 292 } 293 var ( 294 flds = cachedFields(t) 295 dupl = append(flds[:0:0], flds...) // clone 296 ) 297 for i := range dupl { 298 f := &dupl[i] 299 ftyp := typeByIndex(t, f.index) 300 etyp := ftyp 301 302 if etyp.Kind() == reflect.Ptr { 303 etyp = etyp.Elem() 304 } 305 if f.omitNil && (ftyp.Implements(jsonMarshalerType) || reflect.PtrTo(ftyp).Implements(jsonMarshalerType)) { 306 f.omitNullMarshaler = true 307 } 308 if !isNilable(ftyp) { 309 // Disable the omitnil option, to 310 // eliminate a check at runtime. 311 f.omitNil = false 312 } 313 // Generate instruction and empty func of the field. 314 // Only strings, floats, integers, and booleans 315 // types can be quoted. 316 f.instr = newInstruction(ftyp, canAddr, f.quoted && isBasicType(etyp)) 317 if f.omitEmpty { 318 f.empty = cachedEmptyFuncOf(ftyp) 319 } 320 } 321 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 322 return encodeStruct(p, dst, opts, dupl) 323 } 324 } 325 326 func newArrayInstr(t reflect.Type, canAddr bool) instruction { 327 var ( 328 etyp = t.Elem() 329 size = etyp.Size() 330 isba = false 331 ) 332 // Array elements are addressable if the 333 // array itself is addressable. 334 ins := newInstruction(etyp, canAddr, false) 335 336 // Byte arrays does not encode as a string 337 // by default, this behavior is defined by 338 // the encoder's options during marshaling. 339 if etyp.Kind() == reflect.Uint8 { 340 pe := reflect.PtrTo(etyp) 341 if !pe.Implements(jsonMarshalerType) && !pe.Implements(textMarshalerType) { 342 isba = true 343 } 344 } 345 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 346 return encodeArray(p, dst, opts, ins, size, t.Len(), isba) 347 } 348 } 349 350 func newSliceInstr(t reflect.Type) instruction { 351 etyp := t.Elem() 352 353 if etyp.Kind() == reflect.Uint8 { 354 pe := reflect.PtrTo(etyp) 355 if !pe.Implements(jsonMarshalerType) && !pe.Implements(textMarshalerType) { 356 return encodeByteSlice 357 } 358 } 359 // Slice elements are always addressable. 360 // see https://golang.org/pkg/reflect/#Value.CanAddr 361 // for reference. 362 var ( 363 ins = newInstruction(etyp, true, false) 364 size = etyp.Size() 365 ) 366 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 367 return encodeSlice(p, dst, opts, ins, size) 368 } 369 } 370 371 func newMapInstr(t reflect.Type) instruction { 372 var ( 373 ki instruction 374 vi instruction 375 ) 376 kt := t.Key() 377 et := t.Elem() 378 379 if !isString(kt) && !isInteger(kt) && !kt.Implements(textMarshalerType) { 380 return newUnsupportedTypeInstr(t) 381 } 382 // The standard library has a strict precedence order 383 // for map key types, defined by the documentation of 384 // the json.Marshal function. That's why we bypass the 385 // newTypeInstr function if key type is string. 386 if isString(kt) { 387 ki = encodeString 388 } else { 389 ki = newInstruction(kt, false, false) 390 } 391 // Wrap the key instruction for types that 392 // do not encode with quotes by default. 393 if !isString(kt) && !kt.Implements(textMarshalerType) { 394 ki = wrapQuotedInstr(ki) 395 } 396 // See issue golang.org/issue/33675 for reference. 397 if kt.Implements(textMarshalerType) && kt.Kind() == reflect.Ptr { 398 ki = wrapTextMarshalerNilCheck(ki) 399 } 400 vi = newInstruction(et, false, false) 401 402 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 403 return encodeMap(p, dst, opts, t, ki, vi) 404 } 405 } 406 407 func wrapInlineInstr(ins instruction) instruction { 408 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 409 return ins(noescape(unsafe.Pointer(&p)), dst, opts) 410 } 411 } 412 413 func wrapQuotedInstr(ins instruction) instruction { 414 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 415 dst = append(dst, '"') 416 var err error 417 dst, err = ins(p, dst, opts) 418 if err == nil { 419 dst = append(dst, '"') 420 } 421 return dst, err 422 } 423 } 424 425 func wrapTextMarshalerNilCheck(ins instruction) instruction { 426 return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) { 427 if *(*unsafe.Pointer)(p) == nil { 428 return append(dst, `""`...), nil 429 } 430 return ins(p, dst, opts) 431 } 432 }