Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(aarch64): invalid skip number #712

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 9 additions & 68 deletions ast/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
package ast

import (
`encoding/base64`
`runtime`
`strconv`
`unsafe`

`github.com/bytedance/sonic/internal/native/types`
`github.com/bytedance/sonic/internal/rt`
"encoding/base64"
"runtime"
"strconv"
"unsafe"

"github.com/bytedance/sonic/internal/native/types"
"github.com/bytedance/sonic/internal/rt"
"github.com/bytedance/sonic/internal/utils"
)

// Hack: this is used for both checking space and cause firendly compile errors in 32-bit arch.
Expand Down Expand Up @@ -290,67 +291,7 @@ func decodeValue(src string, pos int, skipnum bool) (ret int, v types.JsonState)

//go:nocheckptr
func skipNumber(src string, pos int) (ret int) {
sp := uintptr(rt.IndexChar(src, pos))
se := uintptr(rt.IndexChar(src, len(src)))
if uintptr(sp) >= se {
return -int(types.ERR_EOF)
}

if c := *(*byte)(unsafe.Pointer(sp)); c == '-' {
sp += 1
}
ss := sp

var pointer bool
var exponent bool
var lastIsDigit bool
var nextNeedDigit = true

for ; sp < se; sp += uintptr(1) {
c := *(*byte)(unsafe.Pointer(sp))
if isDigit(c) {
lastIsDigit = true
nextNeedDigit = false
continue
} else if nextNeedDigit {
return -int(types.ERR_INVALID_CHAR)
} else if c == '.' {
if !lastIsDigit || pointer || exponent || sp == ss {
return -int(types.ERR_INVALID_CHAR)
}
pointer = true
lastIsDigit = false
nextNeedDigit = true
continue
} else if c == 'e' || c == 'E' {
if !lastIsDigit || exponent {
return -int(types.ERR_INVALID_CHAR)
}
if sp == se-1 {
return -int(types.ERR_EOF)
}
exponent = true
lastIsDigit = false
nextNeedDigit = false
continue
} else if c == '-' || c == '+' {
if prev := *(*byte)(unsafe.Pointer(sp - 1)); prev != 'e' && prev != 'E' {
return -int(types.ERR_INVALID_CHAR)
}
lastIsDigit = false
nextNeedDigit = true
continue
} else {
break
}
}

if nextNeedDigit {
return -int(types.ERR_EOF)
}

runtime.KeepAlive(src)
return int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
return utils.SkipNumber(src, pos)
}

//go:nocheckptr
Expand Down
21 changes: 21 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2967,3 +2967,24 @@ func BenchmarkDecoderRawMessage(b *testing.B) {
})
})
}


func TestJsonNumber(t *testing.T) {
api := Config {
UseNumber: true,
}.Froze()


type Foo struct {
A json.Number `json:"a"`
B json.Number `json:"b"`
C json.Number `json:"c"`
}

data := []byte(`{"a": 1 , "b": "123", "c": "0.4e+56"}`)
var foo1, foo2 Foo
serr := api.Unmarshal(data, &foo1)
jerr := json.Unmarshal(data, &foo2)
assert.Equal(t, jerr, serr)
assert.Equal(t, foo2, foo1)
}
32 changes: 18 additions & 14 deletions internal/decoder/optdec/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,42 @@ import (
"strconv"

"github.com/bytedance/sonic/internal/native"
"github.com/bytedance/sonic/internal/utils"
"github.com/bytedance/sonic/internal/native/types"
)


func SkipNumberFast(json string, start int) (int, error) {
// find the number ending, we pasred in sonic-cpp, it alway valid
// find the number ending, we pasred in native, it alway valid
pos := start
for pos < len(json) && json[pos] != ']' && json[pos] != '}' && json[pos] != ',' {
if json[pos] >= '0' && json[pos] <= '9' || json[pos] == '.' || json[pos] == '-' || json[pos] == '+' || json[pos] == 'e' || json[pos] == 'E' {
pos += 1
} else {
return pos, error_syntax(pos, json, "invalid number")
break
}
}
return pos, nil
}

func ValidNumberFast(json string) error {
// find the number ending, we pasred in sonic-cpp, it alway valid
pos := 0
for pos < len(json) && json[pos] != ']' && json[pos] != '}' && json[pos] != ',' {
if json[pos] >= '0' && json[pos] <= '9' || json[pos] == '.' || json[pos] == '-' || json[pos] == '+' || json[pos] == 'e' || json[pos] == 'E' {
pos += 1
} else {
return error_syntax(pos, json, "invalid number")
}

func isSpace(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsSpace不是一直都用更快的Go实现?

func isSpace(c byte) bool {
要不也统一下到一个地方?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ast 那里做了编译错误提示的 hack,这里不太好统一。而且这个函数太小了

}

// pos is the start index of the raw
func ValidNumberFast(raw string) bool {
ret := utils.SkipNumber(raw, 0)
if ret < 0 {
return false
}

if pos == 0 {
return error_syntax(pos, json, "invalid number")
// check trainling chars
for ret < len(raw) {
return false
}
return nil

return true
}

func SkipOneFast2(json string, pos *int) (int, error) {
Expand Down
7 changes: 3 additions & 4 deletions internal/decoder/optdec/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,12 +509,11 @@ func (val Node) AsNumber(ctx *Context) (json.Number, bool) {
// parse JSON string as number
if val.IsStr() {
s, _ := val.AsStr(ctx)
err := ValidNumberFast(s)
if err != nil {
if !ValidNumberFast(s) {
return "", false
} else {
return json.Number(s), true
}

return json.Number(s), true
}

return val.NonstrAsNumber(ctx)
Expand Down
79 changes: 79 additions & 0 deletions internal/utils/skip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

package utils

import (
`runtime`
`unsafe`

`github.com/bytedance/sonic/internal/native/types`
`github.com/bytedance/sonic/internal/rt`
)

func isDigit(c byte) bool {
return c >= '0' && c <= '9'
}

//go:nocheckptr
func SkipNumber(src string, pos int) (ret int) {
sp := uintptr(rt.IndexChar(src, pos))
se := uintptr(rt.IndexChar(src, len(src)))
if uintptr(sp) >= se {
return -int(types.ERR_EOF)
}

if c := *(*byte)(unsafe.Pointer(sp)); c == '-' {
sp += 1
}
ss := sp

var pointer bool
var exponent bool
var lastIsDigit bool
var nextNeedDigit = true

for ; sp < se; sp += uintptr(1) {
c := *(*byte)(unsafe.Pointer(sp))
if isDigit(c) {
lastIsDigit = true
nextNeedDigit = false
continue
} else if nextNeedDigit {
return -int(types.ERR_INVALID_CHAR)
} else if c == '.' {
if !lastIsDigit || pointer || exponent || sp == ss {
return -int(types.ERR_INVALID_CHAR)
}
pointer = true
lastIsDigit = false
nextNeedDigit = true
continue
} else if c == 'e' || c == 'E' {
if !lastIsDigit || exponent {
return -int(types.ERR_INVALID_CHAR)
}
if sp == se-1 {
return -int(types.ERR_EOF)
}
exponent = true
lastIsDigit = false
nextNeedDigit = false
continue
} else if c == '-' || c == '+' {
if prev := *(*byte)(unsafe.Pointer(sp - 1)); prev != 'e' && prev != 'E' {
return -int(types.ERR_INVALID_CHAR)
}
lastIsDigit = false
nextNeedDigit = true
continue
} else {
break
}
}

if nextNeedDigit {
return -int(types.ERR_EOF)
}

runtime.KeepAlive(src)
return int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
}
Loading