From b20904f574e4cad2853c551e41ed189143bdec97 Mon Sep 17 00:00:00 2001 From: chenzhuoyu Date: Mon, 16 Aug 2021 18:57:04 +0800 Subject: [PATCH] fix: check EOF after unmarshal --- decoder/decoder.go | 16 ++++++++++++++++ issue67_test.go | 32 ++++++++++++++++++++++++++++++++ sonic.go | 39 +++++++++++++++++++++++++++++++++++---- 3 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 issue67_test.go diff --git a/decoder/decoder.go b/decoder/decoder.go index 9dce6db..8544327 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -30,20 +30,25 @@ const ( _F_disable_unknown ) +// Decoder is the decoder context object type Decoder struct { i int f uint64 s string } +// NewDecoder creates a new decoder instance. func NewDecoder(s string) *Decoder { return &Decoder{s: s} } +// Pos returns the current decoding position. func (self *Decoder) Pos() int { return self.i } +// Decode parses the JSON-encoded data from current position and stores the result +// in the value pointed to by val. func (self *Decoder) Decode(val interface{}) error { vv := rt.UnpackEface(val) vp := vv.Value @@ -68,24 +73,35 @@ func (self *Decoder) Decode(val interface{}) error { return err } +// UseInt64 causes the Decoder to unmarshal an integer into an interface{} as an +// int64 instead of as a float64. func (self *Decoder) UseInt64() { self.f |= 1 << _F_use_int64 self.f &^= 1 << _F_use_number } +// UseNumber causes the Decoder to unmarshal a number into an interface{} as a +// json.Number instead of as a float64. func (self *Decoder) UseNumber() { self.f &^= 1 << _F_use_int64 self.f |= 1 << _F_use_number } +// UseUnicodeErrors causes the Decoder to return an error when encounter invalid +// UTF-8 escape sequences. func (self *Decoder) UseUnicodeErrors() { self.f |= 1 << _F_disable_urc } +// DisallowUnknownFields causes the Decoder to return an error when the destination +// is a struct and the input contains object keys which do not match any +// non-ignored, exported fields in the destination. func (self *Decoder) DisallowUnknownFields() { self.f |= 1 << _F_disable_unknown } +// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in +// order to reduce the first-hit latency. func Pretouch(vt reflect.Type) (err error) { _, err = findOrCompile(rt.UnpackType(vt)) return diff --git a/issue67_test.go b/issue67_test.go new file mode 100644 index 0000000..0a63283 --- /dev/null +++ b/issue67_test.go @@ -0,0 +1,32 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sonic + +import ( + `encoding/json` + `testing` +) + +func TestIssue67_JunkAfterJSON(t *testing.T) { + data := `1e2e3` + var stdobj, sonicobj interface{} + stderr := json.Unmarshal([]byte(data), &stdobj) + sonicerr := Unmarshal([]byte(data), &sonicobj) + if (stderr == nil) != (sonicerr == nil) { + t.Fatalf("exp err: \n%#v, \ngot err: \n%#v\n", stderr, sonicerr) + } +} diff --git a/sonic.go b/sonic.go index 06f4251..61bb473 100644 --- a/sonic.go +++ b/sonic.go @@ -23,6 +23,11 @@ import ( `github.com/bytedance/sonic/ast` `github.com/bytedance/sonic/decoder` `github.com/bytedance/sonic/encoder` + `github.com/bytedance/sonic/internal/native/types` +) + +const ( + _SpaceMask = (1 << ' ') | (1 << '\t') | (1 << '\r') | (1 << '\n') ) // Marshal returns the JSON encoding of v. @@ -38,7 +43,33 @@ func Unmarshal(buf []byte, val interface{}) error { // UnmarshalString is like Unmarshal, except buf is a string. func UnmarshalString(buf string, val interface{}) error { - return decoder.NewDecoder(buf).Decode(val) + dec := decoder.NewDecoder(buf) + err := dec.Decode(val) + pos := dec.Pos() + + /* check for errors */ + if err != nil { + return err + } + + /* skip all the trailing spaces */ + if pos != len(buf) { + for pos < len(buf) && (_SpaceMask & (1 << buf[pos])) != 0 { + pos++ + } + } + + /* then it must be at EOF */ + if pos == len(buf) { + return nil + } + + /* junk after JSON value */ + return decoder.SyntaxError { + Src : buf, + Pos : dec.Pos(), + Code : types.ERR_INVALID_CHAR, + } } // Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in @@ -54,17 +85,17 @@ func Pretouch(vt reflect.Type) error { } // Get searches the given path json, -// and returns its representing ast.Node +// and returns its representing ast.Node. // // Each path arg must be integer or string: -// - Integer means searching current node as array, +// - Integer means searching current node as array // - String means searching current node as object func Get(src []byte, path ...interface{}) (ast.Node, error) { return GetFromString(string(src), path...) } // GetFromString is same with Get except src is string, -// which can reduce unnecessary memory copy +// which can reduce unnecessary memory copy. func GetFromString(src string, path ...interface{}) (ast.Node, error) { return ast.NewSearcher(src).GetByPath(path...) } \ No newline at end of file