Compare commits

..

7 Commits

Author SHA1 Message Date
Masaaki Goshima 68c500590e
Fix string.c 2021-12-29 21:23:36 +09:00
Masaaki Goshima e736de7070
Fix string.go 2021-12-29 02:03:40 +09:00
Masaaki Goshima f2e0e6edea
Refactor decode_rune 2021-12-29 02:03:07 +09:00
Masaaki Goshima 118663d59f
Update SIMD codes 2021-12-28 03:22:39 +09:00
Masaaki Goshima 4019c11e82
Fix SIMD codes 2021-12-28 01:57:53 +09:00
Masaaki Goshima 390aa2d0ea
Fix SIMD API 2021-12-28 01:37:33 +09:00
Masaaki Goshima 38b316a540
Generate SIMD files for encoding 2021-12-28 01:13:18 +09:00
85 changed files with 2242 additions and 4555 deletions

View File

@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
os: [ "ubuntu-latest", "macos-latest", "windows-latest" ]
go-version: [ "1.16", "1.17", "1.18" ]
go-version: [ "1.15", "1.16", "1.17" ]
runs-on: ${{ matrix.os }}
steps:
- name: setup Go ${{ matrix.go-version }}
@ -42,7 +42,7 @@ jobs:
- name: setup Go
uses: actions/setup-go@v2
with:
go-version: 1.18
go-version: 1.17
- name: checkout ( feature )
uses: actions/checkout@v2
- name: run benchmark ( feature )
@ -64,7 +64,7 @@ jobs:
- name: setup Go
uses: actions/setup-go@v2
with:
go-version: 1.18
go-version: 1.17
- name: checkout
uses: actions/checkout@v2
- name: measure coverage

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: golangci/golangci-lint-action@v3
- uses: golangci/golangci-lint-action@v2
with:
version: v1.45.2
version: v1.36.0
args: --timeout=5m

View File

@ -48,14 +48,6 @@ linters:
- nlreturn
- testpackage
- wsl
- varnamelen
- nilnil
- ireturn
- govet
- forcetypeassert
- cyclop
- containedctx
- revive
issues:
exclude-rules:

View File

@ -1,129 +1,3 @@
# v0.10.0 - 2022/11/29
### New features
* Support JSON Path ( #250 )
### Fix bugs
* Fix marshaler for map's key ( #409 )
# v0.9.11 - 2022/08/18
### Fix bugs
* Fix unexpected behavior when buffer ends with backslash ( #383 )
* Fix stream decoding of escaped character ( #387 )
# v0.9.10 - 2022/07/15
### Fix bugs
* Fix boundary exception of type caching ( #382 )
# v0.9.9 - 2022/07/15
### Fix bugs
* Fix encoding of directed interface with typed nil ( #377 )
* Fix embedded primitive type encoding using alias ( #378 )
* Fix slice/array type encoding with types implementing MarshalJSON ( #379 )
* Fix unicode decoding when the expected buffer state is not met after reading ( #380 )
# v0.9.8 - 2022/06/30
### Fix bugs
* Fix decoding of surrogate-pair ( #365 )
* Fix handling of embedded primitive type ( #366 )
* Add validation of escape sequence for decoder ( #367 )
* Fix stream tokenizing respecting UseNumber ( #369 )
* Fix encoding when struct pointer type that implements Marshal JSON is embedded ( #375 )
### Improve performance
* Improve performance of linkRecursiveCode ( #368 )
# v0.9.7 - 2022/04/22
### Fix bugs
#### Encoder
* Add filtering process for encoding on slow path ( #355 )
* Fix encoding of interface{} with pointer type ( #363 )
#### Decoder
* Fix map key decoder that implements UnmarshalJSON ( #353 )
* Fix decoding of []uint8 type ( #361 )
### New features
* Add DebugWith option for encoder ( #356 )
# v0.9.6 - 2022/03/22
### Fix bugs
* Correct the handling of the minimum value of int type for decoder ( #344 )
* Fix bugs of stream decoder's bufferSize ( #349 )
* Add a guard to use typeptr more safely ( #351 )
### Improve decoder performance
* Improve escapeString's performance ( #345 )
### Others
* Update go version for CI ( #347 )
# v0.9.5 - 2022/03/04
### Fix bugs
* Fix panic when decoding time.Time with context ( #328 )
* Fix reading the next character in buffer to nul consideration ( #338 )
* Fix incorrect handling on skipValue ( #341 )
### Improve decoder performance
* Improve performance when a payload contains escape sequence ( #334 )
# v0.9.4 - 2022/01/21
* Fix IsNilForMarshaler for string type with omitempty ( #323 )
* Fix the case where the embedded field is at the end ( #326 )
# v0.9.3 - 2022/01/14
* Fix logic of removing struct field for decoder ( #322 )
# v0.9.2 - 2022/01/14
* Add invalid decoder to delay type error judgment at decode ( #321 )
# v0.9.1 - 2022/01/11
* Fix encoding of MarshalText/MarshalJSON operation with head offset ( #319 )
# v0.9.0 - 2022/01/05
### New feature
* Supports dynamic filtering of struct fields ( #314 )
### Improve encoding performance
* Improve map encoding performance ( #310 )
* Optimize encoding path for escaped string ( #311 )
* Add encoding option for performance ( #312 )
### Fix bugs
* Fix panic at encoding map value on 1.18 ( #310 )
* Fix MarshalIndent for interface type ( #317 )
# v0.8.1 - 2021/12/05
* Fix operation conversion from PtrHead to Head in Recursive type ( #305 )

View File

@ -22,7 +22,7 @@ cover-html: cover
.PHONY: lint
lint: golangci-lint
$(BIN_DIR)/golangci-lint run
golangci-lint run
golangci-lint: | $(BIN_DIR)
@{ \
@ -30,10 +30,10 @@ golangci-lint: | $(BIN_DIR)
GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \
cd $$GOLANGCI_LINT_TMP_DIR; \
go mod init tmp; \
GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.48.0; \
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/...
cd internal/cmd/generator && go generate .

View File

@ -13,7 +13,7 @@ Fast JSON encoder/decoder compatible with encoding/json for Go
```
* version ( expected release date )
* v0.9.0
* v0.8.0
|
| while maintaining compatibility with encoding/json, we will add convenient APIs
|
@ -21,8 +21,9 @@ Fast JSON encoder/decoder compatible with encoding/json for Go
* v1.0.0
```
We are accepting requests for features that will be implemented between v0.9.0 and v.1.0.0.
We are accepting requests for features that will be implemented between v0.8.0 and v.1.0.0.
If you have the API you need, please submit your issue [here](https://github.com/goccy/go-json/issues).
For example, I'm thinking of supporting `context.Context` of `json.Marshaler` and decoding using JSON Path.
# Features
@ -31,7 +32,6 @@ If you have the API you need, please submit your issue [here](https://github.com
- Flexible customization with options
- Coloring the encoded string
- Can propagate context.Context to `MarshalJSON` or `UnmarshalJSON`
- Can dynamically filter the fields of the structure type-safely
# Installation
@ -184,7 +184,7 @@ func Marshal(v interface{}) ([]byte, error) {
`json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process.
In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped.
Therefore, the arguments for `Marshal` and `Unmarshal` are always escaped to the heap.
Therefore, the arguments for `Marshal` and `Unmarshal` are always escape to the heap.
However, `go-json` can use the feature of `reflect.Type` while avoiding escaping.
`reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package.

View File

@ -477,13 +477,3 @@ func Benchmark_Decode_LargeStruct_Stream_GoJsonFirstWinMode(b *testing.B) {
}
}
}
func Benchmark_Decode_LargeSlice_EscapedString_GoJson(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var v []string
if err := gojson.Unmarshal(LargeSliceEscapedString, &v); err != nil {
b.Fatal(err)
}
}
}

View File

@ -2,7 +2,6 @@ package benchmark
import (
"bytes"
"context"
"encoding/json"
"testing"
@ -836,80 +835,3 @@ func Benchmark_Encode_MarshalJSON_GoJson(b *testing.B) {
}
}
}
type queryTestX struct {
XA int
XB string
XC *queryTestY
XD bool
XE float32
}
type queryTestY struct {
YA int
YB string
YC bool
YD float32
}
func Benchmark_Encode_FilterByMap(b *testing.B) {
v := &queryTestX{
XA: 1,
XB: "xb",
XC: &queryTestY{
YA: 2,
YB: "yb",
YC: true,
YD: 4,
},
XD: true,
XE: 5,
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
filteredMap := map[string]interface{}{
"XA": v.XA,
"XB": v.XB,
"XC": map[string]interface{}{
"YA": v.XC.YA,
"YB": v.XC.YB,
},
}
if _, err := gojson.Marshal(filteredMap); err != nil {
b.Fatal(err)
}
}
}
func Benchmark_Encode_FilterByFieldQuery(b *testing.B) {
query, err := gojson.BuildFieldQuery(
"XA",
"XB",
gojson.BuildSubFieldQuery("XC").Fields(
"YA",
"YB",
),
)
if err != nil {
b.Fatal(err)
}
v := &queryTestX{
XA: 1,
XB: "xb",
XC: &queryTestY{
YA: 2,
YB: "yb",
YC: true,
YD: 4,
},
XD: true,
XE: 5,
}
ctx := gojson.SetFieldQueryToContext(context.Background(), query)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := gojson.MarshalContext(ctx, v); err != nil {
b.Fatal(err)
}
}
}

View File

@ -2,7 +2,6 @@ package benchmark
import (
"strconv"
"strings"
"github.com/francoispqt/gojay"
)
@ -209,5 +208,3 @@ func NewLargePayloadEasyJson() *LargePayloadEasyJson {
},
}
}
var LargeSliceEscapedString = []byte("[" + strings.Repeat(",\"simple plain text\\r\\n\"", 10000)[1:] + "]")

View File

@ -1,24 +0,0 @@
package benchmark
import (
"testing"
gojson "github.com/goccy/go-json"
)
func Benchmark_Decode_SmallStruct_UnmarshalPath_GoJson(b *testing.B) {
path, err := gojson.CreatePath("$.st")
if err != nil {
b.Fatal(err)
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var v int
if err := path.Unmarshal(SmallFixture, &v); err != nil {
b.Fatal(err)
}
if v != 1 {
b.Fatal("failed to unmarshal path")
}
}
}

View File

@ -83,37 +83,6 @@ func unmarshalContext(ctx context.Context, data []byte, v interface{}, optFuncs
return validateEndBuf(src, cursor)
}
var (
pathDecoder = decoder.NewPathDecoder()
)
func extractFromPath(path *Path, data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) {
if path.path.RootSelectorOnly {
return [][]byte{data}, nil
}
src := make([]byte, len(data)+1) // append nul byte to the end
copy(src, data)
ctx := decoder.TakeRuntimeContext()
ctx.Buf = src
ctx.Option.Flags = 0
ctx.Option.Flags |= decoder.PathOption
ctx.Option.Path = path.path
for _, optFunc := range optFuncs {
optFunc(ctx.Option)
}
paths, cursor, err := pathDecoder.DecodePath(ctx, 0, 0)
if err != nil {
decoder.ReleaseRuntimeContext(ctx)
return nil, err
}
decoder.ReleaseRuntimeContext(ctx)
if err := validateEndBuf(src, cursor); err != nil {
return nil, err
}
return paths, nil
}
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
src := make([]byte, len(data)+1) // append nul byte to the end
copy(src, data)

View File

@ -1317,12 +1317,6 @@ var unmarshalTests = []unmarshalTest{
ptr: new(string),
out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld",
},
{in: "-128", ptr: new(int8), out: int8(-128)},
{in: "127", ptr: new(int8), out: int8(127)},
{in: "-32768", ptr: new(int16), out: int16(-32768)},
{in: "32767", ptr: new(int16), out: int16(32767)},
{in: "-2147483648", ptr: new(int32), out: int32(-2147483648)},
{in: "2147483647", ptr: new(int32), out: int32(2147483647)},
}
type All struct {
@ -3782,236 +3776,3 @@ func TestIssue282(t *testing.T) {
t.Fatalf("failed to assign map value")
}
}
func TestDecodeStructFieldMap(t *testing.T) {
type Foo struct {
Bar map[float64]float64 `json:"bar,omitempty"`
}
var v Foo
if err := json.Unmarshal([]byte(`{"name":"test"}`), &v); err != nil {
t.Fatal(err)
}
if v.Bar != nil {
t.Fatalf("failed to decode v.Bar = %+v", v.Bar)
}
}
type issue303 struct {
Count int
Type string
Value interface{}
}
func (t *issue303) UnmarshalJSON(b []byte) error {
type tmpType issue303
wrapped := struct {
Value json.RawMessage
tmpType
}{}
if err := json.Unmarshal(b, &wrapped); err != nil {
return err
}
*t = issue303(wrapped.tmpType)
switch wrapped.Type {
case "string":
var str string
if err := json.Unmarshal(wrapped.Value, &str); err != nil {
return err
}
t.Value = str
}
return nil
}
func TestIssue303(t *testing.T) {
var v issue303
if err := json.Unmarshal([]byte(`{"Count":7,"Type":"string","Value":"hello"}`), &v); err != nil {
t.Fatal(err)
}
if v.Count != 7 || v.Type != "string" || v.Value != "hello" {
t.Fatalf("failed to decode. count = %d type = %s value = %v", v.Count, v.Type, v.Value)
}
}
func TestIssue327(t *testing.T) {
var v struct {
Date time.Time `json:"date"`
}
dec := json.NewDecoder(strings.NewReader(`{"date": "2021-11-23T13:47:30+01:00"})`))
if err := dec.DecodeContext(context.Background(), &v); err != nil {
t.Fatal(err)
}
expected := "2021-11-23T13:47:30+01:00"
if got := v.Date.Format(time.RFC3339); got != expected {
t.Fatalf("failed to decode. expected %q but got %q", expected, got)
}
}
func TestIssue337(t *testing.T) {
in := strings.Repeat(" ", 510) + "{}"
var m map[string]string
if err := json.NewDecoder(strings.NewReader(in)).Decode(&m); err != nil {
t.Fatal("unexpected error:", err)
}
if len(m) != 0 {
t.Fatal("unexpected result", m)
}
}
func Benchmark306(b *testing.B) {
type T0 struct {
Str string
}
in := []byte(`{"Str":"` + strings.Repeat(`abcd\"`, 10000) + `"}`)
b.Run("stdjson", func(b *testing.B) {
var x T0
for i := 0; i < b.N; i++ {
stdjson.Unmarshal(in, &x)
}
})
b.Run("go-json", func(b *testing.B) {
var x T0
for i := 0; i < b.N; i++ {
json.Unmarshal(in, &x)
}
})
}
func TestIssue348(t *testing.T) {
in := strings.Repeat("["+strings.Repeat(",1000", 500)[1:]+"]", 2)
dec := json.NewDecoder(strings.NewReader(in))
for dec.More() {
var foo interface{}
if err := dec.Decode(&foo); err != nil {
t.Error(err)
}
}
}
type issue342 string
func (t *issue342) UnmarshalJSON(b []byte) error {
panic("unreachable")
}
func TestIssue342(t *testing.T) {
var v map[issue342]int
in := []byte(`{"a":1}`)
if err := json.Unmarshal(in, &v); err != nil {
t.Errorf("unexpected error: %v", err)
}
expected := 1
if got := v["a"]; got != expected {
t.Errorf("unexpected result: got(%v) != expected(%v)", got, expected)
}
}
func TestIssue360(t *testing.T) {
var uints []uint8
err := json.Unmarshal([]byte(`[0, 1, 2]`), &uints)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(uints) != 3 || !(uints[0] == 0 && uints[1] == 1 && uints[2] == 2) {
t.Errorf("unexpected result: %v", uints)
}
}
func TestIssue359(t *testing.T) {
var a interface{} = 1
var b interface{} = &a
var c interface{} = &b
v, err := json.Marshal(c)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if string(v) != "1" {
t.Errorf("unexpected result: %v", string(v))
}
}
func TestIssue364(t *testing.T) {
var v struct {
Description string `json:"description"`
}
err := json.Unmarshal([]byte(`{"description":"\uD83D\uDE87 Toledo is a metro station"}`), &v)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if v.Description != "🚇 Toledo is a metro station" {
t.Errorf("unexpected result: %v", v.Description)
}
}
func TestIssue362(t *testing.T) {
type AliasedPrimitive int
type Combiner struct {
SomeField int
AliasedPrimitive
}
originalCombiner := Combiner{AliasedPrimitive: 7}
b, err := json.Marshal(originalCombiner)
assertErr(t, err)
newCombiner := Combiner{}
err = json.Unmarshal(b, &newCombiner)
assertErr(t, err)
assertEq(t, "TestEmbeddedPrimitiveAlias", originalCombiner, newCombiner)
}
func TestIssue335(t *testing.T) {
var v []string
in := []byte(`["\u","A"]`)
err := json.Unmarshal(in, &v)
if err == nil {
t.Errorf("unexpected success")
}
}
func TestIssue372(t *testing.T) {
type A int
type T struct {
_ int
*A
}
var v T
err := json.Unmarshal([]byte(`{"A":7}`), &v)
assertErr(t, err)
got := *v.A
expected := A(7)
if got != expected {
t.Errorf("unexpected result: %v != %v", got, expected)
}
}
type issue384 struct{}
func (t *issue384) UnmarshalJSON(b []byte) error {
return nil
}
func TestIssue384(t *testing.T) {
testcases := []string{
`{"data": "` + strings.Repeat("-", 500) + `\""}`,
`["` + strings.Repeat("-", 508) + `\""]`,
}
for _, tc := range testcases {
dec := json.NewDecoder(strings.NewReader(tc))
var v issue384
if err := dec.Decode(&v); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
}
func TestIssue408(t *testing.T) {
type T struct {
Arr [2]int32 `json:"arr"`
}
var v T
if err := json.Unmarshal([]byte(`{"arr": [1,2]}`), &v); err != nil {
t.Fatal(err)
}
}

View File

@ -1,7 +1,7 @@
version: '2'
services:
go-json:
image: golang:1.18
image: golang:1.17
volumes:
- '.:/go/src/go-json'
deploy:

View File

@ -3,7 +3,6 @@ package json
import (
"context"
"io"
"os"
"unsafe"
"github.com/goccy/go-json/internal/encoder"
@ -62,8 +61,6 @@ func (e *Encoder) encodeWithOption(ctx *encoder.RuntimeContext, v interface{}, o
if e.enabledHTMLEscape {
ctx.Option.Flag |= encoder.HTMLEscapeOption
}
ctx.Option.Flag |= encoder.NormalizeUTF8Option
ctx.Option.DebugOut = os.Stdout
for _, optFunc := range optFuncs {
optFunc(ctx.Option)
}
@ -114,7 +111,7 @@ func (e *Encoder) SetIndent(prefix, indent string) {
func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
rctx := encoder.TakeRuntimeContext()
rctx.Option.Flag = 0
rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.ContextOption
rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.ContextOption
rctx.Option.Context = ctx
for _, optFunc := range optFuncs {
optFunc(rctx.Option)
@ -142,7 +139,7 @@ func marshal(v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
ctx := encoder.TakeRuntimeContext()
ctx.Option.Flag = 0
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option)
ctx.Option.Flag |= encoder.HTMLEscapeOption
for _, optFunc := range optFuncs {
optFunc(ctx.Option)
}
@ -169,7 +166,7 @@ func marshalNoEscape(v interface{}) ([]byte, error) {
ctx := encoder.TakeRuntimeContext()
ctx.Option.Flag = 0
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option)
ctx.Option.Flag |= encoder.HTMLEscapeOption
buf, err := encodeNoEscape(ctx, v)
if err != nil {
@ -193,7 +190,7 @@ func marshalIndent(v interface{}, prefix, indent string, optFuncs ...EncodeOptio
ctx := encoder.TakeRuntimeContext()
ctx.Option.Flag = 0
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.IndentOption)
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.IndentOption)
for _, optFunc := range optFuncs {
optFunc(ctx.Option)
}
@ -223,7 +220,7 @@ func encode(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error) {
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
if err != nil {
return nil, err
}
@ -251,7 +248,7 @@ func encodeNoEscape(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error)
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
if err != nil {
return nil, err
}
@ -278,7 +275,7 @@ func encodeIndent(ctx *encoder.RuntimeContext, v interface{}, prefix, indent str
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
if err != nil {
return nil, err
}

View File

@ -7,14 +7,11 @@ import (
stdjson "encoding/json"
"errors"
"fmt"
"io"
"log"
"math"
"math/big"
"reflect"
"regexp"
"strconv"
"strings"
"testing"
"time"
@ -480,8 +477,7 @@ func TestDebugMode(t *testing.T) {
t.Fatal("expected error")
}
}()
var buf bytes.Buffer
json.MarshalWithOption(mustErrTypeForDebug{}, json.Debug(), json.DebugWith(&buf))
json.MarshalWithOption(mustErrTypeForDebug{}, json.Debug())
}
func TestIssue116(t *testing.T) {
@ -554,28 +550,20 @@ func Test_MarshalIndent(t *testing.T) {
prefix := "-"
indent := "\t"
t.Run("struct", func(t *testing.T) {
v := struct {
A int `json:"a"`
B uint `json:"b"`
C string `json:"c"`
D interface{} `json:"d"`
X int `json:"-"` // ignore field
a int `json:"aa"` // private field
bytes, err := json.MarshalIndent(struct {
A int `json:"a"`
B uint `json:"b"`
C string `json:"c"`
D int `json:"-"` // ignore field
a int `json:"aa"` // private field
}{
A: -1,
B: 1,
C: "hello world",
D: struct {
E bool `json:"e"`
}{
E: true,
},
}
expected, err := stdjson.MarshalIndent(v, prefix, indent)
}, prefix, indent)
assertErr(t, err)
got, err := json.MarshalIndent(v, prefix, indent)
assertErr(t, err)
assertEq(t, "struct", string(expected), string(got))
result := "{\n-\t\"a\": -1,\n-\t\"b\": 1,\n-\t\"c\": \"hello world\"\n-}"
assertEq(t, "struct", result, string(bytes))
})
t.Run("slice", func(t *testing.T) {
t.Run("[]int", func(t *testing.T) {
@ -1022,13 +1010,12 @@ func (u *unmarshalerText) UnmarshalText(b []byte) error {
}
func TestTextMarshalerMapKeysAreSorted(t *testing.T) {
data := map[unmarshalerText]int{
b, err := json.Marshal(map[unmarshalerText]int{
{"x", "y"}: 1,
{"y", "x"}: 2,
{"a", "z"}: 3,
{"z", "a"}: 4,
}
b, err := json.Marshal(data)
})
if err != nil {
t.Fatalf("Failed to Marshal text.Marshaler: %v", err)
}
@ -1036,14 +1023,6 @@ func TestTextMarshalerMapKeysAreSorted(t *testing.T) {
if string(b) != want {
t.Errorf("Marshal map with text.Marshaler keys: got %#q, want %#q", b, want)
}
b, err = stdjson.Marshal(data)
if err != nil {
t.Fatalf("Failed to std Marshal text.Marshaler: %v", err)
}
if string(b) != want {
t.Errorf("std Marshal map with text.Marshaler keys: got %#q, want %#q", b, want)
}
}
// https://golang.org/issue/33675
@ -2299,333 +2278,3 @@ func TestRecursivePtrHead(t *testing.T) {
t.Fatalf("failed to encode. expected %q but got %q", expected, got)
}
}
func TestMarshalIndent(t *testing.T) {
v := map[string]map[string]interface{}{
"a": {
"b": "1",
"c": map[string]interface{}{
"d": "1",
},
},
}
expected, err := stdjson.MarshalIndent(v, "", " ")
if err != nil {
t.Fatal(err)
}
got, err := json.MarshalIndent(v, "", " ")
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(expected, got) {
t.Fatalf("expected: %q but got %q", expected, got)
}
}
type issue318Embedded struct {
_ [64]byte
}
type issue318 struct {
issue318Embedded `json:"-"`
ID issue318MarshalText `json:"id,omitempty"`
}
type issue318MarshalText struct {
ID string
}
func (i issue318MarshalText) MarshalText() ([]byte, error) {
return []byte(i.ID), nil
}
func TestIssue318(t *testing.T) {
v := issue318{
ID: issue318MarshalText{ID: "1"},
}
b, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
expected := `{"id":"1"}`
if string(b) != expected {
t.Fatalf("failed to encode. expected %s but got %s", expected, string(b))
}
}
type emptyStringMarshaler struct {
Value stringMarshaler `json:"value,omitempty"`
}
type stringMarshaler string
func (s stringMarshaler) MarshalJSON() ([]byte, error) {
return []byte(`"` + s + `"`), nil
}
func TestEmptyStringMarshaler(t *testing.T) {
value := emptyStringMarshaler{}
expected, err := stdjson.Marshal(value)
assertErr(t, err)
got, err := json.Marshal(value)
assertErr(t, err)
assertEq(t, "struct", string(expected), string(got))
}
func TestIssue324(t *testing.T) {
type T struct {
FieldA *string `json:"fieldA,omitempty"`
FieldB *string `json:"fieldB,omitempty"`
FieldC *bool `json:"fieldC"`
FieldD []string `json:"fieldD,omitempty"`
}
v := &struct {
Code string `json:"code"`
*T
}{
T: &T{},
}
var sv = "Test Field"
v.Code = "Test"
v.T.FieldB = &sv
expected, err := stdjson.Marshal(v)
if err != nil {
t.Fatal(err)
}
got, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(expected, got) {
t.Fatalf("failed to encode. expected %q but got %q", expected, got)
}
}
func TestIssue339(t *testing.T) {
type T1 struct {
*big.Int
}
type T2 struct {
T1 T1 `json:"T1"`
}
v := T2{T1{Int: big.NewInt(10000)}}
b, err := json.Marshal(&v)
assertErr(t, err)
got := string(b)
expected := `{"T1":10000}`
if got != expected {
t.Errorf("unexpected result: %v != %v", got, expected)
}
}
func TestIssue376(t *testing.T) {
type Container struct {
V interface{} `json:"value"`
}
type MapOnly struct {
Map map[string]int64 `json:"map"`
}
b, err := json.Marshal(Container{MapOnly{}})
if err != nil {
t.Fatal(err)
}
got := string(b)
expected := `{"value":{"map":null}}`
if got != expected {
t.Errorf("unexpected result: %v != %v", got, expected)
}
}
type Issue370 struct {
String string
Valid bool
}
func (i *Issue370) MarshalJSON() ([]byte, error) {
if !i.Valid {
return json.Marshal(nil)
}
return json.Marshal(i.String)
}
func TestIssue370(t *testing.T) {
v := []struct {
V Issue370
}{
{V: Issue370{String: "test", Valid: true}},
}
b, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
got := string(b)
expected := `[{"V":"test"}]`
if got != expected {
t.Errorf("unexpected result: %v != %v", got, expected)
}
}
func TestIssue374(t *testing.T) {
r := io.MultiReader(strings.NewReader(strings.Repeat(" ", 505)+`"\u`), strings.NewReader(`0000"`))
var v interface{}
if err := json.NewDecoder(r).Decode(&v); err != nil {
t.Fatal(err)
}
got := v.(string)
expected := "\u0000"
if got != expected {
t.Errorf("unexpected result: %q != %q", got, expected)
}
}
func TestIssue381(t *testing.T) {
var v struct {
Field0 bool
Field1 bool
Field2 bool
Field3 bool
Field4 bool
Field5 bool
Field6 bool
Field7 bool
Field8 bool
Field9 bool
Field10 bool
Field11 bool
Field12 bool
Field13 bool
Field14 bool
Field15 bool
Field16 bool
Field17 bool
Field18 bool
Field19 bool
Field20 bool
Field21 bool
Field22 bool
Field23 bool
Field24 bool
Field25 bool
Field26 bool
Field27 bool
Field28 bool
Field29 bool
Field30 bool
Field31 bool
Field32 bool
Field33 bool
Field34 bool
Field35 bool
Field36 bool
Field37 bool
Field38 bool
Field39 bool
Field40 bool
Field41 bool
Field42 bool
Field43 bool
Field44 bool
Field45 bool
Field46 bool
Field47 bool
Field48 bool
Field49 bool
Field50 bool
Field51 bool
Field52 bool
Field53 bool
Field54 bool
Field55 bool
Field56 bool
Field57 bool
Field58 bool
Field59 bool
Field60 bool
Field61 bool
Field62 bool
Field63 bool
Field64 bool
Field65 bool
Field66 bool
Field67 bool
Field68 bool
Field69 bool
Field70 bool
Field71 bool
Field72 bool
Field73 bool
Field74 bool
Field75 bool
Field76 bool
Field77 bool
Field78 bool
Field79 bool
Field80 bool
Field81 bool
Field82 bool
Field83 bool
Field84 bool
Field85 bool
Field86 bool
Field87 bool
Field88 bool
Field89 bool
Field90 bool
Field91 bool
Field92 bool
Field93 bool
Field94 bool
Field95 bool
Field96 bool
Field97 bool
Field98 bool
Field99 bool
}
// test encoder cache issue, not related to encoder
b, err := json.Marshal(v)
if err != nil {
t.Errorf("failed to marshal %s", err.Error())
t.FailNow()
}
std, err := stdjson.Marshal(v)
if err != nil {
t.Errorf("failed to marshal with encoding/json %s", err.Error())
t.FailNow()
}
if !bytes.Equal(std, b) {
t.Errorf("encoding result not equal to encoding/json")
t.FailNow()
}
}
func TestIssue386(t *testing.T) {
raw := `{"date": null, "platform": "\u6f2b\u753b", "images": {"small": "https://lain.bgm.tv/pic/cover/s/d2/a1/80048_jp.jpg", "grid": "https://lain.bgm.tv/pic/cover/g/d2/a1/80048_jp.jpg", "large": "https://lain.bgm.tv/pic/cover/l/d2/a1/80048_jp.jpg", "medium": "https://lain.bgm.tv/pic/cover/m/d2/a1/80048_jp.jpg", "common": "https://lain.bgm.tv/pic/cover/c/d2/a1/80048_jp.jpg"}, "summary": "\u5929\u624d\u8a2d\u8a08\u58eb\u30fb\u5929\u5bae\uff08\u3042\u307e\u307f\u3084\uff09\u3092\u62b1\u3048\u308b\u6751\u96e8\u7dcf\u5408\u4f01\u753b\u306f\u3001\u771f\u6c34\u5efa\u8a2d\u3068\u63d0\u643a\u3057\u3066\u300c\u3055\u304d\u305f\u307e\u30a2\u30ea\u30fc\u30ca\u300d\u306e\u30b3\u30f3\u30da\u306b\u512a\u52dd\u3059\u308b\u3053\u3068\u306b\u8ced\u3051\u3066\u3044\u305f\u3002\u3057\u304b\u3057\u3001\u73fe\u77e5\u4e8b\u306e\u6d25\u5730\u7530\uff08\u3064\u3061\u3060\uff09\u306f\u5927\u65e5\u5efa\u8a2d\u306b\u512a\u52dd\u3055\u305b\u3088\u3046\u3068\u6697\u8e8d\u3059\u308b\u3002\u305d\u308c\u306f\u73fe\u77e5\u4e8b\u306e\u6d25\u5730\u7530\u3068\u526f\u77e5\u4e8b\u306e\u592a\u7530\uff08\u304a\u304a\u305f\uff09\u306e\u653f\u6cbb\u751f\u547d\u3092\u5de6\u53f3\u3059\u308b\u4e89\u3044\u3068\u306a\u308a\u2026\u2026!?\u3000\u305d\u3057\u3066\u516c\u5171\u4e8b\u696d\u306b\u6e26\u5dfb\u304f\u6df1\u3044\u95c7\u306b\u842c\u7530\u9280\u6b21\u90ce\uff08\u307e\u3093\u3060\u30fb\u304e\u3093\u3058\u308d\u3046\uff09\u306f\u2026\u2026!?", "name": "\u30df\u30ca\u30df\u306e\u5e1d\u738b (48)"}`
var a struct {
Date *string `json:"date"`
Platform *string `json:"platform"`
Summary string `json:"summary"`
Name string `json:"name"`
}
err := json.NewDecoder(strings.NewReader(raw)).Decode(&a)
if err != nil {
t.Error(err)
}
}
type customMapKey string
func (b customMapKey) MarshalJSON() ([]byte, error) {
return []byte("[]"), nil
}
func TestCustomMarshalForMapKey(t *testing.T) {
m := map[customMapKey]string{customMapKey("skipcustom"): "test"}
expected, err := stdjson.Marshal(m)
assertErr(t, err)
got, err := json.Marshal(m)
assertErr(t, err)
assertEq(t, "custom map key", string(expected), string(got))
}

View File

@ -37,5 +37,3 @@ type UnmarshalTypeError = errors.UnmarshalTypeError
type UnsupportedTypeError = errors.UnsupportedTypeError
type UnsupportedValueError = errors.UnsupportedValueError
type PathError = errors.PathError

1
internal/cmd/generator/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.s

View File

@ -0,0 +1,30 @@
FROM golang:1.16
RUN apt-get update; \
apt-get install -y build-essential clang yasm
WORKDIR /work
COPY ./go.* ./
RUN go install \
github.com/minio/asm2plan9s \
github.com/minio/c2goasm \
github.com/klauspost/asmfmt/cmd/asmfmt
COPY ./simd/string.c ./string.c
RUN clang -S \
-O2 \
-mavx2 \
-masm=intel \
-mno-red-zone \
-mstackrealign \
-mllvm \
-inline-threshold=1000 \
-fno-asynchronous-unwind-tables \
-fno-exceptions \
-fno-rtti \
-c string.c
COPY ./simd/string_avx.go ./string_avx.go
RUN c2goasm -a -f ./string.s ./string_avx.s

View File

@ -0,0 +1,8 @@
.PHONY: asm
asm:
clang -Wall -S -O2 -mavx2 -masm=intel -mno-red-zone -mstackrealign -mllvm -inline-threshold=1000 -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -c ./simd/string.c
.PHONY: generate
generate:
docker build -f Dockerfile.simd -t go-json-simd .
docker run --rm -v "$(CURDIR)/simd:/tmp" go-json-simd bash -c "cp /work/string_avx.s /tmp/string_avx.s"

View File

@ -0,0 +1,9 @@
module github.com/goccy/go-json/internal/cmd/generator
go 1.17
require (
github.com/klauspost/asmfmt v1.3.1
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3
)

View File

@ -0,0 +1,6 @@
github.com/klauspost/asmfmt v1.3.1 h1:7xZi1N7s9gTLbqiM8KUv8TLyysavbTRGBT5/ly0bRtw=
github.com/klauspost/asmfmt v1.3.1/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=

View File

@ -8,6 +8,9 @@ import (
"go/printer"
"go/token"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
@ -302,6 +305,32 @@ func generateVM() error {
return nil
}
func generateSIMDSources() error {
root := repoRoot()
genCmd := exec.Command("make", "generate")
genCmd.Stdout = os.Stdout
genCmd.Stderr = os.Stderr
if err := genCmd.Run(); err != nil {
return err
}
for _, srcName := range []string{
"string_avx.s",
"string_avx.go",
} {
srcFile := filepath.Join(root, "internal", "cmd", "generator", "simd", srcName)
dstFile := filepath.Join(root, "internal", "encoder", srcName)
log.Printf("copy %s to %s", srcFile, dstFile)
src, err := os.ReadFile(srcFile)
if err != nil {
return err
}
if err := os.WriteFile(dstFile, src, 0o600); err != nil {
return err
}
}
return nil
}
func repoRoot() string {
_, file, _, _ := runtime.Caller(0)
relativePathFromRepoRoot := filepath.Join("internal", "cmd", "generator")
@ -310,6 +339,9 @@ func repoRoot() string {
//go:generate go run main.go
func main() {
if err := generateSIMDSources(); err != nil {
panic(err)
}
if err := generateVM(); err != nil {
panic(err)
}

View File

@ -0,0 +1,266 @@
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <immintrin.h>
static const bool needEscape[256] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00-0x0F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10-0x1F
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20-0x2F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30-0x3F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40-0x4F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 0x50-0x5F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60-0x6F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70-0x7F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80-0x8F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90-0x9F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xA0-0xAF
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xB0-0xBF
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xC0-0xCF
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xD0-0xDF
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xE0-0xEF
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xF0-0xFF
};
uint64_t findHTMLEscapeIndex64(char *buf, int len) {
static const uint64_t lsb = 0x0101010101010101;
static const uint64_t msb = 0x8080808080808080;
static const uint64_t space = lsb * 0x20;
static const uint64_t quote = lsb * '"';
static const uint64_t escape = lsb * '\\';
static const uint64_t lt = lsb * '<';
static const uint64_t gt = lsb * '>';
static const uint64_t amp = lsb * '&';
char *sp = buf;
size_t chunkLen = len / 8;
int chunkIdx = 0;
for (; chunkIdx < chunkLen; chunkIdx++) {
uint64_t n = *(uint64_t *)sp;
uint64_t mask = n | (n - space) | ((n ^ quote) - lsb) | ((n ^ escape) - lsb) | ((n ^ lt) - lsb) | ((n ^ gt) - lsb) | ((n ^ amp) - lsb);
uint64_t masked = mask & msb;
if (masked != 0) {
return __builtin_ctz(masked) / 8;
}
sp += 8;
}
return chunkIdx * 8;
}
uint64_t findHTMLEscapeIndex128(char *buf, int len) {
static const uint64_t lsb = 0x0101010101010101;
static const uint64_t msb = 0x8080808080808080;
static const __m64 space = (__m64)(lsb * 0x20);
static const __m64 quote = (__m64)(lsb * '"');
static const __m64 escape = (__m64)(lsb * '\\');
static const __m64 lt = (__m64)(lsb * '<');
static const __m64 gt = (__m64)(lsb * '>');
static const __m64 amp = (__m64)(lsb * '&');
__m128i msbV = _mm_set_epi64((__m64)(msb), (__m64)(msb));
__m128i lsbV = _mm_set_epi64((__m64)(lsb), (__m64)(lsb));
__m128i spaceV = _mm_set_epi64(space, space);
__m128i quoteV = _mm_set_epi64(quote, quote);
__m128i escapeV = _mm_set_epi64(escape, escape);
__m128i ltV = _mm_set_epi64(lt, lt);
__m128i gtV = _mm_set_epi64(gt, gt);
__m128i ampV = _mm_set_epi64(amp, amp);
char *sp = buf;
size_t chunkLen = len / 16;
int chunkIdx = 0;
for (; chunkIdx < chunkLen; chunkIdx++) {
__m128i n = _mm_loadu_si128((const void *)sp);
__m128i spaceN = _mm_sub_epi64(n, spaceV);
__m128i quoteN = _mm_sub_epi64(_mm_xor_si128(n, quoteV), lsbV);
__m128i escapeN = _mm_sub_epi64(_mm_xor_si128(n, escapeV), lsbV);
__m128i ltN = _mm_sub_epi64(_mm_xor_si128(n, ltV), lsbV);
__m128i gtN = _mm_sub_epi64(_mm_xor_si128(n, gtV), lsbV);
__m128i ampN = _mm_sub_epi64(_mm_xor_si128(n, ampV), lsbV);
__m128i mask = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_or_si128(n, spaceN), quoteN), escapeN), ltN), gtN), ampN);
int movemask = _mm_movemask_epi8(_mm_and_si128(mask, msbV));
if (movemask != 0) {
return __builtin_ctz(movemask);
}
sp += 16;
}
int idx = 16 * chunkLen;
if (len - idx >= 8) {
return idx + findHTMLEscapeIndex64(sp, len - idx);
}
return idx;
}
uint64_t findHTMLEscapeIndex256(char *buf, int len) {
static const uint64_t lsb = 0x0101010101010101;
static const uint64_t msb = 0x8080808080808080;
static const __m64 space = (__m64)(lsb * 0x20);
static const __m64 quote = (__m64)(lsb * '"');
static const __m64 escape = (__m64)(lsb * '\\');
static const __m64 lt = (__m64)(lsb * '<');
static const __m64 gt = (__m64)(lsb * '>');
static const __m64 amp = (__m64)(lsb * '&');
__m256i msbV = _mm256_set1_epi64x(msb);
__m256i lsbV = _mm256_set1_epi64x(lsb);
__m256i spaceV = _mm256_set1_epi64x(space);
__m256i quoteV = _mm256_set1_epi64x(quote);
__m256i escapeV = _mm256_set1_epi64x(escape);
__m256i ltV = _mm256_set1_epi64x(lt);
__m256i gtV = _mm256_set1_epi64x(gt);
__m256i ampV = _mm256_set1_epi64x(amp);
char *sp = buf;
size_t chunkLen = len / 32;
int chunkIdx = 0;
for (; chunkIdx < chunkLen; chunkIdx++) {
__m256i n = _mm256_loadu_si256((const void *)sp);
__m256i spaceN = _mm256_sub_epi64(n, spaceV);
__m256i quoteN = _mm256_sub_epi64(_mm256_xor_si256(n, quoteV), lsbV);
__m256i escapeN = _mm256_sub_epi64(_mm256_xor_si256(n, escapeV), lsbV);
__m256i ltN = _mm256_sub_epi64(_mm256_xor_si256(n, ltV), lsbV);
__m256i gtN = _mm256_sub_epi64(_mm256_xor_si256(n, gtV), lsbV);
__m256i ampN = _mm256_sub_epi64(_mm256_xor_si256(n, ampV), lsbV);
__m256i mask = _mm256_or_si256(_mm256_or_si256(_mm256_or_si256(_mm256_or_si256(_mm256_or_si256(_mm256_or_si256(n, spaceN), quoteN), escapeN), ltN), gtN), ampN);
int movemask = _mm256_movemask_epi8(_mm256_and_si256(mask, msbV));
if (movemask != 0) {
return __builtin_ctz(movemask);
}
sp += 32;
}
int idx = 32 * chunkLen;
int remainLen = len - idx;
if (remainLen >= 16) {
return idx + findHTMLEscapeIndex128(sp, remainLen);
} else if (remainLen >= 8) {
return idx + findHTMLEscapeIndex64(sp, remainLen);
}
return idx;
}
uint64_t findEscapeIndex64(char *buf, int len) {
static const uint64_t lsb = 0x0101010101010101;
static const uint64_t msb = 0x8080808080808080;
static const uint64_t space = lsb * 0x20;
static const uint64_t quote = lsb * '"';
static const uint64_t escape = lsb * '\\';
char *sp = buf;
size_t chunkLen = len / 8;
int chunkIdx = 0;
for (; chunkIdx < chunkLen; chunkIdx++) {
uint64_t n = *(uint64_t *)sp;
uint64_t mask = n | (n - space) | ((n ^ quote) - lsb) | ((n ^ escape) - lsb);
uint64_t masked = mask & msb;
if (masked != 0) {
return __builtin_ctz(masked) / 8;
}
sp += 8;
}
int idx = 8 * chunkLen;
bool *needEscape = needEscape;
for ( ;idx < len; idx++) {
if (needEscape[buf[idx]] != 0) {
return idx;
}
}
return len;
}
uint64_t findEscapeIndex128(char *buf, int len) {
static const uint64_t lsb = 0x0101010101010101;
static const uint64_t msb = 0x8080808080808080;
static const __m64 space = (__m64)(lsb * 0x20);
static const __m64 quote = (__m64)(lsb * '"');
static const __m64 escape = (__m64)(lsb * '\\');
__m128i msbV = _mm_set_epi64((__m64)(msb), (__m64)(msb));
__m128i lsbV = _mm_set_epi64((__m64)(lsb), (__m64)(lsb));
__m128i spaceV = _mm_set_epi64(space, space);
__m128i quoteV = _mm_set_epi64(quote, quote);
__m128i escapeV = _mm_set_epi64(escape, escape);
char *sp = buf;
size_t chunkLen = len / 16;
int chunkIdx = 0;
for (; chunkIdx < chunkLen; chunkIdx++) {
__m128i n = _mm_loadu_si128((const void *)sp);
__m128i spaceN = _mm_sub_epi64(n, spaceV);
__m128i quoteN = _mm_sub_epi64(_mm_xor_si128(n, quoteV), lsbV);
__m128i escapeN = _mm_sub_epi64(_mm_xor_si128(n, escapeV), lsbV);
__m128i mask = _mm_or_si128(_mm_or_si128(_mm_or_si128(n, spaceN), quoteN), escapeN);
int movemask = _mm_movemask_epi8(_mm_and_si128(mask, msbV));
if (movemask != 0) {
return __builtin_ctz(movemask);
}
sp += 16;
}
int idx = 16 * chunkLen;
int remainLen = len - idx;
if (remainLen >= 8) {
return idx + findEscapeIndex64(sp, remainLen);
}
bool *needEscape = needEscape;
for (; idx < len; idx++) {
if (needEscape[buf[idx]] != 0) {
return idx;
}
}
return len;
}
uint64_t findEscapeIndex256(char *buf, int len) {
static const uint64_t lsb = 0x0101010101010101;
static const uint64_t msb = 0x8080808080808080;
static const __m64 space = (__m64)(lsb * 0x20);
static const __m64 quote = (__m64)(lsb * '"');
static const __m64 escape = (__m64)(lsb * '\\');
__m256i msbV = _mm256_set1_epi64x(msb);
__m256i lsbV = _mm256_set1_epi64x(lsb);
__m256i spaceV = _mm256_set1_epi64x(space);
__m256i quoteV = _mm256_set1_epi64x(quote);
__m256i escapeV = _mm256_set1_epi64x(escape);
char *sp = buf;
size_t chunkLen = len / 32;
int chunkIdx = 0;
for (; chunkIdx < chunkLen; chunkIdx++) {
__m256i n = _mm256_loadu_si256((const void *)sp);
__m256i spaceN = _mm256_sub_epi64(n, spaceV);
__m256i quoteN = _mm256_sub_epi64(_mm256_xor_si256(n, quoteV), lsbV);
__m256i escapeN = _mm256_sub_epi64(_mm256_xor_si256(n, escapeV), lsbV);
__m256i mask = _mm256_or_si256(_mm256_or_si256(_mm256_or_si256(n, spaceN), quoteN), escapeN);
int movemask = _mm256_movemask_epi8(_mm256_and_si256(mask, msbV));
if (movemask != 0) {
return __builtin_ctz(movemask) + chunkIdx * 32;
}
sp += 32;
}
int idx = 32 * chunkLen;
int remainLen = len - idx;
if (remainLen >= 16) {
return idx + findEscapeIndex128(sp, remainLen);
} else if (remainLen >= 8) {
return idx + findEscapeIndex64(sp, remainLen);
}
bool *needEscape = needEscape;
for (; idx < len; idx++) {
if (needEscape[buf[idx]] != 0) {
return idx;
}
}
return len;
}

View File

@ -0,0 +1,27 @@
package encoder
import "unsafe"
//go:nosplit
//go:noescape
func _findHTMLEscapeIndex64(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findHTMLEscapeIndex128(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findHTMLEscapeIndex256(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findEscapeIndex64(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findEscapeIndex128(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findEscapeIndex256(buf unsafe.Pointer, len int) (ret int)

View File

@ -0,0 +1,7 @@
package main
import (
_ "github.com/klauspost/asmfmt/cmd/asmfmt"
_ "github.com/minio/asm2plan9s"
_ "github.com/minio/c2goasm"
)

View File

@ -3,7 +3,6 @@ package vm
import (
"math"
"reflect"
"sort"
"unsafe"
@ -195,15 +194,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ
}
if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b)
code = code.Next
break
}
b = appendNullComma(ctx, b)
code = code.Next
break
}
ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ)))
if err != nil {
return nil, err
}
@ -222,7 +218,8 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
oldOffset := ptrOffset
ptrOffset += totalLength * uintptrSize
oldBaseIndent := ctx.BaseIndent
ctx.BaseIndent += code.Indent
indentDiffFromTop := c.Indent - 1
ctx.BaseIndent += code.Indent - indentDiffFromTop
newLen := offsetNum + totalLength + nextTotalLength
if curlen < newLen {
@ -405,12 +402,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.End.Next
break
}
b = appendStructHead(ctx, b)
unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0
mapCtx := encoder.NewMapContext(mlen, unorderedMap)
mapiterinit(code.Type, uptr, &mapCtx.Iter)
store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx)))
ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx))
b = appendStructHead(ctx, b)
if unorderedMap {
b = appendMapKeyIndent(ctx, code.Next, b)
} else {
@ -709,15 +706,14 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
u64 := ptrToUint64(p, code.NumBitSize)
u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize)
v := u64 & ((1 << code.NumBitSize) - 1)
if v == 0 {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
b = append(b, '"')
b = appendInt(ctx, b, p, code)
b = appendInt(ctx, b, p+uintptr(code.Offset), code)
b = append(b, '"')
b = appendComma(ctx, b)
code = code.Next
@ -2958,10 +2954,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3000,10 +2995,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
iface := ptrToInterface(code, p)
@ -3121,10 +3115,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3163,10 +3156,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {

View File

@ -35,7 +35,3 @@ func (d *anonymousFieldDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
p = *(*unsafe.Pointer)(p)
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
}
func (d *anonymousFieldDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return d.dec.DecodePath(ctx, cursor, depth)
}

View File

@ -1,7 +1,6 @@
package decoder
import (
"fmt"
"unsafe"
"github.com/goccy/go-json/internal/errors"
@ -19,9 +18,7 @@ type arrayDecoder struct {
}
func newArrayDecoder(dec Decoder, elemType *runtime.Type, alen int, structName, fieldName string) *arrayDecoder {
// workaround to avoid checkptr errors. cannot use `*(*unsafe.Pointer)(unsafe_New(elemType))` directly.
zeroValuePtr := unsafe_New(elemType)
zeroValue := **(**unsafe.Pointer)(unsafe.Pointer(&zeroValuePtr))
zeroValue := *(*unsafe.Pointer)(unsafe_New(elemType))
return &arrayDecoder{
valueDecoder: dec,
elemType: elemType,
@ -170,7 +167,3 @@ func (d *arrayDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
}
}
}
func (d *arrayDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: array decoder does not support decode path")
}

View File

@ -1,438 +0,0 @@
package decoder
import (
"fmt"
"reflect"
"strconv"
)
var (
nilValue = reflect.ValueOf(nil)
)
func AssignValue(src, dst reflect.Value) error {
if dst.Type().Kind() != reflect.Ptr {
return fmt.Errorf("invalid dst type. required pointer type: %T", dst.Type())
}
casted, err := castValue(dst.Elem().Type(), src)
if err != nil {
return err
}
dst.Elem().Set(casted)
return nil
}
func castValue(t reflect.Type, v reflect.Value) (reflect.Value, error) {
switch t.Kind() {
case reflect.Int:
vv, err := castInt(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(int(vv.Int())), nil
case reflect.Int8:
vv, err := castInt(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(int8(vv.Int())), nil
case reflect.Int16:
vv, err := castInt(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(int16(vv.Int())), nil
case reflect.Int32:
vv, err := castInt(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(int32(vv.Int())), nil
case reflect.Int64:
return castInt(v)
case reflect.Uint:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uint(vv.Uint())), nil
case reflect.Uint8:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uint8(vv.Uint())), nil
case reflect.Uint16:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uint16(vv.Uint())), nil
case reflect.Uint32:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uint32(vv.Uint())), nil
case reflect.Uint64:
return castUint(v)
case reflect.Uintptr:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uintptr(vv.Uint())), nil
case reflect.String:
return castString(v)
case reflect.Bool:
return castBool(v)
case reflect.Float32:
vv, err := castFloat(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(float32(vv.Float())), nil
case reflect.Float64:
return castFloat(v)
case reflect.Array:
return castArray(t, v)
case reflect.Slice:
return castSlice(t, v)
case reflect.Map:
return castMap(t, v)
case reflect.Struct:
return castStruct(t, v)
}
return v, nil
}
func castInt(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return reflect.ValueOf(int64(v.Uint())), nil
case reflect.String:
i64, err := strconv.ParseInt(v.String(), 10, 64)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(i64), nil
case reflect.Bool:
if v.Bool() {
return reflect.ValueOf(int64(1)), nil
}
return reflect.ValueOf(int64(0)), nil
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(int64(v.Float())), nil
case reflect.Array:
if v.Len() > 0 {
return castInt(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to int64 from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castInt(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to int64 from empty slice")
case reflect.Interface:
return castInt(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to int64 from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to int64 from struct")
case reflect.Ptr:
return castInt(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to int64 from %s", v.Type().Kind())
}
func castUint(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(uint64(v.Int())), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v, nil
case reflect.String:
u64, err := strconv.ParseUint(v.String(), 10, 64)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(u64), nil
case reflect.Bool:
if v.Bool() {
return reflect.ValueOf(uint64(1)), nil
}
return reflect.ValueOf(uint64(0)), nil
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(uint64(v.Float())), nil
case reflect.Array:
if v.Len() > 0 {
return castUint(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to uint64 from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castUint(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to uint64 from empty slice")
case reflect.Interface:
return castUint(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to uint64 from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to uint64 from struct")
case reflect.Ptr:
return castUint(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to uint64 from %s", v.Type().Kind())
}
func castString(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(fmt.Sprint(v.Int())), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return reflect.ValueOf(fmt.Sprint(v.Uint())), nil
case reflect.String:
return v, nil
case reflect.Bool:
if v.Bool() {
return reflect.ValueOf("true"), nil
}
return reflect.ValueOf("false"), nil
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(fmt.Sprint(v.Float())), nil
case reflect.Array:
if v.Len() > 0 {
return castString(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to string from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castString(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to string from empty slice")
case reflect.Interface:
return castString(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to string from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to string from struct")
case reflect.Ptr:
return castString(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to string from %s", v.Type().Kind())
}
func castBool(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch v.Int() {
case 0:
return reflect.ValueOf(false), nil
case 1:
return reflect.ValueOf(true), nil
}
return nilValue, fmt.Errorf("failed to cast to bool from %d", v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
switch v.Uint() {
case 0:
return reflect.ValueOf(false), nil
case 1:
return reflect.ValueOf(true), nil
}
return nilValue, fmt.Errorf("failed to cast to bool from %d", v.Uint())
case reflect.String:
b, err := strconv.ParseBool(v.String())
if err != nil {
return nilValue, err
}
return reflect.ValueOf(b), nil
case reflect.Bool:
return v, nil
case reflect.Float32, reflect.Float64:
switch v.Float() {
case 0:
return reflect.ValueOf(false), nil
case 1:
return reflect.ValueOf(true), nil
}
return nilValue, fmt.Errorf("failed to cast to bool from %f", v.Float())
case reflect.Array:
if v.Len() > 0 {
return castBool(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to string from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castBool(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to string from empty slice")
case reflect.Interface:
return castBool(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to string from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to string from struct")
case reflect.Ptr:
return castBool(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to bool from %s", v.Type().Kind())
}
func castFloat(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(float64(v.Int())), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return reflect.ValueOf(float64(v.Uint())), nil
case reflect.String:
f64, err := strconv.ParseFloat(v.String(), 64)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(f64), nil
case reflect.Bool:
if v.Bool() {
return reflect.ValueOf(float64(1)), nil
}
return reflect.ValueOf(float64(0)), nil
case reflect.Float32, reflect.Float64:
return v, nil
case reflect.Array:
if v.Len() > 0 {
return castFloat(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to float64 from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castFloat(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to float64 from empty slice")
case reflect.Interface:
return castFloat(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to float64 from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to float64 from struct")
case reflect.Ptr:
return castFloat(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to float64 from %s", v.Type().Kind())
}
func castArray(t reflect.Type, v reflect.Value) (reflect.Value, error) {
kind := v.Type().Kind()
if kind == reflect.Interface {
return castArray(t, reflect.ValueOf(v.Interface()))
}
if kind != reflect.Slice && kind != reflect.Array {
return nilValue, fmt.Errorf("failed to cast to array from %s", kind)
}
if t.Elem() == v.Type().Elem() {
return v, nil
}
if t.Len() != v.Len() {
return nilValue, fmt.Errorf("failed to cast [%d]array from slice of %d length", t.Len(), v.Len())
}
ret := reflect.New(t).Elem()
for i := 0; i < v.Len(); i++ {
vv, err := castValue(t.Elem(), v.Index(i))
if err != nil {
return nilValue, err
}
ret.Index(i).Set(vv)
}
return ret, nil
}
func castSlice(t reflect.Type, v reflect.Value) (reflect.Value, error) {
kind := v.Type().Kind()
if kind == reflect.Interface {
return castSlice(t, reflect.ValueOf(v.Interface()))
}
if kind != reflect.Slice && kind != reflect.Array {
return nilValue, fmt.Errorf("failed to cast to slice from %s", kind)
}
if t.Elem() == v.Type().Elem() {
return v, nil
}
ret := reflect.MakeSlice(t, v.Len(), v.Len())
for i := 0; i < v.Len(); i++ {
vv, err := castValue(t.Elem(), v.Index(i))
if err != nil {
return nilValue, err
}
ret.Index(i).Set(vv)
}
return ret, nil
}
func castMap(t reflect.Type, v reflect.Value) (reflect.Value, error) {
ret := reflect.MakeMap(t)
switch v.Type().Kind() {
case reflect.Map:
iter := v.MapRange()
for iter.Next() {
key, err := castValue(t.Key(), iter.Key())
if err != nil {
return nilValue, err
}
value, err := castValue(t.Elem(), iter.Value())
if err != nil {
return nilValue, err
}
ret.SetMapIndex(key, value)
}
return ret, nil
case reflect.Interface:
return castMap(t, reflect.ValueOf(v.Interface()))
case reflect.Slice:
if v.Len() > 0 {
return castMap(t, v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to map from empty slice")
}
return nilValue, fmt.Errorf("failed to cast to map from %s", v.Type().Kind())
}
func castStruct(t reflect.Type, v reflect.Value) (reflect.Value, error) {
ret := reflect.New(t).Elem()
switch v.Type().Kind() {
case reflect.Map:
iter := v.MapRange()
for iter.Next() {
key := iter.Key()
k, err := castString(key)
if err != nil {
return nilValue, err
}
fieldName := k.String()
field, ok := t.FieldByName(fieldName)
if ok {
value, err := castValue(field.Type, iter.Value())
if err != nil {
return nilValue, err
}
ret.FieldByName(fieldName).Set(value)
}
}
return ret, nil
case reflect.Struct:
for i := 0; i < v.Type().NumField(); i++ {
name := v.Type().Field(i).Name
ret.FieldByName(name).Set(v.FieldByName(name))
}
return ret, nil
case reflect.Interface:
return castStruct(t, reflect.ValueOf(v.Interface()))
case reflect.Slice:
if v.Len() > 0 {
return castStruct(t, v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to struct from empty slice")
default:
return nilValue, fmt.Errorf("failed to cast to struct from %s", v.Type().Kind())
}
}

View File

@ -1,7 +1,6 @@
package decoder
import (
"fmt"
"unsafe"
"github.com/goccy/go-json/internal/errors"
@ -77,7 +76,3 @@ func (d *boolDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
}
return 0, errors.ErrUnexpectedEndOfJSON("bool", cursor)
}
func (d *boolDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: bool decoder does not support decode path")
}

View File

@ -2,7 +2,6 @@ package decoder
import (
"encoding/base64"
"fmt"
"unsafe"
"github.com/goccy/go-json/internal/errors"
@ -24,8 +23,9 @@ func byteUnmarshalerSliceDecoder(typ *runtime.Type, structName string, fieldName
unmarshalDecoder = newUnmarshalJSONDecoder(runtime.PtrTo(typ), structName, fieldName)
case runtime.PtrTo(typ).Implements(unmarshalTextType):
unmarshalDecoder = newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName)
default:
unmarshalDecoder, _ = compileUint8(typ, structName, fieldName)
}
if unmarshalDecoder == nil {
return nil
}
return newSliceDecoder(unmarshalDecoder, typ, 1, structName, fieldName)
}
@ -79,10 +79,6 @@ func (d *bytesDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
return cursor, nil
}
func (d *bytesDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: []byte decoder does not support decode path")
}
func (d *bytesDecoder) decodeStreamBinary(s *Stream, depth int64, p unsafe.Pointer) ([]byte, error) {
c := s.skipWhiteSpace()
if c == '[' {

View File

@ -9,6 +9,7 @@ import (
"unicode"
"unsafe"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
@ -24,7 +25,7 @@ func init() {
if typeAddr == nil {
typeAddr = &runtime.TypeAddr{}
}
cachedDecoder = make([]Decoder, typeAddr.AddrRange>>typeAddr.AddrShift+1)
cachedDecoder = make([]Decoder, typeAddr.AddrRange>>typeAddr.AddrShift)
}
func loadDecoderMap() map[uintptr]Decoder {
@ -125,7 +126,13 @@ func compile(typ *runtime.Type, structName, fieldName string, structTypeToDecode
case reflect.Func:
return compileFunc(typ, structName, fieldName)
}
return newInvalidDecoder(typ, structName, fieldName), nil
return nil, &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(typ),
Offset: 0,
Struct: structName,
Field: fieldName,
}
}
func isStringTagSupportedType(typ *runtime.Type) bool {
@ -154,9 +161,6 @@ func compileMapKey(typ *runtime.Type, structName, fieldName string, structTypeTo
if runtime.PtrTo(typ).Implements(unmarshalTextType) {
return newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName), nil
}
if typ.Kind() == reflect.String {
return newStringDecoder(structName, fieldName), nil
}
dec, err := compile(typ, structName, fieldName, structTypeToDecoder)
if err != nil {
return nil, err
@ -170,9 +174,17 @@ func compileMapKey(typ *runtime.Type, structName, fieldName string, structTypeTo
case *ptrDecoder:
dec = t.dec
default:
return newInvalidDecoder(typ, structName, fieldName), nil
goto ERROR
}
}
ERROR:
return nil, &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(typ),
Offset: 0,
Struct: structName,
Field: fieldName,
}
}
func compilePtr(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) {
@ -310,21 +322,64 @@ func compileFunc(typ *runtime.Type, strutName, fieldName string) (Decoder, error
return newFuncDecoder(typ, strutName, fieldName), nil
}
func typeToStructTags(typ *runtime.Type) runtime.StructTags {
tags := runtime.StructTags{}
fieldNum := typ.NumField()
for i := 0; i < fieldNum; i++ {
field := typ.Field(i)
if runtime.IsIgnoredStructField(field) {
func removeConflictFields(fieldMap map[string]*structFieldSet, conflictedMap map[string]struct{}, dec *structDecoder, field reflect.StructField) {
for k, v := range dec.fieldMap {
if _, exists := conflictedMap[k]; exists {
// already conflicted key
continue
}
tags = append(tags, runtime.StructTagFromField(field))
set, exists := fieldMap[k]
if !exists {
fieldSet := &structFieldSet{
dec: v.dec,
offset: field.Offset + v.offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
}
fieldMap[k] = fieldSet
lower := strings.ToLower(k)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
}
continue
}
if set.isTaggedKey {
if v.isTaggedKey {
// conflict tag key
delete(fieldMap, k)
delete(fieldMap, strings.ToLower(k))
conflictedMap[k] = struct{}{}
conflictedMap[strings.ToLower(k)] = struct{}{}
}
} else {
if v.isTaggedKey {
fieldSet := &structFieldSet{
dec: v.dec,
offset: field.Offset + v.offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
}
fieldMap[k] = fieldSet
lower := strings.ToLower(k)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
}
} else {
// conflict tag key
delete(fieldMap, k)
delete(fieldMap, strings.ToLower(k))
conflictedMap[k] = struct{}{}
conflictedMap[strings.ToLower(k)] = struct{}{}
}
}
}
return tags
}
func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) {
fieldNum := typ.NumField()
conflictedMap := map[string]struct{}{}
fieldMap := map[string]*structFieldSet{}
typeptr := uintptr(unsafe.Pointer(typ))
if dec, exists := structTypeToDecoder[typeptr]; exists {
@ -333,8 +388,6 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
structDec := newStructDecoder(structName, fieldName, fieldMap)
structTypeToDecoder[typeptr] = structDec
structName = typ.Name()
tags := typeToStructTags(typ)
allFields := []*structFieldSet{}
for i := 0; i < fieldNum; i++ {
field := typ.Field(i)
if runtime.IsIgnoredStructField(field) {
@ -352,19 +405,7 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
// recursive definition
continue
}
for k, v := range stDec.fieldMap {
if tags.ExistsKey(k) {
continue
}
fieldSet := &structFieldSet{
dec: v.dec,
offset: field.Offset + v.offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
}
allFields = append(allFields, fieldSet)
}
removeConflictFields(fieldMap, conflictedMap, stDec, field)
} else if pdec, ok := dec.(*ptrDecoder); ok {
contentDec := pdec.contentDecoder()
if pdec.typ == typ {
@ -380,38 +421,60 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
}
if dec, ok := contentDec.(*structDecoder); ok {
for k, v := range dec.fieldMap {
if tags.ExistsKey(k) {
if _, exists := conflictedMap[k]; exists {
// already conflicted key
continue
}
fieldSet := &structFieldSet{
dec: newAnonymousFieldDecoder(pdec.typ, v.offset, v.dec),
offset: field.Offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
err: fieldSetErr,
set, exists := fieldMap[k]
if !exists {
fieldSet := &structFieldSet{
dec: newAnonymousFieldDecoder(pdec.typ, v.offset, v.dec),
offset: field.Offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
err: fieldSetErr,
}
fieldMap[k] = fieldSet
lower := strings.ToLower(k)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
}
continue
}
if set.isTaggedKey {
if v.isTaggedKey {
// conflict tag key
delete(fieldMap, k)
delete(fieldMap, strings.ToLower(k))
conflictedMap[k] = struct{}{}
conflictedMap[strings.ToLower(k)] = struct{}{}
}
} else {
if v.isTaggedKey {
fieldSet := &structFieldSet{
dec: newAnonymousFieldDecoder(pdec.typ, v.offset, v.dec),
offset: field.Offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
err: fieldSetErr,
}
fieldMap[k] = fieldSet
lower := strings.ToLower(k)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
}
} else {
// conflict tag key
delete(fieldMap, k)
delete(fieldMap, strings.ToLower(k))
conflictedMap[k] = struct{}{}
conflictedMap[strings.ToLower(k)] = struct{}{}
}
}
allFields = append(allFields, fieldSet)
}
} else {
fieldSet := &structFieldSet{
dec: pdec,
offset: field.Offset,
isTaggedKey: tag.IsTaggedKey,
key: field.Name,
keyLen: int64(len(field.Name)),
}
allFields = append(allFields, fieldSet)
}
} else {
fieldSet := &structFieldSet{
dec: dec,
offset: field.Offset,
isTaggedKey: tag.IsTaggedKey,
key: field.Name,
keyLen: int64(len(field.Name)),
}
allFields = append(allFields, fieldSet)
}
} else {
if tag.IsString && isStringTagSupportedType(runtime.Type2RType(field.Type)) {
@ -430,15 +493,11 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
key: key,
keyLen: int64(len(key)),
}
allFields = append(allFields, fieldSet)
}
}
for _, set := range filterDuplicatedFields(allFields) {
fieldMap[set.key] = set
lower := strings.ToLower(set.key)
if _, exists := fieldMap[lower]; !exists {
// first win
fieldMap[lower] = set
fieldMap[key] = fieldSet
lower := strings.ToLower(key)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
}
}
}
delete(structTypeToDecoder, typeptr)
@ -446,42 +505,6 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
return structDec, nil
}
func filterDuplicatedFields(allFields []*structFieldSet) []*structFieldSet {
fieldMap := map[string][]*structFieldSet{}
for _, field := range allFields {
fieldMap[field.key] = append(fieldMap[field.key], field)
}
duplicatedFieldMap := map[string]struct{}{}
for k, sets := range fieldMap {
sets = filterFieldSets(sets)
if len(sets) != 1 {
duplicatedFieldMap[k] = struct{}{}
}
}
filtered := make([]*structFieldSet, 0, len(allFields))
for _, field := range allFields {
if _, exists := duplicatedFieldMap[field.key]; exists {
continue
}
filtered = append(filtered, field)
}
return filtered
}
func filterFieldSets(sets []*structFieldSet) []*structFieldSet {
if len(sets) == 1 {
return sets
}
filtered := make([]*structFieldSet, 0, len(sets))
for _, set := range sets {
if set.isTaggedKey {
filtered = append(filtered, set)
}
}
return filtered
}
func implementsUnmarshalJSONType(typ *runtime.Type) bool {
return typ.Implements(unmarshalJSONType) || typ.Implements(unmarshalJSONContextType)
}

View File

@ -1,4 +1,3 @@
//go:build !race
// +build !race
package decoder

View File

@ -1,4 +1,3 @@
//go:build race
// +build race
package decoder

View File

@ -156,15 +156,3 @@ func (d *floatDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
d.op(p, f64)
return cursor, nil
}
func (d *floatDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
buf := ctx.Buf
bytes, c, err := d.decodeByte(buf, cursor)
if err != nil {
return nil, 0, err
}
if bytes == nil {
return [][]byte{nullbytes}, c, nil
}
return [][]byte{bytes}, c, nil
}

View File

@ -2,7 +2,6 @@ package decoder
import (
"bytes"
"fmt"
"unsafe"
"github.com/goccy/go-json/internal/errors"
@ -140,7 +139,3 @@ func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
}
return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
}
func (d *funcDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: func decoder does not support decode path")
}

View File

@ -192,15 +192,15 @@ func (d *intDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
}
switch d.kind {
case reflect.Int8:
if i64 < -1*(1<<7) || (1<<7) <= i64 {
if i64 <= -1*(1<<7) || (1<<7) <= i64 {
return d.typeError(bytes, s.totalOffset())
}
case reflect.Int16:
if i64 < -1*(1<<15) || (1<<15) <= i64 {
if i64 <= -1*(1<<15) || (1<<15) <= i64 {
return d.typeError(bytes, s.totalOffset())
}
case reflect.Int32:
if i64 < -1*(1<<31) || (1<<31) <= i64 {
if i64 <= -1*(1<<31) || (1<<31) <= i64 {
return d.typeError(bytes, s.totalOffset())
}
}
@ -225,22 +225,18 @@ func (d *intDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
}
switch d.kind {
case reflect.Int8:
if i64 < -1*(1<<7) || (1<<7) <= i64 {
if i64 <= -1*(1<<7) || (1<<7) <= i64 {
return 0, d.typeError(bytes, cursor)
}
case reflect.Int16:
if i64 < -1*(1<<15) || (1<<15) <= i64 {
if i64 <= -1*(1<<15) || (1<<15) <= i64 {
return 0, d.typeError(bytes, cursor)
}
case reflect.Int32:
if i64 < -1*(1<<31) || (1<<31) <= i64 {
if i64 <= -1*(1<<31) || (1<<31) <= i64 {
return 0, d.typeError(bytes, cursor)
}
}
d.op(p, i64)
return cursor, nil
}
func (d *intDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: int decoder does not support decode path")
}

View File

@ -94,7 +94,6 @@ func (d *interfaceDecoder) numDecoder(s *Stream) Decoder {
var (
emptyInterfaceType = runtime.Type2RType(reflect.TypeOf((*interface{})(nil)).Elem())
EmptyInterfaceType = emptyInterfaceType
interfaceMapType = runtime.Type2RType(
reflect.TypeOf((*map[string]interface{})(nil)).Elem(),
)
@ -457,72 +456,3 @@ func (d *interfaceDecoder) decodeEmptyInterface(ctx *RuntimeContext, cursor, dep
}
return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
}
func NewPathDecoder() Decoder {
ifaceDecoder := &interfaceDecoder{
typ: emptyInterfaceType,
structName: "",
fieldName: "",
floatDecoder: newFloatDecoder("", "", func(p unsafe.Pointer, v float64) {
*(*interface{})(p) = v
}),
numberDecoder: newNumberDecoder("", "", func(p unsafe.Pointer, v json.Number) {
*(*interface{})(p) = v
}),
stringDecoder: newStringDecoder("", ""),
}
ifaceDecoder.sliceDecoder = newSliceDecoder(
ifaceDecoder,
emptyInterfaceType,
emptyInterfaceType.Size(),
"", "",
)
ifaceDecoder.mapDecoder = newMapDecoder(
interfaceMapType,
stringType,
ifaceDecoder.stringDecoder,
interfaceMapType.Elem(),
ifaceDecoder,
"", "",
)
return ifaceDecoder
}
var (
truebytes = []byte("true")
falsebytes = []byte("false")
)
func (d *interfaceDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
buf := ctx.Buf
cursor = skipWhiteSpace(buf, cursor)
switch buf[cursor] {
case '{':
return d.mapDecoder.DecodePath(ctx, cursor, depth)
case '[':
return d.sliceDecoder.DecodePath(ctx, cursor, depth)
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return d.floatDecoder.DecodePath(ctx, cursor, depth)
case '"':
return d.stringDecoder.DecodePath(ctx, cursor, depth)
case 't':
if err := validateTrue(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 4
return [][]byte{truebytes}, cursor, nil
case 'f':
if err := validateFalse(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 5
return [][]byte{falsebytes}, cursor, nil
case 'n':
if err := validateNull(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 4
return [][]byte{nullbytes}, cursor, nil
}
return nil, cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
}

View File

@ -1,55 +0,0 @@
package decoder
import (
"reflect"
"unsafe"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
type invalidDecoder struct {
typ *runtime.Type
kind reflect.Kind
structName string
fieldName string
}
func newInvalidDecoder(typ *runtime.Type, structName, fieldName string) *invalidDecoder {
return &invalidDecoder{
typ: typ,
kind: typ.Kind(),
structName: structName,
fieldName: fieldName,
}
}
func (d *invalidDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
return &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(d.typ),
Offset: s.totalOffset(),
Struct: d.structName,
Field: d.fieldName,
}
}
func (d *invalidDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
return 0, &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(d.typ),
Offset: cursor,
Struct: d.structName,
Field: d.fieldName,
}
}
func (d *invalidDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(d.typ),
Offset: cursor,
Struct: d.structName,
Field: d.fieldName,
}
}

View File

@ -87,13 +87,13 @@ func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
if mapValue == nil {
mapValue = makemap(d.mapType, 0)
}
s.cursor++
if s.equalChar('}') {
if s.buf[s.cursor+1] == '}' {
*(*unsafe.Pointer)(p) = mapValue
s.cursor++
s.cursor += 2
return nil
}
for {
s.cursor++
k := unsafe_New(d.keyType)
if err := d.keyDecoder.DecodeStream(s, depth, k); err != nil {
return err
@ -117,7 +117,6 @@ func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
if !s.equalChar(',') {
return errors.ErrExpected("comma after object value", s.totalOffset())
}
s.cursor++
}
}
@ -185,96 +184,3 @@ func (d *mapDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
cursor++
}
}
func (d *mapDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
buf := ctx.Buf
depth++
if depth > maxDecodeNestingDepth {
return nil, 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
}
cursor = skipWhiteSpace(buf, cursor)
buflen := int64(len(buf))
if buflen < 2 {
return nil, 0, errors.ErrExpected("{} for map", cursor)
}
switch buf[cursor] {
case 'n':
if err := validateNull(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 4
return [][]byte{nullbytes}, cursor, nil
case '{':
default:
return nil, 0, errors.ErrExpected("{ character for map value", cursor)
}
cursor++
cursor = skipWhiteSpace(buf, cursor)
if buf[cursor] == '}' {
cursor++
return nil, cursor, nil
}
keyDecoder, ok := d.keyDecoder.(*stringDecoder)
if !ok {
return nil, 0, &errors.UnmarshalTypeError{
Value: "string",
Type: reflect.TypeOf(""),
Offset: cursor,
Struct: d.structName,
Field: d.fieldName,
}
}
ret := [][]byte{}
for {
key, keyCursor, err := keyDecoder.decodeByte(buf, cursor)
if err != nil {
return nil, 0, err
}
cursor = skipWhiteSpace(buf, keyCursor)
if buf[cursor] != ':' {
return nil, 0, errors.ErrExpected("colon after object key", cursor)
}
cursor++
child, found, err := ctx.Option.Path.Field(string(key))
if err != nil {
return nil, 0, err
}
if found {
if child != nil {
oldPath := ctx.Option.Path.node
ctx.Option.Path.node = child
paths, c, err := d.valueDecoder.DecodePath(ctx, cursor, depth)
if err != nil {
return nil, 0, err
}
ctx.Option.Path.node = oldPath
ret = append(ret, paths...)
cursor = c
} else {
start := cursor
end, err := skipValue(buf, cursor, depth)
if err != nil {
return nil, 0, err
}
ret = append(ret, buf[start:end])
cursor = end
}
} else {
c, err := skipValue(buf, cursor, depth)
if err != nil {
return nil, 0, err
}
cursor = c
}
cursor = skipWhiteSpace(buf, cursor)
if buf[cursor] == '}' {
cursor++
return ret, cursor, nil
}
if buf[cursor] != ',' {
return nil, 0, errors.ErrExpected("comma after object value", cursor)
}
cursor++
}
}

View File

@ -51,17 +51,6 @@ func (d *numberDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
return cursor, nil
}
func (d *numberDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
bytes, c, err := d.decodeByte(ctx.Buf, cursor)
if err != nil {
return nil, 0, err
}
if bytes == nil {
return [][]byte{nullbytes}, c, nil
}
return [][]byte{bytes}, c, nil
}
func (d *numberDecoder) decodeStreamByte(s *Stream) ([]byte, error) {
start := s.cursor
for {

View File

@ -7,11 +7,9 @@ type OptionFlags uint8
const (
FirstWinOption OptionFlags = 1 << iota
ContextOption
PathOption
)
type Option struct {
Flags OptionFlags
Context context.Context
Path *Path
}

View File

@ -1,670 +0,0 @@
package decoder
import (
"fmt"
"reflect"
"strconv"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
type PathString string
func (s PathString) Build() (*Path, error) {
builder := new(PathBuilder)
return builder.Build([]rune(s))
}
type PathBuilder struct {
root PathNode
node PathNode
singleQuotePathSelector bool
doubleQuotePathSelector bool
}
func (b *PathBuilder) Build(buf []rune) (*Path, error) {
node, err := b.build(buf)
if err != nil {
return nil, err
}
return &Path{
node: node,
RootSelectorOnly: node == nil,
SingleQuotePathSelector: b.singleQuotePathSelector,
DoubleQuotePathSelector: b.doubleQuotePathSelector,
}, nil
}
func (b *PathBuilder) build(buf []rune) (PathNode, error) {
if len(buf) == 0 {
return nil, errors.ErrEmptyPath()
}
if buf[0] != '$' {
return nil, errors.ErrInvalidPath("JSON Path must start with a $ character")
}
if len(buf) == 1 {
return nil, nil
}
buf = buf[1:]
offset, err := b.buildNext(buf)
if err != nil {
return nil, err
}
if len(buf) > offset {
return nil, errors.ErrInvalidPath("remain invalid path %q", buf[offset:])
}
return b.root, nil
}
func (b *PathBuilder) buildNextCharIfExists(buf []rune, cursor int) (int, error) {
if len(buf) > cursor {
offset, err := b.buildNext(buf[cursor:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
}
return cursor, nil
}
func (b *PathBuilder) buildNext(buf []rune) (int, error) {
switch buf[0] {
case '.':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
}
offset, err := b.buildSelector(buf[1:])
if err != nil {
return 0, err
}
return offset + 1, nil
case '[':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
}
offset, err := b.buildIndex(buf[1:])
if err != nil {
return 0, err
}
return offset + 1, nil
default:
return 0, errors.ErrInvalidPath("expect dot or left bracket character. but found %c character", buf[0])
}
}
func (b *PathBuilder) buildSelector(buf []rune) (int, error) {
switch buf[0] {
case '.':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with double dot character")
}
offset, err := b.buildPathRecursive(buf[1:])
if err != nil {
return 0, err
}
return 1 + offset, nil
case '[', ']', '$', '*':
return 0, errors.ErrInvalidPath("found invalid path character %c after dot", buf[0])
}
for cursor := 0; cursor < len(buf); cursor++ {
switch buf[cursor] {
case '$', '*', ']':
return 0, errors.ErrInvalidPath("found %c character in field selector context", buf[cursor])
case '.':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
}
selector := buf[:cursor]
b.addSelectorNode(string(selector))
offset, err := b.buildSelector(buf[cursor+1:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
case '[':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
}
selector := buf[:cursor]
b.addSelectorNode(string(selector))
offset, err := b.buildIndex(buf[cursor+1:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
case '"':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with double quote character")
}
offset, err := b.buildQuoteSelector(buf[cursor+1:], DoubleQuotePathSelector)
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
}
}
b.addSelectorNode(string(buf))
return len(buf), nil
}
func (b *PathBuilder) buildQuoteSelector(buf []rune, sel QuotePathSelector) (int, error) {
switch buf[0] {
case '[', ']', '$', '.', '*', '\'', '"':
return 0, errors.ErrInvalidPath("found invalid path character %c after quote", buf[0])
}
for cursor := 0; cursor < len(buf); cursor++ {
switch buf[cursor] {
case '\'':
if sel != SingleQuotePathSelector {
return 0, errors.ErrInvalidPath("found double quote character in field selector with single quote context")
}
if len(buf) <= cursor+1 {
return 0, errors.ErrInvalidPath("JSON Path ends with single quote character in field selector context")
}
if buf[cursor+1] != ']' {
return 0, errors.ErrInvalidPath("expect right bracket for field selector with single quote but found %c", buf[cursor+1])
}
selector := buf[:cursor]
b.addSelectorNode(string(selector))
b.singleQuotePathSelector = true
return b.buildNextCharIfExists(buf, cursor+2)
case '"':
if sel != DoubleQuotePathSelector {
return 0, errors.ErrInvalidPath("found single quote character in field selector with double quote context")
}
selector := buf[:cursor]
b.addSelectorNode(string(selector))
b.doubleQuotePathSelector = true
return b.buildNextCharIfExists(buf, cursor+1)
}
}
return 0, errors.ErrInvalidPath("couldn't find quote character in selector quote path context")
}
func (b *PathBuilder) buildPathRecursive(buf []rune) (int, error) {
switch buf[0] {
case '.', '[', ']', '$', '*':
return 0, errors.ErrInvalidPath("found invalid path character %c after double dot", buf[0])
}
for cursor := 0; cursor < len(buf); cursor++ {
switch buf[cursor] {
case '$', '*', ']':
return 0, errors.ErrInvalidPath("found %c character in field selector context", buf[cursor])
case '.':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
}
selector := buf[:cursor]
b.addRecursiveNode(string(selector))
offset, err := b.buildSelector(buf[cursor+1:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
case '[':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
}
selector := buf[:cursor]
b.addRecursiveNode(string(selector))
offset, err := b.buildIndex(buf[cursor+1:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
}
}
b.addRecursiveNode(string(buf))
return len(buf), nil
}
func (b *PathBuilder) buildIndex(buf []rune) (int, error) {
switch buf[0] {
case '.', '[', ']', '$':
return 0, errors.ErrInvalidPath("found invalid path character %c after left bracket", buf[0])
case '\'':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with single quote character")
}
offset, err := b.buildQuoteSelector(buf[1:], SingleQuotePathSelector)
if err != nil {
return 0, err
}
return 1 + offset, nil
case '*':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with star character")
}
if buf[1] != ']' {
return 0, errors.ErrInvalidPath("expect right bracket character for index all path but found %c character", buf[1])
}
b.addIndexAllNode()
offset := len("*]")
if len(buf) > 2 {
buildOffset, err := b.buildNext(buf[2:])
if err != nil {
return 0, err
}
return offset + buildOffset, nil
}
return offset, nil
}
for cursor := 0; cursor < len(buf); cursor++ {
switch buf[cursor] {
case ']':
index, err := strconv.ParseInt(string(buf[:cursor]), 10, 64)
if err != nil {
return 0, errors.ErrInvalidPath("%q is unexpected index path", buf[:cursor])
}
b.addIndexNode(int(index))
return b.buildNextCharIfExists(buf, cursor+1)
}
}
return 0, errors.ErrInvalidPath("couldn't find right bracket character in index path context")
}
func (b *PathBuilder) addIndexAllNode() {
node := newPathIndexAllNode()
if b.root == nil {
b.root = node
b.node = node
} else {
b.node = b.node.chain(node)
}
}
func (b *PathBuilder) addRecursiveNode(selector string) {
node := newPathRecursiveNode(selector)
if b.root == nil {
b.root = node
b.node = node
} else {
b.node = b.node.chain(node)
}
}
func (b *PathBuilder) addSelectorNode(name string) {
node := newPathSelectorNode(name)
if b.root == nil {
b.root = node
b.node = node
} else {
b.node = b.node.chain(node)
}
}
func (b *PathBuilder) addIndexNode(idx int) {
node := newPathIndexNode(idx)
if b.root == nil {
b.root = node
b.node = node
} else {
b.node = b.node.chain(node)
}
}
type QuotePathSelector int
const (
SingleQuotePathSelector QuotePathSelector = 1
DoubleQuotePathSelector QuotePathSelector = 2
)
type Path struct {
node PathNode
RootSelectorOnly bool
SingleQuotePathSelector bool
DoubleQuotePathSelector bool
}
func (p *Path) Field(sel string) (PathNode, bool, error) {
if p.node == nil {
return nil, false, nil
}
return p.node.Field(sel)
}
func (p *Path) Get(src, dst reflect.Value) error {
if p.node == nil {
return nil
}
return p.node.Get(src, dst)
}
func (p *Path) String() string {
if p.node == nil {
return "$"
}
return p.node.String()
}
type PathNode interface {
fmt.Stringer
Index(idx int) (PathNode, bool, error)
Field(fieldName string) (PathNode, bool, error)
Get(src, dst reflect.Value) error
chain(PathNode) PathNode
target() bool
single() bool
}
type BasePathNode struct {
child PathNode
}
func (n *BasePathNode) chain(node PathNode) PathNode {
n.child = node
return node
}
func (n *BasePathNode) target() bool {
return n.child == nil
}
func (n *BasePathNode) single() bool {
return true
}
type PathSelectorNode struct {
*BasePathNode
selector string
}
func newPathSelectorNode(selector string) *PathSelectorNode {
return &PathSelectorNode{
BasePathNode: &BasePathNode{},
selector: selector,
}
}
func (n *PathSelectorNode) Index(idx int) (PathNode, bool, error) {
return nil, false, &errors.PathError{}
}
func (n *PathSelectorNode) Field(fieldName string) (PathNode, bool, error) {
if n.selector == fieldName {
return n.child, true, nil
}
return nil, false, nil
}
func (n *PathSelectorNode) Get(src, dst reflect.Value) error {
switch src.Type().Kind() {
case reflect.Map:
iter := src.MapRange()
for iter.Next() {
key, ok := iter.Key().Interface().(string)
if !ok {
return fmt.Errorf("invalid map key type %T", src.Type().Key())
}
child, found, err := n.Field(key)
if err != nil {
return err
}
if found {
if child != nil {
return child.Get(iter.Value(), dst)
}
return AssignValue(iter.Value(), dst)
}
}
case reflect.Struct:
typ := src.Type()
for i := 0; i < typ.Len(); i++ {
tag := runtime.StructTagFromField(typ.Field(i))
child, found, err := n.Field(tag.Key)
if err != nil {
return err
}
if found {
if child != nil {
return child.Get(src.Field(i), dst)
}
return AssignValue(src.Field(i), dst)
}
}
case reflect.Ptr:
return n.Get(src.Elem(), dst)
case reflect.Interface:
return n.Get(reflect.ValueOf(src.Interface()), dst)
case reflect.Float64, reflect.String, reflect.Bool:
return AssignValue(src, dst)
}
return fmt.Errorf("failed to get %s value from %s", n.selector, src.Type())
}
func (n *PathSelectorNode) String() string {
s := fmt.Sprintf(".%s", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}
type PathIndexNode struct {
*BasePathNode
selector int
}
func newPathIndexNode(selector int) *PathIndexNode {
return &PathIndexNode{
BasePathNode: &BasePathNode{},
selector: selector,
}
}
func (n *PathIndexNode) Index(idx int) (PathNode, bool, error) {
if n.selector == idx {
return n.child, true, nil
}
return nil, false, nil
}
func (n *PathIndexNode) Field(fieldName string) (PathNode, bool, error) {
return nil, false, &errors.PathError{}
}
func (n *PathIndexNode) Get(src, dst reflect.Value) error {
switch src.Type().Kind() {
case reflect.Array, reflect.Slice:
if src.Len() > n.selector {
if n.child != nil {
return n.child.Get(src.Index(n.selector), dst)
}
return AssignValue(src.Index(n.selector), dst)
}
case reflect.Ptr:
return n.Get(src.Elem(), dst)
case reflect.Interface:
return n.Get(reflect.ValueOf(src.Interface()), dst)
}
return fmt.Errorf("failed to get [%d] value from %s", n.selector, src.Type())
}
func (n *PathIndexNode) String() string {
s := fmt.Sprintf("[%d]", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}
type PathIndexAllNode struct {
*BasePathNode
}
func newPathIndexAllNode() *PathIndexAllNode {
return &PathIndexAllNode{
BasePathNode: &BasePathNode{},
}
}
func (n *PathIndexAllNode) Index(idx int) (PathNode, bool, error) {
return n.child, true, nil
}
func (n *PathIndexAllNode) Field(fieldName string) (PathNode, bool, error) {
return nil, false, &errors.PathError{}
}
func (n *PathIndexAllNode) Get(src, dst reflect.Value) error {
switch src.Type().Kind() {
case reflect.Array, reflect.Slice:
var arr []interface{}
for i := 0; i < src.Len(); i++ {
var v interface{}
rv := reflect.ValueOf(&v)
if n.child != nil {
if err := n.child.Get(src.Index(i), rv); err != nil {
return err
}
} else {
if err := AssignValue(src.Index(i), rv); err != nil {
return err
}
}
arr = append(arr, v)
}
if err := AssignValue(reflect.ValueOf(arr), dst); err != nil {
return err
}
return nil
case reflect.Ptr:
return n.Get(src.Elem(), dst)
case reflect.Interface:
return n.Get(reflect.ValueOf(src.Interface()), dst)
}
return fmt.Errorf("failed to get all value from %s", src.Type())
}
func (n *PathIndexAllNode) String() string {
s := "[*]"
if n.child != nil {
s += n.child.String()
}
return s
}
type PathRecursiveNode struct {
*BasePathNode
selector string
}
func newPathRecursiveNode(selector string) *PathRecursiveNode {
node := newPathSelectorNode(selector)
return &PathRecursiveNode{
BasePathNode: &BasePathNode{
child: node,
},
selector: selector,
}
}
func (n *PathRecursiveNode) Field(fieldName string) (PathNode, bool, error) {
if n.selector == fieldName {
return n.child, true, nil
}
return nil, false, nil
}
func (n *PathRecursiveNode) Index(_ int) (PathNode, bool, error) {
return n, true, nil
}
func valueToSliceValue(v interface{}) []interface{} {
rv := reflect.ValueOf(v)
ret := []interface{}{}
if rv.Type().Kind() == reflect.Slice || rv.Type().Kind() == reflect.Array {
for i := 0; i < rv.Len(); i++ {
ret = append(ret, rv.Index(i).Interface())
}
return ret
}
return []interface{}{v}
}
func (n *PathRecursiveNode) Get(src, dst reflect.Value) error {
if n.child == nil {
return fmt.Errorf("failed to get by recursive path ..%s", n.selector)
}
var arr []interface{}
switch src.Type().Kind() {
case reflect.Map:
iter := src.MapRange()
for iter.Next() {
key, ok := iter.Key().Interface().(string)
if !ok {
return fmt.Errorf("invalid map key type %T", src.Type().Key())
}
child, found, err := n.Field(key)
if err != nil {
return err
}
if found {
var v interface{}
rv := reflect.ValueOf(&v)
_ = child.Get(iter.Value(), rv)
arr = append(arr, valueToSliceValue(v)...)
} else {
var v interface{}
rv := reflect.ValueOf(&v)
_ = n.Get(iter.Value(), rv)
if v != nil {
arr = append(arr, valueToSliceValue(v)...)
}
}
}
_ = AssignValue(reflect.ValueOf(arr), dst)
return nil
case reflect.Struct:
typ := src.Type()
for i := 0; i < typ.Len(); i++ {
tag := runtime.StructTagFromField(typ.Field(i))
child, found, err := n.Field(tag.Key)
if err != nil {
return err
}
if found {
var v interface{}
rv := reflect.ValueOf(&v)
_ = child.Get(src.Field(i), rv)
arr = append(arr, valueToSliceValue(v)...)
} else {
var v interface{}
rv := reflect.ValueOf(&v)
_ = n.Get(src.Field(i), rv)
if v != nil {
arr = append(arr, valueToSliceValue(v)...)
}
}
}
_ = AssignValue(reflect.ValueOf(arr), dst)
return nil
case reflect.Array, reflect.Slice:
for i := 0; i < src.Len(); i++ {
var v interface{}
rv := reflect.ValueOf(&v)
_ = n.Get(src.Index(i), rv)
if v != nil {
arr = append(arr, valueToSliceValue(v)...)
}
}
_ = AssignValue(reflect.ValueOf(arr), dst)
return nil
case reflect.Ptr:
return n.Get(src.Elem(), dst)
case reflect.Interface:
return n.Get(reflect.ValueOf(src.Interface()), dst)
}
return fmt.Errorf("failed to get %s value from %s", n.selector, src.Type())
}
func (n *PathRecursiveNode) String() string {
s := fmt.Sprintf("..%s", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}

View File

@ -1,7 +1,6 @@
package decoder
import (
"fmt"
"unsafe"
"github.com/goccy/go-json/internal/runtime"
@ -35,10 +34,6 @@ func (d *ptrDecoder) contentDecoder() Decoder {
//go:linkname unsafe_New reflect.unsafe_New
func unsafe_New(*runtime.Type) unsafe.Pointer
func UnsafeNew(t *runtime.Type) unsafe.Pointer {
return unsafe_New(t)
}
func (d *ptrDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
if s.skipWhiteSpace() == nul {
s.read()
@ -90,7 +85,3 @@ func (d *ptrDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
cursor = c
return cursor, nil
}
func (d *ptrDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: ptr decoder does not support decode path")
}

View File

@ -299,82 +299,3 @@ func (d *sliceDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
}
}
}
func (d *sliceDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
buf := ctx.Buf
depth++
if depth > maxDecodeNestingDepth {
return nil, 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
}
ret := [][]byte{}
for {
switch buf[cursor] {
case ' ', '\n', '\t', '\r':
cursor++
continue
case 'n':
if err := validateNull(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 4
return [][]byte{nullbytes}, cursor, nil
case '[':
cursor++
cursor = skipWhiteSpace(buf, cursor)
if buf[cursor] == ']' {
cursor++
return ret, cursor, nil
}
idx := 0
for {
child, found, err := ctx.Option.Path.node.Index(idx)
if err != nil {
return nil, 0, err
}
if found {
if child != nil {
oldPath := ctx.Option.Path.node
ctx.Option.Path.node = child
paths, c, err := d.valueDecoder.DecodePath(ctx, cursor, depth)
if err != nil {
return nil, 0, err
}
ctx.Option.Path.node = oldPath
ret = append(ret, paths...)
cursor = c
} else {
start := cursor
end, err := skipValue(buf, cursor, depth)
if err != nil {
return nil, 0, err
}
ret = append(ret, buf[start:end])
cursor = end
}
} else {
c, err := skipValue(buf, cursor, depth)
if err != nil {
return nil, 0, err
}
cursor = c
}
cursor = skipWhiteSpace(buf, cursor)
switch buf[cursor] {
case ']':
cursor++
return ret, cursor, nil
case ',':
idx++
default:
return nil, 0, errors.ErrInvalidCharacter(buf[cursor], "slice", cursor)
}
cursor++
}
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return nil, 0, d.errNumber(cursor)
default:
return nil, 0, errors.ErrUnexpectedEndOfJSON("slice", cursor)
}
}
}

View File

@ -103,7 +103,7 @@ func (s *Stream) statForRetry() ([]byte, int64, unsafe.Pointer) {
func (s *Stream) Reset() {
s.reset()
s.bufSize = int64(len(s.buf))
s.bufSize = initBufSize
}
func (s *Stream) More() bool {
@ -138,11 +138,8 @@ func (s *Stream) Token() (interface{}, error) {
s.cursor++
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
bytes := floatBytes(s)
str := *(*string)(unsafe.Pointer(&bytes))
if s.UseNumber {
return json.Number(str), nil
}
f64, err := strconv.ParseFloat(str, 64)
s := *(*string)(unsafe.Pointer(&bytes))
f64, err := strconv.ParseFloat(s, 64)
if err != nil {
return nil, err
}
@ -280,7 +277,7 @@ func (s *Stream) skipObject(depth int64) error {
if char(p, cursor) == nul {
s.cursor = cursor
if s.read() {
_, cursor, p = s.stat()
_, cursor, p = s.statForRetry()
continue
}
return errors.ErrUnexpectedEndOfJSON("string of object", cursor)
@ -343,7 +340,7 @@ func (s *Stream) skipArray(depth int64) error {
if char(p, cursor) == nul {
s.cursor = cursor
if s.read() {
_, cursor, p = s.stat()
_, cursor, p = s.statForRetry()
continue
}
return errors.ErrUnexpectedEndOfJSON("string of object", cursor)
@ -401,7 +398,7 @@ func (s *Stream) skipValue(depth int64) error {
if char(p, cursor) == nul {
s.cursor = cursor
if s.read() {
_, cursor, p = s.stat()
_, cursor, p = s.statForRetry()
continue
}
return errors.ErrUnexpectedEndOfJSON("value of string", s.totalOffset())
@ -426,6 +423,7 @@ func (s *Stream) skipValue(depth int64) error {
continue
} else if c == nul {
if s.read() {
s.cursor-- // for retry current character
_, cursor, p = s.stat()
continue
}

View File

@ -1,8 +1,6 @@
package decoder
import (
"bytes"
"fmt"
"reflect"
"unicode"
"unicode/utf16"
@ -60,17 +58,6 @@ func (d *stringDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
return cursor, nil
}
func (d *stringDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
bytes, c, err := d.decodeByte(ctx.Buf, cursor)
if err != nil {
return nil, 0, err
}
if bytes == nil {
return [][]byte{nullbytes}, c, nil
}
return [][]byte{bytes}, c, nil
}
var (
hexToInt = [256]int{
'0': 0,
@ -106,30 +93,24 @@ func unicodeToRune(code []byte) rune {
return r
}
func readAtLeast(s *Stream, n int64, p *unsafe.Pointer) bool {
for s.cursor+n >= s.length {
if !s.read() {
return false
}
*p = s.bufptr()
}
return true
}
func decodeUnicodeRune(s *Stream, p unsafe.Pointer) (rune, int64, unsafe.Pointer, error) {
const defaultOffset = 5
const surrogateOffset = 11
if !readAtLeast(s, defaultOffset, &p) {
return rune(0), 0, nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset())
if s.cursor+defaultOffset >= s.length {
if !s.read() {
return rune(0), 0, nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset())
}
p = s.bufptr()
}
r := unicodeToRune(s.buf[s.cursor+1 : s.cursor+defaultOffset])
if utf16.IsSurrogate(r) {
if !readAtLeast(s, surrogateOffset, &p) {
return unicode.ReplacementChar, defaultOffset, p, nil
if s.cursor+surrogateOffset >= s.length {
s.read()
p = s.bufptr()
}
if s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' {
if s.cursor+surrogateOffset >= s.length || s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' {
return unicode.ReplacementChar, defaultOffset, p, nil
}
r2 := unicodeToRune(s.buf[s.cursor+defaultOffset+2 : s.cursor+surrogateOffset])
@ -182,7 +163,6 @@ RETRY:
if !s.read() {
return nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset())
}
p = s.bufptr()
goto RETRY
default:
return nil, errors.ErrUnexpectedEndOfJSON("string", s.totalOffset())
@ -328,36 +308,49 @@ func (d *stringDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, err
cursor++
start := cursor
b := (*sliceHeader)(unsafe.Pointer(&buf)).data
escaped := 0
for {
switch char(b, cursor) {
case '\\':
escaped++
cursor++
switch char(b, cursor) {
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
cursor++
case '"':
buf[cursor] = '"'
buf = append(buf[:cursor-1], buf[cursor:]...)
case '\\':
buf[cursor] = '\\'
buf = append(buf[:cursor-1], buf[cursor:]...)
case '/':
buf[cursor] = '/'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'b':
buf[cursor] = '\b'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'f':
buf[cursor] = '\f'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'n':
buf[cursor] = '\n'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'r':
buf[cursor] = '\r'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 't':
buf[cursor] = '\t'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'u':
buflen := int64(len(buf))
if cursor+5 >= buflen {
return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor)
}
for i := int64(1); i <= 4; i++ {
c := char(b, cursor+i)
if !(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) {
return nil, 0, errors.ErrSyntax(fmt.Sprintf("json: invalid character %c in \\u hexadecimal character escape", c), cursor+i)
}
}
cursor += 5
code := unicodeToRune(buf[cursor+1 : cursor+5])
unicode := []byte(string(code))
buf = append(append(buf[:cursor-1], unicode...), buf[cursor+5:]...)
default:
return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor)
}
continue
case '"':
literal := buf[start:cursor]
if escaped > 0 {
literal = literal[:unescapeString(literal)]
}
cursor++
return literal, cursor, nil
case nul:
@ -376,77 +369,3 @@ func (d *stringDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, err
}
}
}
var unescapeMap = [256]byte{
'"': '"',
'\\': '\\',
'/': '/',
'b': '\b',
'f': '\f',
'n': '\n',
'r': '\r',
't': '\t',
}
func unsafeAdd(ptr unsafe.Pointer, offset int) unsafe.Pointer {
return unsafe.Pointer(uintptr(ptr) + uintptr(offset))
}
func unescapeString(buf []byte) int {
p := (*sliceHeader)(unsafe.Pointer(&buf)).data
end := unsafeAdd(p, len(buf))
src := unsafeAdd(p, bytes.IndexByte(buf, '\\'))
dst := src
for src != end {
c := char(src, 0)
if c == '\\' {
escapeChar := char(src, 1)
if escapeChar != 'u' {
*(*byte)(dst) = unescapeMap[escapeChar]
src = unsafeAdd(src, 2)
dst = unsafeAdd(dst, 1)
} else {
v1 := hexToInt[char(src, 2)]
v2 := hexToInt[char(src, 3)]
v3 := hexToInt[char(src, 4)]
v4 := hexToInt[char(src, 5)]
code := rune((v1 << 12) | (v2 << 8) | (v3 << 4) | v4)
if code >= 0xd800 && code < 0xdc00 && uintptr(unsafeAdd(src, 11)) < uintptr(end) {
if char(src, 6) == '\\' && char(src, 7) == 'u' {
v1 := hexToInt[char(src, 8)]
v2 := hexToInt[char(src, 9)]
v3 := hexToInt[char(src, 10)]
v4 := hexToInt[char(src, 11)]
lo := rune((v1 << 12) | (v2 << 8) | (v3 << 4) | v4)
if lo >= 0xdc00 && lo < 0xe000 {
code = (code-0xd800)<<10 | (lo - 0xdc00) + 0x10000
src = unsafeAdd(src, 6)
}
}
}
var b [utf8.UTFMax]byte
n := utf8.EncodeRune(b[:], code)
switch n {
case 4:
*(*byte)(unsafeAdd(dst, 3)) = b[3]
fallthrough
case 3:
*(*byte)(unsafeAdd(dst, 2)) = b[2]
fallthrough
case 2:
*(*byte)(unsafeAdd(dst, 1)) = b[1]
fallthrough
case 1:
*(*byte)(unsafeAdd(dst, 0)) = b[0]
}
src = unsafeAdd(src, 6)
dst = unsafeAdd(dst, n)
}
} else {
*(*byte)(dst) = c
src = unsafeAdd(src, 1)
dst = unsafeAdd(dst, 1)
}
}
return int(uintptr(dst) - uintptr(p))
}

View File

@ -817,7 +817,3 @@ func (d *structDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
cursor++
}
}
func (d *structDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: struct decoder does not support decode path")
}

View File

@ -10,7 +10,6 @@ import (
type Decoder interface {
Decode(*RuntimeContext, int64, int64, unsafe.Pointer) (int64, error)
DecodePath(*RuntimeContext, int64, int64) ([][]byte, int64, error)
DecodeStream(*Stream, int64, unsafe.Pointer) error
}

View File

@ -188,7 +188,3 @@ func (d *uintDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
d.op(p, u64)
return cursor, nil
}
func (d *uintDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: uint decoder does not support decode path")
}

View File

@ -1,9 +1,7 @@
package decoder
import (
"context"
"encoding/json"
"fmt"
"unsafe"
"github.com/goccy/go-json/internal/errors"
@ -48,20 +46,13 @@ func (d *unmarshalJSONDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Poi
typ: d.typ,
ptr: p,
}))
switch v := v.(type) {
case unmarshalerContext:
var ctx context.Context
if (s.Option.Flags & ContextOption) != 0 {
ctx = s.Option.Context
} else {
ctx = context.Background()
}
if err := v.UnmarshalJSON(ctx, dst); err != nil {
if (s.Option.Flags & ContextOption) != 0 {
if err := v.(unmarshalerContext).UnmarshalJSON(s.Option.Context, dst); err != nil {
d.annotateError(s.cursor, err)
return err
}
case json.Unmarshaler:
if err := v.UnmarshalJSON(dst); err != nil {
} else {
if err := v.(json.Unmarshaler).UnmarshalJSON(dst); err != nil {
d.annotateError(s.cursor, err)
return err
}
@ -98,7 +89,3 @@ func (d *unmarshalJSONDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
}
return end, nil
}
func (d *unmarshalJSONDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: unmarshal json decoder does not support decode path")
}

View File

@ -3,7 +3,6 @@ package decoder
import (
"bytes"
"encoding"
"fmt"
"unicode"
"unicode/utf16"
"unicode/utf8"
@ -143,10 +142,6 @@ func (d *unmarshalTextDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
return end, nil
}
func (d *unmarshalTextDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: unmarshal text decoder does not support decode path")
}
func unquoteBytes(s []byte) (t []byte, ok bool) {
length := len(s)
if length < 2 || s[0] != '"' || s[length-1] != '"' {

View File

@ -1,7 +1,6 @@
package decoder
import (
"fmt"
"reflect"
"unsafe"
@ -67,7 +66,3 @@ func (d *wrappedStringDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
ctx.Buf = oldBuf
return c, nil
}
func (d *wrappedStringDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: wrapped string decoder does not support decode path")
}

View File

@ -2,7 +2,6 @@ package encoder
import (
"fmt"
"reflect"
"unsafe"
"github.com/goccy/go-json/internal/runtime"
@ -11,7 +10,6 @@ import (
type Code interface {
Kind() CodeKind
ToOpcode(*compileContext) Opcodes
Filter(*FieldQuery) Code
}
type AnonymousCode interface {
@ -84,10 +82,6 @@ func (c *IntCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *IntCode) Filter(_ *FieldQuery) Code {
return c
}
type UintCode struct {
typ *runtime.Type
bitSize uint8
@ -114,10 +108,6 @@ func (c *UintCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *UintCode) Filter(_ *FieldQuery) Code {
return c
}
type FloatCode struct {
typ *runtime.Type
bitSize uint8
@ -150,10 +140,6 @@ func (c *FloatCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *FloatCode) Filter(_ *FieldQuery) Code {
return c
}
type StringCode struct {
typ *runtime.Type
isPtr bool
@ -183,10 +169,6 @@ func (c *StringCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *StringCode) Filter(_ *FieldQuery) Code {
return c
}
type BoolCode struct {
typ *runtime.Type
isPtr bool
@ -208,10 +190,6 @@ func (c *BoolCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *BoolCode) Filter(_ *FieldQuery) Code {
return c
}
type BytesCode struct {
typ *runtime.Type
isPtr bool
@ -233,10 +211,6 @@ func (c *BytesCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *BytesCode) Filter(_ *FieldQuery) Code {
return c
}
type SliceCode struct {
typ *runtime.Type
value Code
@ -271,10 +245,6 @@ func (c *SliceCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{header}.Add(codes...).Add(elemCode).Add(end)
}
func (c *SliceCode) Filter(_ *FieldQuery) Code {
return c
}
type ArrayCode struct {
typ *runtime.Type
value Code
@ -316,10 +286,6 @@ func (c *ArrayCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{header}.Add(codes...).Add(elemCode).Add(end)
}
func (c *ArrayCode) Filter(_ *FieldQuery) Code {
return c
}
type MapCode struct {
typ *runtime.Type
key Code
@ -366,10 +332,6 @@ func (c *MapCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{header}.Add(keyCodes...).Add(value).Add(valueCodes...).Add(key).Add(end)
}
func (c *MapCode) Filter(_ *FieldQuery) Code {
return c
}
type StructCode struct {
typ *runtime.Type
fields []*StructFieldCode
@ -384,7 +346,7 @@ func (c *StructCode) Kind() CodeKind {
}
func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *Opcode {
if isEmbeddedStruct(field) {
if field.isAnonymous {
return c.lastAnonymousFieldCode(firstField)
}
lastField := firstField
@ -437,13 +399,12 @@ func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes {
}
if isEndField {
endField := fieldCodes.Last()
if isEmbeddedStruct(field) {
if len(codes) > 0 {
codes.First().End = endField
} else if field.isAnonymous {
firstField.End = endField
lastField := c.lastAnonymousFieldCode(firstField)
lastField.NextField = endField
}
if len(codes) > 0 {
codes.First().End = endField
} else {
firstField.End = endField
}
@ -559,45 +520,6 @@ func (c *StructCode) enableIndirect() {
structCode.enableIndirect()
}
func (c *StructCode) Filter(query *FieldQuery) Code {
fieldMap := map[string]*FieldQuery{}
for _, field := range query.Fields {
fieldMap[field.Name] = field
}
fields := make([]*StructFieldCode, 0, len(c.fields))
for _, field := range c.fields {
query, exists := fieldMap[field.key]
if !exists {
continue
}
fieldCode := &StructFieldCode{
typ: field.typ,
key: field.key,
tag: field.tag,
value: field.value,
offset: field.offset,
isAnonymous: field.isAnonymous,
isTaggedKey: field.isTaggedKey,
isNilableType: field.isNilableType,
isNilCheck: field.isNilCheck,
isAddrForMarshaler: field.isAddrForMarshaler,
isNextOpPtrType: field.isNextOpPtrType,
}
if len(query.Fields) > 0 {
fieldCode.value = fieldCode.value.Filter(query)
}
fields = append(fields, fieldCode)
}
return &StructCode{
typ: c.typ,
fields: fields,
isPtr: c.isPtr,
disableIndirectConversion: c.disableIndirectConversion,
isIndirect: c.isIndirect,
isRecursive: c.isRecursive,
}
}
type StructFieldCode struct {
typ *runtime.Type
key string
@ -610,7 +532,6 @@ type StructFieldCode struct {
isNilCheck bool
isAddrForMarshaler bool
isNextOpPtrType bool
isMarshalerContext bool
}
func (c *StructFieldCode) getStruct() *StructCode {
@ -653,12 +574,8 @@ func (c *StructFieldCode) headerOpcodes(ctx *compileContext, field *Opcode, valu
value := valueCodes.First()
op := optimizeStructHeader(value, c.tag)
field.Op = op
if value.Flags&MarshalerContextFlags != 0 {
field.Flags |= MarshalerContextFlags
}
field.NumBitSize = value.NumBitSize
field.PtrNum = value.PtrNum
field.FieldQuery = value.FieldQuery
fieldCodes := Opcodes{field}
if op.IsMultipleOpHead() {
field.Next = value
@ -673,12 +590,8 @@ func (c *StructFieldCode) fieldOpcodes(ctx *compileContext, field *Opcode, value
value := valueCodes.First()
op := optimizeStructField(value, c.tag)
field.Op = op
if value.Flags&MarshalerContextFlags != 0 {
field.Flags |= MarshalerContextFlags
}
field.NumBitSize = value.NumBitSize
field.PtrNum = value.PtrNum
field.FieldQuery = value.FieldQuery
fieldCodes := Opcodes{field}
if op.IsMultipleOpField() {
@ -732,9 +645,6 @@ func (c *StructFieldCode) flags() OpFlags {
if c.isAnonymous {
flags |= AnonymousKeyFlags
}
if c.isMarshalerContext {
flags |= MarshalerContextFlags
}
return flags
}
@ -815,9 +725,8 @@ func isEnableStructEndOptimization(value Code) bool {
}
type InterfaceCode struct {
typ *runtime.Type
fieldQuery *FieldQuery
isPtr bool
typ *runtime.Type
isPtr bool
}
func (c *InterfaceCode) Kind() CodeKind {
@ -832,7 +741,6 @@ func (c *InterfaceCode) ToOpcode(ctx *compileContext) Opcodes {
default:
code = newOpCode(ctx, c.typ, OpInterface)
}
code.FieldQuery = c.fieldQuery
if c.typ.NumMethod() > 0 {
code.Flags |= NonEmptyInterfaceFlags
}
@ -840,17 +748,8 @@ func (c *InterfaceCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *InterfaceCode) Filter(query *FieldQuery) Code {
return &InterfaceCode{
typ: c.typ,
fieldQuery: query,
isPtr: c.isPtr,
}
}
type MarshalJSONCode struct {
typ *runtime.Type
fieldQuery *FieldQuery
isAddrForMarshaler bool
isNilableType bool
isMarshalerContext bool
@ -862,7 +761,6 @@ func (c *MarshalJSONCode) Kind() CodeKind {
func (c *MarshalJSONCode) ToOpcode(ctx *compileContext) Opcodes {
code := newOpCode(ctx, c.typ, OpMarshalJSON)
code.FieldQuery = c.fieldQuery
if c.isAddrForMarshaler {
code.Flags |= AddrForMarshalerFlags
}
@ -878,19 +776,8 @@ func (c *MarshalJSONCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *MarshalJSONCode) Filter(query *FieldQuery) Code {
return &MarshalJSONCode{
typ: c.typ,
fieldQuery: query,
isAddrForMarshaler: c.isAddrForMarshaler,
isNilableType: c.isNilableType,
isMarshalerContext: c.isMarshalerContext,
}
}
type MarshalTextCode struct {
typ *runtime.Type
fieldQuery *FieldQuery
isAddrForMarshaler bool
isNilableType bool
}
@ -901,7 +788,6 @@ func (c *MarshalTextCode) Kind() CodeKind {
func (c *MarshalTextCode) ToOpcode(ctx *compileContext) Opcodes {
code := newOpCode(ctx, c.typ, OpMarshalText)
code.FieldQuery = c.fieldQuery
if c.isAddrForMarshaler {
code.Flags |= AddrForMarshalerFlags
}
@ -914,15 +800,6 @@ func (c *MarshalTextCode) ToOpcode(ctx *compileContext) Opcodes {
return Opcodes{code}
}
func (c *MarshalTextCode) Filter(query *FieldQuery) Code {
return &MarshalTextCode{
typ: c.typ,
fieldQuery: query,
isAddrForMarshaler: c.isAddrForMarshaler,
isNilableType: c.isNilableType,
}
}
type PtrCode struct {
typ *runtime.Type
value Code
@ -953,14 +830,6 @@ func (c *PtrCode) ToAnonymousOpcode(ctx *compileContext) Opcodes {
return codes
}
func (c *PtrCode) Filter(query *FieldQuery) Code {
return &PtrCode{
typ: c.typ,
value: c.value.Filter(query),
ptrNum: c.ptrNum,
}
}
func convertPtrOp(code *Opcode) OpType {
ptrHeadOp := code.Op.HeadToPtrHead()
if code.Op != ptrHeadOp {
@ -1004,14 +873,3 @@ func convertPtrOp(code *Opcode) OpType {
}
return code.Op
}
func isEmbeddedStruct(field *StructFieldCode) bool {
if !field.isAnonymous {
return false
}
t := field.typ
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t.Kind() == reflect.Struct
}

View File

@ -31,7 +31,7 @@ func init() {
if typeAddr == nil {
typeAddr = &runtime.TypeAddr{}
}
cachedOpcodeSets = make([]*OpcodeSet, typeAddr.AddrRange>>typeAddr.AddrShift+1)
cachedOpcodeSets = make([]*OpcodeSet, typeAddr.AddrRange>>typeAddr.AddrShift)
}
func loadOpcodeMap() map[uintptr]*OpcodeSet {
@ -63,27 +63,6 @@ func compileToGetCodeSetSlowPath(typeptr uintptr) (*OpcodeSet, error) {
return codeSet, nil
}
func getFilteredCodeSetIfNeeded(ctx *RuntimeContext, codeSet *OpcodeSet) (*OpcodeSet, error) {
if (ctx.Option.Flag & ContextOption) == 0 {
return codeSet, nil
}
query := FieldQueryFromContext(ctx.Option.Context)
if query == nil {
return codeSet, nil
}
ctx.Option.Flag |= FieldQueryOption
cacheCodeSet := codeSet.getQueryCache(query.Hash())
if cacheCodeSet != nil {
return cacheCodeSet, nil
}
queryCodeSet, err := newCompiler().codeToOpcodeSet(codeSet.Type, codeSet.Code.Filter(query))
if err != nil {
return nil, err
}
codeSet.setQueryCache(query.Hash(), queryCodeSet)
return queryCodeSet, nil
}
type Compiler struct {
structTypeToCode map[uintptr]*StructCode
}
@ -101,10 +80,6 @@ func (c *Compiler) compile(typeptr uintptr) (*OpcodeSet, error) {
if err != nil {
return nil, err
}
return c.codeToOpcodeSet(typ, code)
}
func (c *Compiler) codeToOpcodeSet(typ *runtime.Type, code Code) (*OpcodeSet, error) {
noescapeKeyCode := c.codeToOpcode(&compileContext{
structTypeToCodes: map[uintptr]Opcodes{},
recursiveCodes: &Opcodes{},
@ -132,8 +107,6 @@ func (c *Compiler) codeToOpcodeSet(typ *runtime.Type, code Code) (*OpcodeSet, er
InterfaceEscapeKeyCode: interfaceEscapeKeyCode,
CodeLength: codeLength,
EndCode: ToEndCode(interfaceNoescapeKeyCode),
Code: code,
QueryCache: map[string]*OpcodeSet{},
}, nil
}
@ -487,10 +460,7 @@ func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) {
case typ.Kind() == reflect.Map:
return c.ptrCode(runtime.PtrTo(typ))
default:
// isPtr was originally used to indicate whether the type of top level is pointer.
// However, since the slice/array element is a specification that can get the pointer address, explicitly set isPtr to true.
// See here for related issues: https://github.com/goccy/go-json/issues/370
code, err := c.typeToCodeWithPtr(typ, true)
code, err := c.typeToCodeWithPtr(typ, false)
if err != nil {
return nil, err
}
@ -506,6 +476,8 @@ func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) {
func (c *Compiler) mapKeyCode(typ *runtime.Type) (Code, error) {
switch {
case c.implementsMarshalJSON(typ):
return c.marshalJSONCode(typ)
case c.implementsMarshalText(typ):
return c.marshalTextCode(typ)
}
@ -854,9 +826,6 @@ func (c *Compiler) implementsMarshalText(typ *runtime.Type) bool {
}
func (c *Compiler) isNilableType(typ *runtime.Type) bool {
if !runtime.IfaceIndir(typ) {
return true
}
switch typ.Kind() {
case reflect.Ptr:
return true
@ -889,40 +858,29 @@ func (c *Compiler) codeToOpcode(ctx *compileContext, typ *runtime.Type, code Cod
}
func (c *Compiler) linkRecursiveCode(ctx *compileContext) {
recursiveCodes := map[uintptr]*CompiledCode{}
for _, recursive := range *ctx.recursiveCodes {
typeptr := uintptr(unsafe.Pointer(recursive.Type))
codes := ctx.structTypeToCodes[typeptr]
if recursiveCode, ok := recursiveCodes[typeptr]; ok {
*recursive.Jmp = *recursiveCode
continue
}
code := copyOpcode(codes.First())
compiled := recursive.Jmp
compiled.Code = copyOpcode(codes.First())
code := compiled.Code
code.End.Next = newEndOp(&compileContext{}, recursive.Type)
code.Op = code.Op.PtrHeadToHead()
lastCode := newEndOp(&compileContext{}, recursive.Type)
lastCode.Op = OpRecursiveEnd
// OpRecursiveEnd must set before call TotalLength
code.End.Next = lastCode
beforeLastCode := code.End
lastCode := beforeLastCode.Next
totalLength := code.TotalLength()
// Idx, ElemIdx, Length must set after call TotalLength
lastCode.Idx = uint32((totalLength + 1) * uintptrSize)
lastCode.ElemIdx = lastCode.Idx + uintptrSize
lastCode.Length = lastCode.Idx + 2*uintptrSize
code.End.Next.Op = OpRecursiveEnd
// extend length to alloc slot for elemIdx + length
curTotalLength := uintptr(recursive.TotalLength()) + 3
nextTotalLength := uintptr(totalLength) + 3
compiled := recursive.Jmp
compiled.Code = code
compiled.CurLen = curTotalLength
compiled.NextLen = nextTotalLength
compiled.Linked = true
recursiveCodes[typeptr] = compiled
}
}

View File

@ -3,30 +3,18 @@
package encoder
func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr || typeptr < typeAddr.BaseTypeAddr {
codeSet, err := compileToGetCodeSetSlowPath(typeptr)
if err != nil {
return nil, err
}
return getFilteredCodeSetIfNeeded(ctx, codeSet)
func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr {
return compileToGetCodeSetSlowPath(typeptr)
}
index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet)
if err != nil {
return nil, err
}
return filtered, nil
return codeSet, nil
}
codeSet, err := newCompiler().compile(typeptr)
if err != nil {
return nil, err
}
filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet)
if err != nil {
return nil, err
}
cachedOpcodeSets[index] = codeSet
return filtered, nil
return codeSet, nil
}

View File

@ -9,24 +9,15 @@ import (
var setsMu sync.RWMutex
func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr || typeptr < typeAddr.BaseTypeAddr {
codeSet, err := compileToGetCodeSetSlowPath(typeptr)
if err != nil {
return nil, err
}
return getFilteredCodeSetIfNeeded(ctx, codeSet)
func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr {
return compileToGetCodeSetSlowPath(typeptr)
}
index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift
setsMu.RLock()
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet)
if err != nil {
setsMu.RUnlock()
return nil, err
}
setsMu.RUnlock()
return filtered, nil
return codeSet, nil
}
setsMu.RUnlock()
@ -34,12 +25,8 @@ func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, erro
if err != nil {
return nil, err
}
filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet)
if err != nil {
return nil, err
}
setsMu.Lock()
cachedOpcodeSets[index] = codeSet
setsMu.Unlock()
return filtered, nil
return codeSet, nil
}

View File

@ -6,13 +6,11 @@ import (
)
func TestDumpOpcode(t *testing.T) {
ctx := TakeRuntimeContext()
defer ReleaseRuntimeContext(ctx)
var v interface{} = 1
header := (*emptyInterface)(unsafe.Pointer(&v))
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := CompileToGetCodeSet(ctx, typeptr)
codeSet, err := CompileToGetCodeSet(typeptr)
if err != nil {
t.Fatal(err)
}

View File

@ -101,22 +101,6 @@ type OpcodeSet struct {
InterfaceEscapeKeyCode *Opcode
CodeLength int
EndCode *Opcode
Code Code
QueryCache map[string]*OpcodeSet
cacheMu sync.RWMutex
}
func (s *OpcodeSet) getQueryCache(hash string) *OpcodeSet {
s.cacheMu.RLock()
codeSet := s.QueryCache[hash]
s.cacheMu.RUnlock()
return codeSet
}
func (s *OpcodeSet) setQueryCache(hash string, codeSet *OpcodeSet) {
s.cacheMu.Lock()
s.QueryCache[hash] = codeSet
s.cacheMu.Unlock()
}
type CompiledCode struct {
@ -413,11 +397,7 @@ func AppendMarshalJSON(ctx *RuntimeContext, code *Opcode, b []byte, v interface{
if !ok {
return AppendNull(ctx, b), nil
}
stdctx := ctx.Option.Context
if ctx.Option.Flag&FieldQueryOption != 0 {
stdctx = SetFieldQueryToContext(stdctx, code.FieldQuery)
}
b, err := marshaler.MarshalJSON(stdctx)
b, err := marshaler.MarshalJSON(ctx.Option.Context)
if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
@ -589,8 +569,6 @@ func IsNilForMarshaler(v interface{}) bool {
return rv.IsNil()
case reflect.Slice:
return rv.IsNil() || rv.Len() == 0
case reflect.String:
return rv.Len() == 0
}
return false
}

View File

@ -39,7 +39,6 @@ type Opcode struct {
Type *runtime.Type // go type
Jmp *CompiledCode // for recursive call
FieldQuery *FieldQuery // field query for Interface / MarshalJSON / MarshalText
ElemIdx uint32 // offset to access array/slice elem
Length uint32 // offset to access slice length or array length
Indent uint32 // indent number
@ -334,7 +333,6 @@ func copyOpcode(code *Opcode) *Opcode {
Idx: c.Idx,
Offset: c.Offset,
Type: c.Type,
FieldQuery: c.FieldQuery,
DisplayIdx: c.DisplayIdx,
DisplayKey: c.DisplayKey,
ElemIdx: c.ElemIdx,
@ -363,7 +361,7 @@ func copyOpcode(code *Opcode) *Opcode {
func setTotalLengthToInterfaceOp(code *Opcode) {
for c := code; !c.IsEnd(); {
if c.Op == OpInterface || c.Op == OpInterfacePtr {
if c.Op == OpInterface {
c.Length = uint32(code.TotalLength())
}
c = c.IterNext()

View File

@ -1,9 +1,6 @@
package encoder
import (
"context"
"io"
)
import "context"
type OptionFlag uint8
@ -14,15 +11,12 @@ const (
DebugOption
ColorizeOption
ContextOption
NormalizeUTF8Option
FieldQueryOption
)
type Option struct {
Flag OptionFlag
ColorScheme *ColorScheme
Context context.Context
DebugOut io.Writer
}
type EncodeFormat struct {

View File

@ -1,135 +0,0 @@
package encoder
import (
"context"
"fmt"
"reflect"
)
var (
Marshal func(interface{}) ([]byte, error)
Unmarshal func([]byte, interface{}) error
)
type FieldQuery struct {
Name string
Fields []*FieldQuery
hash string
}
func (q *FieldQuery) Hash() string {
if q.hash != "" {
return q.hash
}
b, _ := Marshal(q)
q.hash = string(b)
return q.hash
}
func (q *FieldQuery) MarshalJSON() ([]byte, error) {
if q.Name != "" {
if len(q.Fields) > 0 {
return Marshal(map[string][]*FieldQuery{q.Name: q.Fields})
}
return Marshal(q.Name)
}
return Marshal(q.Fields)
}
func (q *FieldQuery) QueryString() (FieldQueryString, error) {
b, err := Marshal(q)
if err != nil {
return "", err
}
return FieldQueryString(b), nil
}
type FieldQueryString string
func (s FieldQueryString) Build() (*FieldQuery, error) {
var query interface{}
if err := Unmarshal([]byte(s), &query); err != nil {
return nil, err
}
return s.build(reflect.ValueOf(query))
}
func (s FieldQueryString) build(v reflect.Value) (*FieldQuery, error) {
switch v.Type().Kind() {
case reflect.String:
return s.buildString(v)
case reflect.Map:
return s.buildMap(v)
case reflect.Slice:
return s.buildSlice(v)
case reflect.Interface:
return s.build(reflect.ValueOf(v.Interface()))
}
return nil, fmt.Errorf("failed to build field query")
}
func (s FieldQueryString) buildString(v reflect.Value) (*FieldQuery, error) {
b := []byte(v.String())
switch b[0] {
case '[', '{':
var query interface{}
if err := Unmarshal(b, &query); err != nil {
return nil, err
}
if str, ok := query.(string); ok {
return &FieldQuery{Name: str}, nil
}
return s.build(reflect.ValueOf(query))
}
return &FieldQuery{Name: string(b)}, nil
}
func (s FieldQueryString) buildSlice(v reflect.Value) (*FieldQuery, error) {
fields := make([]*FieldQuery, 0, v.Len())
for i := 0; i < v.Len(); i++ {
def, err := s.build(v.Index(i))
if err != nil {
return nil, err
}
fields = append(fields, def)
}
return &FieldQuery{Fields: fields}, nil
}
func (s FieldQueryString) buildMap(v reflect.Value) (*FieldQuery, error) {
keys := v.MapKeys()
if len(keys) != 1 {
return nil, fmt.Errorf("failed to build field query object")
}
key := keys[0]
if key.Type().Kind() != reflect.String {
return nil, fmt.Errorf("failed to build field query. invalid object key type")
}
name := key.String()
def, err := s.build(v.MapIndex(key))
if err != nil {
return nil, err
}
return &FieldQuery{
Name: name,
Fields: def.Fields,
}, nil
}
type queryKey struct{}
func FieldQueryFromContext(ctx context.Context) *FieldQuery {
query := ctx.Value(queryKey{})
if query == nil {
return nil
}
q, ok := query.(*FieldQuery)
if !ok {
return nil
}
return q
}
func SetFieldQueryToContext(ctx context.Context, query *FieldQuery) context.Context {
return context.WithValue(ctx, queryKey{}, query)
}

View File

@ -4,6 +4,8 @@ import (
"math/bits"
"reflect"
"unsafe"
"github.com/goccy/go-json/internal/runtime"
)
const (
@ -11,6 +13,341 @@ const (
msb = 0x8080808080808080
)
var needEscapeWithHTML = [256]bool{
'"': true,
'&': true,
'<': true,
'>': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0x7f */
0x80: true,
0x81: true,
0x82: true,
0x83: true,
0x84: true,
0x85: true,
0x86: true,
0x87: true,
0x88: true,
0x89: true,
0x8a: true,
0x8b: true,
0x8c: true,
0x8d: true,
0x8e: true,
0x8f: true,
0x90: true,
0x91: true,
0x92: true,
0x93: true,
0x94: true,
0x95: true,
0x96: true,
0x97: true,
0x98: true,
0x99: true,
0x9a: true,
0x9b: true,
0x9c: true,
0x9d: true,
0x9e: true,
0x9f: true,
0xa0: true,
0xa1: true,
0xa2: true,
0xa3: true,
0xa4: true,
0xa5: true,
0xa6: true,
0xa7: true,
0xa8: true,
0xa9: true,
0xaa: true,
0xab: true,
0xac: true,
0xad: true,
0xae: true,
0xaf: true,
0xb0: true,
0xb1: true,
0xb2: true,
0xb3: true,
0xb4: true,
0xb5: true,
0xb6: true,
0xb7: true,
0xb8: true,
0xb9: true,
0xba: true,
0xbb: true,
0xbc: true,
0xbd: true,
0xbe: true,
0xbf: true,
0xc0: true,
0xc1: true,
0xc2: true,
0xc3: true,
0xc4: true,
0xc5: true,
0xc6: true,
0xc7: true,
0xc8: true,
0xc9: true,
0xca: true,
0xcb: true,
0xcc: true,
0xcd: true,
0xce: true,
0xcf: true,
0xd0: true,
0xd1: true,
0xd2: true,
0xd3: true,
0xd4: true,
0xd5: true,
0xd6: true,
0xd7: true,
0xd8: true,
0xd9: true,
0xda: true,
0xdb: true,
0xdc: true,
0xdd: true,
0xde: true,
0xdf: true,
0xe0: true,
0xe1: true,
0xe2: true,
0xe3: true,
0xe4: true,
0xe5: true,
0xe6: true,
0xe7: true,
0xe8: true,
0xe9: true,
0xea: true,
0xeb: true,
0xec: true,
0xed: true,
0xee: true,
0xef: true,
0xf0: true,
0xf1: true,
0xf2: true,
0xf3: true,
0xf4: true,
0xf5: true,
0xf6: true,
0xf7: true,
0xf8: true,
0xf9: true,
0xfa: true,
0xfb: true,
0xfc: true,
0xfd: true,
0xfe: true,
0xff: true,
}
var needEscape = [256]bool{
'"': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0x7f */
0x80: true,
0x81: true,
0x82: true,
0x83: true,
0x84: true,
0x85: true,
0x86: true,
0x87: true,
0x88: true,
0x89: true,
0x8a: true,
0x8b: true,
0x8c: true,
0x8d: true,
0x8e: true,
0x8f: true,
0x90: true,
0x91: true,
0x92: true,
0x93: true,
0x94: true,
0x95: true,
0x96: true,
0x97: true,
0x98: true,
0x99: true,
0x9a: true,
0x9b: true,
0x9c: true,
0x9d: true,
0x9e: true,
0x9f: true,
0xa0: true,
0xa1: true,
0xa2: true,
0xa3: true,
0xa4: true,
0xa5: true,
0xa6: true,
0xa7: true,
0xa8: true,
0xa9: true,
0xaa: true,
0xab: true,
0xac: true,
0xad: true,
0xae: true,
0xaf: true,
0xb0: true,
0xb1: true,
0xb2: true,
0xb3: true,
0xb4: true,
0xb5: true,
0xb6: true,
0xb7: true,
0xb8: true,
0xb9: true,
0xba: true,
0xbb: true,
0xbc: true,
0xbd: true,
0xbe: true,
0xbf: true,
0xc0: true,
0xc1: true,
0xc2: true,
0xc3: true,
0xc4: true,
0xc5: true,
0xc6: true,
0xc7: true,
0xc8: true,
0xc9: true,
0xca: true,
0xcb: true,
0xcc: true,
0xcd: true,
0xce: true,
0xcf: true,
0xd0: true,
0xd1: true,
0xd2: true,
0xd3: true,
0xd4: true,
0xd5: true,
0xd6: true,
0xd7: true,
0xd8: true,
0xd9: true,
0xda: true,
0xdb: true,
0xdc: true,
0xdd: true,
0xde: true,
0xdf: true,
0xe0: true,
0xe1: true,
0xe2: true,
0xe3: true,
0xe4: true,
0xe5: true,
0xe6: true,
0xe7: true,
0xe8: true,
0xe9: true,
0xea: true,
0xeb: true,
0xec: true,
0xed: true,
0xee: true,
0xef: true,
0xf0: true,
0xf1: true,
0xf2: true,
0xf3: true,
0xf4: true,
0xf5: true,
0xf6: true,
0xf7: true,
0xf8: true,
0xf9: true,
0xfa: true,
0xfb: true,
0xfc: true,
0xfd: true,
0xfe: true,
0xff: true,
}
var hex = "0123456789abcdef"
//nolint:govet
@ -23,19 +360,9 @@ func stringToUint64Slice(s string) []uint64 {
}
func AppendString(ctx *RuntimeContext, buf []byte, s string) []byte {
if ctx.Option.Flag&HTMLEscapeOption != 0 {
if ctx.Option.Flag&NormalizeUTF8Option != 0 {
return appendNormalizedHTMLString(buf, s)
}
return appendHTMLString(buf, s)
if ctx.Option.Flag&HTMLEscapeOption == 0 {
return appendString(buf, s)
}
if ctx.Option.Flag&NormalizeUTF8Option != 0 {
return appendNormalizedString(buf, s)
}
return appendString(buf, s)
}
func appendNormalizedHTMLString(buf []byte, s string) []byte {
valLen := len(s)
if valLen == 0 {
return append(buf, `""`...)
@ -44,42 +371,166 @@ func appendNormalizedHTMLString(buf []byte, s string) []byte {
var (
i, j int
)
if valLen >= 8 {
chunks := stringToUint64Slice(s)
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | (n - (lsb * 0x20)) |
((n ^ (lsb * '"')) - lsb) |
((n ^ (lsb * '\\')) - lsb) |
((n ^ (lsb * '<')) - lsb) |
((n ^ (lsb * '>')) - lsb) |
((n ^ (lsb * '&')) - lsb)
if (mask & msb) != 0 {
j = bits.TrailingZeros64(mask&msb) / 8
goto ESCAPE_END
}
orgLen := valLen
base := (*runtime.SliceHeader)(unsafe.Pointer(&s)).Data
for {
valLen = len(s) - j
if valLen <= 0 {
return append(append(buf, s[i:]...), '"')
}
for i := len(chunks) * 8; i < valLen; i++ {
if needEscapeHTMLNormalizeUTF8[s[i]] {
j = i
goto ESCAPE_END
data := unsafe.Pointer(uintptr(base) + uintptr(j))
switch valLen {
case 1:
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 2:
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 3:
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 4:
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 5:
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 6:
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 7:
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
j++
if needEscapeWithHTML[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 8, 9, 10, 11, 12, 13, 14, 15:
chunks := stringToUint64Slice(s[j:])
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | (n - (lsb * 0x20)) |
((n ^ (lsb * '"')) - lsb) |
((n ^ (lsb * '\\')) - lsb) |
((n ^ (lsb * '<')) - lsb) |
((n ^ (lsb * '>')) - lsb) |
((n ^ (lsb * '&')) - lsb)
if (mask & msb) != 0 {
j += bits.TrailingZeros64(mask&msb) / 8
goto ESCAPE
}
}
j += len(chunks) * 8
case 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31:
j += _findHTMLEscapeIndex128(data, valLen)
default:
j += _findHTMLEscapeIndex256(data, valLen)
}
// no found any escape characters.
return append(append(buf, s...), '"')
}
ESCAPE_END:
for j < valLen {
if j >= orgLen {
return append(append(buf, s[i:]...), '"')
}
ESCAPE:
c := s[j]
if !needEscapeHTMLNormalizeUTF8[c] {
// fast path: most of the time, printable ascii characters are used
if !needEscapeWithHTML[c] {
j++
continue
}
switch c {
case '\\', '"':
buf = append(buf, s[i:j]...)
@ -156,220 +607,6 @@ ESCAPE_END:
}
j += size
}
return append(append(buf, s[i:]...), '"')
}
func appendHTMLString(buf []byte, s string) []byte {
valLen := len(s)
if valLen == 0 {
return append(buf, `""`...)
}
buf = append(buf, '"')
var (
i, j int
)
if valLen >= 8 {
chunks := stringToUint64Slice(s)
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | (n - (lsb * 0x20)) |
((n ^ (lsb * '"')) - lsb) |
((n ^ (lsb * '\\')) - lsb) |
((n ^ (lsb * '<')) - lsb) |
((n ^ (lsb * '>')) - lsb) |
((n ^ (lsb * '&')) - lsb)
if (mask & msb) != 0 {
j = bits.TrailingZeros64(mask&msb) / 8
goto ESCAPE_END
}
}
for i := len(chunks) * 8; i < valLen; i++ {
if needEscapeHTML[s[i]] {
j = i
goto ESCAPE_END
}
}
// no found any escape characters.
return append(append(buf, s...), '"')
}
ESCAPE_END:
for j < valLen {
c := s[j]
if !needEscapeHTML[c] {
// fast path: most of the time, printable ascii characters are used
j++
continue
}
switch c {
case '\\', '"':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', c)
i = j + 1
j = j + 1
continue
case '\n':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 'n')
i = j + 1
j = j + 1
continue
case '\r':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 'r')
i = j + 1
j = j + 1
continue
case '\t':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 't')
i = j + 1
j = j + 1
continue
case '<', '>', '&':
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
i = j + 1
j = j + 1
continue
case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
i = j + 1
j = j + 1
continue
}
j++
}
return append(append(buf, s[i:]...), '"')
}
func appendNormalizedString(buf []byte, s string) []byte {
valLen := len(s)
if valLen == 0 {
return append(buf, `""`...)
}
buf = append(buf, '"')
var (
i, j int
)
if valLen >= 8 {
chunks := stringToUint64Slice(s)
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | (n - (lsb * 0x20)) |
((n ^ (lsb * '"')) - lsb) |
((n ^ (lsb * '\\')) - lsb)
if (mask & msb) != 0 {
j = bits.TrailingZeros64(mask&msb) / 8
goto ESCAPE_END
}
}
valLen := len(s)
for i := len(chunks) * 8; i < valLen; i++ {
if needEscapeNormalizeUTF8[s[i]] {
j = i
goto ESCAPE_END
}
}
return append(append(buf, s...), '"')
}
ESCAPE_END:
for j < valLen {
c := s[j]
if !needEscapeNormalizeUTF8[c] {
// fast path: most of the time, printable ascii characters are used
j++
continue
}
switch c {
case '\\', '"':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', c)
i = j + 1
j = j + 1
continue
case '\n':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 'n')
i = j + 1
j = j + 1
continue
case '\r':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 'r')
i = j + 1
j = j + 1
continue
case '\t':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 't')
i = j + 1
j = j + 1
continue
case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
i = j + 1
j = j + 1
continue
}
state, size := decodeRuneInString(s[j:])
switch state {
case runeErrorState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\ufffd`...)
i = j + 1
j = j + 1
continue
// U+2028 is LINE SEPARATOR.
// U+2029 is PARAGRAPH SEPARATOR.
// They are both technically valid characters in JSON strings,
// but don't work in JSONP, which has to be evaluated as JavaScript,
// and can lead to security holes there. It is valid JSON to
// escape them, so we do so unconditionally.
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
case lineSepState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\u2028`...)
i = j + 3
j = j + 3
continue
case paragraphSepState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\u2029`...)
i = j + 3
j = j + 3
continue
}
j += size
}
return append(append(buf, s[i:]...), '"')
}
func appendString(buf []byte, s string) []byte {
@ -381,39 +618,146 @@ func appendString(buf []byte, s string) []byte {
var (
i, j int
)
if valLen >= 8 {
chunks := stringToUint64Slice(s)
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | (n - (lsb * 0x20)) |
((n ^ (lsb * '"')) - lsb) |
((n ^ (lsb * '\\')) - lsb)
if (mask & msb) != 0 {
j = bits.TrailingZeros64(mask&msb) / 8
goto ESCAPE_END
}
base := (*runtime.SliceHeader)(unsafe.Pointer(&s)).Data
orgLen := valLen
for {
valLen = len(s) - j
if valLen <= 0 {
return append(append(buf, s[i:]...), '"')
}
valLen := len(s)
for i := len(chunks) * 8; i < valLen; i++ {
if needEscape[s[i]] {
j = i
goto ESCAPE_END
data := unsafe.Pointer(uintptr(base) + uintptr(j))
switch valLen {
case 1:
if needEscape[s[j]] {
goto ESCAPE
}
return append(buf, s[i], '"')
case 2:
if needEscape[s[j]] {
goto ESCAPE
}
}
return append(append(buf, s...), '"')
}
ESCAPE_END:
for j < valLen {
c := s[j]
if !needEscape[c] {
// fast path: most of the time, printable ascii characters are used
j++
continue
if needEscape[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 3:
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 4:
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 5:
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 6:
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 7:
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
j++
if needEscape[s[j]] {
goto ESCAPE
}
return append(append(buf, s[i:]...), '"')
case 8, 9, 10, 11, 12, 13, 14, 15:
j += _findEscapeIndex64(data, valLen)
case 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31:
j += _findEscapeIndex128(data, valLen)
default:
j += _findEscapeIndex256(data, valLen)
}
if j == orgLen {
return append(append(buf, s[i:]...), '"')
}
ESCAPE:
c := s[j]
switch c {
case '\\', '"':
buf = append(buf, s[i:j]...)
@ -452,8 +796,34 @@ ESCAPE_END:
j = j + 1
continue
}
j++
state, size := decodeRuneInString(s[j:])
switch state {
case runeErrorState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\ufffd`...)
i = j + 1
j = j + 1
continue
// U+2028 is LINE SEPARATOR.
// U+2029 is PARAGRAPH SEPARATOR.
// They are both technically valid characters in JSON strings,
// but don't work in JSONP, which has to be evaluated as JavaScript,
// and can lead to security holes there. It is valid JSON to
// escape them, so we do so unconditionally.
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
case lineSepState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\u2028`...)
i = j + 3
j = j + 3
continue
case paragraphSepState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\u2029`...)
i = j + 3
j = j + 3
continue
}
j += size
}
return append(append(buf, s[i:]...), '"')
}

View File

@ -0,0 +1,27 @@
package encoder
import "unsafe"
//go:nosplit
//go:noescape
func _findHTMLEscapeIndex64(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findHTMLEscapeIndex128(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findHTMLEscapeIndex256(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findEscapeIndex64(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findEscapeIndex128(buf unsafe.Pointer, len int) (ret int)
//go:nosplit
//go:noescape
func _findEscapeIndex256(buf unsafe.Pointer, len int) (ret int)

View File

@ -0,0 +1,818 @@
//+build !noasm !appengine
// AUTO-GENERATED BY C2GOASM -- DO NOT EDIT
TEXT ·_findHTMLEscapeIndex64(SB), $0-24
MOVQ buf+0(FP), DI
MOVQ len+8(FP), SI
WORD $0xf089 // mov eax, esi
WORD $0xf8c1; BYTE $0x1f // sar eax, 31
WORD $0xe8c1; BYTE $0x1d // shr eax, 29
WORD $0xf001 // add eax, esi
WORD $0xf8c1; BYTE $0x03 // sar eax, 3
WORD $0x9848 // cdqe
WORD $0xc683; BYTE $0x07 // add esi, 7
WORD $0xfe83; BYTE $0x0f // cmp esi, 15
JB LBB0_5
QUAD $0xfefefefefeffbd49; WORD $0xfefe // mov r13, -72340172838076673
WORD $0xd231 // xor edx, edx
QUAD $0x222222222222b949; WORD $0x2222 // mov r9, 2459565876494606882
QUAD $0x5c5c5c5c5c5cba49; WORD $0x5c5c // mov r10, 6655295901103053916
QUAD $0x3c3c3c3c3c3cbb49; WORD $0x3c3c // mov r11, 4340410370284600380
QUAD $0x3e3e3e3e3e3ebe49; WORD $0x3e3e // mov r14, 4485090715960753726
QUAD $0x262626262626bf49; WORD $0x2626 // mov r15, 2748926567846913574
QUAD $0x808080808080bc49; WORD $0x8080 // mov r12, -9187201950435737472
LBB0_2:
LONG $0xd71c8b48 // mov rbx, qword [rdi + 8*rdx]
QUAD $0xdfdfdfdfdfe0b948; WORD $0xdfdf // mov rcx, -2314885530818453536
LONG $0x0b348d48 // lea rsi, [rbx + rcx]
WORD $0x0948; BYTE $0xde // or rsi, rbx
WORD $0x8948; BYTE $0xd9 // mov rcx, rbx
WORD $0x314c; BYTE $0xc9 // xor rcx, r9
WORD $0x014c; BYTE $0xe9 // add rcx, r13
WORD $0x0948; BYTE $0xf1 // or rcx, rsi
WORD $0x8948; BYTE $0xde // mov rsi, rbx
WORD $0x314c; BYTE $0xd6 // xor rsi, r10
WORD $0x014c; BYTE $0xee // add rsi, r13
WORD $0x8949; BYTE $0xd8 // mov r8, rbx
WORD $0x314d; BYTE $0xd8 // xor r8, r11
WORD $0x014d; BYTE $0xe8 // add r8, r13
WORD $0x0949; BYTE $0xf0 // or r8, rsi
WORD $0x0949; BYTE $0xc8 // or r8, rcx
WORD $0x8948; BYTE $0xd9 // mov rcx, rbx
WORD $0x314c; BYTE $0xf1 // xor rcx, r14
WORD $0x014c; BYTE $0xe9 // add rcx, r13
WORD $0x314c; BYTE $0xfb // xor rbx, r15
WORD $0x014c; BYTE $0xeb // add rbx, r13
WORD $0x0948; BYTE $0xcb // or rbx, rcx
WORD $0x094c; BYTE $0xc3 // or rbx, r8
WORD $0x214c; BYTE $0xe3 // and rbx, r12
JNE LBB0_3
LONG $0x01c28348 // add rdx, 1
WORD $0x3948; BYTE $0xc2 // cmp rdx, rax
JB LBB0_2
LBB0_5:
LONG $0x03e0c148 // shl rax, 3
JMP LBB0_6
LBB0_3:
WORD $0xbc0f; BYTE $0xc3 // bsf eax, ebx
WORD $0xe8c1; BYTE $0x03 // shr eax, 3
LBB0_6:
MOVQ AX, ret+16(FP)
RET
DATA LCDATA1<>+0x000(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA1<>+0x008(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA1<>+0x010(SB)/8, $0x2222222222222222
DATA LCDATA1<>+0x018(SB)/8, $0x2222222222222222
DATA LCDATA1<>+0x020(SB)/8, $0xfefefefefefefeff
DATA LCDATA1<>+0x028(SB)/8, $0xfefefefefefefeff
DATA LCDATA1<>+0x030(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA1<>+0x038(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA1<>+0x040(SB)/8, $0x3c3c3c3c3c3c3c3c
DATA LCDATA1<>+0x048(SB)/8, $0x3c3c3c3c3c3c3c3c
DATA LCDATA1<>+0x050(SB)/8, $0x3e3e3e3e3e3e3e3e
DATA LCDATA1<>+0x058(SB)/8, $0x3e3e3e3e3e3e3e3e
DATA LCDATA1<>+0x060(SB)/8, $0x2626262626262626
DATA LCDATA1<>+0x068(SB)/8, $0x2626262626262626
DATA LCDATA1<>+0x070(SB)/8, $0x8080808080808080
DATA LCDATA1<>+0x078(SB)/8, $0x8080808080808080
GLOBL LCDATA1<>(SB), 8, $128
TEXT ·_findHTMLEscapeIndex128(SB), $16-24
MOVQ buf+0(FP), DI
MOVQ len+8(FP), SI
ADDQ $8, SP
LEAQ LCDATA1<>(SB), BP
WORD $0xf089 // mov eax, esi
WORD $0xf8c1; BYTE $0x1f // sar eax, 31
WORD $0xe8c1; BYTE $0x1c // shr eax, 28
WORD $0xf001 // add eax, esi
WORD $0xf8c1; BYTE $0x04 // sar eax, 4
WORD $0x4e8d; BYTE $0x0f // lea ecx, [rsi + 15]
WORD $0xf983; BYTE $0x1f // cmp ecx, 31
JB LBB1_5
WORD $0x6348; BYTE $0xc8 // movsxd rcx, eax
WORD $0xd231 // xor edx, edx
LONG $0x456f79c5; BYTE $0x00 // vmovdqa xmm8, oword 0[rbp] /* [rip + .LCPI1_0] */
LONG $0x4d6f79c5; BYTE $0x10 // vmovdqa xmm9, oword 16[rbp] /* [rip + .LCPI1_1] */
LONG $0x556ff9c5; BYTE $0x20 // vmovdqa xmm2, oword 32[rbp] /* [rip + .LCPI1_2] */
LONG $0x556f79c5; BYTE $0x30 // vmovdqa xmm10, oword 48[rbp] /* [rip + .LCPI1_3] */
LONG $0x5d6f79c5; BYTE $0x40 // vmovdqa xmm11, oword 64[rbp] /* [rip + .LCPI1_4] */
LONG $0x656f79c5; BYTE $0x50 // vmovdqa xmm12, oword 80[rbp] /* [rip + .LCPI1_5] */
LONG $0x6d6f79c5; BYTE $0x60 // vmovdqa xmm13, oword 96[rbp] /* [rip + .LCPI1_6] */
LONG $0x7d6ff9c5; BYTE $0x70 // vmovdqa xmm7, oword 112[rbp] /* [rip + .LCPI1_7] */
LBB1_2:
LONG $0x076ffac5 // vmovdqu xmm0, oword [rdi]
LONG $0xd479c1c4; BYTE $0xc8 // vpaddq xmm1, xmm0, xmm8
LONG $0xef79c1c4; BYTE $0xd9 // vpxor xmm3, xmm0, xmm9
LONG $0xdad4e1c5 // vpaddq xmm3, xmm3, xmm2
LONG $0xef79c1c4; BYTE $0xe2 // vpxor xmm4, xmm0, xmm10
LONG $0xe2d4d9c5 // vpaddq xmm4, xmm4, xmm2
LONG $0xef79c1c4; BYTE $0xeb // vpxor xmm5, xmm0, xmm11
LONG $0xead4d1c5 // vpaddq xmm5, xmm5, xmm2
LONG $0xe5ebd9c5 // vpor xmm4, xmm4, xmm5
LONG $0xef79c1c4; BYTE $0xec // vpxor xmm5, xmm0, xmm12
LONG $0xead4d1c5 // vpaddq xmm5, xmm5, xmm2
LONG $0xef79c1c4; BYTE $0xf5 // vpxor xmm6, xmm0, xmm13
LONG $0xf2d4c9c5 // vpaddq xmm6, xmm6, xmm2
LONG $0xeeebd1c5 // vpor xmm5, xmm5, xmm6
LONG $0xc0ebf1c5 // vpor xmm0, xmm1, xmm0
LONG $0xc3ebf9c5 // vpor xmm0, xmm0, xmm3
LONG $0xc4ebf9c5 // vpor xmm0, xmm0, xmm4
LONG $0xc5ebf9c5 // vpor xmm0, xmm0, xmm5
LONG $0xc7dbf9c5 // vpand xmm0, xmm0, xmm7
LONG $0xd8d7f9c5 // vpmovmskb ebx, xmm0
WORD $0xdb85 // test ebx, ebx
JNE LBB1_3
LONG $0x10c78348 // add rdi, 16
LONG $0x01c28348 // add rdx, 1
WORD $0x3948; BYTE $0xca // cmp rdx, rcx
JB LBB1_2
LBB1_5:
WORD $0xe0c1; BYTE $0x04 // shl eax, 4
WORD $0xc629 // sub esi, eax
WORD $0x9848 // cdqe
WORD $0xfe83; BYTE $0x08 // cmp esi, 8
JL LBB1_12
LONG $0x24048948 // mov qword [rsp], rax
QUAD $0xfefefefefeffba49; WORD $0xfefe // mov r10, -72340172838076673
WORD $0x8941; BYTE $0xf0 // mov r8d, esi
LONG $0x03e8c141 // shr r8d, 3
WORD $0x3145; BYTE $0xdb // xor r11d, r11d
QUAD $0x3c3c3c3c3c3cbe49; WORD $0x3c3c // mov r14, 4340410370284600380
QUAD $0x3e3e3e3e3e3ebf49; WORD $0x3e3e // mov r15, 4485090715960753726
QUAD $0x262626262626bc49; WORD $0x2626 // mov r12, 2748926567846913574
QUAD $0x808080808080bd49; WORD $0x8080 // mov r13, -9187201950435737472
LBB1_7:
LONG $0xdf1c8b4a // mov rbx, qword [rdi + 8*r11]
QUAD $0xdfdfdfdfdfe0b848; WORD $0xdfdf // mov rax, -2314885530818453536
LONG $0x030c8d4c // lea r9, [rbx + rax]
WORD $0x0949; BYTE $0xd9 // or r9, rbx
WORD $0x8948; BYTE $0xd8 // mov rax, rbx
QUAD $0x222222222222b948; WORD $0x2222 // mov rcx, 2459565876494606882
WORD $0x3148; BYTE $0xc8 // xor rax, rcx
WORD $0x014c; BYTE $0xd0 // add rax, r10
WORD $0x094c; BYTE $0xc8 // or rax, r9
WORD $0x8948; BYTE $0xd9 // mov rcx, rbx
QUAD $0x5c5c5c5c5c5cba48; WORD $0x5c5c // mov rdx, 6655295901103053916
WORD $0x3148; BYTE $0xd1 // xor rcx, rdx
WORD $0x014c; BYTE $0xd1 // add rcx, r10
WORD $0x8948; BYTE $0xda // mov rdx, rbx
WORD $0x314c; BYTE $0xf2 // xor rdx, r14
WORD $0x014c; BYTE $0xd2 // add rdx, r10
WORD $0x0948; BYTE $0xca // or rdx, rcx
WORD $0x0948; BYTE $0xc2 // or rdx, rax
WORD $0x8948; BYTE $0xd8 // mov rax, rbx
WORD $0x314c; BYTE $0xf8 // xor rax, r15
WORD $0x014c; BYTE $0xd0 // add rax, r10
WORD $0x314c; BYTE $0xe3 // xor rbx, r12
WORD $0x014c; BYTE $0xd3 // add rbx, r10
WORD $0x0948; BYTE $0xc3 // or rbx, rax
WORD $0x0948; BYTE $0xd3 // or rbx, rdx
WORD $0x214c; BYTE $0xeb // and rbx, r13
JNE LBB1_8
LONG $0x01c38349 // add r11, 1
WORD $0x394d; BYTE $0xc3 // cmp r11, r8
JB LBB1_7
WORD $0xe683; BYTE $0xf8 // and esi, -8
JMP LBB1_11
LBB1_3:
WORD $0xbc0f; BYTE $0xc3 // bsf eax, ebx
JMP LBB1_12
LBB1_8:
WORD $0xbc0f; BYTE $0xf3 // bsf esi, ebx
WORD $0xeec1; BYTE $0x03 // shr esi, 3
LBB1_11:
WORD $0xf089 // mov eax, esi
LONG $0x240c8b48 // mov rcx, qword [rsp]
WORD $0x0148; BYTE $0xc1 // add rcx, rax
WORD $0x8948; BYTE $0xc8 // mov rax, rcx
LBB1_12:
SUBQ $8, SP
MOVQ AX, ret+16(FP)
RET
DATA LCDATA2<>+0x000(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA2<>+0x008(SB)/8, $0x2222222222222222
DATA LCDATA2<>+0x010(SB)/8, $0xfefefefefefefeff
DATA LCDATA2<>+0x018(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA2<>+0x020(SB)/8, $0x3c3c3c3c3c3c3c3c
DATA LCDATA2<>+0x028(SB)/8, $0x3e3e3e3e3e3e3e3e
DATA LCDATA2<>+0x030(SB)/8, $0x2626262626262626
DATA LCDATA2<>+0x038(SB)/8, $0x0000000000000000
DATA LCDATA2<>+0x040(SB)/8, $0x8080808080808080
DATA LCDATA2<>+0x048(SB)/8, $0x8080808080808080
DATA LCDATA2<>+0x050(SB)/8, $0x8080808080808080
DATA LCDATA2<>+0x058(SB)/8, $0x8080808080808080
DATA LCDATA2<>+0x060(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA2<>+0x068(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA2<>+0x070(SB)/8, $0x2222222222222222
DATA LCDATA2<>+0x078(SB)/8, $0x2222222222222222
DATA LCDATA2<>+0x080(SB)/8, $0xfefefefefefefeff
DATA LCDATA2<>+0x088(SB)/8, $0xfefefefefefefeff
DATA LCDATA2<>+0x090(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA2<>+0x098(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA2<>+0x0a0(SB)/8, $0x3c3c3c3c3c3c3c3c
DATA LCDATA2<>+0x0a8(SB)/8, $0x3c3c3c3c3c3c3c3c
DATA LCDATA2<>+0x0b0(SB)/8, $0x3e3e3e3e3e3e3e3e
DATA LCDATA2<>+0x0b8(SB)/8, $0x3e3e3e3e3e3e3e3e
DATA LCDATA2<>+0x0c0(SB)/8, $0x2626262626262626
DATA LCDATA2<>+0x0c8(SB)/8, $0x2626262626262626
DATA LCDATA2<>+0x0d0(SB)/8, $0x8080808080808080
DATA LCDATA2<>+0x0d8(SB)/8, $0x8080808080808080
GLOBL LCDATA2<>(SB), 8, $224
TEXT ·_findHTMLEscapeIndex256(SB), $16-24
MOVQ buf+0(FP), DI
MOVQ len+8(FP), SI
ADDQ $8, SP
LEAQ LCDATA2<>(SB), BP
WORD $0xf089 // mov eax, esi
WORD $0xf8c1; BYTE $0x1f // sar eax, 31
WORD $0xe8c1; BYTE $0x1b // shr eax, 27
WORD $0xf001 // add eax, esi
WORD $0xf8c1; BYTE $0x05 // sar eax, 5
WORD $0x4e8d; BYTE $0x1f // lea ecx, [rsi + 31]
WORD $0xf983; BYTE $0x3f // cmp ecx, 63
JB LBB2_4
WORD $0x6348; BYTE $0xc8 // movsxd rcx, eax
LONG $0x597de2c4; WORD $0x0045 // vpbroadcastq ymm0, qword 0[rbp] /* [rip + .LCPI2_0] */
LONG $0x597de2c4; WORD $0x084d // vpbroadcastq ymm1, qword 8[rbp] /* [rip + .LCPI2_1] */
LONG $0x597de2c4; WORD $0x1055 // vpbroadcastq ymm2, qword 16[rbp] /* [rip + .LCPI2_2] */
LONG $0x597de2c4; WORD $0x185d // vpbroadcastq ymm3, qword 24[rbp] /* [rip + .LCPI2_3] */
LONG $0x597de2c4; WORD $0x2065 // vpbroadcastq ymm4, qword 32[rbp] /* [rip + .LCPI2_4] */
LONG $0x597de2c4; WORD $0x286d // vpbroadcastq ymm5, qword 40[rbp] /* [rip + .LCPI2_5] */
WORD $0xd231 // xor edx, edx
LONG $0x597de2c4; WORD $0x3075 // vpbroadcastq ymm6, qword 48[rbp] /* [rip + .LCPI2_6] */
LONG $0x7d6ffdc5; BYTE $0x40 // vmovdqa ymm7, yword 64[rbp] /* [rip + .LCPI2_7] */
LBB2_2:
LONG $0x076f7ec5 // vmovdqu ymm8, yword [rdi]
LONG $0xc8d43dc5 // vpaddq ymm9, ymm8, ymm0
LONG $0xd1ef3dc5 // vpxor ymm10, ymm8, ymm1
LONG $0xd2d42dc5 // vpaddq ymm10, ymm10, ymm2
LONG $0xdbef3dc5 // vpxor ymm11, ymm8, ymm3
LONG $0xdad425c5 // vpaddq ymm11, ymm11, ymm2
LONG $0xe4ef3dc5 // vpxor ymm12, ymm8, ymm4
LONG $0xe2d41dc5 // vpaddq ymm12, ymm12, ymm2
LONG $0xeb2541c4; BYTE $0xdc // vpor ymm11, ymm11, ymm12
LONG $0xe5ef3dc5 // vpxor ymm12, ymm8, ymm5
LONG $0xe2d41dc5 // vpaddq ymm12, ymm12, ymm2
LONG $0xeeef3dc5 // vpxor ymm13, ymm8, ymm6
LONG $0xead415c5 // vpaddq ymm13, ymm13, ymm2
LONG $0xeb1d41c4; BYTE $0xe5 // vpor ymm12, ymm12, ymm13
LONG $0xeb3541c4; BYTE $0xc0 // vpor ymm8, ymm9, ymm8
LONG $0xeb3d41c4; BYTE $0xc2 // vpor ymm8, ymm8, ymm10
LONG $0xeb3d41c4; BYTE $0xc3 // vpor ymm8, ymm8, ymm11
LONG $0xeb3d41c4; BYTE $0xc4 // vpor ymm8, ymm8, ymm12
LONG $0xc7db3dc5 // vpand ymm8, ymm8, ymm7
LONG $0xd77dc1c4; BYTE $0xd8 // vpmovmskb ebx, ymm8
WORD $0xdb85 // test ebx, ebx
JNE LBB2_18
LONG $0x20c78348 // add rdi, 32
LONG $0x01c28348 // add rdx, 1
WORD $0x3948; BYTE $0xca // cmp rdx, rcx
JB LBB2_2
LBB2_4:
WORD $0xe0c1; BYTE $0x05 // shl eax, 5
WORD $0xc629 // sub esi, eax
WORD $0xfe83; BYTE $0x10 // cmp esi, 16
JL LBB2_13
WORD $0x6348; BYTE $0xd8 // movsxd rbx, eax
WORD $0xf089 // mov eax, esi
WORD $0xe8c1; BYTE $0x04 // shr eax, 4
WORD $0xc931 // xor ecx, ecx
LONG $0x456f79c5; BYTE $0x60 // vmovdqa xmm8, oword 96[rbp] /* [rip + .LCPI2_8] */
LONG $0x4d6f79c5; BYTE $0x70 // vmovdqa xmm9, oword 112[rbp] /* [rip + .LCPI2_9] */
QUAD $0x00000080956ff9c5 // vmovdqa xmm2, oword 128[rbp] /* [rip + .LCPI2_10] */
QUAD $0x00000090956f79c5 // vmovdqa xmm10, oword 144[rbp] /* [rip + .LCPI2_11] */
QUAD $0x000000a09d6f79c5 // vmovdqa xmm11, oword 160[rbp] /* [rip + .LCPI2_12] */
QUAD $0x000000b0a56f79c5 // vmovdqa xmm12, oword 176[rbp] /* [rip + .LCPI2_13] */
QUAD $0x000000c0ad6f79c5 // vmovdqa xmm13, oword 192[rbp] /* [rip + .LCPI2_14] */
QUAD $0x000000d0bd6ff9c5 // vmovdqa xmm7, oword 208[rbp] /* [rip + .LCPI2_15] */
LBB2_6:
LONG $0x076ffac5 // vmovdqu xmm0, oword [rdi]
LONG $0xd479c1c4; BYTE $0xc8 // vpaddq xmm1, xmm0, xmm8
LONG $0xef79c1c4; BYTE $0xd9 // vpxor xmm3, xmm0, xmm9
LONG $0xdad4e1c5 // vpaddq xmm3, xmm3, xmm2
LONG $0xef79c1c4; BYTE $0xe2 // vpxor xmm4, xmm0, xmm10
LONG $0xe2d4d9c5 // vpaddq xmm4, xmm4, xmm2
LONG $0xef79c1c4; BYTE $0xeb // vpxor xmm5, xmm0, xmm11
LONG $0xead4d1c5 // vpaddq xmm5, xmm5, xmm2
LONG $0xe5ebd9c5 // vpor xmm4, xmm4, xmm5
LONG $0xef79c1c4; BYTE $0xec // vpxor xmm5, xmm0, xmm12
LONG $0xead4d1c5 // vpaddq xmm5, xmm5, xmm2
LONG $0xef79c1c4; BYTE $0xf5 // vpxor xmm6, xmm0, xmm13
LONG $0xf2d4c9c5 // vpaddq xmm6, xmm6, xmm2
LONG $0xeeebd1c5 // vpor xmm5, xmm5, xmm6
LONG $0xc0ebf1c5 // vpor xmm0, xmm1, xmm0
LONG $0xc3ebf9c5 // vpor xmm0, xmm0, xmm3
LONG $0xc4ebf9c5 // vpor xmm0, xmm0, xmm4
LONG $0xc5ebf9c5 // vpor xmm0, xmm0, xmm5
LONG $0xc7dbf9c5 // vpand xmm0, xmm0, xmm7
LONG $0xd0d7f9c5 // vpmovmskb edx, xmm0
WORD $0xd285 // test edx, edx
JNE LBB2_19
LONG $0x10c78348 // add rdi, 16
LONG $0x01c18348 // add rcx, 1
WORD $0x3948; BYTE $0xc1 // cmp rcx, rax
JB LBB2_6
WORD $0xf089 // mov eax, esi
WORD $0xe083; BYTE $0xf0 // and eax, -16
WORD $0xc629 // sub esi, eax
WORD $0x9848 // cdqe
WORD $0xfe83; BYTE $0x08 // cmp esi, 8
JL LBB2_24
LONG $0x241c8948 // mov qword [rsp], rbx
QUAD $0xfefefefefeffbe49; WORD $0xfefe // mov r14, -72340172838076673
QUAD $0x262626262626bc49; WORD $0x2626 // mov r12, 2748926567846913574
QUAD $0x222222222222bd49; WORD $0x2222 // mov r13, 2459565876494606882
WORD $0x8941; BYTE $0xf3 // mov r11d, esi
LONG $0x03ebc141 // shr r11d, 3
WORD $0x3145; BYTE $0xff // xor r15d, r15d
LBB2_10:
LONG $0xff048b4e // mov r8, qword [rdi + 8*r15]
QUAD $0xdfdfdfdfdfe0b948; WORD $0xdfdf // mov rcx, -2314885530818453536
LONG $0x080c8d4d // lea r9, [r8 + rcx]
WORD $0x094d; BYTE $0xc1 // or r9, r8
WORD $0x894d; BYTE $0xc2 // mov r10, r8
WORD $0x314d; BYTE $0xea // xor r10, r13
WORD $0x014d; BYTE $0xf2 // add r10, r14
WORD $0x094d; BYTE $0xca // or r10, r9
WORD $0x894c; BYTE $0xc1 // mov rcx, r8
QUAD $0x5c5c5c5c5c5cba48; WORD $0x5c5c // mov rdx, 6655295901103053916
WORD $0x3148; BYTE $0xd1 // xor rcx, rdx
WORD $0x014c; BYTE $0xf1 // add rcx, r14
WORD $0x894c; BYTE $0xc2 // mov rdx, r8
QUAD $0x3c3c3c3c3c3cbb48; WORD $0x3c3c // mov rbx, 4340410370284600380
WORD $0x3148; BYTE $0xda // xor rdx, rbx
WORD $0x014c; BYTE $0xf2 // add rdx, r14
WORD $0x0948; BYTE $0xca // or rdx, rcx
WORD $0x094c; BYTE $0xd2 // or rdx, r10
WORD $0x894c; BYTE $0xc1 // mov rcx, r8
QUAD $0x3e3e3e3e3e3ebb48; WORD $0x3e3e // mov rbx, 4485090715960753726
WORD $0x3148; BYTE $0xd9 // xor rcx, rbx
WORD $0x014c; BYTE $0xf1 // add rcx, r14
WORD $0x314d; BYTE $0xe0 // xor r8, r12
WORD $0x014d; BYTE $0xf0 // add r8, r14
WORD $0x0949; BYTE $0xc8 // or r8, rcx
WORD $0x0949; BYTE $0xd0 // or r8, rdx
QUAD $0x808080808080b948; WORD $0x8080 // mov rcx, -9187201950435737472
WORD $0x2149; BYTE $0xc8 // and r8, rcx
JNE LBB2_22
LONG $0x01c78349 // add r15, 1
WORD $0x394d; BYTE $0xdf // cmp r15, r11
JB LBB2_10
WORD $0xe683; BYTE $0xf8 // and esi, -8
JMP LBB2_23
LBB2_13:
WORD $0x9848 // cdqe
WORD $0xfe83; BYTE $0x08 // cmp esi, 8
JL LBB2_25
LONG $0x24048948 // mov qword [rsp], rax
QUAD $0xfefefefefeffbd49; WORD $0xfefe // mov r13, -72340172838076673
QUAD $0x808080808080b949; WORD $0x8080 // mov r9, -9187201950435737472
QUAD $0x5c5c5c5c5c5cba49; WORD $0x5c5c // mov r10, 6655295901103053916
QUAD $0x3e3e3e3e3e3ebb49; WORD $0x3e3e // mov r11, 4485090715960753726
QUAD $0x3c3c3c3c3c3cbe49; WORD $0x3c3c // mov r14, 4340410370284600380
QUAD $0x262626262626bf49; WORD $0x2626 // mov r15, 2748926567846913574
QUAD $0x222222222222bc49; WORD $0x2222 // mov r12, 2459565876494606882
WORD $0xeec1; BYTE $0x03 // shr esi, 3
WORD $0xe683; BYTE $0x1f // and esi, 31
WORD $0xdb31 // xor ebx, ebx
LBB2_15:
LONG $0xdf148b48 // mov rdx, qword [rdi + 8*rbx]
QUAD $0xdfdfdfdfdfe0b848; WORD $0xdfdf // mov rax, -2314885530818453536
LONG $0x020c8d48 // lea rcx, [rdx + rax]
WORD $0x0948; BYTE $0xd1 // or rcx, rdx
WORD $0x8949; BYTE $0xd0 // mov r8, rdx
WORD $0x314d; BYTE $0xe0 // xor r8, r12
WORD $0x014d; BYTE $0xe8 // add r8, r13
WORD $0x0949; BYTE $0xc8 // or r8, rcx
WORD $0x8948; BYTE $0xd1 // mov rcx, rdx
WORD $0x314c; BYTE $0xd1 // xor rcx, r10
WORD $0x014c; BYTE $0xe9 // add rcx, r13
WORD $0x8948; BYTE $0xd0 // mov rax, rdx
WORD $0x314c; BYTE $0xf0 // xor rax, r14
WORD $0x014c; BYTE $0xe8 // add rax, r13
WORD $0x0948; BYTE $0xc8 // or rax, rcx
WORD $0x094c; BYTE $0xc0 // or rax, r8
WORD $0x8948; BYTE $0xd1 // mov rcx, rdx
WORD $0x314c; BYTE $0xd9 // xor rcx, r11
WORD $0x014c; BYTE $0xe9 // add rcx, r13
WORD $0x314c; BYTE $0xfa // xor rdx, r15
WORD $0x014c; BYTE $0xea // add rdx, r13
WORD $0x0948; BYTE $0xca // or rdx, rcx
WORD $0x0948; BYTE $0xc2 // or rdx, rax
WORD $0x214c; BYTE $0xca // and rdx, r9
JNE LBB2_20
LONG $0x01c38348 // add rbx, 1
WORD $0x3948; BYTE $0xf3 // cmp rbx, rsi
JB LBB2_15
WORD $0xe6c1; BYTE $0x03 // shl esi, 3
JMP LBB2_21
LBB2_18:
WORD $0xbc0f; BYTE $0xc3 // bsf eax, ebx
JMP LBB2_25
LBB2_19:
WORD $0xbc0f; BYTE $0xc2 // bsf eax, edx
JMP LBB2_24
LBB2_20:
WORD $0xbc0f; BYTE $0xf2 // bsf esi, edx
WORD $0xeec1; BYTE $0x03 // shr esi, 3
LBB2_21:
WORD $0xf089 // mov eax, esi
LONG $0x240c8b48 // mov rcx, qword [rsp]
WORD $0x0148; BYTE $0xc1 // add rcx, rax
WORD $0x8948; BYTE $0xc8 // mov rax, rcx
JMP LBB2_25
LBB2_22:
LONG $0xf0bc0f41 // bsf esi, r8d
WORD $0xeec1; BYTE $0x03 // shr esi, 3
LBB2_23:
WORD $0xf189 // mov ecx, esi
WORD $0x0148; BYTE $0xc8 // add rax, rcx
LONG $0x241c8b48 // mov rbx, qword [rsp]
LBB2_24:
WORD $0x0148; BYTE $0xd8 // add rax, rbx
LBB2_25:
SUBQ $8, SP
VZEROUPPER
MOVQ AX, ret+16(FP)
RET
TEXT ·_findEscapeIndex64(SB), $0-24
MOVQ buf+0(FP), DI
MOVQ len+8(FP), SI
WORD $0xf089 // mov eax, esi
WORD $0xf8c1; BYTE $0x1f // sar eax, 31
WORD $0xe8c1; BYTE $0x1d // shr eax, 29
WORD $0xf001 // add eax, esi
WORD $0xf8c1; BYTE $0x03 // sar eax, 3
WORD $0x9848 // cdqe
WORD $0xc683; BYTE $0x07 // add esi, 7
WORD $0xfe83; BYTE $0x0f // cmp esi, 15
JB LBB3_5
QUAD $0xfefefefefeffbe49; WORD $0xfefe // mov r14, -72340172838076673
WORD $0xd231 // xor edx, edx
QUAD $0xdfdfdfdfdfe0b849; WORD $0xdfdf // mov r8, -2314885530818453536
QUAD $0x222222222222b949; WORD $0x2222 // mov r9, 2459565876494606882
QUAD $0x5c5c5c5c5c5cba49; WORD $0x5c5c // mov r10, 6655295901103053916
QUAD $0x808080808080bb49; WORD $0x8080 // mov r11, -9187201950435737472
LBB3_2:
LONG $0xd7348b48 // mov rsi, qword [rdi + 8*rdx]
LONG $0x061c8d4a // lea rbx, [rsi + r8]
WORD $0x0948; BYTE $0xf3 // or rbx, rsi
WORD $0x8948; BYTE $0xf1 // mov rcx, rsi
WORD $0x314c; BYTE $0xc9 // xor rcx, r9
WORD $0x014c; BYTE $0xf1 // add rcx, r14
WORD $0x0948; BYTE $0xd9 // or rcx, rbx
WORD $0x314c; BYTE $0xd6 // xor rsi, r10
WORD $0x014c; BYTE $0xf6 // add rsi, r14
WORD $0x0948; BYTE $0xce // or rsi, rcx
WORD $0x214c; BYTE $0xde // and rsi, r11
JNE LBB3_3
LONG $0x01c28348 // add rdx, 1
WORD $0x3948; BYTE $0xc2 // cmp rdx, rax
JB LBB3_2
LBB3_5:
LONG $0x03e0c148 // shl rax, 3
JMP LBB3_6
LBB3_3:
WORD $0xbc0f; BYTE $0xc6 // bsf eax, esi
WORD $0xe8c1; BYTE $0x03 // shr eax, 3
LBB3_6:
MOVQ AX, ret+16(FP)
RET
DATA LCDATA3<>+0x000(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA3<>+0x008(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA3<>+0x010(SB)/8, $0x2222222222222222
DATA LCDATA3<>+0x018(SB)/8, $0x2222222222222222
DATA LCDATA3<>+0x020(SB)/8, $0xfefefefefefefeff
DATA LCDATA3<>+0x028(SB)/8, $0xfefefefefefefeff
DATA LCDATA3<>+0x030(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA3<>+0x038(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA3<>+0x040(SB)/8, $0x8080808080808080
DATA LCDATA3<>+0x048(SB)/8, $0x8080808080808080
GLOBL LCDATA3<>(SB), 8, $80
TEXT ·_findEscapeIndex128(SB), $0-24
MOVQ buf+0(FP), DI
MOVQ len+8(FP), SI
LEAQ LCDATA3<>(SB), BP
WORD $0xf089 // mov eax, esi
WORD $0xf8c1; BYTE $0x1f // sar eax, 31
WORD $0xe8c1; BYTE $0x1c // shr eax, 28
WORD $0xf001 // add eax, esi
WORD $0xf8c1; BYTE $0x04 // sar eax, 4
WORD $0x4e8d; BYTE $0x0f // lea ecx, [rsi + 15]
WORD $0xf983; BYTE $0x1f // cmp ecx, 31
JB LBB4_5
WORD $0x6348; BYTE $0xc8 // movsxd rcx, eax
WORD $0xd231 // xor edx, edx
LONG $0x456f79c5; BYTE $0x00 // vmovdqa xmm8, oword 0[rbp] /* [rip + .LCPI4_0] */
LONG $0x4d6ff9c5; BYTE $0x10 // vmovdqa xmm1, oword 16[rbp] /* [rip + .LCPI4_1] */
LONG $0x556ff9c5; BYTE $0x20 // vmovdqa xmm2, oword 32[rbp] /* [rip + .LCPI4_2] */
LONG $0x5d6ff9c5; BYTE $0x30 // vmovdqa xmm3, oword 48[rbp] /* [rip + .LCPI4_3] */
LONG $0x656ff9c5; BYTE $0x40 // vmovdqa xmm4, oword 64[rbp] /* [rip + .LCPI4_4] */
LBB4_2:
LONG $0x2f6ffac5 // vmovdqu xmm5, oword [rdi]
LONG $0xd451c1c4; BYTE $0xf0 // vpaddq xmm6, xmm5, xmm8
LONG $0xf9efd1c5 // vpxor xmm7, xmm5, xmm1
LONG $0xfad4c1c5 // vpaddq xmm7, xmm7, xmm2
LONG $0xc3efd1c5 // vpxor xmm0, xmm5, xmm3
LONG $0xc2d4f9c5 // vpaddq xmm0, xmm0, xmm2
LONG $0xedebc9c5 // vpor xmm5, xmm6, xmm5
LONG $0xefebd1c5 // vpor xmm5, xmm5, xmm7
LONG $0xc0ebd1c5 // vpor xmm0, xmm5, xmm0
LONG $0xc4dbf9c5 // vpand xmm0, xmm0, xmm4
LONG $0xd8d7f9c5 // vpmovmskb ebx, xmm0
WORD $0xdb85 // test ebx, ebx
JNE LBB4_3
LONG $0x10c78348 // add rdi, 16
LONG $0x01c28348 // add rdx, 1
WORD $0x3948; BYTE $0xca // cmp rdx, rcx
JB LBB4_2
LBB4_5:
WORD $0xe0c1; BYTE $0x04 // shl eax, 4
WORD $0xc629 // sub esi, eax
WORD $0x9848 // cdqe
WORD $0xfe83; BYTE $0x08 // cmp esi, 8
JL LBB4_12
QUAD $0xfefefefefeffb849; WORD $0xfefe // mov r8, -72340172838076673
WORD $0x8941; BYTE $0xf7 // mov r15d, esi
LONG $0x03efc141 // shr r15d, 3
WORD $0xc931 // xor ecx, ecx
QUAD $0xdfdfdfdfdfe0b949; WORD $0xdfdf // mov r9, -2314885530818453536
QUAD $0x222222222222ba49; WORD $0x2222 // mov r10, 2459565876494606882
QUAD $0x5c5c5c5c5c5cbb49; WORD $0x5c5c // mov r11, 6655295901103053916
QUAD $0x808080808080be49; WORD $0x8080 // mov r14, -9187201950435737472
LBB4_7:
LONG $0xcf1c8b48 // mov rbx, qword [rdi + 8*rcx]
LONG $0x0b248d4e // lea r12, [rbx + r9]
WORD $0x0949; BYTE $0xdc // or r12, rbx
WORD $0x8948; BYTE $0xda // mov rdx, rbx
WORD $0x314c; BYTE $0xd2 // xor rdx, r10
WORD $0x014c; BYTE $0xc2 // add rdx, r8
WORD $0x094c; BYTE $0xe2 // or rdx, r12
WORD $0x314c; BYTE $0xdb // xor rbx, r11
WORD $0x014c; BYTE $0xc3 // add rbx, r8
WORD $0x0948; BYTE $0xd3 // or rbx, rdx
WORD $0x214c; BYTE $0xf3 // and rbx, r14
JNE LBB4_8
LONG $0x01c18348 // add rcx, 1
WORD $0x394c; BYTE $0xf9 // cmp rcx, r15
JB LBB4_7
WORD $0xe683; BYTE $0xf8 // and esi, -8
JMP LBB4_11
LBB4_3:
WORD $0xbc0f; BYTE $0xc3 // bsf eax, ebx
JMP LBB4_12
LBB4_8:
WORD $0xbc0f; BYTE $0xf3 // bsf esi, ebx
WORD $0xeec1; BYTE $0x03 // shr esi, 3
LBB4_11:
WORD $0xf189 // mov ecx, esi
WORD $0x0148; BYTE $0xc8 // add rax, rcx
LBB4_12:
MOVQ AX, ret+16(FP)
RET
DATA LCDATA4<>+0x000(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA4<>+0x008(SB)/8, $0x2222222222222222
DATA LCDATA4<>+0x010(SB)/8, $0xfefefefefefefeff
DATA LCDATA4<>+0x018(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA4<>+0x020(SB)/8, $0x8080808080808080
DATA LCDATA4<>+0x028(SB)/8, $0x8080808080808080
DATA LCDATA4<>+0x030(SB)/8, $0x8080808080808080
DATA LCDATA4<>+0x038(SB)/8, $0x8080808080808080
DATA LCDATA4<>+0x040(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA4<>+0x048(SB)/8, $0xdfdfdfdfdfdfdfe0
DATA LCDATA4<>+0x050(SB)/8, $0x2222222222222222
DATA LCDATA4<>+0x058(SB)/8, $0x2222222222222222
DATA LCDATA4<>+0x060(SB)/8, $0xfefefefefefefeff
DATA LCDATA4<>+0x068(SB)/8, $0xfefefefefefefeff
DATA LCDATA4<>+0x070(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA4<>+0x078(SB)/8, $0x5c5c5c5c5c5c5c5c
DATA LCDATA4<>+0x080(SB)/8, $0x8080808080808080
DATA LCDATA4<>+0x088(SB)/8, $0x8080808080808080
GLOBL LCDATA4<>(SB), 8, $144
TEXT ·_findEscapeIndex256(SB), $0-24
MOVQ buf+0(FP), DI
MOVQ len+8(FP), SI
LEAQ LCDATA4<>(SB), BP
WORD $0xf089 // mov eax, esi
WORD $0xf8c1; BYTE $0x1f // sar eax, 31
WORD $0xe8c1; BYTE $0x1b // shr eax, 27
WORD $0xf001 // add eax, esi
WORD $0xf8c1; BYTE $0x05 // sar eax, 5
WORD $0x4e8d; BYTE $0x1f // lea ecx, [rsi + 31]
WORD $0xf983; BYTE $0x3f // cmp ecx, 63
JB LBB5_4
WORD $0x6348; BYTE $0xc8 // movsxd rcx, eax
LONG $0x597de2c4; WORD $0x0045 // vpbroadcastq ymm0, qword 0[rbp] /* [rip + .LCPI5_0] */
LONG $0x597de2c4; WORD $0x084d // vpbroadcastq ymm1, qword 8[rbp] /* [rip + .LCPI5_1] */
LONG $0x597de2c4; WORD $0x1055 // vpbroadcastq ymm2, qword 16[rbp] /* [rip + .LCPI5_2] */
WORD $0xd231 // xor edx, edx
LONG $0x597de2c4; WORD $0x185d // vpbroadcastq ymm3, qword 24[rbp] /* [rip + .LCPI5_3] */
LONG $0x656ffdc5; BYTE $0x20 // vmovdqa ymm4, yword 32[rbp] /* [rip + .LCPI5_4] */
LBB5_2:
LONG $0x2f6ffec5 // vmovdqu ymm5, yword [rdi]
LONG $0xf0d4d5c5 // vpaddq ymm6, ymm5, ymm0
LONG $0xf9efd5c5 // vpxor ymm7, ymm5, ymm1
LONG $0xfad4c5c5 // vpaddq ymm7, ymm7, ymm2
LONG $0xc3ef55c5 // vpxor ymm8, ymm5, ymm3
LONG $0xc2d43dc5 // vpaddq ymm8, ymm8, ymm2
LONG $0xedebcdc5 // vpor ymm5, ymm6, ymm5
LONG $0xefebd5c5 // vpor ymm5, ymm5, ymm7
LONG $0xeb55c1c4; BYTE $0xe8 // vpor ymm5, ymm5, ymm8
LONG $0xecdbd5c5 // vpand ymm5, ymm5, ymm4
LONG $0xddd7fdc5 // vpmovmskb ebx, ymm5
WORD $0xdb85 // test ebx, ebx
JNE LBB5_18
LONG $0x20c78348 // add rdi, 32
LONG $0x01c28348 // add rdx, 1
WORD $0x3948; BYTE $0xca // cmp rdx, rcx
JB LBB5_2
LBB5_4:
WORD $0xe0c1; BYTE $0x05 // shl eax, 5
WORD $0xc629 // sub esi, eax
WORD $0xfe83; BYTE $0x10 // cmp esi, 16
JL LBB5_13
WORD $0x634c; BYTE $0xc0 // movsxd r8, eax
WORD $0xf089 // mov eax, esi
WORD $0xe8c1; BYTE $0x04 // shr eax, 4
WORD $0xc931 // xor ecx, ecx
LONG $0x456f79c5; BYTE $0x40 // vmovdqa xmm8, oword 64[rbp] /* [rip + .LCPI5_5] */
LONG $0x4d6ff9c5; BYTE $0x50 // vmovdqa xmm1, oword 80[rbp] /* [rip + .LCPI5_6] */
LONG $0x556ff9c5; BYTE $0x60 // vmovdqa xmm2, oword 96[rbp] /* [rip + .LCPI5_7] */
LONG $0x5d6ff9c5; BYTE $0x70 // vmovdqa xmm3, oword 112[rbp] /* [rip + .LCPI5_8] */
QUAD $0x00000080a56ff9c5 // vmovdqa xmm4, oword 128[rbp] /* [rip + .LCPI5_9] */
LBB5_6:
LONG $0x2f6ffac5 // vmovdqu xmm5, oword [rdi]
LONG $0xd451c1c4; BYTE $0xf0 // vpaddq xmm6, xmm5, xmm8
LONG $0xf9efd1c5 // vpxor xmm7, xmm5, xmm1
LONG $0xfad4c1c5 // vpaddq xmm7, xmm7, xmm2
LONG $0xc3efd1c5 // vpxor xmm0, xmm5, xmm3
LONG $0xc2d4f9c5 // vpaddq xmm0, xmm0, xmm2
LONG $0xedebc9c5 // vpor xmm5, xmm6, xmm5
LONG $0xefebd1c5 // vpor xmm5, xmm5, xmm7
LONG $0xc0ebd1c5 // vpor xmm0, xmm5, xmm0
LONG $0xc4dbf9c5 // vpand xmm0, xmm0, xmm4
LONG $0xd0d7f9c5 // vpmovmskb edx, xmm0
WORD $0xd285 // test edx, edx
JNE LBB5_19
LONG $0x10c78348 // add rdi, 16
LONG $0x01c18348 // add rcx, 1
WORD $0x3948; BYTE $0xc1 // cmp rcx, rax
JB LBB5_6
WORD $0xf089 // mov eax, esi
WORD $0xe083; BYTE $0xf0 // and eax, -16
WORD $0xc629 // sub esi, eax
WORD $0x9848 // cdqe
WORD $0xfe83; BYTE $0x08 // cmp esi, 8
JL LBB5_24
QUAD $0xfefefefefeffbf49; WORD $0xfefe // mov r15, -72340172838076673
QUAD $0xdfdfdfdfdfe0b949; WORD $0xdfdf // mov r9, -2314885530818453536
QUAD $0x808080808080ba49; WORD $0x8080 // mov r10, -9187201950435737472
QUAD $0x5c5c5c5c5c5cbb49; WORD $0x5c5c // mov r11, 6655295901103053916
QUAD $0x222222222222be49; WORD $0x2222 // mov r14, 2459565876494606882
WORD $0x8941; BYTE $0xf4 // mov r12d, esi
LONG $0x03ecc141 // shr r12d, 3
WORD $0xc931 // xor ecx, ecx
LBB5_10:
LONG $0xcf148b48 // mov rdx, qword [rdi + 8*rcx]
LONG $0x0a2c8d4e // lea r13, [rdx + r9]
WORD $0x0949; BYTE $0xd5 // or r13, rdx
WORD $0x8948; BYTE $0xd3 // mov rbx, rdx
WORD $0x314c; BYTE $0xf3 // xor rbx, r14
WORD $0x014c; BYTE $0xfb // add rbx, r15
WORD $0x094c; BYTE $0xeb // or rbx, r13
WORD $0x314c; BYTE $0xda // xor rdx, r11
WORD $0x014c; BYTE $0xfa // add rdx, r15
WORD $0x0948; BYTE $0xda // or rdx, rbx
WORD $0x214c; BYTE $0xd2 // and rdx, r10
JNE LBB5_22
LONG $0x01c18348 // add rcx, 1
WORD $0x394c; BYTE $0xe1 // cmp rcx, r12
JB LBB5_10
WORD $0xe683; BYTE $0xf8 // and esi, -8
JMP LBB5_23
LBB5_13:
WORD $0x9848 // cdqe
WORD $0xfe83; BYTE $0x08 // cmp esi, 8
JL LBB5_25
QUAD $0xfefefefefeffbe49; WORD $0xfefe // mov r14, -72340172838076673
QUAD $0xdfdfdfdfdfe0b849; WORD $0xdfdf // mov r8, -2314885530818453536
QUAD $0x808080808080b949; WORD $0x8080 // mov r9, -9187201950435737472
QUAD $0x5c5c5c5c5c5cba49; WORD $0x5c5c // mov r10, 6655295901103053916
QUAD $0x222222222222bb49; WORD $0x2222 // mov r11, 2459565876494606882
WORD $0xeec1; BYTE $0x03 // shr esi, 3
WORD $0xe683; BYTE $0x1f // and esi, 31
WORD $0xdb31 // xor ebx, ebx
LBB5_15:
LONG $0xdf148b48 // mov rdx, qword [rdi + 8*rbx]
LONG $0x023c8d4e // lea r15, [rdx + r8]
WORD $0x0949; BYTE $0xd7 // or r15, rdx
WORD $0x8948; BYTE $0xd1 // mov rcx, rdx
WORD $0x314c; BYTE $0xd9 // xor rcx, r11
WORD $0x014c; BYTE $0xf1 // add rcx, r14
WORD $0x094c; BYTE $0xf9 // or rcx, r15
WORD $0x314c; BYTE $0xd2 // xor rdx, r10
WORD $0x014c; BYTE $0xf2 // add rdx, r14
WORD $0x0948; BYTE $0xca // or rdx, rcx
WORD $0x214c; BYTE $0xca // and rdx, r9
JNE LBB5_20
LONG $0x01c38348 // add rbx, 1
WORD $0x3948; BYTE $0xf3 // cmp rbx, rsi
JB LBB5_15
WORD $0xe6c1; BYTE $0x03 // shl esi, 3
JMP LBB5_21
LBB5_18:
WORD $0xbc0f; BYTE $0xc3 // bsf eax, ebx
JMP LBB5_25
LBB5_19:
WORD $0xbc0f; BYTE $0xc2 // bsf eax, edx
JMP LBB5_24
LBB5_20:
WORD $0xbc0f; BYTE $0xf2 // bsf esi, edx
WORD $0xeec1; BYTE $0x03 // shr esi, 3
LBB5_21:
WORD $0xf189 // mov ecx, esi
WORD $0x0148; BYTE $0xc8 // add rax, rcx
JMP LBB5_25
LBB5_22:
WORD $0xbc0f; BYTE $0xf2 // bsf esi, edx
WORD $0xeec1; BYTE $0x03 // shr esi, 3
LBB5_23:
WORD $0xf189 // mov ecx, esi
WORD $0x0148; BYTE $0xc8 // add rax, rcx
LBB5_24:
WORD $0x014c; BYTE $0xc0 // add rax, r8
LBB5_25:
VZEROUPPER
MOVQ AX, ret+16(FP)
RET

View File

@ -1,415 +0,0 @@
package encoder
var needEscapeHTMLNormalizeUTF8 = [256]bool{
'"': true,
'&': true,
'<': true,
'>': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0x7f */
0x80: true,
0x81: true,
0x82: true,
0x83: true,
0x84: true,
0x85: true,
0x86: true,
0x87: true,
0x88: true,
0x89: true,
0x8a: true,
0x8b: true,
0x8c: true,
0x8d: true,
0x8e: true,
0x8f: true,
0x90: true,
0x91: true,
0x92: true,
0x93: true,
0x94: true,
0x95: true,
0x96: true,
0x97: true,
0x98: true,
0x99: true,
0x9a: true,
0x9b: true,
0x9c: true,
0x9d: true,
0x9e: true,
0x9f: true,
0xa0: true,
0xa1: true,
0xa2: true,
0xa3: true,
0xa4: true,
0xa5: true,
0xa6: true,
0xa7: true,
0xa8: true,
0xa9: true,
0xaa: true,
0xab: true,
0xac: true,
0xad: true,
0xae: true,
0xaf: true,
0xb0: true,
0xb1: true,
0xb2: true,
0xb3: true,
0xb4: true,
0xb5: true,
0xb6: true,
0xb7: true,
0xb8: true,
0xb9: true,
0xba: true,
0xbb: true,
0xbc: true,
0xbd: true,
0xbe: true,
0xbf: true,
0xc0: true,
0xc1: true,
0xc2: true,
0xc3: true,
0xc4: true,
0xc5: true,
0xc6: true,
0xc7: true,
0xc8: true,
0xc9: true,
0xca: true,
0xcb: true,
0xcc: true,
0xcd: true,
0xce: true,
0xcf: true,
0xd0: true,
0xd1: true,
0xd2: true,
0xd3: true,
0xd4: true,
0xd5: true,
0xd6: true,
0xd7: true,
0xd8: true,
0xd9: true,
0xda: true,
0xdb: true,
0xdc: true,
0xdd: true,
0xde: true,
0xdf: true,
0xe0: true,
0xe1: true,
0xe2: true,
0xe3: true,
0xe4: true,
0xe5: true,
0xe6: true,
0xe7: true,
0xe8: true,
0xe9: true,
0xea: true,
0xeb: true,
0xec: true,
0xed: true,
0xee: true,
0xef: true,
0xf0: true,
0xf1: true,
0xf2: true,
0xf3: true,
0xf4: true,
0xf5: true,
0xf6: true,
0xf7: true,
0xf8: true,
0xf9: true,
0xfa: true,
0xfb: true,
0xfc: true,
0xfd: true,
0xfe: true,
0xff: true,
}
var needEscapeNormalizeUTF8 = [256]bool{
'"': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0x7f */
0x80: true,
0x81: true,
0x82: true,
0x83: true,
0x84: true,
0x85: true,
0x86: true,
0x87: true,
0x88: true,
0x89: true,
0x8a: true,
0x8b: true,
0x8c: true,
0x8d: true,
0x8e: true,
0x8f: true,
0x90: true,
0x91: true,
0x92: true,
0x93: true,
0x94: true,
0x95: true,
0x96: true,
0x97: true,
0x98: true,
0x99: true,
0x9a: true,
0x9b: true,
0x9c: true,
0x9d: true,
0x9e: true,
0x9f: true,
0xa0: true,
0xa1: true,
0xa2: true,
0xa3: true,
0xa4: true,
0xa5: true,
0xa6: true,
0xa7: true,
0xa8: true,
0xa9: true,
0xaa: true,
0xab: true,
0xac: true,
0xad: true,
0xae: true,
0xaf: true,
0xb0: true,
0xb1: true,
0xb2: true,
0xb3: true,
0xb4: true,
0xb5: true,
0xb6: true,
0xb7: true,
0xb8: true,
0xb9: true,
0xba: true,
0xbb: true,
0xbc: true,
0xbd: true,
0xbe: true,
0xbf: true,
0xc0: true,
0xc1: true,
0xc2: true,
0xc3: true,
0xc4: true,
0xc5: true,
0xc6: true,
0xc7: true,
0xc8: true,
0xc9: true,
0xca: true,
0xcb: true,
0xcc: true,
0xcd: true,
0xce: true,
0xcf: true,
0xd0: true,
0xd1: true,
0xd2: true,
0xd3: true,
0xd4: true,
0xd5: true,
0xd6: true,
0xd7: true,
0xd8: true,
0xd9: true,
0xda: true,
0xdb: true,
0xdc: true,
0xdd: true,
0xde: true,
0xdf: true,
0xe0: true,
0xe1: true,
0xe2: true,
0xe3: true,
0xe4: true,
0xe5: true,
0xe6: true,
0xe7: true,
0xe8: true,
0xe9: true,
0xea: true,
0xeb: true,
0xec: true,
0xed: true,
0xee: true,
0xef: true,
0xf0: true,
0xf1: true,
0xf2: true,
0xf3: true,
0xf4: true,
0xf5: true,
0xf6: true,
0xf7: true,
0xf8: true,
0xf9: true,
0xfa: true,
0xfb: true,
0xfc: true,
0xfd: true,
0xfe: true,
0xff: true,
}
var needEscapeHTML = [256]bool{
'"': true,
'&': true,
'<': true,
'>': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0xff */
}
var needEscape = [256]bool{
'"': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0xff */
}

View File

@ -16,17 +16,16 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
}
if err := recover(); err != nil {
w := ctx.Option.DebugOut
fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Fprintln(w, "* [TYPE]")
fmt.Fprintln(w, codeSet.Type)
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Fprintln(w, code.Dump())
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [CONTEXT]")
fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
fmt.Println("=============[DEBUG]===============")
fmt.Println("* [TYPE]")
fmt.Println(codeSet.Type)
fmt.Printf("\n")
fmt.Println("* [ALL OPCODE]")
fmt.Println(code.Dump())
fmt.Printf("\n")
fmt.Println("* [CONTEXT]")
fmt.Printf("%+v\n", ctx)
fmt.Println("===================================")
panic(err)
}
}()

View File

@ -3,7 +3,6 @@ package vm
import (
"math"
"reflect"
"sort"
"unsafe"
@ -195,15 +194,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ
}
if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b)
code = code.Next
break
}
b = appendNullComma(ctx, b)
code = code.Next
break
}
ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ)))
if err != nil {
return nil, err
}
@ -222,7 +218,8 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
oldOffset := ptrOffset
ptrOffset += totalLength * uintptrSize
oldBaseIndent := ctx.BaseIndent
ctx.BaseIndent += code.Indent
indentDiffFromTop := c.Indent - 1
ctx.BaseIndent += code.Indent - indentDiffFromTop
newLen := offsetNum + totalLength + nextTotalLength
if curlen < newLen {
@ -405,12 +402,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.End.Next
break
}
b = appendStructHead(ctx, b)
unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0
mapCtx := encoder.NewMapContext(mlen, unorderedMap)
mapiterinit(code.Type, uptr, &mapCtx.Iter)
store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx)))
ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx))
b = appendStructHead(ctx, b)
if unorderedMap {
b = appendMapKeyIndent(ctx, code.Next, b)
} else {
@ -709,15 +706,14 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
u64 := ptrToUint64(p, code.NumBitSize)
u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize)
v := u64 & ((1 << code.NumBitSize) - 1)
if v == 0 {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
b = append(b, '"')
b = appendInt(ctx, b, p, code)
b = appendInt(ctx, b, p+uintptr(code.Offset), code)
b = append(b, '"')
b = appendComma(ctx, b)
code = code.Next
@ -2958,10 +2954,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3000,10 +2995,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
iface := ptrToInterface(code, p)
@ -3121,10 +3115,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3163,10 +3156,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {

View File

@ -16,17 +16,16 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() {
if err := recover(); err != nil {
w := ctx.Option.DebugOut
fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Fprintln(w, "* [TYPE]")
fmt.Fprintln(w, codeSet.Type)
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Fprintln(w, code.Dump())
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [CONTEXT]")
fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
fmt.Println("=============[DEBUG]===============")
fmt.Println("* [TYPE]")
fmt.Println(codeSet.Type)
fmt.Printf("\n")
fmt.Println("* [ALL OPCODE]")
fmt.Println(code.Dump())
fmt.Printf("\n")
fmt.Println("* [CONTEXT]")
fmt.Printf("%+v\n", ctx)
fmt.Println("===================================")
panic(err)
}
}()

View File

@ -3,7 +3,6 @@ package vm_color
import (
"math"
"reflect"
"sort"
"unsafe"
@ -195,15 +194,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ
}
if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b)
code = code.Next
break
}
b = appendNullComma(ctx, b)
code = code.Next
break
}
ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ)))
if err != nil {
return nil, err
}
@ -222,7 +218,8 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
oldOffset := ptrOffset
ptrOffset += totalLength * uintptrSize
oldBaseIndent := ctx.BaseIndent
ctx.BaseIndent += code.Indent
indentDiffFromTop := c.Indent - 1
ctx.BaseIndent += code.Indent - indentDiffFromTop
newLen := offsetNum + totalLength + nextTotalLength
if curlen < newLen {
@ -405,12 +402,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.End.Next
break
}
b = appendStructHead(ctx, b)
unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0
mapCtx := encoder.NewMapContext(mlen, unorderedMap)
mapiterinit(code.Type, uptr, &mapCtx.Iter)
store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx)))
ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx))
b = appendStructHead(ctx, b)
if unorderedMap {
b = appendMapKeyIndent(ctx, code.Next, b)
} else {
@ -709,15 +706,14 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
u64 := ptrToUint64(p, code.NumBitSize)
u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize)
v := u64 & ((1 << code.NumBitSize) - 1)
if v == 0 {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
b = append(b, '"')
b = appendInt(ctx, b, p, code)
b = appendInt(ctx, b, p+uintptr(code.Offset), code)
b = append(b, '"')
b = appendComma(ctx, b)
code = code.Next
@ -2958,10 +2954,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3000,10 +2995,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
iface := ptrToInterface(code, p)
@ -3121,10 +3115,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3163,10 +3156,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {

View File

@ -16,17 +16,16 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() {
if err := recover(); err != nil {
w := ctx.Option.DebugOut
fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Fprintln(w, "* [TYPE]")
fmt.Fprintln(w, codeSet.Type)
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Fprintln(w, code.Dump())
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [CONTEXT]")
fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
fmt.Println("=============[DEBUG]===============")
fmt.Println("* [TYPE]")
fmt.Println(codeSet.Type)
fmt.Printf("\n")
fmt.Println("* [ALL OPCODE]")
fmt.Println(code.Dump())
fmt.Printf("\n")
fmt.Println("* [CONTEXT]")
fmt.Printf("%+v\n", ctx)
fmt.Println("===================================")
panic(err)
}
}()

View File

@ -3,7 +3,6 @@ package vm_color_indent
import (
"math"
"reflect"
"sort"
"unsafe"
@ -195,15 +194,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ
}
if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b)
code = code.Next
break
}
b = appendNullComma(ctx, b)
code = code.Next
break
}
ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ)))
if err != nil {
return nil, err
}
@ -222,7 +218,8 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
oldOffset := ptrOffset
ptrOffset += totalLength * uintptrSize
oldBaseIndent := ctx.BaseIndent
ctx.BaseIndent += code.Indent
indentDiffFromTop := c.Indent - 1
ctx.BaseIndent += code.Indent - indentDiffFromTop
newLen := offsetNum + totalLength + nextTotalLength
if curlen < newLen {
@ -405,12 +402,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.End.Next
break
}
b = appendStructHead(ctx, b)
unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0
mapCtx := encoder.NewMapContext(mlen, unorderedMap)
mapiterinit(code.Type, uptr, &mapCtx.Iter)
store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx)))
ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx))
b = appendStructHead(ctx, b)
if unorderedMap {
b = appendMapKeyIndent(ctx, code.Next, b)
} else {
@ -709,15 +706,14 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
u64 := ptrToUint64(p, code.NumBitSize)
u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize)
v := u64 & ((1 << code.NumBitSize) - 1)
if v == 0 {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
b = append(b, '"')
b = appendInt(ctx, b, p, code)
b = appendInt(ctx, b, p+uintptr(code.Offset), code)
b = append(b, '"')
b = appendComma(ctx, b)
code = code.Next
@ -2958,10 +2954,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3000,10 +2995,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
iface := ptrToInterface(code, p)
@ -3121,10 +3115,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3163,10 +3156,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {

View File

@ -16,17 +16,16 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() {
if err := recover(); err != nil {
w := ctx.Option.DebugOut
fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Fprintln(w, "* [TYPE]")
fmt.Fprintln(w, codeSet.Type)
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Fprintln(w, code.Dump())
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [CONTEXT]")
fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
fmt.Println("=============[DEBUG]===============")
fmt.Println("* [TYPE]")
fmt.Println(codeSet.Type)
fmt.Printf("\n")
fmt.Println("* [ALL OPCODE]")
fmt.Println(code.Dump())
fmt.Printf("\n")
fmt.Println("* [CONTEXT]")
fmt.Printf("%+v\n", ctx)
fmt.Println("===================================")
panic(err)
}
}()

View File

@ -3,7 +3,6 @@ package vm_indent
import (
"math"
"reflect"
"sort"
"unsafe"
@ -195,15 +194,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ
}
if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b)
code = code.Next
break
}
b = appendNullComma(ctx, b)
code = code.Next
break
}
ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ)))
if err != nil {
return nil, err
}
@ -222,7 +218,8 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
oldOffset := ptrOffset
ptrOffset += totalLength * uintptrSize
oldBaseIndent := ctx.BaseIndent
ctx.BaseIndent += code.Indent
indentDiffFromTop := c.Indent - 1
ctx.BaseIndent += code.Indent - indentDiffFromTop
newLen := offsetNum + totalLength + nextTotalLength
if curlen < newLen {
@ -405,12 +402,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
code = code.End.Next
break
}
b = appendStructHead(ctx, b)
unorderedMap := (ctx.Option.Flag & encoder.UnorderedMapOption) != 0
mapCtx := encoder.NewMapContext(mlen, unorderedMap)
mapiterinit(code.Type, uptr, &mapCtx.Iter)
store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx)))
ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx))
b = appendStructHead(ctx, b)
if unorderedMap {
b = appendMapKeyIndent(ctx, code.Next, b)
} else {
@ -709,15 +706,14 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
u64 := ptrToUint64(p, code.NumBitSize)
u64 := ptrToUint64(p+uintptr(code.Offset), code.NumBitSize)
v := u64 & ((1 << code.NumBitSize) - 1)
if v == 0 {
code = code.NextField
} else {
b = appendStructKey(ctx, code, b)
b = append(b, '"')
b = appendInt(ctx, b, p, code)
b = appendInt(ctx, b, p+uintptr(code.Offset), code)
b = append(b, '"')
b = appendComma(ctx, b)
code = code.Next
@ -2958,10 +2954,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3000,10 +2995,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalJSON {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
iface := ptrToInterface(code, p)
@ -3121,10 +3115,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
b = appendStructHead(ctx, b)
}
b = appendStructKey(ctx, code, b)
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {
@ -3163,10 +3156,9 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
if code.Flags&encoder.AnonymousHeadFlags == 0 {
b = appendStructHead(ctx, b)
}
p += uintptr(code.Offset)
if (code.Flags & encoder.IsNilableTypeFlags) != 0 {
if (code.Flags&encoder.IndirectFlags) != 0 || code.Op == encoder.OpStructPtrHeadOmitEmptyMarshalText {
p = ptrToPtr(p)
p = ptrToPtr(p + uintptr(code.Offset))
}
}
if p == 0 && (code.Flags&encoder.NilCheckFlags) != 0 {

View File

@ -162,22 +162,3 @@ func ErrInvalidBeginningOfValue(c byte, cursor int64) *SyntaxError {
Offset: cursor,
}
}
type PathError struct {
msg string
}
func (e *PathError) Error() string {
return fmt.Sprintf("json: invalid path format: %s", e.msg)
}
func ErrInvalidPath(msg string, args ...interface{}) *PathError {
if len(args) != 0 {
return &PathError{msg: fmt.Sprintf(msg, args...)}
}
return &PathError{msg: msg}
}
func ErrEmptyPath() *PathError {
return &PathError{msg: "path is empty"}
}

View File

@ -13,11 +13,7 @@ func getTag(field reflect.StructField) string {
func IsIgnoredStructField(field reflect.StructField) bool {
if field.PkgPath != "" {
if field.Anonymous {
t := field.Type
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
if !(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct) && field.Type.Kind() != reflect.Struct {
return true
}
} else {

View File

@ -364,8 +364,3 @@ func Valid(data []byte) bool {
}
return decoder.InputOffset() >= int64(len(data))
}
func init() {
encoder.Marshal = Marshal
encoder.Unmarshal = Unmarshal
}

View File

@ -1,8 +1,6 @@
package json
import (
"io"
"github.com/goccy/go-json/internal/decoder"
"github.com/goccy/go-json/internal/encoder"
)
@ -17,23 +15,12 @@ func UnorderedMap() EncodeOptionFunc {
}
}
// DisableHTMLEscape disables escaping of HTML characters ( '&', '<', '>' ) when encoding string.
func DisableHTMLEscape() EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.Flag &= ^encoder.HTMLEscapeOption
}
}
// DisableNormalizeUTF8
// By default, when encoding string, UTF8 characters in the range of 0x80 - 0xFF are processed by applying \ufffd for invalid code and escaping for \u2028 and \u2029.
// This option disables this behaviour. You can expect faster speeds by applying this option, but be careful.
// encoding/json implements here: https://github.com/golang/go/blob/6178d25fc0b28724b1b5aec2b1b74fc06d9294c7/src/encoding/json/encode.go#L1067-L1093.
func DisableNormalizeUTF8() EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.Flag &= ^encoder.NormalizeUTF8Option
}
}
// Debug outputs debug information when panic occurs during encoding.
func Debug() EncodeOptionFunc {
return func(opt *EncodeOption) {
@ -41,13 +28,6 @@ func Debug() EncodeOptionFunc {
}
}
// DebugWith sets the destination to write debug messages.
func DebugWith(w io.Writer) EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.DebugOut = w
}
}
// Colorize add an identifier for coloring to the string of the encoded result.
func Colorize(scheme *ColorScheme) EncodeOptionFunc {
return func(opt *EncodeOption) {

84
path.go
View File

@ -1,84 +0,0 @@
package json
import (
"reflect"
"github.com/goccy/go-json/internal/decoder"
)
// CreatePath creates JSON Path.
//
// JSON Path rule
// $ : root object or element. The JSON Path format must start with this operator, which refers to the outermost level of the JSON-formatted string.
// . : child operator. You can identify child values using dot-notation.
// .. : recursive descent.
// [] : subscript operator. If the JSON object is an array, you can use brackets to specify the array index.
// [*] : all objects/elements for array.
//
// Reserved words must be properly escaped when included in Path.
//
// Escape Rule
// single quote style escape: e.g.) `$['a.b'].c`
// double quote style escape: e.g.) `$."a.b".c`
func CreatePath(p string) (*Path, error) {
path, err := decoder.PathString(p).Build()
if err != nil {
return nil, err
}
return &Path{path: path}, nil
}
// Path represents JSON Path.
type Path struct {
path *decoder.Path
}
// RootSelectorOnly whether only the root selector ($) is used.
func (p *Path) RootSelectorOnly() bool {
return p.path.RootSelectorOnly
}
// UsedSingleQuotePathSelector whether single quote-based escaping was done when building the JSON Path.
func (p *Path) UsedSingleQuotePathSelector() bool {
return p.path.SingleQuotePathSelector
}
// UsedSingleQuotePathSelector whether double quote-based escaping was done when building the JSON Path.
func (p *Path) UsedDoubleQuotePathSelector() bool {
return p.path.DoubleQuotePathSelector
}
// Extract extracts a specific JSON string.
func (p *Path) Extract(data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) {
return extractFromPath(p, data, optFuncs...)
}
// PathString returns original JSON Path string.
func (p *Path) PathString() string {
return p.path.String()
}
// Unmarshal extract and decode the value of the part corresponding to JSON Path from the input data.
func (p *Path) Unmarshal(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
contents, err := extractFromPath(p, data, optFuncs...)
if err != nil {
return err
}
results := make([]interface{}, 0, len(contents))
for _, content := range contents {
var result interface{}
if err := Unmarshal(content, &result); err != nil {
return err
}
results = append(results, result)
}
if err := decoder.AssignValue(reflect.ValueOf(results), reflect.ValueOf(v)); err != nil {
return err
}
return nil
}
// Get extract and substitute the value of the part corresponding to JSON Path from the input value.
func (p *Path) Get(src, dst interface{}) error {
return p.path.Get(reflect.ValueOf(src), reflect.ValueOf(dst))
}

View File

@ -1,234 +0,0 @@
package json_test
import (
"bytes"
"reflect"
"sort"
"testing"
"github.com/goccy/go-json"
)
func TestExtractPath(t *testing.T) {
src := []byte(`{"a":{"b":10,"c":true},"b":"text"}`)
t.Run("$.a.b", func(t *testing.T) {
path, err := json.CreatePath("$.a.b")
if err != nil {
t.Fatal(err)
}
contents, err := path.Extract(src)
if err != nil {
t.Fatal(err)
}
if len(contents) != 1 {
t.Fatal("failed to extract")
}
if !bytes.Equal(contents[0], []byte("10")) {
t.Fatal("failed to extract")
}
})
t.Run("$.b", func(t *testing.T) {
path, err := json.CreatePath("$.b")
if err != nil {
t.Fatal(err)
}
contents, err := path.Extract(src)
if err != nil {
t.Fatal(err)
}
if len(contents) != 1 {
t.Fatal("failed to extract")
}
if !bytes.Equal(contents[0], []byte(`"text"`)) {
t.Fatal("failed to extract")
}
})
t.Run("$.a", func(t *testing.T) {
path, err := json.CreatePath("$.a")
if err != nil {
t.Fatal(err)
}
contents, err := path.Extract(src)
if err != nil {
t.Fatal(err)
}
if len(contents) != 1 {
t.Fatal("failed to extract")
}
if !bytes.Equal(contents[0], []byte(`{"b":10,"c":true}`)) {
t.Fatal("failed to extract")
}
})
}
func TestUnmarshalPath(t *testing.T) {
t.Run("int", func(t *testing.T) {
src := []byte(`{"a":{"b":10,"c":true},"b":"text"}`)
t.Run("success", func(t *testing.T) {
path, err := json.CreatePath("$.a.b")
if err != nil {
t.Fatal(err)
}
var v int
if err := path.Unmarshal(src, &v); err != nil {
t.Fatal(err)
}
if v != 10 {
t.Fatal("failed to unmarshal path")
}
})
t.Run("failure", func(t *testing.T) {
path, err := json.CreatePath("$.a.c")
if err != nil {
t.Fatal(err)
}
var v map[string]interface{}
if err := path.Unmarshal(src, &v); err == nil {
t.Fatal("expected error")
}
})
})
t.Run("bool", func(t *testing.T) {
src := []byte(`{"a":{"b":10,"c":true},"b":"text"}`)
t.Run("success", func(t *testing.T) {
path, err := json.CreatePath("$.a.c")
if err != nil {
t.Fatal(err)
}
var v bool
if err := path.Unmarshal(src, &v); err != nil {
t.Fatal(err)
}
if !v {
t.Fatal("failed to unmarshal path")
}
})
t.Run("failure", func(t *testing.T) {
path, err := json.CreatePath("$.a.b")
if err != nil {
t.Fatal(err)
}
var v bool
if err := path.Unmarshal(src, &v); err == nil {
t.Fatal("expected error")
}
})
})
t.Run("map", func(t *testing.T) {
src := []byte(`{"a":{"b":10,"c":true},"b":"text"}`)
t.Run("success", func(t *testing.T) {
path, err := json.CreatePath("$.a")
if err != nil {
t.Fatal(err)
}
var v map[string]interface{}
if err := path.Unmarshal(src, &v); err != nil {
t.Fatal(err)
}
if len(v) != 2 {
t.Fatal("failed to decode map")
}
})
})
t.Run("path with single quote selector", func(t *testing.T) {
path, err := json.CreatePath("$['a.b'].c")
if err != nil {
t.Fatal(err)
}
var v string
if err := path.Unmarshal([]byte(`{"a.b": {"c": "world"}}`), &v); err != nil {
t.Fatal(err)
}
if v != "world" {
t.Fatal("failed to unmarshal path")
}
})
t.Run("path with double quote selector", func(t *testing.T) {
path, err := json.CreatePath(`$."a.b".c`)
if err != nil {
t.Fatal(err)
}
var v string
if err := path.Unmarshal([]byte(`{"a.b": {"c": "world"}}`), &v); err != nil {
t.Fatal(err)
}
if v != "world" {
t.Fatal("failed to unmarshal path")
}
})
}
func TestGetPath(t *testing.T) {
t.Run("selector", func(t *testing.T) {
var v interface{}
if err := json.Unmarshal([]byte(`{"a":{"b":10,"c":true},"b":"text"}`), &v); err != nil {
t.Fatal(err)
}
path, err := json.CreatePath("$.a.b")
if err != nil {
t.Fatal(err)
}
var b int
if err := path.Get(v, &b); err != nil {
t.Fatal(err)
}
if b != 10 {
t.Fatalf("failed to decode by json.Get")
}
})
t.Run("index", func(t *testing.T) {
var v interface{}
if err := json.Unmarshal([]byte(`{"a":[{"b":10,"c":true},{"b":"text"}]}`), &v); err != nil {
t.Fatal(err)
}
path, err := json.CreatePath("$.a[0].b")
if err != nil {
t.Fatal(err)
}
var b int
if err := path.Get(v, &b); err != nil {
t.Fatal(err)
}
if b != 10 {
t.Fatalf("failed to decode by json.Get")
}
})
t.Run("indexAll", func(t *testing.T) {
var v interface{}
if err := json.Unmarshal([]byte(`{"a":[{"b":1,"c":true},{"b":2},{"b":3}]}`), &v); err != nil {
t.Fatal(err)
}
path, err := json.CreatePath("$.a[*].b")
if err != nil {
t.Fatal(err)
}
var b []int
if err := path.Get(v, &b); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(b, []int{1, 2, 3}) {
t.Fatalf("failed to decode by json.Get")
}
})
t.Run("recursive", func(t *testing.T) {
var v interface{}
if err := json.Unmarshal([]byte(`{"a":[{"b":1,"c":true},{"b":2},{"b":3}],"a2":{"b":4}}`), &v); err != nil {
t.Fatal(err)
}
path, err := json.CreatePath("$..b")
if err != nil {
t.Fatal(err)
}
var b []int
if err := path.Get(v, &b); err != nil {
t.Fatal(err)
}
sort.Ints(b)
if !reflect.DeepEqual(b, []int{1, 2, 3, 4}) {
t.Fatalf("failed to decode by json.Get")
}
})
}

View File

@ -1,47 +0,0 @@
package json
import (
"github.com/goccy/go-json/internal/encoder"
)
type (
// FieldQuery you can dynamically filter the fields in the structure by creating a FieldQuery,
// adding it to context.Context using SetFieldQueryToContext and then passing it to MarshalContext.
// This is a type-safe operation, so it is faster than filtering using map[string]interface{}.
FieldQuery = encoder.FieldQuery
FieldQueryString = encoder.FieldQueryString
)
var (
// FieldQueryFromContext get current FieldQuery from context.Context.
FieldQueryFromContext = encoder.FieldQueryFromContext
// SetFieldQueryToContext set current FieldQuery to context.Context.
SetFieldQueryToContext = encoder.SetFieldQueryToContext
)
// BuildFieldQuery builds FieldQuery by fieldName or sub field query.
// First, specify the field name that you want to keep in structure type.
// If the field you want to keep is a structure type, by creating a sub field query using BuildSubFieldQuery,
// you can select the fields you want to keep in the structure.
// This description can be written recursively.
func BuildFieldQuery(fields ...FieldQueryString) (*FieldQuery, error) {
query, err := Marshal(fields)
if err != nil {
return nil, err
}
return FieldQueryString(query).Build()
}
// BuildSubFieldQuery builds sub field query.
func BuildSubFieldQuery(name string) *SubFieldQuery {
return &SubFieldQuery{name: name}
}
type SubFieldQuery struct {
name string
}
func (q *SubFieldQuery) Fields(fields ...FieldQueryString) FieldQueryString {
query, _ := Marshal(map[string][]FieldQueryString{q.name: fields})
return FieldQueryString(query)
}

View File

@ -1,121 +0,0 @@
package json_test
import (
"context"
"reflect"
"testing"
"github.com/goccy/go-json"
)
type queryTestX struct {
XA int
XB string
XC *queryTestY
XD bool
XE float32
}
type queryTestY struct {
YA int
YB string
YC *queryTestZ
YD bool
YE float32
}
type queryTestZ struct {
ZA string
ZB bool
ZC int
}
func (z *queryTestZ) MarshalJSON(ctx context.Context) ([]byte, error) {
type _queryTestZ queryTestZ
return json.MarshalContext(ctx, (*_queryTestZ)(z))
}
func TestFieldQuery(t *testing.T) {
query, err := json.BuildFieldQuery(
"XA",
"XB",
json.BuildSubFieldQuery("XC").Fields(
"YA",
"YB",
json.BuildSubFieldQuery("YC").Fields(
"ZA",
"ZB",
),
),
)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(query, &json.FieldQuery{
Fields: []*json.FieldQuery{
{
Name: "XA",
},
{
Name: "XB",
},
{
Name: "XC",
Fields: []*json.FieldQuery{
{
Name: "YA",
},
{
Name: "YB",
},
{
Name: "YC",
Fields: []*json.FieldQuery{
{
Name: "ZA",
},
{
Name: "ZB",
},
},
},
},
},
},
}) {
t.Fatal("cannot get query")
}
queryStr, err := query.QueryString()
if err != nil {
t.Fatal(err)
}
if queryStr != `["XA","XB",{"XC":["YA","YB",{"YC":["ZA","ZB"]}]}]` {
t.Fatalf("failed to create query string. %s", queryStr)
}
ctx := json.SetFieldQueryToContext(context.Background(), query)
b, err := json.MarshalContext(ctx, &queryTestX{
XA: 1,
XB: "xb",
XC: &queryTestY{
YA: 2,
YB: "yb",
YC: &queryTestZ{
ZA: "za",
ZB: true,
ZC: 3,
},
YD: true,
YE: 4,
},
XD: true,
XE: 5,
})
if err != nil {
t.Fatal(err)
}
expected := `{"XA":1,"XB":"xb","XC":{"YA":2,"YB":"yb","YC":{"ZA":"za","ZB":true}}}`
got := string(b)
if expected != got {
t.Fatalf("failed to encode with field query: expected %q but got %q", expected, got)
}
}

View File

@ -11,7 +11,7 @@ func TestOpcodeSize(t *testing.T) {
const uintptrSize = 4 << (^uintptr(0) >> 63)
if uintptrSize == 8 {
size := unsafe.Sizeof(encoder.Opcode{})
if size != 120 {
if size != 112 {
t.Fatalf("unexpected opcode size: expected 112bytes but got %dbytes", size)
}
}

View File

@ -7,7 +7,6 @@ package json_test
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"log"
@ -432,16 +431,6 @@ func TestDecodeInStream(t *testing.T) {
}
}
func TestDecodeStreamUseNumber(t *testing.T) {
dec := json.NewDecoder(strings.NewReader(`3.14`))
dec.UseNumber()
v, err := dec.Token()
if err != nil {
t.Errorf("unexpected error: %#v", err)
}
assertEq(t, "json.Number", "json.Number", fmt.Sprintf("%T", v))
}
// Test from golang.org/issue/11893
func TestHTTPDecoding(t *testing.T) {
const raw = `{ "foo": "bar" }`

View File

@ -1,96 +0,0 @@
package json_test
import (
"context"
"fmt"
"log"
"github.com/goccy/go-json"
)
type User struct {
ID int64
Name string
Age int
Address UserAddressResolver
}
type UserAddress struct {
UserID int64
PostCode string
City string
Address1 string
Address2 string
}
type UserRepository struct {
uaRepo *UserAddressRepository
}
func NewUserRepository() *UserRepository {
return &UserRepository{
uaRepo: NewUserAddressRepository(),
}
}
type UserAddressRepository struct{}
func NewUserAddressRepository() *UserAddressRepository {
return &UserAddressRepository{}
}
type UserAddressResolver func(context.Context) (*UserAddress, error)
func (resolver UserAddressResolver) MarshalJSON(ctx context.Context) ([]byte, error) {
address, err := resolver(ctx)
if err != nil {
return nil, err
}
return json.MarshalContext(ctx, address)
}
func (r *UserRepository) FindByID(ctx context.Context, id int64) (*User, error) {
user := &User{ID: id, Name: "Ken", Age: 20}
// resolve relation from User to UserAddress
user.Address = func(ctx context.Context) (*UserAddress, error) {
return r.uaRepo.FindByUserID(ctx, user.ID)
}
return user, nil
}
func (*UserAddressRepository) FindByUserID(ctx context.Context, id int64) (*UserAddress, error) {
return &UserAddress{
UserID: id,
City: "A",
Address1: "foo",
Address2: "bar",
}, nil
}
func Example_fieldQuery() {
ctx := context.Background()
userRepo := NewUserRepository()
user, err := userRepo.FindByID(ctx, 1)
if err != nil {
log.Fatal(err)
}
query, err := json.BuildFieldQuery(
"Name",
"Age",
json.BuildSubFieldQuery("Address").Fields(
"City",
),
)
if err != nil {
log.Fatal(err)
}
ctx = json.SetFieldQueryToContext(ctx, query)
b, err := json.MarshalContext(ctx, user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
// Output:
// {"Name":"Ken","Age":20,"Address":{"City":"A"}}
}