Merge pull request #152 from goccy/feature/reduce-memory-usage-at-compile

Reduce memory usage at compile time
This commit is contained in:
Masaaki Goshima 2021-03-18 21:19:05 +09:00 committed by GitHub
commit 141e3992af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 23722 additions and 21605 deletions

View File

@ -42,7 +42,7 @@ jobs:
- name: checkout
uses: actions/checkout@v2
- name: measure coverage
run: go test -v -coverprofile=coverage.out ./ -count=1
run: make cover
- name: report coverage
run: |
bash <(curl -s https://codecov.io/bash)

View File

@ -62,6 +62,10 @@ issues:
- path: rtype.go
linters:
- golint
- stylecheck
- path: error.go
linters:
- staticcheck
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-issues-per-linter: 0

View File

@ -1,13 +1,20 @@
PKG := github.com/goccy/go-json
BIN_DIR := $(CURDIR)/bin
PKGS := $(shell go list ./... | grep -v internal/cmd)
COVER_PKGS := $(foreach pkg,$(PKGS),$(subst $(PKG),.,$(pkg)))
COMMA := ,
EMPTY :=
SPACE := $(EMPTY) $(EMPTY)
COVERPKG_OPT := $(subst $(SPACE),$(COMMA),$(COVER_PKGS))
$(BIN_DIR):
@mkdir -p $(BIN_DIR)
.PHONY: cover
cover:
@ go test -coverprofile=cover.tmp.out . ; \
cat cover.tmp.out | grep -v "encode_optype.go" > cover.out; \
rm cover.tmp.out
go test -coverpkg=$(COVERPKG_OPT) -coverprofile=cover.out .
.PHONY: cover-html
cover-html: cover
@ -26,3 +33,7 @@ golangci-lint: | $(BIN_DIR)
GOBIN=$(BIN_DIR) go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.36.0; \
rm -rf $$GOLANGCI_LINT_TMP_DIR; \
}
.PHONY: generate
generate:
go generate ./internal/...

View File

@ -12,8 +12,6 @@ const (
)
var (
cachedOpcodeSets []*opcodeSet
cachedOpcodeMap unsafe.Pointer // map[uintptr]*opcodeSet
cachedDecoder []decoder
cachedDecoderMap unsafe.Pointer // map[uintptr]decoder
baseTypeAddr uintptr
@ -66,7 +64,6 @@ func setupCodec() error {
if addrRange > maxAcceptableTypeAddrRange {
return fmt.Errorf("too big address range %d", addrRange)
}
cachedOpcodeSets = make([]*opcodeSet, addrRange)
cachedDecoder = make([]decoder, addrRange)
baseTypeAddr = min
maxTypeAddr = max
@ -77,22 +74,6 @@ func init() {
_ = setupCodec()
}
func loadOpcodeMap() map[uintptr]*opcodeSet {
p := atomic.LoadPointer(&cachedOpcodeMap)
return *(*map[uintptr]*opcodeSet)(unsafe.Pointer(&p))
}
func storeOpcodeSet(typ uintptr, set *opcodeSet, m map[uintptr]*opcodeSet) {
newOpcodeMap := make(map[uintptr]*opcodeSet, len(m)+1)
newOpcodeMap[typ] = set
for k, v := range m {
newOpcodeMap[k] = v
}
atomic.StorePointer(&cachedOpcodeMap, *(*unsafe.Pointer)(unsafe.Pointer(&newOpcodeMap)))
}
func loadDecoderMap() map[uintptr]decoder {
p := atomic.LoadPointer(&cachedDecoderMap)
return *(*map[uintptr]decoder)(unsafe.Pointer(&p))

View File

@ -37,7 +37,7 @@ func unmarshal(data []byte, v interface{}) error {
src := make([]byte, len(data)+1) // append nul byte to the end
copy(src, data)
header := (*interfaceHeader)(unsafe.Pointer(&v))
header := (*emptyInterface)(unsafe.Pointer(&v))
if err := validateType(header.typ, uintptr(header.ptr)); err != nil {
return err
@ -56,7 +56,7 @@ func unmarshalNoEscape(data []byte, v interface{}) error {
src := make([]byte, len(data)+1) // append nul byte to the end
copy(src, data)
header := (*interfaceHeader)(unsafe.Pointer(&v))
header := (*emptyInterface)(unsafe.Pointer(&v))
if err := validateType(header.typ, uintptr(header.ptr)); err != nil {
return err
@ -129,7 +129,7 @@ func (d *Decoder) prepareForDecode() error {
// See the documentation for Unmarshal for details about
// the conversion of JSON into a Go value.
func (d *Decoder) Decode(v interface{}) error {
header := (*interfaceHeader)(unsafe.Pointer(&v))
header := (*emptyInterface)(unsafe.Pointer(&v))
typ := header.typ
ptr := uintptr(header.ptr)
typeptr := uintptr(unsafe.Pointer(typ))

View File

@ -1,11 +1,18 @@
package json
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"unicode"
"unsafe"
"github.com/goccy/go-json/internal/runtime"
)
var (
jsonNumberType = reflect.TypeOf(json.Number(""))
)
func decodeCompileToGetDecoderSlowPath(typeptr uintptr, typ *rtype) (decoder, error) {
@ -322,16 +329,16 @@ func decodeCompileStruct(typ *rtype, structName, fieldName string, structTypeToD
structName = typ.Name()
for i := 0; i < fieldNum; i++ {
field := typ.Field(i)
if isIgnoredStructField(field) {
if runtime.IsIgnoredStructField(field) {
continue
}
isUnexportedField := unicode.IsLower([]rune(field.Name)[0])
tag := structTagFromField(field)
tag := runtime.StructTagFromField(field)
dec, err := decodeCompile(type2rtype(field.Type), structName, field.Name, structTypeToDecoder)
if err != nil {
return nil, err
}
if field.Anonymous && !tag.isTaggedKey {
if field.Anonymous && !tag.IsTaggedKey {
if stDec, ok := dec.(*structDecoder); ok {
if type2rtype(field.Type) == typ {
// recursive definition
@ -409,19 +416,19 @@ func decodeCompileStruct(typ *rtype, structName, fieldName string, structTypeToD
}
}
} else {
if tag.isString {
if tag.IsString {
dec = newWrappedStringDecoder(type2rtype(field.Type), dec, structName, field.Name)
}
var key string
if tag.key != "" {
key = tag.key
if tag.Key != "" {
key = tag.Key
} else {
key = field.Name
}
fieldSet := &structFieldSet{
dec: dec,
offset: field.Offset,
isTaggedKey: tag.isTaggedKey,
isTaggedKey: tag.IsTaggedKey,
key: key,
keyLen: int64(len(key)),
}

View File

@ -140,7 +140,7 @@ func (d *floatDecoder) decodeStream(s *stream, depth int64, p unsafe.Pointer) er
str := *(*string)(unsafe.Pointer(&bytes))
f64, err := strconv.ParseFloat(str, 64)
if err != nil {
return &SyntaxError{msg: err.Error(), Offset: s.totalOffset()}
return errSyntax(err.Error(), s.totalOffset())
}
d.op(p, f64)
return nil
@ -161,7 +161,7 @@ func (d *floatDecoder) decode(buf []byte, cursor, depth int64, p unsafe.Pointer)
s := *(*string)(unsafe.Pointer(&bytes))
f64, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, &SyntaxError{msg: err.Error(), Offset: cursor}
return 0, errSyntax(err.Error(), cursor)
}
d.op(p, f64)
return cursor, nil

View File

@ -202,7 +202,7 @@ func (d *interfaceDecoder) decodeStreamEmptyInterface(s *stream, depth int64, p
}
func (d *interfaceDecoder) decodeStream(s *stream, depth int64, p unsafe.Pointer) error {
runtimeInterfaceValue := *(*interface{})(unsafe.Pointer(&interfaceHeader{
runtimeInterfaceValue := *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: d.typ,
ptr: p,
}))
@ -217,7 +217,7 @@ func (d *interfaceDecoder) decodeStream(s *stream, depth int64, p unsafe.Pointer
return d.errUnmarshalType(rv.Type(), s.totalOffset())
}
iface := rv.Interface()
ifaceHeader := (*interfaceHeader)(unsafe.Pointer(&iface))
ifaceHeader := (*emptyInterface)(unsafe.Pointer(&iface))
typ := ifaceHeader.typ
if ifaceHeader.ptr == nil || d.typ == typ || typ == nil {
// concrete type is empty interface
@ -252,7 +252,7 @@ func (d *interfaceDecoder) errUnmarshalType(typ reflect.Type, offset int64) *Unm
}
func (d *interfaceDecoder) decode(buf []byte, cursor, depth int64, p unsafe.Pointer) (int64, error) {
runtimeInterfaceValue := *(*interface{})(unsafe.Pointer(&interfaceHeader{
runtimeInterfaceValue := *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: d.typ,
ptr: p,
}))
@ -268,7 +268,7 @@ func (d *interfaceDecoder) decode(buf []byte, cursor, depth int64, p unsafe.Poin
}
iface := rv.Interface()
ifaceHeader := (*interfaceHeader)(unsafe.Pointer(&iface))
ifaceHeader := (*emptyInterface)(unsafe.Pointer(&iface))
typ := ifaceHeader.typ
if ifaceHeader.ptr == nil || d.typ == typ || typ == nil {
// concrete type is empty interface

View File

@ -27,7 +27,7 @@ func (d *numberDecoder) decodeStream(s *stream, depth int64, p unsafe.Pointer) e
return err
}
if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&bytes)), 64); err != nil {
return &SyntaxError{msg: err.Error(), Offset: s.totalOffset()}
return errSyntax(err.Error(), s.totalOffset())
}
d.op(p, Number(string(bytes)))
s.reset()
@ -40,7 +40,7 @@ func (d *numberDecoder) decode(buf []byte, cursor, depth int64, p unsafe.Pointer
return 0, err
}
if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&bytes)), 64); err != nil {
return 0, &SyntaxError{msg: err.Error(), Offset: c}
return 0, errSyntax(err.Error(), c)
}
cursor = c
s := *(*string)(unsafe.Pointer(&bytes))

View File

@ -1786,80 +1786,82 @@ func equalError(a, b error) bool {
func TestUnmarshal(t *testing.T) {
for i, tt := range unmarshalTests {
in := []byte(tt.in)
if tt.ptr == nil {
continue
}
typ := reflect.TypeOf(tt.ptr)
if typ.Kind() != reflect.Ptr {
t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr)
continue
}
typ = typ.Elem()
// v = new(right-type)
v := reflect.New(typ)
if !reflect.DeepEqual(tt.ptr, v.Interface()) {
// There's no reason for ptr to point to non-zero data,
// as we decode into new(right-type), so the data is
// discarded.
// This can easily mean tests that silently don't test
// what they should. To test decoding into existing
// data, see TestPrefilled.
t.Errorf("#%d: unmarshalTest.ptr %#v is not a pointer to a zero value", i, tt.ptr)
continue
}
dec := json.NewDecoder(bytes.NewReader(in))
if tt.useNumber {
dec.UseNumber()
}
if tt.disallowUnknownFields {
dec.DisallowUnknownFields()
}
if err := dec.Decode(v.Interface()); !equalError(err, tt.err) {
t.Errorf("#%d: %v, want %v", i, err, tt.err)
continue
} else if err != nil {
continue
}
if !reflect.DeepEqual(v.Elem().Interface(), tt.out) {
t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), tt.out)
data, _ := json.Marshal(v.Elem().Interface())
println(string(data))
data, _ = json.Marshal(tt.out)
println(string(data))
continue
}
// Check round trip also decodes correctly.
if tt.err == nil {
enc, err := json.Marshal(v.Interface())
if err != nil {
t.Errorf("#%d: error re-marshaling: %v", i, err)
continue
t.Run(fmt.Sprintf("%d_%q", i, tt.in), func(t *testing.T) {
in := []byte(tt.in)
if tt.ptr == nil {
return
}
if tt.golden && !bytes.Equal(enc, in) {
t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, in)
typ := reflect.TypeOf(tt.ptr)
if typ.Kind() != reflect.Ptr {
t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr)
return
}
vv := reflect.New(reflect.TypeOf(tt.ptr).Elem())
dec = json.NewDecoder(bytes.NewReader(enc))
typ = typ.Elem()
// v = new(right-type)
v := reflect.New(typ)
if !reflect.DeepEqual(tt.ptr, v.Interface()) {
// There's no reason for ptr to point to non-zero data,
// as we decode into new(right-type), so the data is
// discarded.
// This can easily mean tests that silently don't test
// what they should. To test decoding into existing
// data, see TestPrefilled.
t.Errorf("#%d: unmarshalTest.ptr %#v is not a pointer to a zero value", i, tt.ptr)
return
}
dec := json.NewDecoder(bytes.NewReader(in))
if tt.useNumber {
dec.UseNumber()
}
if err := dec.Decode(vv.Interface()); err != nil {
t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err)
continue
if tt.disallowUnknownFields {
dec.DisallowUnknownFields()
}
if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) {
t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface())
t.Errorf(" In: %q", strings.Map(noSpace, string(in)))
t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc)))
continue
if err := dec.Decode(v.Interface()); !equalError(err, tt.err) {
t.Errorf("#%d: %v, want %v", i, err, tt.err)
return
} else if err != nil {
return
}
}
if !reflect.DeepEqual(v.Elem().Interface(), tt.out) {
t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), tt.out)
data, _ := json.Marshal(v.Elem().Interface())
println(string(data))
data, _ = json.Marshal(tt.out)
println(string(data))
return
}
// Check round trip also decodes correctly.
if tt.err == nil {
enc, err := json.Marshal(v.Interface())
if err != nil {
t.Errorf("#%d: error re-marshaling: %v", i, err)
return
}
if tt.golden && !bytes.Equal(enc, in) {
t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, in)
}
vv := reflect.New(reflect.TypeOf(tt.ptr).Elem())
dec = json.NewDecoder(bytes.NewReader(enc))
if tt.useNumber {
dec.UseNumber()
}
if err := dec.Decode(vv.Interface()); err != nil {
t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err)
return
}
if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) {
t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface())
t.Errorf(" In: %q", strings.Map(noSpace, string(in)))
t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc)))
return
}
}
})
}
}

View File

@ -38,7 +38,7 @@ func (d *unmarshalJSONDecoder) decodeStream(s *stream, depth int64, p unsafe.Poi
dst := make([]byte, len(src))
copy(dst, src)
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{
v := *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: d.typ,
ptr: p,
}))
@ -60,7 +60,7 @@ func (d *unmarshalJSONDecoder) decode(buf []byte, cursor, depth int64, p unsafe.
dst := make([]byte, len(src))
copy(dst, src)
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{
v := *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: d.typ,
ptr: p,
}))

View File

@ -77,7 +77,7 @@ func (d *unmarshalTextDecoder) decodeStream(s *stream, depth int64, p unsafe.Poi
if b, ok := unquoteBytes(dst); ok {
dst = b
}
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{
v := *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: d.typ,
ptr: p,
}))
@ -127,7 +127,7 @@ func (d *unmarshalTextDecoder) decode(buf []byte, cursor, depth int64, p unsafe.
if s, ok := unquoteBytes(src); ok {
src = s
}
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{
v := *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: d.typ,
ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)),
}))

View File

@ -7,7 +7,7 @@ services:
deploy:
resources:
limits:
memory: 2048M
memory: 600M
working_dir: /go/src/go-json
command: |
sh -c "go test -c . && ls go-json.test"

318
encode.go
View File

@ -1,17 +1,15 @@
package json
import (
"bytes"
"encoding"
"encoding/base64"
"fmt"
"io"
"math"
"reflect"
"strconv"
"strings"
"sync"
"unsafe"
"github.com/goccy/go-json/internal/encoder"
"github.com/goccy/go-json/internal/encoder/vm"
"github.com/goccy/go-json/internal/encoder/vm_escaped"
"github.com/goccy/go-json/internal/encoder/vm_escaped_indent"
"github.com/goccy/go-json/internal/encoder/vm_indent"
)
// An Encoder writes JSON values to an output stream.
@ -38,20 +36,20 @@ const (
var (
encRuntimeContextPool = sync.Pool{
New: func() interface{} {
return &encodeRuntimeContext{
buf: make([]byte, 0, bufSize),
ptrs: make([]uintptr, 128),
keepRefs: make([]unsafe.Pointer, 0, 8),
return &encoder.RuntimeContext{
Buf: make([]byte, 0, bufSize),
Ptrs: make([]uintptr, 128),
KeepRefs: make([]unsafe.Pointer, 0, 8),
}
},
}
)
func takeEncodeRuntimeContext() *encodeRuntimeContext {
return encRuntimeContextPool.Get().(*encodeRuntimeContext)
func takeEncodeRuntimeContext() *encoder.RuntimeContext {
return encRuntimeContextPool.Get().(*encoder.RuntimeContext)
}
func releaseEncodeRuntimeContext(ctx *encodeRuntimeContext) {
func releaseEncodeRuntimeContext(ctx *encoder.RuntimeContext) {
encRuntimeContextPool.Put(ctx)
}
@ -77,7 +75,7 @@ func (e *Encoder) EncodeWithOption(v interface{}, optFuncs ...EncodeOptionFunc)
return err
}
func (e *Encoder) encodeWithOption(ctx *encodeRuntimeContext, v interface{}, optFuncs ...EncodeOptionFunc) error {
func (e *Encoder) encodeWithOption(ctx *encoder.RuntimeContext, v interface{}, optFuncs ...EncodeOptionFunc) error {
var opt EncodeOption
if e.enabledHTMLEscape {
opt |= EncodeOptionHTMLEscape
@ -188,321 +186,103 @@ func marshalIndent(v interface{}, prefix, indent string, opt EncodeOption) ([]by
return copied, nil
}
func encode(ctx *encodeRuntimeContext, v interface{}, opt EncodeOption) ([]byte, error) {
b := ctx.buf[:0]
func encode(ctx *encoder.RuntimeContext, v interface{}, opt EncodeOption) ([]byte, error) {
b := ctx.Buf[:0]
if v == nil {
b = encodeNull(b)
b = encodeComma(b)
b = encoder.AppendNull(b)
b = encoder.AppendComma(b)
return b, nil
}
header := (*interfaceHeader)(unsafe.Pointer(&v))
header := (*emptyInterface)(unsafe.Pointer(&v))
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encodeCompileToGetCodeSet(typeptr)
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
if err != nil {
return nil, err
}
p := uintptr(header.ptr)
ctx.init(p, codeSet.codeLength)
ctx.Init(p, codeSet.CodeLength)
ctx.KeepRefs = append(ctx.KeepRefs, header.ptr)
buf, err := encodeRunCode(ctx, b, codeSet, opt)
ctx.keepRefs = append(ctx.keepRefs, header.ptr)
if err != nil {
return nil, err
}
ctx.buf = buf
ctx.Buf = buf
return buf, nil
}
func encodeNoEscape(ctx *encodeRuntimeContext, v interface{}, opt EncodeOption) ([]byte, error) {
b := ctx.buf[:0]
func encodeNoEscape(ctx *encoder.RuntimeContext, v interface{}, opt EncodeOption) ([]byte, error) {
b := ctx.Buf[:0]
if v == nil {
b = encodeNull(b)
b = encodeComma(b)
b = encoder.AppendNull(b)
b = encoder.AppendComma(b)
return b, nil
}
header := (*interfaceHeader)(unsafe.Pointer(&v))
header := (*emptyInterface)(unsafe.Pointer(&v))
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encodeCompileToGetCodeSet(typeptr)
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
if err != nil {
return nil, err
}
p := uintptr(header.ptr)
ctx.init(p, codeSet.codeLength)
ctx.Init(p, codeSet.CodeLength)
buf, err := encodeRunCode(ctx, b, codeSet, opt)
if err != nil {
return nil, err
}
ctx.buf = buf
ctx.Buf = buf
return buf, nil
}
func encodeIndent(ctx *encodeRuntimeContext, v interface{}, prefix, indent string, opt EncodeOption) ([]byte, error) {
b := ctx.buf[:0]
func encodeIndent(ctx *encoder.RuntimeContext, v interface{}, prefix, indent string, opt EncodeOption) ([]byte, error) {
b := ctx.Buf[:0]
if v == nil {
b = encodeNull(b)
b = encodeIndentComma(b)
b = encoder.AppendNull(b)
b = encoder.AppendCommaIndent(b)
return b, nil
}
header := (*interfaceHeader)(unsafe.Pointer(&v))
header := (*emptyInterface)(unsafe.Pointer(&v))
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encodeCompileToGetCodeSet(typeptr)
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
if err != nil {
return nil, err
}
p := uintptr(header.ptr)
ctx.init(p, codeSet.codeLength)
ctx.Init(p, codeSet.CodeLength)
buf, err := encodeRunIndentCode(ctx, b, codeSet, prefix, indent, opt)
ctx.keepRefs = append(ctx.keepRefs, header.ptr)
ctx.KeepRefs = append(ctx.KeepRefs, header.ptr)
if err != nil {
return nil, err
}
ctx.buf = buf
ctx.Buf = buf
return buf, nil
}
func encodeRunCode(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt EncodeOption) ([]byte, error) {
func encodeRunCode(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt EncodeOption) ([]byte, error) {
if (opt & EncodeOptionHTMLEscape) != 0 {
return encodeRunEscaped(ctx, b, codeSet, opt)
return vm_escaped.Run(ctx, b, codeSet, encoder.Option(opt))
}
return encodeRun(ctx, b, codeSet, opt)
return vm.Run(ctx, b, codeSet, encoder.Option(opt))
}
func encodeRunIndentCode(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, prefix, indent string, opt EncodeOption) ([]byte, error) {
ctx.prefix = []byte(prefix)
ctx.indentStr = []byte(indent)
func encodeRunIndentCode(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, prefix, indent string, opt EncodeOption) ([]byte, error) {
ctx.Prefix = []byte(prefix)
ctx.IndentStr = []byte(indent)
if (opt & EncodeOptionHTMLEscape) != 0 {
return encodeRunEscapedIndent(ctx, b, codeSet, opt)
return vm_escaped_indent.Run(ctx, b, codeSet, encoder.Option(opt))
}
return encodeRunIndent(ctx, b, codeSet, opt)
}
func encodeFloat32(b []byte, v float32) []byte {
f64 := float64(v)
abs := math.Abs(f64)
fmt := byte('f')
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
if abs != 0 {
f32 := float32(abs)
if f32 < 1e-6 || f32 >= 1e21 {
fmt = 'e'
}
}
return strconv.AppendFloat(b, f64, fmt, -1, 32)
}
func encodeFloat64(b []byte, v float64) []byte {
abs := math.Abs(v)
fmt := byte('f')
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
if abs != 0 {
if abs < 1e-6 || abs >= 1e21 {
fmt = 'e'
}
}
return strconv.AppendFloat(b, v, fmt, -1, 64)
}
func encodeBool(b []byte, v bool) []byte {
if v {
return append(b, "true"...)
}
return append(b, "false"...)
}
func encodeNull(b []byte) []byte {
return append(b, "null"...)
}
func encodeComma(b []byte) []byte {
return append(b, ',')
}
func encodeIndentComma(b []byte) []byte {
return append(b, ',', '\n')
}
func appendStructEnd(b []byte) []byte {
return append(b, '}', ',')
}
func appendStructEndIndent(ctx *encodeRuntimeContext, b []byte, indent int) []byte {
b = append(b, '\n')
b = append(b, ctx.prefix...)
b = append(b, bytes.Repeat(ctx.indentStr, ctx.baseIndent+indent)...)
return append(b, '}', ',', '\n')
}
func encodeByteSlice(b []byte, src []byte) []byte {
encodedLen := base64.StdEncoding.EncodedLen(len(src))
b = append(b, '"')
pos := len(b)
remainLen := cap(b[pos:])
var buf []byte
if remainLen > encodedLen {
buf = b[pos : pos+encodedLen]
} else {
buf = make([]byte, encodedLen)
}
base64.StdEncoding.Encode(buf, src)
return append(append(b, buf...), '"')
}
func encodeNumber(b []byte, n Number) ([]byte, error) {
if len(n) == 0 {
return append(b, '0'), nil
}
for i := 0; i < len(n); i++ {
if !floatTable[n[i]] {
return nil, fmt.Errorf("json: invalid number literal %q", n)
}
}
b = append(b, n...)
return b, nil
}
func appendIndent(ctx *encodeRuntimeContext, b []byte, indent int) []byte {
b = append(b, ctx.prefix...)
return append(b, bytes.Repeat(ctx.indentStr, ctx.baseIndent+indent)...)
}
func encodeIsNilForMarshaler(v interface{}) bool {
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Interface, reflect.Map, reflect.Ptr:
return rv.IsNil()
case reflect.Slice:
return rv.IsNil() || rv.Len() == 0
}
return false
}
func encodeMarshalJSON(code *opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.addrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(Marshaler)
if !ok {
return encodeNull(b), nil
}
bb, err := marshaler.MarshalJSON()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
buf := bytes.NewBuffer(b)
//TODO: we should validate buffer with `compact`
if err := compact(buf, bb, escape); err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
return buf.Bytes(), nil
}
func encodeMarshalJSONIndent(ctx *encodeRuntimeContext, code *opcode, b []byte, v interface{}, indent int, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.addrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(Marshaler)
if !ok {
return encodeNull(b), nil
}
bb, err := marshaler.MarshalJSON()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
var compactBuf bytes.Buffer
if err := compact(&compactBuf, bb, escape); err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
var indentBuf bytes.Buffer
if err := encodeWithIndent(
&indentBuf,
compactBuf.Bytes(),
string(ctx.prefix)+strings.Repeat(string(ctx.indentStr), ctx.baseIndent+indent+1),
string(ctx.indentStr),
); err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
return append(b, indentBuf.Bytes()...), nil
}
func encodeMarshalText(code *opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.addrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(encoding.TextMarshaler)
if !ok {
return encodeNull(b), nil
}
bytes, err := marshaler.MarshalText()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
if escape {
return encodeEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
return encodeNoEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
func encodeMarshalTextIndent(code *opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.addrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(encoding.TextMarshaler)
if !ok {
return encodeNull(b), nil
}
bytes, err := marshaler.MarshalText()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
if escape {
return encodeEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
return encodeNoEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
return vm_indent.Run(ctx, b, codeSet, encoder.Option(opt))
}

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
// +build !race
package json
import "unsafe"
func encodeCompileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) {
if typeptr > maxTypeAddr {
return encodeCompileToGetCodeSetSlowPath(typeptr)
}
index := typeptr - baseTypeAddr
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
return codeSet, nil
}
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**rtype)(unsafe.Pointer(&typeptr))
code, err := encodeCompileHead(&encodeCompileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*compiledCode{},
})
if err != nil {
return nil, err
}
code = copyOpcode(code)
codeLength := code.totalLength()
codeSet := &opcodeSet{
code: code,
codeLength: codeLength,
}
cachedOpcodeSets[index] = codeSet
return codeSet, nil
}

View File

@ -1,44 +0,0 @@
// +build race
package json
import (
"sync"
"unsafe"
)
var setsMu sync.RWMutex
func encodeCompileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) {
if typeptr > maxTypeAddr {
return encodeCompileToGetCodeSetSlowPath(typeptr)
}
index := typeptr - baseTypeAddr
setsMu.RLock()
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
setsMu.RUnlock()
return codeSet, nil
}
setsMu.RUnlock()
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**rtype)(unsafe.Pointer(&typeptr))
code, err := encodeCompileHead(&encodeCompileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*compiledCode{},
})
if err != nil {
return nil, err
}
code = copyOpcode(code)
codeLength := code.totalLength()
codeSet := &opcodeSet{
code: code,
codeLength: codeLength,
}
setsMu.Lock()
cachedOpcodeSets[index] = codeSet
setsMu.Unlock()
return codeSet, nil
}

View File

@ -1,164 +0,0 @@
package json
import (
"bytes"
"sync"
"unsafe"
)
type mapItem struct {
key []byte
value []byte
}
type mapslice struct {
items []mapItem
}
func (m *mapslice) Len() int {
return len(m.items)
}
func (m *mapslice) Less(i, j int) bool {
return bytes.Compare(m.items[i].key, m.items[j].key) < 0
}
func (m *mapslice) Swap(i, j int) {
m.items[i], m.items[j] = m.items[j], m.items[i]
}
type encodeMapContext struct {
pos []int
slice *mapslice
buf []byte
}
var mapContextPool = sync.Pool{
New: func() interface{} {
return &encodeMapContext{}
},
}
func newMapContext(mapLen int) *encodeMapContext {
ctx := mapContextPool.Get().(*encodeMapContext)
if ctx.slice == nil {
ctx.slice = &mapslice{
items: make([]mapItem, 0, mapLen),
}
}
if cap(ctx.pos) < (mapLen*2 + 1) {
ctx.pos = make([]int, 0, mapLen*2+1)
ctx.slice.items = make([]mapItem, 0, mapLen)
} else {
ctx.pos = ctx.pos[:0]
ctx.slice.items = ctx.slice.items[:0]
}
ctx.buf = ctx.buf[:0]
return ctx
}
func releaseMapContext(c *encodeMapContext) {
mapContextPool.Put(c)
}
type encodeCompileContext struct {
typ *rtype
opcodeIndex int
ptrIndex int
indent int
structTypeToCompiledCode map[uintptr]*compiledCode
parent *encodeCompileContext
}
func (c *encodeCompileContext) context() *encodeCompileContext {
return &encodeCompileContext{
typ: c.typ,
opcodeIndex: c.opcodeIndex,
ptrIndex: c.ptrIndex,
indent: c.indent,
structTypeToCompiledCode: c.structTypeToCompiledCode,
parent: c,
}
}
func (c *encodeCompileContext) withType(typ *rtype) *encodeCompileContext {
ctx := c.context()
ctx.typ = typ
return ctx
}
func (c *encodeCompileContext) incIndent() *encodeCompileContext {
ctx := c.context()
ctx.indent++
return ctx
}
func (c *encodeCompileContext) decIndent() *encodeCompileContext {
ctx := c.context()
ctx.indent--
return ctx
}
func (c *encodeCompileContext) incIndex() {
c.incOpcodeIndex()
c.incPtrIndex()
}
func (c *encodeCompileContext) decIndex() {
c.decOpcodeIndex()
c.decPtrIndex()
}
func (c *encodeCompileContext) incOpcodeIndex() {
c.opcodeIndex++
if c.parent != nil {
c.parent.incOpcodeIndex()
}
}
func (c *encodeCompileContext) decOpcodeIndex() {
c.opcodeIndex--
if c.parent != nil {
c.parent.decOpcodeIndex()
}
}
func (c *encodeCompileContext) incPtrIndex() {
c.ptrIndex++
if c.parent != nil {
c.parent.incPtrIndex()
}
}
func (c *encodeCompileContext) decPtrIndex() {
c.ptrIndex--
if c.parent != nil {
c.parent.decPtrIndex()
}
}
type encodeRuntimeContext struct {
buf []byte
ptrs []uintptr
keepRefs []unsafe.Pointer
seenPtr []uintptr
baseIndent int
prefix []byte
indentStr []byte
}
func (c *encodeRuntimeContext) init(p uintptr, codelen int) {
if len(c.ptrs) < codelen {
c.ptrs = make([]uintptr, codelen)
}
c.ptrs[0] = p
c.keepRefs = c.keepRefs[:0]
c.seenPtr = c.seenPtr[:0]
c.baseIndent = 0
}
func (c *encodeRuntimeContext) ptr() uintptr {
header := (*sliceHeader)(unsafe.Pointer(&c.ptrs))
return uintptr(header.data)
}

View File

@ -1,8 +0,0 @@
// +build !go1.13
package json
import "unsafe"
//go:linkname mapitervalue reflect.mapitervalue
func mapitervalue(it unsafe.Pointer) unsafe.Pointer

View File

@ -1,8 +0,0 @@
// +build go1.13
package json
import "unsafe"
//go:linkname mapitervalue reflect.mapiterelem
func mapitervalue(it unsafe.Pointer) unsafe.Pointer

View File

@ -1,512 +0,0 @@
package json
import (
"fmt"
"strings"
"unsafe"
)
const uintptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const
type opcode struct {
op opType // operation type
typ *rtype // go type
displayIdx int // opcode index
key []byte // struct field key
escapedKey []byte // struct field key ( HTML escaped )
ptrNum int // pointer number: e.g. double pointer is 2.
displayKey string // key text to display
isTaggedKey bool // whether tagged key
anonymousKey bool // whether anonymous key
anonymousHead bool // whether anonymous head or not
indirect bool // whether indirect or not
nilcheck bool // whether needs to nilcheck or not
addrForMarshaler bool // whether needs to addr for marshaler or not
rshiftNum uint8 // use to take bit for judging whether negative integer or not
mask uint64 // mask for number
indent int // indent number
idx uintptr // offset to access ptr
headIdx uintptr // offset to access slice/struct head
elemIdx uintptr // offset to access array/slice/map elem
length uintptr // offset to access slice/map length or array length
mapIter uintptr // offset to access map iterator
mapPos uintptr // offset to access position list for sorted map
offset uintptr // offset size from struct header
size uintptr // array/slice elem size
mapKey *opcode // map key
mapValue *opcode // map value
elem *opcode // array/slice elem
end *opcode // array/slice/struct/map end
prevField *opcode // prev struct field
nextField *opcode // next struct field
next *opcode // next opcode
jmp *compiledCode // for recursive call
}
func newOpCode(ctx *encodeCompileContext, op opType) *opcode {
return newOpCodeWithNext(ctx, op, newEndOp(ctx))
}
func opcodeOffset(idx int) uintptr {
return uintptr(idx) * uintptrSize
}
func copyOpcode(code *opcode) *opcode {
codeMap := map[uintptr]*opcode{}
return code.copy(codeMap)
}
func newOpCodeWithNext(ctx *encodeCompileContext, op opType, next *opcode) *opcode {
return &opcode{
op: op,
typ: ctx.typ,
displayIdx: ctx.opcodeIndex,
indent: ctx.indent,
idx: opcodeOffset(ctx.ptrIndex),
next: next,
}
}
func newEndOp(ctx *encodeCompileContext) *opcode {
return newOpCodeWithNext(ctx, opEnd, nil)
}
func (c *opcode) copy(codeMap map[uintptr]*opcode) *opcode {
if c == nil {
return nil
}
addr := uintptr(unsafe.Pointer(c))
if code, exists := codeMap[addr]; exists {
return code
}
copied := &opcode{
op: c.op,
typ: c.typ,
displayIdx: c.displayIdx,
key: c.key,
escapedKey: c.escapedKey,
displayKey: c.displayKey,
ptrNum: c.ptrNum,
mask: c.mask,
rshiftNum: c.rshiftNum,
isTaggedKey: c.isTaggedKey,
anonymousKey: c.anonymousKey,
anonymousHead: c.anonymousHead,
indirect: c.indirect,
nilcheck: c.nilcheck,
addrForMarshaler: c.addrForMarshaler,
indent: c.indent,
idx: c.idx,
headIdx: c.headIdx,
elemIdx: c.elemIdx,
length: c.length,
mapIter: c.mapIter,
mapPos: c.mapPos,
offset: c.offset,
size: c.size,
}
codeMap[addr] = copied
copied.mapKey = c.mapKey.copy(codeMap)
copied.mapValue = c.mapValue.copy(codeMap)
copied.elem = c.elem.copy(codeMap)
copied.end = c.end.copy(codeMap)
copied.prevField = c.prevField.copy(codeMap)
copied.nextField = c.nextField.copy(codeMap)
copied.next = c.next.copy(codeMap)
copied.jmp = c.jmp
return copied
}
func (c *opcode) beforeLastCode() *opcode {
code := c
for {
var nextCode *opcode
switch code.op.codeType() {
case codeArrayElem, codeSliceElem, codeMapKey:
nextCode = code.end
default:
nextCode = code.next
}
if nextCode.op == opEnd {
return code
}
code = nextCode
}
}
func (c *opcode) totalLength() int {
var idx int
for code := c; code.op != opEnd; {
idx = int(code.idx / uintptrSize)
if code.op == opStructFieldRecursiveEnd {
break
}
switch code.op.codeType() {
case codeArrayElem, codeSliceElem, codeMapKey:
code = code.end
default:
code = code.next
}
}
return idx + 2 // opEnd + 1
}
func (c *opcode) decOpcodeIndex() {
for code := c; code.op != opEnd; {
code.displayIdx--
code.idx -= uintptrSize
if code.headIdx > 0 {
code.headIdx -= uintptrSize
}
if code.elemIdx > 0 {
code.elemIdx -= uintptrSize
}
if code.mapIter > 0 {
code.mapIter -= uintptrSize
}
if code.length > 0 && code.op.codeType() != codeArrayHead && code.op.codeType() != codeArrayElem {
code.length -= uintptrSize
}
switch code.op.codeType() {
case codeArrayElem, codeSliceElem, codeMapKey:
code = code.end
default:
code = code.next
}
}
}
func (c *opcode) decIndent() {
for code := c; code.op != opEnd; {
code.indent--
switch code.op.codeType() {
case codeArrayElem, codeSliceElem, codeMapKey:
code = code.end
default:
code = code.next
}
}
}
func (c *opcode) dumpHead(code *opcode) string {
var length uintptr
if code.op.codeType() == codeArrayHead {
length = code.length
} else {
length = code.length / uintptrSize
}
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d])`,
code.displayIdx,
strings.Repeat("-", code.indent),
code.op,
code.idx/uintptrSize,
code.headIdx/uintptrSize,
code.elemIdx/uintptrSize,
length,
)
}
func (c *opcode) dumpMapHead(code *opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
code.displayIdx,
strings.Repeat("-", code.indent),
code.op,
code.idx/uintptrSize,
code.headIdx/uintptrSize,
code.elemIdx/uintptrSize,
code.length/uintptrSize,
code.mapIter/uintptrSize,
)
}
func (c *opcode) dumpMapEnd(code *opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][mapPos:%d][length:%d])`,
code.displayIdx,
strings.Repeat("-", code.indent),
code.op,
code.idx/uintptrSize,
code.mapPos/uintptrSize,
code.length/uintptrSize,
)
}
func (c *opcode) dumpElem(code *opcode) string {
var length uintptr
if code.op.codeType() == codeArrayElem {
length = code.length
} else {
length = code.length / uintptrSize
}
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d][size:%d])`,
code.displayIdx,
strings.Repeat("-", code.indent),
code.op,
code.idx/uintptrSize,
code.headIdx/uintptrSize,
code.elemIdx/uintptrSize,
length,
code.size,
)
}
func (c *opcode) dumpField(code *opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][key:%s][offset:%d][headIdx:%d])`,
code.displayIdx,
strings.Repeat("-", code.indent),
code.op,
code.idx/uintptrSize,
code.displayKey,
code.offset,
code.headIdx/uintptrSize,
)
}
func (c *opcode) dumpKey(code *opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
code.displayIdx,
strings.Repeat("-", code.indent),
code.op,
code.idx/uintptrSize,
code.elemIdx/uintptrSize,
code.length/uintptrSize,
code.mapIter/uintptrSize,
)
}
func (c *opcode) dumpValue(code *opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][mapIter:%d])`,
code.displayIdx,
strings.Repeat("-", code.indent),
code.op,
code.idx/uintptrSize,
code.mapIter/uintptrSize,
)
}
func (c *opcode) dump() string {
codes := []string{}
for code := c; code.op != opEnd; {
switch code.op.codeType() {
case codeSliceHead:
codes = append(codes, c.dumpHead(code))
code = code.next
case codeMapHead:
codes = append(codes, c.dumpMapHead(code))
code = code.next
case codeArrayElem, codeSliceElem:
codes = append(codes, c.dumpElem(code))
code = code.end
case codeMapKey:
codes = append(codes, c.dumpKey(code))
code = code.end
case codeMapValue:
codes = append(codes, c.dumpValue(code))
code = code.next
case codeMapEnd:
codes = append(codes, c.dumpMapEnd(code))
code = code.next
case codeStructField:
codes = append(codes, c.dumpField(code))
code = code.next
case codeStructEnd:
codes = append(codes, c.dumpField(code))
code = code.next
default:
codes = append(codes, fmt.Sprintf(
"[%d]%s%s ([idx:%d])",
code.displayIdx,
strings.Repeat("-", code.indent),
code.op,
code.idx/uintptrSize,
))
code = code.next
}
}
return strings.Join(codes, "\n")
}
func prevField(code *opcode, removedFields map[*opcode]struct{}) *opcode {
if _, exists := removedFields[code]; exists {
return prevField(code.prevField, removedFields)
}
return code
}
func nextField(code *opcode, removedFields map[*opcode]struct{}) *opcode {
if _, exists := removedFields[code]; exists {
return nextField(code.nextField, removedFields)
}
return code
}
func encodeLinkPrevToNextField(cur *opcode, removedFields map[*opcode]struct{}) {
prev := prevField(cur.prevField, removedFields)
prev.nextField = nextField(cur.nextField, removedFields)
code := prev
fcode := cur
for {
var nextCode *opcode
switch code.op.codeType() {
case codeArrayElem, codeSliceElem, codeMapKey:
nextCode = code.end
default:
nextCode = code.next
}
if nextCode == fcode {
code.next = fcode.next
break
} else if nextCode.op == opEnd {
break
}
code = nextCode
}
}
func newSliceHeaderCode(ctx *encodeCompileContext) *opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
length := opcodeOffset(ctx.ptrIndex)
return &opcode{
op: opSlice,
displayIdx: ctx.opcodeIndex,
idx: idx,
headIdx: idx,
elemIdx: elemIdx,
length: length,
indent: ctx.indent,
}
}
func newSliceElemCode(ctx *encodeCompileContext, head *opcode, size uintptr) *opcode {
return &opcode{
op: opSliceElem,
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
headIdx: head.idx,
elemIdx: head.elemIdx,
length: head.length,
indent: ctx.indent,
size: size,
}
}
func newArrayHeaderCode(ctx *encodeCompileContext, alen int) *opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
return &opcode{
op: opArray,
displayIdx: ctx.opcodeIndex,
idx: idx,
headIdx: idx,
elemIdx: elemIdx,
indent: ctx.indent,
length: uintptr(alen),
}
}
func newArrayElemCode(ctx *encodeCompileContext, head *opcode, length int, size uintptr) *opcode {
return &opcode{
op: opArrayElem,
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
elemIdx: head.elemIdx,
headIdx: head.headIdx,
length: uintptr(length),
indent: ctx.indent,
size: size,
}
}
func newMapHeaderCode(ctx *encodeCompileContext) *opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
length := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
mapIter := opcodeOffset(ctx.ptrIndex)
return &opcode{
op: opMap,
typ: ctx.typ,
displayIdx: ctx.opcodeIndex,
idx: idx,
elemIdx: elemIdx,
length: length,
mapIter: mapIter,
indent: ctx.indent,
}
}
func newMapKeyCode(ctx *encodeCompileContext, head *opcode) *opcode {
return &opcode{
op: opMapKey,
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
elemIdx: head.elemIdx,
length: head.length,
mapIter: head.mapIter,
indent: ctx.indent,
}
}
func newMapValueCode(ctx *encodeCompileContext, head *opcode) *opcode {
return &opcode{
op: opMapValue,
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
elemIdx: head.elemIdx,
length: head.length,
mapIter: head.mapIter,
indent: ctx.indent,
}
}
func newMapEndCode(ctx *encodeCompileContext, head *opcode) *opcode {
mapPos := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
idx := opcodeOffset(ctx.ptrIndex)
return &opcode{
op: opMapEnd,
displayIdx: ctx.opcodeIndex,
idx: idx,
length: head.length,
mapPos: mapPos,
indent: ctx.indent,
next: newEndOp(ctx),
}
}
func newInterfaceCode(ctx *encodeCompileContext) *opcode {
return &opcode{
op: opInterface,
typ: ctx.typ,
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
indent: ctx.indent,
next: newEndOp(ctx),
}
}
func newRecursiveCode(ctx *encodeCompileContext, jmp *compiledCode) *opcode {
return &opcode{
op: opStructFieldRecursive,
typ: ctx.typ,
displayIdx: ctx.opcodeIndex,
idx: opcodeOffset(ctx.ptrIndex),
indent: ctx.indent,
next: newEndOp(ctx),
jmp: jmp,
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

140
error.go
View File

@ -1,9 +1,7 @@
package json
import (
"fmt"
"reflect"
"strconv"
"github.com/goccy/go-json/internal/errors"
)
// Before Go 1.2, an InvalidUTF8Error was returned by Marshal when
@ -12,142 +10,40 @@ import (
// replacing invalid bytes with the Unicode replacement rune U+FFFD.
//
// Deprecated: No longer used; kept for compatibility.
type InvalidUTF8Error struct {
S string // the whole string value that caused the error
}
func (e *InvalidUTF8Error) Error() string {
return fmt.Sprintf("json: invalid UTF-8 in string: %s", strconv.Quote(e.S))
}
type InvalidUTF8Error = errors.InvalidUTF8Error
// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
// (The argument to Unmarshal must be a non-nil pointer.)
type InvalidUnmarshalError struct {
Type reflect.Type
}
func (e *InvalidUnmarshalError) Error() string {
if e.Type == nil {
return "json: Unmarshal(nil)"
}
if e.Type.Kind() != reflect.Ptr {
return fmt.Sprintf("json: Unmarshal(non-pointer %s)", e.Type)
}
return fmt.Sprintf("json: Unmarshal(nil %s)", e.Type)
}
type InvalidUnmarshalError = errors.InvalidUnmarshalError
// A MarshalerError represents an error from calling a MarshalJSON or MarshalText method.
type MarshalerError struct {
Type reflect.Type
Err error
sourceFunc string
}
func (e *MarshalerError) Error() string {
srcFunc := e.sourceFunc
if srcFunc == "" {
srcFunc = "MarshalJSON"
}
return fmt.Sprintf("json: error calling %s for type %s: %s", srcFunc, e.Type, e.Err.Error())
}
// Unwrap returns the underlying error.
func (e *MarshalerError) Unwrap() error { return e.Err }
type MarshalerError = errors.MarshalerError
// A SyntaxError is a description of a JSON syntax error.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
type SyntaxError = errors.SyntaxError
// An UnmarshalFieldError describes a JSON object key that
// led to an unexported (and therefore unwritable) struct field.
//
// Deprecated: No longer used; kept for compatibility.
type UnmarshalFieldError struct {
Key string
Type reflect.Type
Field reflect.StructField
}
func (e *UnmarshalFieldError) Error() string {
return fmt.Sprintf("json: cannot unmarshal object key %s into unexported field %s of type %s",
strconv.Quote(e.Key), e.Field.Name, e.Type.String(),
)
}
type UnmarshalFieldError = errors.UnmarshalFieldError
// An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
Value string // description of JSON value - "bool", "array", "number -5"
Type reflect.Type // type of Go value it could not be assigned to
Offset int64 // error occurred after reading Offset bytes
Struct string // name of the struct type containing the field
Field string // the full path from root node to the field
}
func (e *UnmarshalTypeError) Error() string {
if e.Struct != "" || e.Field != "" {
return fmt.Sprintf("json: cannot unmarshal %s into Go struct field %s.%s of type %s",
e.Value, e.Struct, e.Field, e.Type,
)
}
return fmt.Sprintf("json: cannot unmarshal %s into Go value of type %s", e.Value, e.Type)
}
type UnmarshalTypeError = errors.UnmarshalTypeError
// An UnsupportedTypeError is returned by Marshal when attempting
// to encode an unsupported value type.
type UnsupportedTypeError struct {
Type reflect.Type
}
type UnsupportedTypeError = errors.UnsupportedTypeError
func (e *UnsupportedTypeError) Error() string {
return fmt.Sprintf("json: unsupported type: %s", e.Type)
}
type UnsupportedValueError = errors.UnsupportedValueError
type UnsupportedValueError struct {
Value reflect.Value
Str string
}
func (e *UnsupportedValueError) Error() string {
return fmt.Sprintf("json: unsupported value: %s", e.Str)
}
func errExceededMaxDepth(c byte, cursor int64) *SyntaxError {
return &SyntaxError{
msg: fmt.Sprintf(`invalid character "%c" exceeded max depth`, c),
Offset: cursor,
}
}
func errNotAtBeginningOfValue(cursor int64) *SyntaxError {
return &SyntaxError{msg: "not at beginning of value", Offset: cursor}
}
func errUnexpectedEndOfJSON(msg string, cursor int64) *SyntaxError {
return &SyntaxError{
msg: fmt.Sprintf("json: %s unexpected end of JSON input", msg),
Offset: cursor,
}
}
func errExpected(msg string, cursor int64) *SyntaxError {
return &SyntaxError{msg: fmt.Sprintf("expected %s", msg), Offset: cursor}
}
func errInvalidCharacter(c byte, context string, cursor int64) *SyntaxError {
if c == 0 {
return &SyntaxError{
msg: fmt.Sprintf("json: invalid character as %s", context),
Offset: cursor,
}
}
return &SyntaxError{
msg: fmt.Sprintf("json: invalid character %c as %s", c, context),
Offset: cursor,
}
}
var (
errExceededMaxDepth = errors.ErrExceededMaxDepth
errNotAtBeginningOfValue = errors.ErrNotAtBeginningOfValue
errUnexpectedEndOfJSON = errors.ErrUnexpectedEndOfJSON
errExpected = errors.ErrExpected
errInvalidCharacter = errors.ErrInvalidCharacter
errSyntax = errors.ErrSyntax
errMarshaler = errors.ErrMarshaler
)

View File

@ -1,18 +1,6 @@
package json
import "reflect"
func NewSyntaxError(msg string, offset int64) *SyntaxError {
return &SyntaxError{
msg: msg,
Offset: offset,
}
}
func NewMarshalerError(typ reflect.Type, err error, msg string) *MarshalerError {
return &MarshalerError{
Type: typ,
Err: err,
sourceFunc: msg,
}
}
var (
NewSyntaxError = errSyntax
NewMarshalerError = errMarshaler
)

View File

@ -66,18 +66,18 @@ func createOpType(op, code string) opType {
}
func _main() error {
tmpl, err := template.New("").Parse(`// Code generated by cmd/generator. DO NOT EDIT!
package json
tmpl, err := template.New("").Parse(`// Code generated by internal/cmd/generator. DO NOT EDIT!
package encoder
import (
"strings"
)
type codeType int
type CodeType int
const (
{{- range $index, $type := .CodeTypes }}
code{{ $type }} codeType = {{ $index }}
Code{{ $type }} CodeType = {{ $index }}
{{- end }}
)
@ -87,92 +87,86 @@ var opTypeStrings = [{{ .OpLen }}]string{
{{- end }}
}
type opType int
type OpType int
const (
{{- range $index, $type := .OpTypes }}
op{{ $type.Op }} opType = {{ $index }}
Op{{ $type.Op }} OpType = {{ $index }}
{{- end }}
)
func (t opType) String() string {
func (t OpType) String() string {
if int(t) >= {{ .OpLen }} {
return ""
}
return opTypeStrings[int(t)]
}
func (t opType) codeType() codeType {
func (t OpType) CodeType() CodeType {
if strings.Contains(t.String(), "Struct") {
if strings.Contains(t.String(), "End") {
return codeStructEnd
return CodeStructEnd
}
return codeStructField
return CodeStructField
}
if t.String() == "Array" || t.String() == "ArrayPtr" {
return codeArrayHead
}
if strings.Contains(t.String(), "ArrayElem") {
return codeArrayElem
}
if t.String() == "Slice" || t.String() == "SlicePtr" {
return codeSliceHead
}
if strings.Contains(t.String(), "SliceElem") {
return codeSliceElem
}
if t.String() == "Map" || t.String() == "MapPtr" {
return codeMapHead
}
if strings.Contains(t.String(), "MapKey") {
return codeMapKey
}
if strings.Contains(t.String(), "MapValue") {
return codeMapValue
}
if strings.Contains(t.String(), "MapEnd") {
return codeMapEnd
switch t {
case OpArray, OpArrayPtr:
return CodeArrayHead
case OpArrayElem:
return CodeArrayElem
case OpSlice, OpSlicePtr:
return CodeSliceHead
case OpSliceElem:
return CodeSliceElem
case OpMap, OpMapPtr:
return CodeMapHead
case OpMapKey:
return CodeMapKey
case OpMapValue:
return CodeMapValue
case OpMapEnd:
return CodeMapEnd
}
return codeOp
return CodeOp
}
func (t opType) headToPtrHead() opType {
func (t OpType) HeadToPtrHead() OpType {
if strings.Index(t.String(), "PtrHead") > 0 {
return t
}
idx := strings.Index(t.String(), "Field")
idx := strings.Index(t.String(), "Head")
if idx == -1 {
return t
}
suffix := "Ptr"+t.String()[idx+len("Field"):]
suffix := "PtrHead"+t.String()[idx+len("Head"):]
const toPtrOffset = 3
if strings.Contains(opType(int(t) + toPtrOffset).String(), suffix) {
return opType(int(t) + toPtrOffset)
if strings.Contains(OpType(int(t) + toPtrOffset).String(), suffix) {
return OpType(int(t) + toPtrOffset)
}
return t
}
func (t opType) headToOmitEmptyHead() opType {
func (t OpType) HeadToOmitEmptyHead() OpType {
const toOmitEmptyOffset = 1
if strings.Contains(opType(int(t) + toOmitEmptyOffset).String(), "OmitEmpty") {
return opType(int(t) + toOmitEmptyOffset)
if strings.Contains(OpType(int(t) + toOmitEmptyOffset).String(), "OmitEmpty") {
return OpType(int(t) + toOmitEmptyOffset)
}
return t
}
func (t opType) headToStringTagHead() opType {
func (t OpType) HeadToStringTagHead() OpType {
const toStringTagOffset = 2
if strings.Contains(opType(int(t) + toStringTagOffset).String(), "StringTag") {
return opType(int(t) + toStringTagOffset)
if strings.Contains(OpType(int(t) + toStringTagOffset).String(), "StringTag") {
return OpType(int(t) + toStringTagOffset)
}
return t
}
func (t opType) ptrHeadToHead() opType {
func (t OpType) PtrHeadToHead() OpType {
idx := strings.Index(t.String(), "Ptr")
if idx == -1 {
return t
@ -180,36 +174,40 @@ func (t opType) ptrHeadToHead() opType {
suffix := t.String()[idx+len("Ptr"):]
const toPtrOffset = 3
if strings.Contains(opType(int(t) - toPtrOffset).String(), suffix) {
return opType(int(t) - toPtrOffset)
if strings.Contains(OpType(int(t) - toPtrOffset).String(), suffix) {
return OpType(int(t) - toPtrOffset)
}
return t
}
func (t opType) fieldToEnd() opType {
switch t {
{{- range $type := .OpTypes }}
{{- if $type.IsFieldToEnd }}
case op{{ $type.Op }}:
return op{{ call $type.FieldToEnd }}
{{- end }}
{{- end }}
func (t OpType) FieldToEnd() OpType {
idx := strings.Index(t.String(), "Field")
if idx == -1 {
return t
}
suffix := t.String()[idx+len("Field"):]
if suffix == "" || suffix == "OmitEmpty" || suffix == "StringTag" {
return t
}
const toEndOffset = 3
if strings.Contains(OpType(int(t) + toEndOffset).String(), "End"+suffix) {
return OpType(int(t) + toEndOffset)
}
return t
}
func (t opType) fieldToOmitEmptyField() opType {
func (t OpType) FieldToOmitEmptyField() OpType {
const toOmitEmptyOffset = 1
if strings.Contains(opType(int(t) + toOmitEmptyOffset).String(), "OmitEmpty") {
return opType(int(t) + toOmitEmptyOffset)
if strings.Contains(OpType(int(t) + toOmitEmptyOffset).String(), "OmitEmpty") {
return OpType(int(t) + toOmitEmptyOffset)
}
return t
}
func (t opType) fieldToStringTagField() opType {
func (t OpType) FieldToStringTagField() OpType {
const toStringTagOffset = 2
if strings.Contains(opType(int(t) + toStringTagOffset).String(), "StringTag") {
return opType(int(t) + toStringTagOffset)
if strings.Contains(OpType(int(t) + toStringTagOffset).String(), "StringTag") {
return OpType(int(t) + toStringTagOffset)
}
return t
}
@ -269,7 +267,7 @@ func (t opType) fieldToStringTagField() opType {
typ := typ
op := fmt.Sprintf(
"StructField%sHead%s%s",
"Struct%sHead%s%s",
ptrOrNot,
opt,
typ,
@ -279,28 +277,28 @@ func (t opType) fieldToStringTagField() opType {
Code: "StructField",
HeadToPtrHead: func() string {
return fmt.Sprintf(
"StructFieldPtrHead%s%s",
"StructPtrHead%s%s",
opt,
typ,
)
},
HeadToOmitEmptyHead: func() string {
return fmt.Sprintf(
"StructField%sHeadOmitEmpty%s",
"Struct%sHeadOmitEmpty%s",
ptrOrNot,
typ,
)
},
HeadToStringTagHead: func() string {
return fmt.Sprintf(
"StructField%sHeadStringTag%s",
"Struct%sHeadStringTag%s",
ptrOrNot,
typ,
)
},
PtrHeadToHead: func() string {
return fmt.Sprintf(
"StructFieldHead%s%s",
"StructHead%s%s",
opt,
typ,
)
@ -354,8 +352,6 @@ func (t opType) fieldToStringTagField() opType {
},
})
}
}
for _, typ := range append(primitiveTypesUpper, "") {
for _, opt := range []string{"", "OmitEmpty", "StringTag"} {
opt := opt
typ := typ
@ -390,7 +386,7 @@ func (t opType) fieldToStringTagField() opType {
}); err != nil {
return err
}
path := filepath.Join(repoRoot(), "encode_optype.go")
path := filepath.Join(repoRoot(), "internal", "encoder", "optype.go")
buf, err := format.Source(b.Bytes())
if err != nil {
return err
@ -400,10 +396,11 @@ func (t opType) fieldToStringTagField() opType {
func repoRoot() string {
_, file, _, _ := runtime.Caller(0)
relativePathFromRepoRoot := filepath.Join("cmd", "generator")
relativePathFromRepoRoot := filepath.Join("internal", "cmd", "generator")
return strings.TrimSuffix(filepath.Dir(file), relativePathFromRepoRoot)
}
//go:generate go run main.go
func main() {
if err := _main(); err != nil {
panic(err)

View File

@ -1,12 +1,14 @@
package json
package encoder
import (
"bytes"
"github.com/goccy/go-json/internal/errors"
)
func compact(dst *bytes.Buffer, src []byte, escape bool) error {
func Compact(dst *bytes.Buffer, src []byte, escape bool) error {
if len(src) == 0 {
return errUnexpectedEndOfJSON("", 0)
return errors.ErrUnexpectedEndOfJSON("", 0)
}
length := len(src)
for cursor := 0; cursor < length; cursor++ {
@ -39,8 +41,8 @@ func compact(dst *bytes.Buffer, src []byte, escape bool) error {
}
case '"':
goto LOOP_END
case nul:
return errUnexpectedEndOfJSON("string", int64(length))
case '\000':
return errors.ErrUnexpectedEndOfJSON("string", int64(length))
}
}
default:

1214
internal/encoder/compiler.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
// +build !race
package encoder
import (
"unsafe"
"github.com/goccy/go-json/internal/runtime"
)
func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr {
return compileToGetCodeSetSlowPath(typeptr)
}
index := typeptr - typeAddr.BaseTypeAddr
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
return codeSet, nil
}
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr))
code, err := compileHead(&compileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
})
if err != nil {
return nil, err
}
code = copyOpcode(code)
codeLength := code.TotalLength()
codeSet := &OpcodeSet{
Code: code,
CodeLength: codeLength,
}
cachedOpcodeSets[index] = codeSet
return codeSet, nil
}

View File

@ -0,0 +1,46 @@
// +build race
package encoder
import (
"sync"
"unsafe"
"github.com/goccy/go-json/internal/runtime"
)
var setsMu sync.RWMutex
func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr {
return compileToGetCodeSetSlowPath(typeptr)
}
index := typeptr - typeAddr.BaseTypeAddr
setsMu.RLock()
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
setsMu.RUnlock()
return codeSet, nil
}
setsMu.RUnlock()
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr))
code, err := compileHead(&compileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
})
if err != nil {
return nil, err
}
code = copyOpcode(code)
codeLength := code.TotalLength()
codeSet := &OpcodeSet{
Code: code,
CodeLength: codeLength,
}
setsMu.Lock()
cachedOpcodeSets[index] = codeSet
setsMu.Unlock()
return codeSet, nil
}

View File

@ -0,0 +1,82 @@
package encoder
import (
"github.com/goccy/go-json/internal/runtime"
)
type compileContext struct {
typ *runtime.Type
opcodeIndex int
ptrIndex int
indent int
structTypeToCompiledCode map[uintptr]*CompiledCode
parent *compileContext
}
func (c *compileContext) context() *compileContext {
return &compileContext{
typ: c.typ,
opcodeIndex: c.opcodeIndex,
ptrIndex: c.ptrIndex,
indent: c.indent,
structTypeToCompiledCode: c.structTypeToCompiledCode,
parent: c,
}
}
func (c *compileContext) withType(typ *runtime.Type) *compileContext {
ctx := c.context()
ctx.typ = typ
return ctx
}
func (c *compileContext) incIndent() *compileContext {
ctx := c.context()
ctx.indent++
return ctx
}
func (c *compileContext) decIndent() *compileContext {
ctx := c.context()
ctx.indent--
return ctx
}
func (c *compileContext) incIndex() {
c.incOpcodeIndex()
c.incPtrIndex()
}
func (c *compileContext) decIndex() {
c.decOpcodeIndex()
c.decPtrIndex()
}
func (c *compileContext) incOpcodeIndex() {
c.opcodeIndex++
if c.parent != nil {
c.parent.incOpcodeIndex()
}
}
func (c *compileContext) decOpcodeIndex() {
c.opcodeIndex--
if c.parent != nil {
c.parent.decOpcodeIndex()
}
}
func (c *compileContext) incPtrIndex() {
c.ptrIndex++
if c.parent != nil {
c.parent.incPtrIndex()
}
}
func (c *compileContext) decPtrIndex() {
c.ptrIndex--
if c.parent != nil {
c.parent.decPtrIndex()
}
}

View File

@ -1,4 +1,4 @@
package json
package encoder
import (
"testing"
@ -7,12 +7,12 @@ import (
func TestDumpOpcode(t *testing.T) {
var v interface{} = 1
header := (*interfaceHeader)(unsafe.Pointer(&v))
header := (*emptyInterface)(unsafe.Pointer(&v))
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encodeCompileToGetCodeSet(typeptr)
codeSet, err := CompileToGetCodeSet(typeptr)
if err != nil {
t.Fatal(err)
}
codeSet.code.dump()
codeSet.Code.Dump()
}

568
internal/encoder/encoder.go Normal file
View File

@ -0,0 +1,568 @@
package encoder
import (
"bytes"
"encoding"
"encoding/base64"
"encoding/json"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"sync"
"unsafe"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
type Option int
const (
HTMLEscapeOption Option = 1 << iota
IndentOption
UnorderedMapOption
)
func (t OpType) IsMultipleOpHead() bool {
switch t {
case OpStructHead:
return true
case OpStructHeadSlice:
return true
case OpStructHeadArray:
return true
case OpStructHeadMap:
return true
case OpStructHeadStruct:
return true
case OpStructHeadOmitEmpty:
return true
case OpStructHeadOmitEmptySlice:
return true
case OpStructHeadStringTagSlice:
return true
case OpStructHeadOmitEmptyArray:
return true
case OpStructHeadStringTagArray:
return true
case OpStructHeadOmitEmptyMap:
return true
case OpStructHeadStringTagMap:
return true
case OpStructHeadOmitEmptyStruct:
return true
case OpStructHeadStringTag:
return true
case OpStructHeadSlicePtr:
return true
case OpStructHeadOmitEmptySlicePtr:
return true
case OpStructHeadStringTagSlicePtr:
return true
case OpStructHeadArrayPtr:
return true
case OpStructHeadOmitEmptyArrayPtr:
return true
case OpStructHeadStringTagArrayPtr:
return true
case OpStructHeadMapPtr:
return true
case OpStructHeadOmitEmptyMapPtr:
return true
case OpStructHeadStringTagMapPtr:
return true
}
return false
}
func (t OpType) IsMultipleOpField() bool {
switch t {
case OpStructField:
return true
case OpStructFieldSlice:
return true
case OpStructFieldArray:
return true
case OpStructFieldMap:
return true
case OpStructFieldStruct:
return true
case OpStructFieldOmitEmpty:
return true
case OpStructFieldOmitEmptySlice:
return true
case OpStructFieldStringTagSlice:
return true
case OpStructFieldOmitEmptyArray:
return true
case OpStructFieldStringTagArray:
return true
case OpStructFieldOmitEmptyMap:
return true
case OpStructFieldStringTagMap:
return true
case OpStructFieldOmitEmptyStruct:
return true
case OpStructFieldStringTag:
return true
case OpStructFieldSlicePtr:
return true
case OpStructFieldOmitEmptySlicePtr:
return true
case OpStructFieldStringTagSlicePtr:
return true
case OpStructFieldArrayPtr:
return true
case OpStructFieldOmitEmptyArrayPtr:
return true
case OpStructFieldStringTagArrayPtr:
return true
case OpStructFieldMapPtr:
return true
case OpStructFieldOmitEmptyMapPtr:
return true
case OpStructFieldStringTagMapPtr:
return true
}
return false
}
type OpcodeSet struct {
Code *Opcode
CodeLength int
}
type CompiledCode struct {
Code *Opcode
Linked bool // whether recursive code already have linked
CurLen uintptr
NextLen uintptr
}
const StartDetectingCyclesAfter = 1000
func Load(base uintptr, idx uintptr) uintptr {
addr := base + idx
return **(**uintptr)(unsafe.Pointer(&addr))
}
func Store(base uintptr, idx uintptr, p uintptr) {
addr := base + idx
**(**uintptr)(unsafe.Pointer(&addr)) = p
}
func LoadNPtr(base uintptr, idx uintptr, ptrNum int) uintptr {
addr := base + idx
p := **(**uintptr)(unsafe.Pointer(&addr))
if p == 0 {
return 0
}
return PtrToPtr(p)
/*
for i := 0; i < ptrNum; i++ {
if p == 0 {
return p
}
p = PtrToPtr(p)
}
return p
*/
}
func PtrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func PtrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func PtrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func PtrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
func PtrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) }
func PtrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) }
func PtrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) }
func PtrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) }
func PtrToPtr(p uintptr) uintptr {
return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p)))
}
func PtrToNPtr(p uintptr, ptrNum int) uintptr {
for i := 0; i < ptrNum; i++ {
if p == 0 {
return 0
}
p = PtrToPtr(p)
}
return p
}
func PtrToUnsafePtr(p uintptr) unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&p))
}
func PtrToInterface(code *Opcode, p uintptr) interface{} {
return *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: code.Type,
ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)),
}))
}
func ErrUnsupportedValue(code *Opcode, ptr uintptr) *errors.UnsupportedValueError {
v := *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: code.Type,
ptr: *(*unsafe.Pointer)(unsafe.Pointer(&ptr)),
}))
return &errors.UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: fmt.Sprintf("encountered a cycle via %s", code.Type),
}
}
func ErrUnsupportedFloat(v float64) *errors.UnsupportedValueError {
return &errors.UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
func ErrMarshalerWithCode(code *Opcode, err error) *errors.MarshalerError {
return &errors.MarshalerError{
Type: runtime.RType2Type(code.Type),
Err: err,
}
}
type emptyInterface struct {
typ *runtime.Type
ptr unsafe.Pointer
}
type MapItem struct {
Key []byte
Value []byte
}
type Mapslice struct {
Items []MapItem
}
func (m *Mapslice) Len() int {
return len(m.Items)
}
func (m *Mapslice) Less(i, j int) bool {
return bytes.Compare(m.Items[i].Key, m.Items[j].Key) < 0
}
func (m *Mapslice) Swap(i, j int) {
m.Items[i], m.Items[j] = m.Items[j], m.Items[i]
}
type MapContext struct {
Pos []int
Slice *Mapslice
Buf []byte
}
var mapContextPool = sync.Pool{
New: func() interface{} {
return &MapContext{}
},
}
func NewMapContext(mapLen int) *MapContext {
ctx := mapContextPool.Get().(*MapContext)
if ctx.Slice == nil {
ctx.Slice = &Mapslice{
Items: make([]MapItem, 0, mapLen),
}
}
if cap(ctx.Pos) < (mapLen*2 + 1) {
ctx.Pos = make([]int, 0, mapLen*2+1)
ctx.Slice.Items = make([]MapItem, 0, mapLen)
} else {
ctx.Pos = ctx.Pos[:0]
ctx.Slice.Items = ctx.Slice.Items[:0]
}
ctx.Buf = ctx.Buf[:0]
return ctx
}
func ReleaseMapContext(c *MapContext) {
mapContextPool.Put(c)
}
//go:linkname MapIterInit reflect.mapiterinit
//go:noescape
func MapIterInit(mapType *runtime.Type, m unsafe.Pointer) unsafe.Pointer
//go:linkname MapIterKey reflect.mapiterkey
//go:noescape
func MapIterKey(it unsafe.Pointer) unsafe.Pointer
//go:linkname MapIterNext reflect.mapiternext
//go:noescape
func MapIterNext(it unsafe.Pointer)
//go:linkname MapLen reflect.maplen
//go:noescape
func MapLen(m unsafe.Pointer) int
type RuntimeContext struct {
Buf []byte
Ptrs []uintptr
KeepRefs []unsafe.Pointer
SeenPtr []uintptr
BaseIndent int
Prefix []byte
IndentStr []byte
}
func (c *RuntimeContext) Init(p uintptr, codelen int) {
if len(c.Ptrs) < codelen {
c.Ptrs = make([]uintptr, codelen)
}
c.Ptrs[0] = p
c.KeepRefs = c.KeepRefs[:0]
c.SeenPtr = c.SeenPtr[:0]
c.BaseIndent = 0
}
func (c *RuntimeContext) Ptr() uintptr {
header := (*runtime.SliceHeader)(unsafe.Pointer(&c.Ptrs))
return uintptr(header.Data)
}
func AppendByteSlice(b []byte, src []byte) []byte {
if src == nil {
return append(b, `null`...)
}
encodedLen := base64.StdEncoding.EncodedLen(len(src))
b = append(b, '"')
pos := len(b)
remainLen := cap(b[pos:])
var buf []byte
if remainLen > encodedLen {
buf = b[pos : pos+encodedLen]
} else {
buf = make([]byte, encodedLen)
}
base64.StdEncoding.Encode(buf, src)
return append(append(b, buf...), '"')
}
func AppendFloat32(b []byte, v float32) []byte {
f64 := float64(v)
abs := math.Abs(f64)
fmt := byte('f')
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
if abs != 0 {
f32 := float32(abs)
if f32 < 1e-6 || f32 >= 1e21 {
fmt = 'e'
}
}
return strconv.AppendFloat(b, f64, fmt, -1, 32)
}
func AppendFloat64(b []byte, v float64) []byte {
abs := math.Abs(v)
fmt := byte('f')
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
if abs != 0 {
if abs < 1e-6 || abs >= 1e21 {
fmt = 'e'
}
}
return strconv.AppendFloat(b, v, fmt, -1, 64)
}
func AppendBool(b []byte, v bool) []byte {
if v {
return append(b, "true"...)
}
return append(b, "false"...)
}
var (
floatTable = [256]bool{
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
'.': true,
'e': true,
'E': true,
'+': true,
'-': true,
}
)
func AppendNumber(b []byte, n json.Number) ([]byte, error) {
if len(n) == 0 {
return append(b, '0'), nil
}
for i := 0; i < len(n); i++ {
if !floatTable[n[i]] {
return nil, fmt.Errorf("json: invalid number literal %q", n)
}
}
b = append(b, n...)
return b, nil
}
func AppendMarshalJSON(code *Opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.AddrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(json.Marshaler)
if !ok {
return AppendNull(b), nil
}
bb, err := marshaler.MarshalJSON()
if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
buf := bytes.NewBuffer(b)
// TODO: we should validate buffer with `compact`
if err := Compact(buf, bb, escape); err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
return buf.Bytes(), nil
}
func AppendMarshalJSONIndent(ctx *RuntimeContext, code *Opcode, b []byte, v interface{}, indent int, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.AddrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(json.Marshaler)
if !ok {
return AppendNull(b), nil
}
bb, err := marshaler.MarshalJSON()
if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
var compactBuf bytes.Buffer
if err := Compact(&compactBuf, bb, escape); err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
var indentBuf bytes.Buffer
if err := Indent(
&indentBuf,
compactBuf.Bytes(),
string(ctx.Prefix)+strings.Repeat(string(ctx.IndentStr), ctx.BaseIndent+indent+1),
string(ctx.IndentStr),
); err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
return append(b, indentBuf.Bytes()...), nil
}
func AppendMarshalText(code *Opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.AddrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(encoding.TextMarshaler)
if !ok {
return AppendNull(b), nil
}
bytes, err := marshaler.MarshalText()
if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
if escape {
return AppendEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
return AppendString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
func AppendMarshalTextIndent(code *Opcode, b []byte, v interface{}, escape bool) ([]byte, error) {
rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.AddrForMarshaler {
if rv.CanAddr() {
rv = rv.Addr()
} else {
newV := reflect.New(rv.Type())
newV.Elem().Set(rv)
rv = newV
}
}
v = rv.Interface()
marshaler, ok := v.(encoding.TextMarshaler)
if !ok {
return AppendNull(b), nil
}
bytes, err := marshaler.MarshalText()
if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
if escape {
return AppendEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
return AppendString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
func AppendNull(b []byte) []byte {
return append(b, "null"...)
}
func AppendComma(b []byte) []byte {
return append(b, ',')
}
func AppendCommaIndent(b []byte) []byte {
return append(b, ',', '\n')
}
func AppendStructEnd(b []byte) []byte {
return append(b, '}', ',')
}
func AppendStructEndIndent(ctx *RuntimeContext, b []byte, indent int) []byte {
b = append(b, '\n')
b = append(b, ctx.Prefix...)
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
return append(b, '}', ',', '\n')
}
func AppendIndent(ctx *RuntimeContext, b []byte, indent int) []byte {
b = append(b, ctx.Prefix...)
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
}
func IsNilForMarshaler(v interface{}) bool {
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Interface, reflect.Map, reflect.Ptr:
return rv.IsNil()
case reflect.Slice:
return rv.IsNil() || rv.Len() == 0
}
return false
}

View File

@ -1,8 +1,12 @@
package json
package encoder
import "bytes"
import (
"bytes"
func encodeWithIndent(dst *bytes.Buffer, src []byte, prefix, indentStr string) error {
"github.com/goccy/go-json/internal/errors"
)
func Indent(dst *bytes.Buffer, src []byte, prefix, indentStr string) error {
length := int64(len(src))
indentNum := 0
indentBytes := []byte(indentStr)
@ -28,8 +32,8 @@ func encodeWithIndent(dst *bytes.Buffer, src []byte, prefix, indentStr string) e
}
case '"':
goto LOOP_END
case nul:
return errUnexpectedEndOfJSON("string", length)
case '\000':
return errors.ErrUnexpectedEndOfJSON("string", length)
}
}
case '{':
@ -50,7 +54,7 @@ func encodeWithIndent(dst *bytes.Buffer, src []byte, prefix, indentStr string) e
case '}':
indentNum--
if indentNum < 0 {
return errInvalidCharacter('}', "}", cursor)
return errors.ErrInvalidCharacter('}', "}", cursor)
}
b := []byte{'\n'}
b = append(b, prefix...)
@ -77,7 +81,7 @@ func encodeWithIndent(dst *bytes.Buffer, src []byte, prefix, indentStr string) e
case ']':
indentNum--
if indentNum < 0 {
return errInvalidCharacter(']', "]", cursor)
return errors.ErrInvalidCharacter(']', "]", cursor)
}
b := []byte{'\n'}
b = append(b, prefix...)

View File

@ -1,4 +1,4 @@
package json
package encoder
import (
"unsafe"
@ -49,9 +49,9 @@ var intBELookup = [100]uint16{
var intLookup = [2]*[100]uint16{&intLELookup, &intBELookup}
func appendInt(out []byte, u64 uint64, code *opcode) []byte {
n := u64 & code.mask
negative := (u64>>code.rshiftNum)&1 == 1
func AppendInt(out []byte, u64 uint64, code *Opcode) []byte {
n := u64 & code.Mask
negative := (u64>>code.RshiftNum)&1 == 1
if !negative {
if n < 10 {
return append(out, byte(n+'0'))
@ -60,7 +60,7 @@ func appendInt(out []byte, u64 uint64, code *opcode) []byte {
return append(out, byte(u), byte(u>>8))
}
} else {
n = -n & code.mask
n = -n & code.Mask
}
lookup := intLookup[endianness]
@ -91,8 +91,8 @@ func appendInt(out []byte, u64 uint64, code *opcode) []byte {
return append(out, b[i:]...)
}
func appendUint(out []byte, u64 uint64, code *opcode) []byte {
n := u64 & code.mask
func AppendUint(out []byte, u64 uint64, code *Opcode) []byte {
n := u64 & code.Mask
if n < 10 {
return append(out, byte(n+'0'))
} else if n < 100 {

View File

@ -0,0 +1,8 @@
// +build !go1.13
package encoder
import "unsafe"
//go:linkname MapIterValue reflect.mapitervalue
func MapIterValue(it unsafe.Pointer) unsafe.Pointer

View File

@ -0,0 +1,8 @@
// +build go1.13
package encoder
import "unsafe"
//go:linkname MapIterValue reflect.mapiterelem
func MapIterValue(it unsafe.Pointer) unsafe.Pointer

647
internal/encoder/opcode.go Normal file
View File

@ -0,0 +1,647 @@
package encoder
import (
"fmt"
"math"
"strings"
"unsafe"
"github.com/goccy/go-json/internal/runtime"
)
const uintptrSize = 4 << (^uintptr(0) >> 63)
type Opcode struct {
Op OpType // operation type
Type *runtime.Type // go type
DisplayIdx int // opcode index
Key []byte // struct field key
EscapedKey []byte // struct field key ( HTML escaped )
PtrNum int // pointer number: e.g. double pointer is 2.
DisplayKey string // key text to display
IsTaggedKey bool // whether tagged key
AnonymousKey bool // whether anonymous key
AnonymousHead bool // whether anonymous head or not
Indirect bool // whether indirect or not
Nilcheck bool // whether needs to nilcheck or not
AddrForMarshaler bool // whether needs to addr for marshaler or not
RshiftNum uint8 // use to take bit for judging whether negative integer or not
Mask uint64 // mask for number
Indent int // indent number
Idx uintptr // offset to access ptr
HeadIdx uintptr // offset to access slice/struct head
ElemIdx uintptr // offset to access array/slice/map elem
Length uintptr // offset to access slice/map length or array length
MapIter uintptr // offset to access map iterator
MapPos uintptr // offset to access position list for sorted map
Offset uintptr // offset size from struct header
Size uintptr // array/slice elem size
MapKey *Opcode // map key
MapValue *Opcode // map value
Elem *Opcode // array/slice elem
End *Opcode // array/slice/struct/map end
PrevField *Opcode // prev struct field
NextField *Opcode // next struct field
Next *Opcode // next opcode
Jmp *CompiledCode // for recursive call
}
func rshitNum(bitSize uint8) uint8 {
return bitSize - 1
}
func (c *Opcode) setMaskAndRshiftNum(bitSize uint8) {
switch bitSize {
case 8:
c.Mask = math.MaxUint8
case 16:
c.Mask = math.MaxUint16
case 32:
c.Mask = math.MaxUint32
case 64:
c.Mask = math.MaxUint64
}
c.RshiftNum = rshitNum(bitSize)
}
func (c *Opcode) ToHeaderType() OpType {
switch c.Op {
case OpInt:
return OpStructHeadInt
case OpIntPtr:
return OpStructHeadIntPtr
case OpUint:
return OpStructHeadUint
case OpUintPtr:
return OpStructHeadUintPtr
case OpFloat32:
return OpStructHeadFloat32
case OpFloat32Ptr:
return OpStructHeadFloat32Ptr
case OpFloat64:
return OpStructHeadFloat64
case OpFloat64Ptr:
return OpStructHeadFloat64Ptr
case OpString:
return OpStructHeadString
case OpStringPtr:
return OpStructHeadStringPtr
case OpNumber:
return OpStructHeadNumber
case OpNumberPtr:
return OpStructHeadNumberPtr
case OpBool:
return OpStructHeadBool
case OpBoolPtr:
return OpStructHeadBoolPtr
case OpMap:
return OpStructHeadMap
case OpMapPtr:
c.Op = OpMap
return OpStructHeadMapPtr
case OpArray:
return OpStructHeadArray
case OpArrayPtr:
c.Op = OpArray
return OpStructHeadArrayPtr
case OpSlice:
return OpStructHeadSlice
case OpSlicePtr:
c.Op = OpSlice
return OpStructHeadSlicePtr
case OpMarshalJSON:
return OpStructHeadMarshalJSON
case OpMarshalJSONPtr:
return OpStructHeadMarshalJSONPtr
case OpMarshalText:
return OpStructHeadMarshalText
case OpMarshalTextPtr:
return OpStructHeadMarshalTextPtr
}
return OpStructHead
}
func (c *Opcode) ToFieldType() OpType {
switch c.Op {
case OpInt:
return OpStructFieldInt
case OpIntPtr:
return OpStructFieldIntPtr
case OpUint:
return OpStructFieldUint
case OpUintPtr:
return OpStructFieldUintPtr
case OpFloat32:
return OpStructFieldFloat32
case OpFloat32Ptr:
return OpStructFieldFloat32Ptr
case OpFloat64:
return OpStructFieldFloat64
case OpFloat64Ptr:
return OpStructFieldFloat64Ptr
case OpString:
return OpStructFieldString
case OpStringPtr:
return OpStructFieldStringPtr
case OpNumber:
return OpStructFieldNumber
case OpNumberPtr:
return OpStructFieldNumberPtr
case OpBool:
return OpStructFieldBool
case OpBoolPtr:
return OpStructFieldBoolPtr
case OpMap:
return OpStructFieldMap
case OpMapPtr:
c.Op = OpMap
return OpStructFieldMapPtr
case OpArray:
return OpStructFieldArray
case OpArrayPtr:
c.Op = OpArray
return OpStructFieldArrayPtr
case OpSlice:
return OpStructFieldSlice
case OpSlicePtr:
c.Op = OpSlice
return OpStructFieldSlicePtr
case OpMarshalJSON:
return OpStructFieldMarshalJSON
case OpMarshalJSONPtr:
return OpStructFieldMarshalJSONPtr
case OpMarshalText:
return OpStructFieldMarshalText
case OpMarshalTextPtr:
return OpStructFieldMarshalTextPtr
}
return OpStructField
}
func newOpCode(ctx *compileContext, op OpType) *Opcode {
return newOpCodeWithNext(ctx, op, newEndOp(ctx))
}
func opcodeOffset(idx int) uintptr {
return uintptr(idx) * uintptrSize
}
func copyOpcode(code *Opcode) *Opcode {
codeMap := map[uintptr]*Opcode{}
return code.copy(codeMap)
}
func newOpCodeWithNext(ctx *compileContext, op OpType, next *Opcode) *Opcode {
return &Opcode{
Op: op,
Type: ctx.typ,
DisplayIdx: ctx.opcodeIndex,
Indent: ctx.indent,
Idx: opcodeOffset(ctx.ptrIndex),
Next: next,
}
}
func newEndOp(ctx *compileContext) *Opcode {
return newOpCodeWithNext(ctx, OpEnd, nil)
}
func (c *Opcode) copy(codeMap map[uintptr]*Opcode) *Opcode {
if c == nil {
return nil
}
addr := uintptr(unsafe.Pointer(c))
if code, exists := codeMap[addr]; exists {
return code
}
copied := &Opcode{
Op: c.Op,
Type: c.Type,
DisplayIdx: c.DisplayIdx,
Key: c.Key,
EscapedKey: c.EscapedKey,
DisplayKey: c.DisplayKey,
PtrNum: c.PtrNum,
Mask: c.Mask,
RshiftNum: c.RshiftNum,
IsTaggedKey: c.IsTaggedKey,
AnonymousKey: c.AnonymousKey,
AnonymousHead: c.AnonymousHead,
Indirect: c.Indirect,
Nilcheck: c.Nilcheck,
AddrForMarshaler: c.AddrForMarshaler,
Indent: c.Indent,
Idx: c.Idx,
HeadIdx: c.HeadIdx,
ElemIdx: c.ElemIdx,
Length: c.Length,
MapIter: c.MapIter,
MapPos: c.MapPos,
Offset: c.Offset,
Size: c.Size,
}
codeMap[addr] = copied
copied.MapKey = c.MapKey.copy(codeMap)
copied.MapValue = c.MapValue.copy(codeMap)
copied.Elem = c.Elem.copy(codeMap)
copied.End = c.End.copy(codeMap)
copied.PrevField = c.PrevField.copy(codeMap)
copied.NextField = c.NextField.copy(codeMap)
copied.Next = c.Next.copy(codeMap)
copied.Jmp = c.Jmp
return copied
}
func (c *Opcode) BeforeLastCode() *Opcode {
code := c
for {
var nextCode *Opcode
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
nextCode = code.End
default:
nextCode = code.Next
}
if nextCode.Op == OpEnd {
return code
}
code = nextCode
}
}
func (c *Opcode) TotalLength() int {
var idx int
for code := c; code.Op != OpEnd; {
idx = int(code.Idx / uintptrSize)
if code.Op == OpStructFieldRecursiveEnd {
break
}
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
default:
code = code.Next
}
}
return idx + 2 // opEnd + 1
}
func (c *Opcode) decOpcodeIndex() {
for code := c; code.Op != OpEnd; {
code.DisplayIdx--
code.Idx -= uintptrSize
if code.HeadIdx > 0 {
code.HeadIdx -= uintptrSize
}
if code.ElemIdx > 0 {
code.ElemIdx -= uintptrSize
}
if code.MapIter > 0 {
code.MapIter -= uintptrSize
}
if code.Length > 0 && code.Op.CodeType() != CodeArrayHead && code.Op.CodeType() != CodeArrayElem {
code.Length -= uintptrSize
}
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
default:
code = code.Next
}
}
}
func (c *Opcode) decIndent() {
for code := c; code.Op != OpEnd; {
code.Indent--
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
default:
code = code.Next
}
}
}
func (c *Opcode) dumpHead(code *Opcode) string {
var length uintptr
if code.Op.CodeType() == CodeArrayHead {
length = code.Length
} else {
length = code.Length / uintptrSize
}
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d])`,
code.DisplayIdx,
strings.Repeat("-", code.Indent),
code.Op,
code.Idx/uintptrSize,
code.HeadIdx/uintptrSize,
code.ElemIdx/uintptrSize,
length,
)
}
func (c *Opcode) dumpMapHead(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
code.DisplayIdx,
strings.Repeat("-", code.Indent),
code.Op,
code.Idx/uintptrSize,
code.HeadIdx/uintptrSize,
code.ElemIdx/uintptrSize,
code.Length/uintptrSize,
code.MapIter/uintptrSize,
)
}
func (c *Opcode) dumpMapEnd(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][mapPos:%d][length:%d])`,
code.DisplayIdx,
strings.Repeat("-", code.Indent),
code.Op,
code.Idx/uintptrSize,
code.MapPos/uintptrSize,
code.Length/uintptrSize,
)
}
func (c *Opcode) dumpElem(code *Opcode) string {
var length uintptr
if code.Op.CodeType() == CodeArrayElem {
length = code.Length
} else {
length = code.Length / uintptrSize
}
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d][size:%d])`,
code.DisplayIdx,
strings.Repeat("-", code.Indent),
code.Op,
code.Idx/uintptrSize,
code.HeadIdx/uintptrSize,
code.ElemIdx/uintptrSize,
length,
code.Size,
)
}
func (c *Opcode) dumpField(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][key:%s][offset:%d][headIdx:%d])`,
code.DisplayIdx,
strings.Repeat("-", code.Indent),
code.Op,
code.Idx/uintptrSize,
code.DisplayKey,
code.Offset,
code.HeadIdx/uintptrSize,
)
}
func (c *Opcode) dumpKey(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
code.DisplayIdx,
strings.Repeat("-", code.Indent),
code.Op,
code.Idx/uintptrSize,
code.ElemIdx/uintptrSize,
code.Length/uintptrSize,
code.MapIter/uintptrSize,
)
}
func (c *Opcode) dumpValue(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][mapIter:%d])`,
code.DisplayIdx,
strings.Repeat("-", code.Indent),
code.Op,
code.Idx/uintptrSize,
code.MapIter/uintptrSize,
)
}
func (c *Opcode) Dump() string {
codes := []string{}
for code := c; code.Op != OpEnd; {
switch code.Op.CodeType() {
case CodeSliceHead:
codes = append(codes, c.dumpHead(code))
code = code.Next
case CodeMapHead:
codes = append(codes, c.dumpMapHead(code))
code = code.Next
case CodeArrayElem, CodeSliceElem:
codes = append(codes, c.dumpElem(code))
code = code.End
case CodeMapKey:
codes = append(codes, c.dumpKey(code))
code = code.End
case CodeMapValue:
codes = append(codes, c.dumpValue(code))
code = code.Next
case CodeMapEnd:
codes = append(codes, c.dumpMapEnd(code))
code = code.Next
case CodeStructField:
codes = append(codes, c.dumpField(code))
code = code.Next
case CodeStructEnd:
codes = append(codes, c.dumpField(code))
code = code.Next
default:
codes = append(codes, fmt.Sprintf(
"[%d]%s%s ([idx:%d])",
code.DisplayIdx,
strings.Repeat("-", code.Indent),
code.Op,
code.Idx/uintptrSize,
))
code = code.Next
}
}
return strings.Join(codes, "\n")
}
func prevField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode {
if _, exists := removedFields[code]; exists {
return prevField(code.PrevField, removedFields)
}
return code
}
func nextField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode {
if _, exists := removedFields[code]; exists {
return nextField(code.NextField, removedFields)
}
return code
}
func linkPrevToNextField(cur *Opcode, removedFields map[*Opcode]struct{}) {
prev := prevField(cur.PrevField, removedFields)
prev.NextField = nextField(cur.NextField, removedFields)
code := prev
fcode := cur
for {
var nextCode *Opcode
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
nextCode = code.End
default:
nextCode = code.Next
}
if nextCode == fcode {
code.Next = fcode.Next
break
} else if nextCode.Op == OpEnd {
break
}
code = nextCode
}
}
func newSliceHeaderCode(ctx *compileContext) *Opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
length := opcodeOffset(ctx.ptrIndex)
return &Opcode{
Op: OpSlice,
DisplayIdx: ctx.opcodeIndex,
Idx: idx,
HeadIdx: idx,
ElemIdx: elemIdx,
Length: length,
Indent: ctx.indent,
}
}
func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode {
return &Opcode{
Op: OpSliceElem,
DisplayIdx: ctx.opcodeIndex,
Idx: opcodeOffset(ctx.ptrIndex),
HeadIdx: head.Idx,
ElemIdx: head.ElemIdx,
Length: head.Length,
Indent: ctx.indent,
Size: size,
}
}
func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
return &Opcode{
Op: OpArray,
DisplayIdx: ctx.opcodeIndex,
Idx: idx,
HeadIdx: idx,
ElemIdx: elemIdx,
Indent: ctx.indent,
Length: uintptr(alen),
}
}
func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintptr) *Opcode {
return &Opcode{
Op: OpArrayElem,
DisplayIdx: ctx.opcodeIndex,
Idx: opcodeOffset(ctx.ptrIndex),
ElemIdx: head.ElemIdx,
HeadIdx: head.HeadIdx,
Length: uintptr(length),
Indent: ctx.indent,
Size: size,
}
}
func newMapHeaderCode(ctx *compileContext) *Opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
length := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
mapIter := opcodeOffset(ctx.ptrIndex)
return &Opcode{
Op: OpMap,
Type: ctx.typ,
DisplayIdx: ctx.opcodeIndex,
Idx: idx,
ElemIdx: elemIdx,
Length: length,
MapIter: mapIter,
Indent: ctx.indent,
}
}
func newMapKeyCode(ctx *compileContext, head *Opcode) *Opcode {
return &Opcode{
Op: OpMapKey,
DisplayIdx: ctx.opcodeIndex,
Idx: opcodeOffset(ctx.ptrIndex),
ElemIdx: head.ElemIdx,
Length: head.Length,
MapIter: head.MapIter,
Indent: ctx.indent,
}
}
func newMapValueCode(ctx *compileContext, head *Opcode) *Opcode {
return &Opcode{
Op: OpMapValue,
DisplayIdx: ctx.opcodeIndex,
Idx: opcodeOffset(ctx.ptrIndex),
ElemIdx: head.ElemIdx,
Length: head.Length,
MapIter: head.MapIter,
Indent: ctx.indent,
}
}
func newMapEndCode(ctx *compileContext, head *Opcode) *Opcode {
mapPos := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
idx := opcodeOffset(ctx.ptrIndex)
return &Opcode{
Op: OpMapEnd,
DisplayIdx: ctx.opcodeIndex,
Idx: idx,
Length: head.Length,
MapPos: mapPos,
Indent: ctx.indent,
Next: newEndOp(ctx),
}
}
func newInterfaceCode(ctx *compileContext) *Opcode {
return &Opcode{
Op: OpInterface,
Type: ctx.typ,
DisplayIdx: ctx.opcodeIndex,
Idx: opcodeOffset(ctx.ptrIndex),
Indent: ctx.indent,
Next: newEndOp(ctx),
}
}
func newRecursiveCode(ctx *compileContext, jmp *CompiledCode) *Opcode {
return &Opcode{
Op: OpStructFieldRecursive,
Type: ctx.typ,
DisplayIdx: ctx.opcodeIndex,
Idx: opcodeOffset(ctx.ptrIndex),
Indent: ctx.indent,
Next: newEndOp(ctx),
Jmp: jmp,
}
}

1028
internal/encoder/optype.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package json
package encoder
import (
"math/bits"
@ -405,7 +405,7 @@ func stringToUint64Slice(s string) []uint64 {
}))
}
func encodeEscapedString(buf []byte, s string) []byte {
func AppendEscapedString(buf []byte, s string) []byte {
valLen := len(s)
if valLen == 0 {
return append(buf, `""`...)
@ -531,7 +531,7 @@ ESCAPE_END:
return append(append(buf, s[i:]...), '"')
}
func encodeNoEscapedString(buf []byte, s string) []byte {
func AppendString(buf []byte, s string) []byte {
valLen := len(s)
if valLen == 0 {
return append(buf, `""`...)

View File

@ -0,0 +1,78 @@
package vm
import (
"encoding/json"
"unsafe"
"github.com/goccy/go-json/internal/encoder"
"github.com/goccy/go-json/internal/runtime"
)
func load(base uintptr, idx uintptr) uintptr {
addr := base + idx
return **(**uintptr)(unsafe.Pointer(&addr))
}
func store(base uintptr, idx uintptr, p uintptr) {
addr := base + idx
**(**uintptr)(unsafe.Pointer(&addr)) = p
}
func loadNPtr(base uintptr, idx uintptr, _ int) uintptr {
addr := base + idx
p := **(**uintptr)(unsafe.Pointer(&addr))
if p == 0 {
return 0
}
return ptrToPtr(p)
}
func ptrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
func ptrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) }
func ptrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) }
func ptrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) }
func ptrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) }
func ptrToPtr(p uintptr) uintptr {
return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p)))
}
func ptrToNPtr(p uintptr, ptrNum int) uintptr {
for i := 0; i < ptrNum; i++ {
if p == 0 {
return 0
}
p = ptrToPtr(p)
}
return p
}
func ptrToUnsafePtr(p uintptr) unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&p))
}
func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} {
return *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: code.Type,
ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)),
}))
}
func appendBool(b []byte, v bool) []byte {
if v {
return append(b, "true"...)
}
return append(b, "false"...)
}
func appendNull(b []byte) []byte {
return append(b, "null"...)
}
func appendComma(b []byte) []byte {
return append(b, ',')
}
func appendStructEnd(b []byte) []byte {
return append(b, '}', ',')
}

4486
internal/encoder/vm/vm.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
package vm_escaped
import (
"encoding/json"
"unsafe"
"github.com/goccy/go-json/internal/encoder"
"github.com/goccy/go-json/internal/runtime"
)
func load(base uintptr, idx uintptr) uintptr {
addr := base + idx
return **(**uintptr)(unsafe.Pointer(&addr))
}
func store(base uintptr, idx uintptr, p uintptr) {
addr := base + idx
**(**uintptr)(unsafe.Pointer(&addr)) = p
}
func loadNPtr(base uintptr, idx uintptr, _ int) uintptr {
addr := base + idx
p := **(**uintptr)(unsafe.Pointer(&addr))
if p == 0 {
return 0
}
return ptrToPtr(p)
}
func ptrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
func ptrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) }
func ptrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) }
func ptrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) }
func ptrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) }
func ptrToPtr(p uintptr) uintptr {
return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p)))
}
func ptrToNPtr(p uintptr, ptrNum int) uintptr {
for i := 0; i < ptrNum; i++ {
if p == 0 {
return 0
}
p = ptrToPtr(p)
}
return p
}
func ptrToUnsafePtr(p uintptr) unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&p))
}
func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} {
return *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: code.Type,
ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)),
}))
}
func appendBool(b []byte, v bool) []byte {
if v {
return append(b, "true"...)
}
return append(b, "false"...)
}
func appendNull(b []byte) []byte {
return append(b, "null"...)
}
func appendComma(b []byte) []byte {
return append(b, ',')
}
func appendStructEnd(b []byte) []byte {
return append(b, '}', ',')
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
package vm_escaped_indent
import (
"bytes"
"encoding/json"
"unsafe"
"github.com/goccy/go-json/internal/encoder"
"github.com/goccy/go-json/internal/runtime"
)
func load(base uintptr, idx uintptr) uintptr {
addr := base + idx
return **(**uintptr)(unsafe.Pointer(&addr))
}
func store(base uintptr, idx uintptr, p uintptr) {
addr := base + idx
**(**uintptr)(unsafe.Pointer(&addr)) = p
}
func loadNPtr(base uintptr, idx uintptr, _ int) uintptr {
addr := base + idx
p := **(**uintptr)(unsafe.Pointer(&addr))
if p == 0 {
return 0
}
return ptrToPtr(p)
}
func ptrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
func ptrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) }
func ptrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) }
func ptrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) }
func ptrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) }
func ptrToPtr(p uintptr) uintptr {
return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p)))
}
func ptrToNPtr(p uintptr, ptrNum int) uintptr {
for i := 0; i < ptrNum; i++ {
if p == 0 {
return 0
}
p = ptrToPtr(p)
}
return p
}
func ptrToUnsafePtr(p uintptr) unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&p))
}
func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} {
return *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: code.Type,
ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)),
}))
}
func appendBool(b []byte, v bool) []byte {
if v {
return append(b, "true"...)
}
return append(b, "false"...)
}
func appendNull(b []byte) []byte {
return append(b, "null"...)
}
func appendComma(b []byte) []byte {
return append(b, ',', '\n')
}
func appendStructEnd(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
b = append(b, '\n')
b = append(b, ctx.Prefix...)
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
return append(b, '}', ',', '\n')
}
func appendIndent(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
b = append(b, ctx.Prefix...)
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
package vm_indent
import (
"bytes"
"encoding/json"
"unsafe"
"github.com/goccy/go-json/internal/encoder"
"github.com/goccy/go-json/internal/runtime"
)
func load(base uintptr, idx uintptr) uintptr {
addr := base + idx
return **(**uintptr)(unsafe.Pointer(&addr))
}
func store(base uintptr, idx uintptr, p uintptr) {
addr := base + idx
**(**uintptr)(unsafe.Pointer(&addr)) = p
}
func loadNPtr(base uintptr, idx uintptr, _ int) uintptr {
addr := base + idx
p := **(**uintptr)(unsafe.Pointer(&addr))
if p == 0 {
return 0
}
return ptrToPtr(p)
}
func ptrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
func ptrToBytes(p uintptr) []byte { return **(**[]byte)(unsafe.Pointer(&p)) }
func ptrToNumber(p uintptr) json.Number { return **(**json.Number)(unsafe.Pointer(&p)) }
func ptrToString(p uintptr) string { return **(**string)(unsafe.Pointer(&p)) }
func ptrToSlice(p uintptr) *runtime.SliceHeader { return *(**runtime.SliceHeader)(unsafe.Pointer(&p)) }
func ptrToPtr(p uintptr) uintptr {
return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&p)))
}
func ptrToNPtr(p uintptr, ptrNum int) uintptr {
for i := 0; i < ptrNum; i++ {
if p == 0 {
return 0
}
p = ptrToPtr(p)
}
return p
}
func ptrToUnsafePtr(p uintptr) unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&p))
}
func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} {
return *(*interface{})(unsafe.Pointer(&emptyInterface{
typ: code.Type,
ptr: *(*unsafe.Pointer)(unsafe.Pointer(&p)),
}))
}
func appendBool(b []byte, v bool) []byte {
if v {
return append(b, "true"...)
}
return append(b, "false"...)
}
func appendNull(b []byte) []byte {
return append(b, "null"...)
}
func appendComma(b []byte) []byte {
return append(b, ',', '\n')
}
func appendStructEnd(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
b = append(b, '\n')
b = append(b, ctx.Prefix...)
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
return append(b, '}', ',', '\n')
}
func appendIndent(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
b = append(b, ctx.Prefix...)
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
}

File diff suppressed because it is too large Load Diff

157
internal/errors/error.go Normal file
View File

@ -0,0 +1,157 @@
package errors
import (
"fmt"
"reflect"
"strconv"
)
type InvalidUTF8Error struct {
S string // the whole string value that caused the error
}
func (e *InvalidUTF8Error) Error() string {
return fmt.Sprintf("json: invalid UTF-8 in string: %s", strconv.Quote(e.S))
}
type InvalidUnmarshalError struct {
Type reflect.Type
}
func (e *InvalidUnmarshalError) Error() string {
if e.Type == nil {
return "json: Unmarshal(nil)"
}
if e.Type.Kind() != reflect.Ptr {
return fmt.Sprintf("json: Unmarshal(non-pointer %s)", e.Type)
}
return fmt.Sprintf("json: Unmarshal(nil %s)", e.Type)
}
// A MarshalerError represents an error from calling a MarshalJSON or MarshalText method.
type MarshalerError struct {
Type reflect.Type
Err error
sourceFunc string
}
func (e *MarshalerError) Error() string {
srcFunc := e.sourceFunc
if srcFunc == "" {
srcFunc = "MarshalJSON"
}
return fmt.Sprintf("json: error calling %s for type %s: %s", srcFunc, e.Type, e.Err.Error())
}
// Unwrap returns the underlying error.
func (e *MarshalerError) Unwrap() error { return e.Err }
// A SyntaxError is a description of a JSON syntax error.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
// An UnmarshalFieldError describes a JSON object key that
// led to an unexported (and therefore unwritable) struct field.
//
// Deprecated: No longer used; kept for compatibility.
type UnmarshalFieldError struct {
Key string
Type reflect.Type
Field reflect.StructField
}
func (e *UnmarshalFieldError) Error() string {
return fmt.Sprintf("json: cannot unmarshal object key %s into unexported field %s of type %s",
strconv.Quote(e.Key), e.Field.Name, e.Type.String(),
)
}
// An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
Value string // description of JSON value - "bool", "array", "number -5"
Type reflect.Type // type of Go value it could not be assigned to
Offset int64 // error occurred after reading Offset bytes
Struct string // name of the struct type containing the field
Field string // the full path from root node to the field
}
func (e *UnmarshalTypeError) Error() string {
if e.Struct != "" || e.Field != "" {
return fmt.Sprintf("json: cannot unmarshal %s into Go struct field %s.%s of type %s",
e.Value, e.Struct, e.Field, e.Type,
)
}
return fmt.Sprintf("json: cannot unmarshal %s into Go value of type %s", e.Value, e.Type)
}
// An UnsupportedTypeError is returned by Marshal when attempting
// to encode an unsupported value type.
type UnsupportedTypeError struct {
Type reflect.Type
}
func (e *UnsupportedTypeError) Error() string {
return fmt.Sprintf("json: unsupported type: %s", e.Type)
}
type UnsupportedValueError struct {
Value reflect.Value
Str string
}
func (e *UnsupportedValueError) Error() string {
return fmt.Sprintf("json: unsupported value: %s", e.Str)
}
func ErrSyntax(msg string, offset int64) *SyntaxError {
return &SyntaxError{msg: msg, Offset: offset}
}
func ErrMarshaler(typ reflect.Type, err error, msg string) *MarshalerError {
return &MarshalerError{
Type: typ,
Err: err,
sourceFunc: msg,
}
}
func ErrExceededMaxDepth(c byte, cursor int64) *SyntaxError {
return &SyntaxError{
msg: fmt.Sprintf(`invalid character "%c" exceeded max depth`, c),
Offset: cursor,
}
}
func ErrNotAtBeginningOfValue(cursor int64) *SyntaxError {
return &SyntaxError{msg: "not at beginning of value", Offset: cursor}
}
func ErrUnexpectedEndOfJSON(msg string, cursor int64) *SyntaxError {
return &SyntaxError{
msg: fmt.Sprintf("json: %s unexpected end of JSON input", msg),
Offset: cursor,
}
}
func ErrExpected(msg string, cursor int64) *SyntaxError {
return &SyntaxError{msg: fmt.Sprintf("expected %s", msg), Offset: cursor}
}
func ErrInvalidCharacter(c byte, context string, cursor int64) *SyntaxError {
if c == 0 {
return &SyntaxError{
msg: fmt.Sprintf("json: invalid character as %s", context),
Offset: cursor,
}
}
return &SyntaxError{
msg: fmt.Sprintf("json: invalid character %c as %s", c, context),
Offset: cursor,
}
}

263
internal/runtime/rtype.go Normal file
View File

@ -0,0 +1,263 @@
package runtime
import (
"reflect"
"unsafe"
)
// Type representing reflect.rtype for noescape trick
type Type struct{}
//go:linkname rtype_Align reflect.(*rtype).Align
//go:noescape
func rtype_Align(*Type) int
func (t *Type) Align() int {
return rtype_Align(t)
}
//go:linkname rtype_FieldAlign reflect.(*rtype).FieldAlign
//go:noescape
func rtype_FieldAlign(*Type) int
func (t *Type) FieldAlign() int {
return rtype_FieldAlign(t)
}
//go:linkname rtype_Method reflect.(*rtype).Method
//go:noescape
func rtype_Method(*Type, int) reflect.Method
func (t *Type) Method(a0 int) reflect.Method {
return rtype_Method(t, a0)
}
//go:linkname rtype_MethodByName reflect.(*rtype).MethodByName
//go:noescape
func rtype_MethodByName(*Type, string) (reflect.Method, bool)
func (t *Type) MethodByName(a0 string) (reflect.Method, bool) {
return rtype_MethodByName(t, a0)
}
//go:linkname rtype_NumMethod reflect.(*rtype).NumMethod
//go:noescape
func rtype_NumMethod(*Type) int
func (t *Type) NumMethod() int {
return rtype_NumMethod(t)
}
//go:linkname rtype_Name reflect.(*rtype).Name
//go:noescape
func rtype_Name(*Type) string
func (t *Type) Name() string {
return rtype_Name(t)
}
//go:linkname rtype_PkgPath reflect.(*rtype).PkgPath
//go:noescape
func rtype_PkgPath(*Type) string
func (t *Type) PkgPath() string {
return rtype_PkgPath(t)
}
//go:linkname rtype_Size reflect.(*rtype).Size
//go:noescape
func rtype_Size(*Type) uintptr
func (t *Type) Size() uintptr {
return rtype_Size(t)
}
//go:linkname rtype_String reflect.(*rtype).String
//go:noescape
func rtype_String(*Type) string
func (t *Type) String() string {
return rtype_String(t)
}
//go:linkname rtype_Kind reflect.(*rtype).Kind
//go:noescape
func rtype_Kind(*Type) reflect.Kind
func (t *Type) Kind() reflect.Kind {
return rtype_Kind(t)
}
//go:linkname rtype_Implements reflect.(*rtype).Implements
//go:noescape
func rtype_Implements(*Type, reflect.Type) bool
func (t *Type) Implements(u reflect.Type) bool {
return rtype_Implements(t, u)
}
//go:linkname rtype_AssignableTo reflect.(*rtype).AssignableTo
//go:noescape
func rtype_AssignableTo(*Type, reflect.Type) bool
func (t *Type) AssignableTo(u reflect.Type) bool {
return rtype_AssignableTo(t, u)
}
//go:linkname rtype_ConvertibleTo reflect.(*rtype).ConvertibleTo
//go:noescape
func rtype_ConvertibleTo(*Type, reflect.Type) bool
func (t *Type) ConvertibleTo(u reflect.Type) bool {
return rtype_ConvertibleTo(t, u)
}
//go:linkname rtype_Comparable reflect.(*rtype).Comparable
//go:noescape
func rtype_Comparable(*Type) bool
func (t *Type) Comparable() bool {
return rtype_Comparable(t)
}
//go:linkname rtype_Bits reflect.(*rtype).Bits
//go:noescape
func rtype_Bits(*Type) int
func (t *Type) Bits() int {
return rtype_Bits(t)
}
//go:linkname rtype_ChanDir reflect.(*rtype).ChanDir
//go:noescape
func rtype_ChanDir(*Type) reflect.ChanDir
func (t *Type) ChanDir() reflect.ChanDir {
return rtype_ChanDir(t)
}
//go:linkname rtype_IsVariadic reflect.(*rtype).IsVariadic
//go:noescape
func rtype_IsVariadic(*Type) bool
func (t *Type) IsVariadic() bool {
return rtype_IsVariadic(t)
}
//go:linkname rtype_Elem reflect.(*rtype).Elem
//go:noescape
func rtype_Elem(*Type) reflect.Type
func (t *Type) Elem() *Type {
return Type2RType(rtype_Elem(t))
}
//go:linkname rtype_Field reflect.(*rtype).Field
//go:noescape
func rtype_Field(*Type, int) reflect.StructField
func (t *Type) Field(i int) reflect.StructField {
return rtype_Field(t, i)
}
//go:linkname rtype_FieldByIndex reflect.(*rtype).FieldByIndex
//go:noescape
func rtype_FieldByIndex(*Type, []int) reflect.StructField
func (t *Type) FieldByIndex(index []int) reflect.StructField {
return rtype_FieldByIndex(t, index)
}
//go:linkname rtype_FieldByName reflect.(*rtype).FieldByName
//go:noescape
func rtype_FieldByName(*Type, string) (reflect.StructField, bool)
func (t *Type) FieldByName(name string) (reflect.StructField, bool) {
return rtype_FieldByName(t, name)
}
//go:linkname rtype_FieldByNameFunc reflect.(*rtype).FieldByNameFunc
//go:noescape
func rtype_FieldByNameFunc(*Type, func(string) bool) (reflect.StructField, bool)
func (t *Type) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) {
return rtype_FieldByNameFunc(t, match)
}
//go:linkname rtype_In reflect.(*rtype).In
//go:noescape
func rtype_In(*Type, int) reflect.Type
func (t *Type) In(i int) reflect.Type {
return rtype_In(t, i)
}
//go:linkname rtype_Key reflect.(*rtype).Key
//go:noescape
func rtype_Key(*Type) reflect.Type
func (t *Type) Key() *Type {
return Type2RType(rtype_Key(t))
}
//go:linkname rtype_Len reflect.(*rtype).Len
//go:noescape
func rtype_Len(*Type) int
func (t *Type) Len() int {
return rtype_Len(t)
}
//go:linkname rtype_NumField reflect.(*rtype).NumField
//go:noescape
func rtype_NumField(*Type) int
func (t *Type) NumField() int {
return rtype_NumField(t)
}
//go:linkname rtype_NumIn reflect.(*rtype).NumIn
//go:noescape
func rtype_NumIn(*Type) int
func (t *Type) NumIn() int {
return rtype_NumIn(t)
}
//go:linkname rtype_NumOut reflect.(*rtype).NumOut
//go:noescape
func rtype_NumOut(*Type) int
func (t *Type) NumOut() int {
return rtype_NumOut(t)
}
//go:linkname rtype_Out reflect.(*rtype).Out
//go:noescape
func rtype_Out(*Type, int) reflect.Type
//go:linkname PtrTo reflect.(*rtype).ptrTo
//go:noescape
func PtrTo(*Type) *Type
func (t *Type) Out(i int) reflect.Type {
return rtype_Out(t, i)
}
//go:linkname IfaceIndir reflect.ifaceIndir
//go:noescape
func IfaceIndir(*Type) bool
//go:linkname RType2Type reflect.toType
//go:noescape
func RType2Type(t *Type) reflect.Type
//go:nolint structcheck
type emptyInterface struct {
_ *Type
ptr unsafe.Pointer
}
func Type2RType(t reflect.Type) *Type {
return (*Type)(((*emptyInterface)(unsafe.Pointer(&t))).ptr)
}

View File

@ -1,4 +1,4 @@
package json
package runtime
import (
"reflect"
@ -10,7 +10,7 @@ func getTag(field reflect.StructField) string {
return field.Tag.Get("json")
}
func isIgnoredStructField(field reflect.StructField) bool {
func IsIgnoredStructField(field reflect.StructField) bool {
if field.PkgPath != "" {
if field.Anonymous {
if !(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct) && field.Type.Kind() != reflect.Struct {
@ -25,19 +25,19 @@ func isIgnoredStructField(field reflect.StructField) bool {
return tag == "-"
}
type structTag struct {
key string
isTaggedKey bool
isOmitEmpty bool
isString bool
field reflect.StructField
type StructTag struct {
Key string
IsTaggedKey bool
IsOmitEmpty bool
IsString bool
Field reflect.StructField
}
type structTags []*structTag
type StructTags []*StructTag
func (t structTags) existsKey(key string) bool {
func (t StructTags) ExistsKey(key string) bool {
for _, tt := range t {
if tt.key == key {
if tt.Key == key {
return true
}
}
@ -61,21 +61,21 @@ func isValidTag(s string) bool {
return true
}
func structTagFromField(field reflect.StructField) *structTag {
func StructTagFromField(field reflect.StructField) *StructTag {
keyName := field.Name
tag := getTag(field)
st := &structTag{field: field}
st := &StructTag{Field: field}
opts := strings.Split(tag, ",")
if len(opts) > 0 {
if opts[0] != "" && isValidTag(opts[0]) {
keyName = opts[0]
st.isTaggedKey = true
st.IsTaggedKey = true
}
}
st.key = keyName
st.Key = keyName
if len(opts) > 1 {
st.isOmitEmpty = opts[1] == "omitempty"
st.isString = opts[1] == "string"
st.IsOmitEmpty = opts[1] == "omitempty"
st.IsString = opts[1] == "string"
}
return st
}

87
internal/runtime/type.go Normal file
View File

@ -0,0 +1,87 @@
package runtime
import (
"reflect"
"unsafe"
)
type SliceHeader struct {
Data unsafe.Pointer
Len int
Cap int
}
const (
maxAcceptableTypeAddrRange = 1024 * 1024 * 2 // 2 Mib
)
type TypeAddr struct {
BaseTypeAddr uintptr
MaxTypeAddr uintptr
AddrRange uintptr
}
var (
typeAddr *TypeAddr
alreadyAnalyzed bool
)
//go:linkname typelinks reflect.typelinks
func typelinks() ([]unsafe.Pointer, [][]int32)
//go:linkname rtypeOff reflect.rtypeOff
func rtypeOff(unsafe.Pointer, int32) unsafe.Pointer
func AnalyzeTypeAddr() *TypeAddr {
defer func() {
alreadyAnalyzed = true
}()
if alreadyAnalyzed {
return typeAddr
}
sections, offsets := typelinks()
if len(sections) != 1 {
return nil
}
if len(offsets) != 1 {
return nil
}
section := sections[0]
offset := offsets[0]
var (
min uintptr = uintptr(^uint(0))
max uintptr = 0
)
for i := 0; i < len(offset); i++ {
typ := (*Type)(rtypeOff(section, offset[i]))
addr := uintptr(unsafe.Pointer(typ))
if min > addr {
min = addr
}
if max < addr {
max = addr
}
if typ.Kind() == reflect.Ptr {
addr = uintptr(unsafe.Pointer(typ.Elem()))
if min > addr {
min = addr
}
if max < addr {
max = addr
}
}
}
addrRange := max - min
if addrRange == 0 {
return nil
}
if addrRange > maxAcceptableTypeAddrRange {
return nil
}
typeAddr = &TypeAddr{
BaseTypeAddr: min,
MaxTypeAddr: max,
AddrRange: addrRange,
}
return typeAddr
}

View File

@ -4,6 +4,8 @@ import (
"bytes"
"encoding/json"
"errors"
"github.com/goccy/go-json/internal/encoder"
)
// Marshaler is the interface implemented by types that
@ -308,7 +310,7 @@ func (m *RawMessage) UnmarshalJSON(data []byte) error {
// Compact appends to dst the JSON-encoded src with
// insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error {
return compact(dst, src, false)
return encoder.Compact(dst, src, false)
}
// Indent appends to dst an indented form of the JSON-encoded src.
@ -323,7 +325,7 @@ func Compact(dst *bytes.Buffer, src []byte) error {
// For example, if src has no trailing spaces, neither will dst;
// if src ends in a trailing newline, so will dst.
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
return encodeWithIndent(dst, src, prefix, indent)
return encoder.Indent(dst, src, prefix, indent)
}
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029

263
rtype.go
View File

@ -3,260 +3,25 @@ package json
import (
"reflect"
"unsafe"
"github.com/goccy/go-json/internal/runtime"
)
// rtype representing reflect.rtype for noescape trick
type rtype struct{}
type rtype = runtime.Type
//go:linkname rtype_Align reflect.(*rtype).Align
//go:noescape
func rtype_Align(*rtype) int
func (t *rtype) Align() int {
return rtype_Align(t)
}
//go:linkname rtype_FieldAlign reflect.(*rtype).FieldAlign
//go:noescape
func rtype_FieldAlign(*rtype) int
func (t *rtype) FieldAlign() int {
return rtype_FieldAlign(t)
}
//go:linkname rtype_Method reflect.(*rtype).Method
//go:noescape
func rtype_Method(*rtype, int) reflect.Method
func (t *rtype) Method(a0 int) reflect.Method {
return rtype_Method(t, a0)
}
//go:linkname rtype_MethodByName reflect.(*rtype).MethodByName
//go:noescape
func rtype_MethodByName(*rtype, string) (reflect.Method, bool)
func (t *rtype) MethodByName(a0 string) (reflect.Method, bool) {
return rtype_MethodByName(t, a0)
}
//go:linkname rtype_NumMethod reflect.(*rtype).NumMethod
//go:noescape
func rtype_NumMethod(*rtype) int
func (t *rtype) NumMethod() int {
return rtype_NumMethod(t)
}
//go:linkname rtype_Name reflect.(*rtype).Name
//go:noescape
func rtype_Name(*rtype) string
func (t *rtype) Name() string {
return rtype_Name(t)
}
//go:linkname rtype_PkgPath reflect.(*rtype).PkgPath
//go:noescape
func rtype_PkgPath(*rtype) string
func (t *rtype) PkgPath() string {
return rtype_PkgPath(t)
}
//go:linkname rtype_Size reflect.(*rtype).Size
//go:noescape
func rtype_Size(*rtype) uintptr
func (t *rtype) Size() uintptr {
return rtype_Size(t)
}
//go:linkname rtype_String reflect.(*rtype).String
//go:noescape
func rtype_String(*rtype) string
func (t *rtype) String() string {
return rtype_String(t)
}
//go:linkname rtype_Kind reflect.(*rtype).Kind
//go:noescape
func rtype_Kind(*rtype) reflect.Kind
func (t *rtype) Kind() reflect.Kind {
return rtype_Kind(t)
}
//go:linkname rtype_Implements reflect.(*rtype).Implements
//go:noescape
func rtype_Implements(*rtype, reflect.Type) bool
func (t *rtype) Implements(u reflect.Type) bool {
return rtype_Implements(t, u)
}
//go:linkname rtype_AssignableTo reflect.(*rtype).AssignableTo
//go:noescape
func rtype_AssignableTo(*rtype, reflect.Type) bool
func (t *rtype) AssignableTo(u reflect.Type) bool {
return rtype_AssignableTo(t, u)
}
//go:linkname rtype_ConvertibleTo reflect.(*rtype).ConvertibleTo
//go:noescape
func rtype_ConvertibleTo(*rtype, reflect.Type) bool
func (t *rtype) ConvertibleTo(u reflect.Type) bool {
return rtype_ConvertibleTo(t, u)
}
//go:linkname rtype_Comparable reflect.(*rtype).Comparable
//go:noescape
func rtype_Comparable(*rtype) bool
func (t *rtype) Comparable() bool {
return rtype_Comparable(t)
}
//go:linkname rtype_Bits reflect.(*rtype).Bits
//go:noescape
func rtype_Bits(*rtype) int
func (t *rtype) Bits() int {
return rtype_Bits(t)
}
//go:linkname rtype_ChanDir reflect.(*rtype).ChanDir
//go:noescape
func rtype_ChanDir(*rtype) reflect.ChanDir
func (t *rtype) ChanDir() reflect.ChanDir {
return rtype_ChanDir(t)
}
//go:linkname rtype_IsVariadic reflect.(*rtype).IsVariadic
//go:noescape
func rtype_IsVariadic(*rtype) bool
func (t *rtype) IsVariadic() bool {
return rtype_IsVariadic(t)
}
//go:linkname rtype_Elem reflect.(*rtype).Elem
//go:noescape
func rtype_Elem(*rtype) reflect.Type
func (t *rtype) Elem() *rtype {
return type2rtype(rtype_Elem(t))
}
//go:linkname rtype_Field reflect.(*rtype).Field
//go:noescape
func rtype_Field(*rtype, int) reflect.StructField
func (t *rtype) Field(i int) reflect.StructField {
return rtype_Field(t, i)
}
//go:linkname rtype_FieldByIndex reflect.(*rtype).FieldByIndex
//go:noescape
func rtype_FieldByIndex(*rtype, []int) reflect.StructField
func (t *rtype) FieldByIndex(index []int) reflect.StructField {
return rtype_FieldByIndex(t, index)
}
//go:linkname rtype_FieldByName reflect.(*rtype).FieldByName
//go:noescape
func rtype_FieldByName(*rtype, string) (reflect.StructField, bool)
func (t *rtype) FieldByName(name string) (reflect.StructField, bool) {
return rtype_FieldByName(t, name)
}
//go:linkname rtype_FieldByNameFunc reflect.(*rtype).FieldByNameFunc
//go:noescape
func rtype_FieldByNameFunc(*rtype, func(string) bool) (reflect.StructField, bool)
func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) {
return rtype_FieldByNameFunc(t, match)
}
//go:linkname rtype_In reflect.(*rtype).In
//go:noescape
func rtype_In(*rtype, int) reflect.Type
func (t *rtype) In(i int) reflect.Type {
return rtype_In(t, i)
}
//go:linkname rtype_Key reflect.(*rtype).Key
//go:noescape
func rtype_Key(*rtype) reflect.Type
func (t *rtype) Key() *rtype {
return type2rtype(rtype_Key(t))
}
//go:linkname rtype_Len reflect.(*rtype).Len
//go:noescape
func rtype_Len(*rtype) int
func (t *rtype) Len() int {
return rtype_Len(t)
}
//go:linkname rtype_NumField reflect.(*rtype).NumField
//go:noescape
func rtype_NumField(*rtype) int
func (t *rtype) NumField() int {
return rtype_NumField(t)
}
//go:linkname rtype_NumIn reflect.(*rtype).NumIn
//go:noescape
func rtype_NumIn(*rtype) int
func (t *rtype) NumIn() int {
return rtype_NumIn(t)
}
//go:linkname rtype_NumOut reflect.(*rtype).NumOut
//go:noescape
func rtype_NumOut(*rtype) int
func (t *rtype) NumOut() int {
return rtype_NumOut(t)
}
//go:linkname rtype_Out reflect.(*rtype).Out
//go:noescape
func rtype_Out(*rtype, int) reflect.Type
//go:linkname rtype_ptrTo reflect.(*rtype).ptrTo
//go:noescape
func rtype_ptrTo(*rtype) *rtype
func (t *rtype) Out(i int) reflect.Type {
return rtype_Out(t, i)
}
//go:linkname ifaceIndir reflect.ifaceIndir
//go:noescape
func ifaceIndir(*rtype) bool
//go:linkname rtype2type reflect.toType
//go:noescape
func rtype2type(t *rtype) reflect.Type
type interfaceHeader struct {
type emptyInterface struct {
typ *rtype
ptr unsafe.Pointer
}
func type2rtype(t reflect.Type) *rtype {
return (*rtype)(((*interfaceHeader)(unsafe.Pointer(&t))).ptr)
func rtype_ptrTo(t *rtype) *rtype {
return runtime.PtrTo(t)
}
func rtype2type(t *rtype) reflect.Type {
return runtime.RType2Type(t)
}
func type2rtype(t reflect.Type) *rtype {
return runtime.Type2RType(t)
}