mirror of https://github.com/go-gitea/gitea
Git with a cup of tea, painless self-hosted git service
Mirror for internal git.with.parts use
https://git.with.parts
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
294 lines
7.0 KiB
294 lines
7.0 KiB
package decoder
|
|
|
|
import (
|
|
"reflect"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"github.com/goccy/go-json/internal/errors"
|
|
"github.com/goccy/go-json/internal/runtime"
|
|
)
|
|
|
|
type sliceDecoder struct {
|
|
elemType *runtime.Type
|
|
isElemPointerType bool
|
|
valueDecoder Decoder
|
|
size uintptr
|
|
arrayPool sync.Pool
|
|
structName string
|
|
fieldName string
|
|
}
|
|
|
|
// If use reflect.SliceHeader, data type is uintptr.
|
|
// In this case, Go compiler cannot trace reference created by newArray().
|
|
// So, define using unsafe.Pointer as data type
|
|
type sliceHeader struct {
|
|
data unsafe.Pointer
|
|
len int
|
|
cap int
|
|
}
|
|
|
|
const (
|
|
defaultSliceCapacity = 2
|
|
)
|
|
|
|
func newSliceDecoder(dec Decoder, elemType *runtime.Type, size uintptr, structName, fieldName string) *sliceDecoder {
|
|
return &sliceDecoder{
|
|
valueDecoder: dec,
|
|
elemType: elemType,
|
|
isElemPointerType: elemType.Kind() == reflect.Ptr || elemType.Kind() == reflect.Map,
|
|
size: size,
|
|
arrayPool: sync.Pool{
|
|
New: func() interface{} {
|
|
return &sliceHeader{
|
|
data: newArray(elemType, defaultSliceCapacity),
|
|
len: 0,
|
|
cap: defaultSliceCapacity,
|
|
}
|
|
},
|
|
},
|
|
structName: structName,
|
|
fieldName: fieldName,
|
|
}
|
|
}
|
|
|
|
func (d *sliceDecoder) newSlice(src *sliceHeader) *sliceHeader {
|
|
slice := d.arrayPool.Get().(*sliceHeader)
|
|
if src.len > 0 {
|
|
// copy original elem
|
|
if slice.cap < src.cap {
|
|
data := newArray(d.elemType, src.cap)
|
|
slice = &sliceHeader{data: data, len: src.len, cap: src.cap}
|
|
} else {
|
|
slice.len = src.len
|
|
}
|
|
copySlice(d.elemType, *slice, *src)
|
|
} else {
|
|
slice.len = 0
|
|
}
|
|
return slice
|
|
}
|
|
|
|
func (d *sliceDecoder) releaseSlice(p *sliceHeader) {
|
|
d.arrayPool.Put(p)
|
|
}
|
|
|
|
//go:linkname copySlice reflect.typedslicecopy
|
|
func copySlice(elemType *runtime.Type, dst, src sliceHeader) int
|
|
|
|
//go:linkname newArray reflect.unsafe_NewArray
|
|
func newArray(*runtime.Type, int) unsafe.Pointer
|
|
|
|
//go:linkname typedmemmove reflect.typedmemmove
|
|
func typedmemmove(t *runtime.Type, dst, src unsafe.Pointer)
|
|
|
|
func (d *sliceDecoder) errNumber(offset int64) *errors.UnmarshalTypeError {
|
|
return &errors.UnmarshalTypeError{
|
|
Value: "number",
|
|
Type: reflect.SliceOf(runtime.RType2Type(d.elemType)),
|
|
Struct: d.structName,
|
|
Field: d.fieldName,
|
|
Offset: offset,
|
|
}
|
|
}
|
|
|
|
func (d *sliceDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
|
|
depth++
|
|
if depth > maxDecodeNestingDepth {
|
|
return errors.ErrExceededMaxDepth(s.char(), s.cursor)
|
|
}
|
|
|
|
for {
|
|
switch s.char() {
|
|
case ' ', '\n', '\t', '\r':
|
|
s.cursor++
|
|
continue
|
|
case 'n':
|
|
if err := nullBytes(s); err != nil {
|
|
return err
|
|
}
|
|
*(*unsafe.Pointer)(p) = nil
|
|
return nil
|
|
case '[':
|
|
s.cursor++
|
|
if s.skipWhiteSpace() == ']' {
|
|
dst := (*sliceHeader)(p)
|
|
if dst.data == nil {
|
|
dst.data = newArray(d.elemType, 0)
|
|
} else {
|
|
dst.len = 0
|
|
}
|
|
s.cursor++
|
|
return nil
|
|
}
|
|
idx := 0
|
|
slice := d.newSlice((*sliceHeader)(p))
|
|
srcLen := slice.len
|
|
capacity := slice.cap
|
|
data := slice.data
|
|
for {
|
|
if capacity <= idx {
|
|
src := sliceHeader{data: data, len: idx, cap: capacity}
|
|
capacity *= 2
|
|
data = newArray(d.elemType, capacity)
|
|
dst := sliceHeader{data: data, len: idx, cap: capacity}
|
|
copySlice(d.elemType, dst, src)
|
|
}
|
|
ep := unsafe.Pointer(uintptr(data) + uintptr(idx)*d.size)
|
|
|
|
// if srcLen is greater than idx, keep the original reference
|
|
if srcLen <= idx {
|
|
if d.isElemPointerType {
|
|
**(**unsafe.Pointer)(unsafe.Pointer(&ep)) = nil // initialize elem pointer
|
|
} else {
|
|
// assign new element to the slice
|
|
typedmemmove(d.elemType, ep, unsafe_New(d.elemType))
|
|
}
|
|
}
|
|
|
|
if err := d.valueDecoder.DecodeStream(s, depth, ep); err != nil {
|
|
return err
|
|
}
|
|
s.skipWhiteSpace()
|
|
RETRY:
|
|
switch s.char() {
|
|
case ']':
|
|
slice.cap = capacity
|
|
slice.len = idx + 1
|
|
slice.data = data
|
|
dst := (*sliceHeader)(p)
|
|
dst.len = idx + 1
|
|
if dst.len > dst.cap {
|
|
dst.data = newArray(d.elemType, dst.len)
|
|
dst.cap = dst.len
|
|
}
|
|
copySlice(d.elemType, *dst, *slice)
|
|
d.releaseSlice(slice)
|
|
s.cursor++
|
|
return nil
|
|
case ',':
|
|
idx++
|
|
case nul:
|
|
if s.read() {
|
|
goto RETRY
|
|
}
|
|
slice.cap = capacity
|
|
slice.data = data
|
|
d.releaseSlice(slice)
|
|
goto ERROR
|
|
default:
|
|
slice.cap = capacity
|
|
slice.data = data
|
|
d.releaseSlice(slice)
|
|
goto ERROR
|
|
}
|
|
s.cursor++
|
|
}
|
|
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
return d.errNumber(s.totalOffset())
|
|
case nul:
|
|
if s.read() {
|
|
continue
|
|
}
|
|
goto ERROR
|
|
default:
|
|
goto ERROR
|
|
}
|
|
}
|
|
ERROR:
|
|
return errors.ErrUnexpectedEndOfJSON("slice", s.totalOffset())
|
|
}
|
|
|
|
func (d *sliceDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
|
|
buf := ctx.Buf
|
|
depth++
|
|
if depth > maxDecodeNestingDepth {
|
|
return 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
|
|
}
|
|
|
|
for {
|
|
switch buf[cursor] {
|
|
case ' ', '\n', '\t', '\r':
|
|
cursor++
|
|
continue
|
|
case 'n':
|
|
if err := validateNull(buf, cursor); err != nil {
|
|
return 0, err
|
|
}
|
|
cursor += 4
|
|
*(*unsafe.Pointer)(p) = nil
|
|
return cursor, nil
|
|
case '[':
|
|
cursor++
|
|
cursor = skipWhiteSpace(buf, cursor)
|
|
if buf[cursor] == ']' {
|
|
dst := (*sliceHeader)(p)
|
|
if dst.data == nil {
|
|
dst.data = newArray(d.elemType, 0)
|
|
} else {
|
|
dst.len = 0
|
|
}
|
|
cursor++
|
|
return cursor, nil
|
|
}
|
|
idx := 0
|
|
slice := d.newSlice((*sliceHeader)(p))
|
|
srcLen := slice.len
|
|
capacity := slice.cap
|
|
data := slice.data
|
|
for {
|
|
if capacity <= idx {
|
|
src := sliceHeader{data: data, len: idx, cap: capacity}
|
|
capacity *= 2
|
|
data = newArray(d.elemType, capacity)
|
|
dst := sliceHeader{data: data, len: idx, cap: capacity}
|
|
copySlice(d.elemType, dst, src)
|
|
}
|
|
ep := unsafe.Pointer(uintptr(data) + uintptr(idx)*d.size)
|
|
// if srcLen is greater than idx, keep the original reference
|
|
if srcLen <= idx {
|
|
if d.isElemPointerType {
|
|
**(**unsafe.Pointer)(unsafe.Pointer(&ep)) = nil // initialize elem pointer
|
|
} else {
|
|
// assign new element to the slice
|
|
typedmemmove(d.elemType, ep, unsafe_New(d.elemType))
|
|
}
|
|
}
|
|
c, err := d.valueDecoder.Decode(ctx, cursor, depth, ep)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
cursor = c
|
|
cursor = skipWhiteSpace(buf, cursor)
|
|
switch buf[cursor] {
|
|
case ']':
|
|
slice.cap = capacity
|
|
slice.len = idx + 1
|
|
slice.data = data
|
|
dst := (*sliceHeader)(p)
|
|
dst.len = idx + 1
|
|
if dst.len > dst.cap {
|
|
dst.data = newArray(d.elemType, dst.len)
|
|
dst.cap = dst.len
|
|
}
|
|
copySlice(d.elemType, *dst, *slice)
|
|
d.releaseSlice(slice)
|
|
cursor++
|
|
return cursor, nil
|
|
case ',':
|
|
idx++
|
|
default:
|
|
slice.cap = capacity
|
|
slice.data = data
|
|
d.releaseSlice(slice)
|
|
return 0, errors.ErrInvalidCharacter(buf[cursor], "slice", cursor)
|
|
}
|
|
cursor++
|
|
}
|
|
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
return 0, d.errNumber(cursor)
|
|
default:
|
|
return 0, errors.ErrUnexpectedEndOfJSON("slice", cursor)
|
|
}
|
|
}
|
|
}
|
|
|