github.com/trim21/go-phpserialize@v0.0.22-0.20240301204449-2fca0319b3f0/internal/decoder/slice.go (about)

     1  package decoder
     2  
     3  import (
     4  	"reflect"
     5  	"sync"
     6  	"unsafe"
     7  
     8  	"github.com/trim21/go-phpserialize/internal/errors"
     9  	"github.com/trim21/go-phpserialize/internal/runtime"
    10  )
    11  
    12  var (
    13  	sliceType = runtime.Type2RType(
    14  		reflect.TypeOf((*sliceHeader)(nil)).Elem(),
    15  	)
    16  	nilSlice = unsafe.Pointer(&sliceHeader{})
    17  )
    18  
    19  type sliceDecoder struct {
    20  	elemType          *runtime.Type
    21  	isElemPointerType bool
    22  	valueDecoder      Decoder
    23  	size              uintptr
    24  	arrayPool         sync.Pool
    25  	structName        string
    26  	fieldName         string
    27  }
    28  
    29  // If use reflect.SliceHeader, data type is uintptr.
    30  // In this case, Go compiler cannot trace reference created by newArray().
    31  // So, define using unsafe.Pointer as data type
    32  type sliceHeader struct {
    33  	data unsafe.Pointer
    34  	len  int
    35  	cap  int
    36  }
    37  
    38  const (
    39  	defaultSliceCapacity = 2
    40  )
    41  
    42  func newSliceDecoder(dec Decoder, elemType *runtime.Type, size uintptr, structName, fieldName string) *sliceDecoder {
    43  	return &sliceDecoder{
    44  		valueDecoder:      dec,
    45  		elemType:          elemType,
    46  		isElemPointerType: elemType.Kind() == reflect.Ptr || elemType.Kind() == reflect.Map,
    47  		size:              size,
    48  		arrayPool: sync.Pool{
    49  			New: func() any {
    50  				return &sliceHeader{
    51  					data: newArray(elemType, defaultSliceCapacity),
    52  					len:  0,
    53  					cap:  defaultSliceCapacity,
    54  				}
    55  			},
    56  		},
    57  		structName: structName,
    58  		fieldName:  fieldName,
    59  	}
    60  }
    61  
    62  func (d *sliceDecoder) releaseSlice(p *sliceHeader) {
    63  	d.arrayPool.Put(p)
    64  }
    65  
    66  //go:linkname copySlice reflect.typedslicecopy
    67  func copySlice(elemType *runtime.Type, dst, src sliceHeader) int
    68  
    69  //go:linkname newArray reflect.unsafe_NewArray
    70  func newArray(*runtime.Type, int) unsafe.Pointer
    71  
    72  //go:linkname typedmemmove reflect.typedmemmove
    73  func typedmemmove(t *runtime.Type, dst, src unsafe.Pointer)
    74  
    75  func (d *sliceDecoder) errNumber(offset int64) *errors.UnmarshalTypeError {
    76  	return &errors.UnmarshalTypeError{
    77  		Value:  "number",
    78  		Type:   reflect.SliceOf(runtime.RType2Type(d.elemType)),
    79  		Struct: d.structName,
    80  		Field:  d.fieldName,
    81  		Offset: offset,
    82  	}
    83  }
    84  
    85  func (d *sliceDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
    86  	buf := ctx.Buf
    87  	depth++
    88  	if depth > maxDecodeNestingDepth {
    89  		return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
    90  	}
    91  
    92  	switch buf[cursor] {
    93  	case 'N':
    94  		if err := validateNull(buf, cursor); err != nil {
    95  			return 0, err
    96  		}
    97  		cursor += 2
    98  		typedmemmove(sliceType, p, nilSlice)
    99  		return cursor, nil
   100  	case 'a':
   101  		cursor++
   102  		if buf[cursor] != ':' {
   103  			return cursor, errors.ErrExpected("':' before array length", cursor)
   104  		}
   105  
   106  		cursor++
   107  		if buf[cursor] == '0' {
   108  			err := validateEmptyArray(buf, cursor)
   109  			if err != nil {
   110  				return cursor, err
   111  			}
   112  
   113  			dst := (*sliceHeader)(p)
   114  			if dst.data == nil {
   115  				dst.data = newArray(d.elemType, 0)
   116  			} else {
   117  				dst.len = 0
   118  			}
   119  
   120  			return cursor + 4, nil
   121  		}
   122  
   123  		arrLen, end, err := readLengthInt(buf, cursor-1)
   124  		if err != nil {
   125  			return cursor, err
   126  		}
   127  		cursor = end
   128  
   129  		if buf[cursor] != '{' {
   130  			return cursor, errors.ErrInvalidBeginningOfArray(buf[cursor], cursor)
   131  		}
   132  		cursor++
   133  
   134  		// pre-alloc
   135  		slice := &sliceHeader{
   136  			data: newArray(d.elemType, arrLen),
   137  			len:  0,
   138  			cap:  arrLen,
   139  		}
   140  		srcLen := slice.len
   141  		capacity := slice.cap
   142  		data := slice.data
   143  
   144  		idx := 0
   145  		for {
   146  			currentIndex, end, err := readInt(buf, cursor)
   147  			if err != nil {
   148  				return 0, err
   149  			}
   150  
   151  			idx = currentIndex
   152  			cursor = end
   153  
   154  			if capacity <= idx {
   155  				src := sliceHeader{data: data, len: idx, cap: capacity}
   156  				capacity *= 2
   157  				data = newArray(d.elemType, capacity)
   158  				dst := sliceHeader{data: data, len: idx, cap: capacity}
   159  				copySlice(d.elemType, dst, src)
   160  			}
   161  			ep := unsafe.Pointer(uintptr(data) + uintptr(idx)*d.size)
   162  			// if srcLen is greater than idx, keep the original reference
   163  			if srcLen <= idx {
   164  				if d.isElemPointerType {
   165  					**(**unsafe.Pointer)(unsafe.Pointer(&ep)) = nil // initialize elem pointer
   166  				} else {
   167  					// assign new element to the slice
   168  					typedmemmove(d.elemType, ep, unsafe_New(d.elemType))
   169  				}
   170  			}
   171  
   172  			c, err := d.valueDecoder.Decode(ctx, cursor, depth, ep)
   173  			if err != nil {
   174  				return 0, err
   175  			}
   176  
   177  			cursor = c
   178  			if buf[cursor] == '}' {
   179  				slice.cap = capacity
   180  				slice.len = idx + 1
   181  				slice.data = data
   182  				dst := (*sliceHeader)(p)
   183  				dst.len = idx + 1
   184  				if dst.len > dst.cap {
   185  					dst.data = newArray(d.elemType, dst.len)
   186  					dst.cap = dst.len
   187  				}
   188  				copySlice(d.elemType, *dst, *slice)
   189  				d.releaseSlice(slice)
   190  				cursor++
   191  				return cursor, nil
   192  			}
   193  		}
   194  	case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   195  		return 0, d.errNumber(cursor)
   196  	default:
   197  		return 0, errors.ErrUnexpectedEnd("slice", cursor)
   198  	}
   199  }