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  }