From 8c0513a04ff3870f57bde230507aaa8c6b7e6e86 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Tue, 16 Aug 2022 00:15:57 -0600 Subject: Get borrowed_decode_test.go passing --- compat/json/borrowed_decode_test.go | 5 +-- compat/json/compat.go | 30 +++++++++-------- compat/json/compat_test.go | 7 ++-- decode.go | 66 ++++++++++++++++++++++++------------- decode_scan.go | 30 +++++++++++++---- errors.go | 4 +++ parse.go | 2 +- 7 files changed, 94 insertions(+), 50 deletions(-) diff --git a/compat/json/borrowed_decode_test.go b/compat/json/borrowed_decode_test.go index 4b84718..4b9d0cb 100644 --- a/compat/json/borrowed_decode_test.go +++ b/compat/json/borrowed_decode_test.go @@ -10,7 +10,6 @@ import ( "errors" "fmt" "image" - "io" // MODIFIED "math" "math/big" "net" @@ -19,8 +18,6 @@ import ( "strings" "testing" "time" - - "git.lukeshu.com/go/lowmemjson" // MODIFIED ) type T struct { @@ -1093,7 +1090,7 @@ func equalError(a, b error) bool { func TestUnmarshal(t *testing.T) { for i, tt := range unmarshalTests { - scan := lowmemjson.ReEncoder{Out: io.Discard} // MODIFIED + var scan scanner in := []byte(tt.in) if err := checkValid(in, &scan); err != nil { if !equalError(err, tt.err) { diff --git a/compat/json/compat.go b/compat/json/compat.go index 7145a86..570afc9 100644 --- a/compat/json/compat.go +++ b/compat/json/compat.go @@ -75,31 +75,32 @@ func HTMLEscape(dst *bytes.Buffer, src []byte) { _, _ = formatter.Write(src) } +func reencode(src []byte, formatter *lowmemjson.ReEncoder) error { + _, err := formatter.Write(src) + if se, ok := err.(*lowmemjson.ReEncodeSyntaxError); ok { + err = &SyntaxError{ + msg: se.Err.Error(), + Offset: se.Offset + 1, + } + } + return err +} + func Compact(dst *bytes.Buffer, src []byte) error { - formatter := &lowmemjson.ReEncoder{ + return reencode(src, &lowmemjson.ReEncoder{ Out: dst, Compact: true, BackslashEscape: lowmemjson.EscapePreserve, - } - _, err := formatter.Write(src) - return err + }) } func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { - formatter := &lowmemjson.ReEncoder{ + return reencode(src, &lowmemjson.ReEncoder{ Out: dst, Indent: indent, Prefix: prefix, BackslashEscape: lowmemjson.EscapePreserve, - } - _, err := formatter.Write(src) - if se, ok := err.(*lowmemjson.ReEncodeSyntaxError); ok { - err = &SyntaxError{ - msg: se.Err.Error(), - Offset: se.Offset + 1, - } - } - return err + }) } func Valid(data []byte) bool { @@ -153,6 +154,7 @@ func (dec *Decoder) Decode(ptr any) error { } } else if _, isArgErr := terr.Err.(*lowmemjson.DecodeArgumentError); terr.Err != nil && !isArgErr && + !errors.Is(terr.Err, lowmemjson.ErrDecodeNonEmptyInterface) && !errors.Is(terr.Err, strconv.ErrSyntax) && !errors.Is(terr.Err, strconv.ErrRange) { err = terr.Err diff --git a/compat/json/compat_test.go b/compat/json/compat_test.go index 5a34d22..2cb1e87 100644 --- a/compat/json/compat_test.go +++ b/compat/json/compat_test.go @@ -6,6 +6,7 @@ package json import ( "bytes" + "io" "git.lukeshu.com/go/lowmemjson" "git.lukeshu.com/go/lowmemjson/internal" @@ -13,9 +14,11 @@ import ( var parseTag = internal.ParseTag +type scanner = lowmemjson.ReEncoder + func checkValid(in []byte, scan *lowmemjson.ReEncoder) error { - _, err := scan.Write(in) - return err + scan.Out = io.Discard + return reencode(in, scan) } const ( diff --git a/decode.go b/decode.go index b1cc90f..f630541 100644 --- a/decode.go +++ b/decode.go @@ -91,20 +91,25 @@ func (dec *Decoder) stackStr() string { } func (dec *Decoder) stackParent() string { - if len(dec.stack) > 0 && dec.stack[len(dec.stack)-1].par.Kind() == reflect.Struct { - return dec.stack[len(dec.stack)-1].par.Name() + last := len(dec.stack) - 1 + if last > 0 && dec.stack[last].par.Kind() != reflect.Struct && dec.stack[last-1].par.Kind() == reflect.Struct { + last-- + } + if last >= 0 && dec.stack[last].par.Kind() == reflect.Struct { + return dec.stack[last].par.Name() } return "" } func (dec *Decoder) stackName() string { - var fields []string - for i := len(dec.stack) - 1; i >= 0 && dec.stack[i].par.Kind() == reflect.Struct; i-- { - fields = append(fields, dec.stack[i].idx.(string)) + if dec.stackParent() == "" { + return "" } - for i := 0; i < len(fields)/2; i++ { - j := (len(fields) - 1) - i - fields[i], fields[j] = fields[j], fields[i] + var fields []string + for _, elem := range dec.stack { + if elem.par.Kind() == reflect.Struct { + fields = append(fields, elem.idx.(string)) + } } return strings.Join(fields, ".") } @@ -298,10 +303,10 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) { return } var buf bytes.Buffer - dec.decodeString(typ, &buf) + dec.decodeString(reflect.PointerTo(typ), &buf) obj := val.Addr().Interface().(encoding.TextUnmarshaler) if err := obj.UnmarshalText(buf.Bytes()); err != nil { - dec.panicType("string", typ, err) + dec.panicType("string", reflect.PointerTo(typ), err) } default: switch kind := typ.Kind(); kind { @@ -354,7 +359,13 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) { } var buf strings.Builder if typ == numberType { - dec.scanNumber(typ, &buf) + t := dec.peekRuneType() + dec.scan(&buf) + if !t.IsNumber() { + dec.panicType(t.jsonType(), typ, + fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", + buf.String())) + } val.SetString(buf.String()) } else { dec.decodeString(typ, &buf) @@ -362,25 +373,32 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) { } case reflect.Interface: if typ.NumMethod() > 0 { - dec.panicType("", typ, fmt.Errorf("cannot decode in to non-empty interface")) + dec.panicType(dec.peekRuneType().jsonType(), typ, ErrDecodeNonEmptyInterface) } // If the interface stores a pointer, try to use the type information of the pointer. if !val.IsNil() && val.Elem().Kind() == reflect.Pointer { // Follow a chain of pointers until we find the first settable // pointer (if any). ptr := val.Elem() - for ptr.Kind() == reflect.Pointer { - if ptr.CanSet() { - break - } - if ptr.IsNil() { + for { + if ptr.CanSet() || ptr.IsNil() || ptr.Elem().Kind() != reflect.Pointer { + // We've reached the end of the line, good or bad. break } ptr = ptr.Elem() } - // We only neet to be able to set the pointer itself if we're - // decoding "null", so add a "||" clause. - if ptr.Kind() == reflect.Pointer && (ptr.CanSet() || dec.peekRuneType() != RuneTypeNullN) { + // ptr.Elem() != val + // + // Avoid the loop of an interface storing a pointer to its own + // address. We only need to worry about this at the leaf (and not + // in the loop) because the only way it's possible is if there's + // an interface in there, which'd break from the loop on its own. + // + // ptr.CanSet() || dec.peekRuneType() != RuneTypeNullN + // + // We only need the pointer itself to be settable if we're + // decoding null. + if ptr.Elem() != val && (ptr.CanSet() || dec.peekRuneType() != RuneTypeNullN) { dec.decode(ptr, false) break } @@ -406,8 +424,8 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) { defer dec.stackPop() idx, ok := index.byName[name] if !ok { - for oname, oidx := range index.byName { - if strings.EqualFold(name, oname) { + for oidx := range index.byPos { + if strings.EqualFold(name, index.byPos[oidx].Name) { idx = oidx ok = true break @@ -426,7 +444,9 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) { for _, idx := range field.Path { if fVal.Kind() == reflect.Pointer { if fVal.IsNil() && !fVal.CanSet() { // https://golang.org/issue/21357 - dec.panicType("", fVal.Type().Elem(), fmt.Errorf("cannot set embedded pointer to unexported type")) + dec.panicType("", fVal.Type().Elem(), + fmt.Errorf("json: cannot set embedded pointer to unexported struct: %v", + fVal.Type().Elem())) } if dec.peekRuneType() != RuneTypeNullN { if fVal.IsNil() { diff --git a/decode_scan.go b/decode_scan.go index 3c41df6..fee9ec6 100644 --- a/decode_scan.go +++ b/decode_scan.go @@ -34,20 +34,37 @@ type runeTypeScannerImpl struct { parser Parser offset int64 - repeat bool - stuck bool - rRune rune - rSize int - rType RuneType - rErr error + repeat bool + stuck bool + rRune rune + rRuneOK bool + rSize int + rType RuneType + rErr error } var _ runeTypeScanner = (*runeTypeScannerImpl)(nil) func (sc *runeTypeScannerImpl) Reset() { sc.parser.Reset() + unread := sc.stuck && sc.rType == RuneTypeEOF && sc.rRuneOK sc.stuck = false sc.repeat = false + if unread { + // re-figure the rType and rErr + var err error + sc.rType, err = sc.parser.HandleRune(sc.rRune) + if err != nil { + sc.rErr = &DecodeSyntaxError{ + Offset: sc.offset, + Err: err, + } + } else { + sc.rErr = nil + } + // tell it to use that rType and rErr + sc.UnreadRune() + } } func (sc *runeTypeScannerImpl) ReadRuneType() (rune, int, RuneType, error) { @@ -62,6 +79,7 @@ func (sc *runeTypeScannerImpl) ReadRuneType() (rune, int, RuneType, error) { var err error sc.rRune, sc.rSize, err = sc.inner.ReadRune() sc.offset += int64(sc.rSize) + sc.rRuneOK = err == nil switch err { case nil: sc.rType, err = sc.parser.HandleRune(sc.rRune) diff --git a/errors.go b/errors.go index 2d8820e..e71d79a 100644 --- a/errors.go +++ b/errors.go @@ -76,6 +76,10 @@ func (e *DecodeTypeError) Error() string { func (e *DecodeTypeError) Unwrap() error { return e.Err } +var ( + ErrDecodeNonEmptyInterface = errors.New("cannot decode in to non-empty interface") +) + // high-level decode errors //////////////////////////////////////////////////////////////////////// // A *DecodeArgumentError is returned from Decode if the argument is diff --git a/parse.go b/parse.go index fb72280..77d1b08 100644 --- a/parse.go +++ b/parse.go @@ -658,7 +658,7 @@ func (par *Parser) HandleRune(c rune) (RuneType, error) { func (par *Parser) expectRune(c, exp rune, typ RuneType, context string, pop bool) (RuneType, error) { if c != exp { - return RuneTypeError, fmt.Errorf("%s: unexpected character: %q", context, c) + return RuneTypeError, fmt.Errorf("invalid character %q in literal %s (expecting %q)", c, context, exp) } if pop { par.popState() -- cgit v1.1-4-g5e80