From 34396640d516b550f7535bd44511aef62df11137 Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Mon, 19 Apr 2021 19:55:12 +0900 Subject: [PATCH] Fix Indent ( validate the input buffer ) --- internal/encoder/encoder.go | 20 +-- internal/encoder/indent.go | 270 ++++++++++++++++++++++-------------- json_test.go | 2 +- 3 files changed, 180 insertions(+), 112 deletions(-) diff --git a/internal/encoder/encoder.go b/internal/encoder/encoder.go index 8815d62..44adee0 100644 --- a/internal/encoder/encoder.go +++ b/internal/encoder/encoder.go @@ -464,20 +464,20 @@ func AppendMarshalJSONIndent(ctx *RuntimeContext, code *Opcode, b []byte, v inte if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } - var compactBuf bytes.Buffer - if err := Compact(&compactBuf, bb, escape); err != nil { - return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} - } - var indentBuf bytes.Buffer - if err := Indent( - &indentBuf, - compactBuf.Bytes(), + marshalBuf := ctx.MarshalBuf[:0] + marshalBuf = append(append(marshalBuf, bb...), nul) + indentedBuf, err := doIndent( + b, + marshalBuf, string(ctx.Prefix)+strings.Repeat(string(ctx.IndentStr), ctx.BaseIndent+indent), string(ctx.IndentStr), - ); err != nil { + escape, + ) + if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } - return append(b, indentBuf.Bytes()...), nil + ctx.MarshalBuf = marshalBuf + return indentedBuf, nil } func AppendMarshalText(code *Opcode, b []byte, v interface{}, escape bool) ([]byte, error) { diff --git a/internal/encoder/indent.go b/internal/encoder/indent.go index fba4519..7eed1d8 100644 --- a/internal/encoder/indent.go +++ b/internal/encoder/indent.go @@ -2,111 +2,179 @@ package encoder import ( "bytes" + "fmt" "github.com/goccy/go-json/internal/errors" ) -func Indent(dst *bytes.Buffer, src []byte, prefix, indentStr string) error { - length := int64(len(src)) - indentNum := 0 - indentBytes := []byte(indentStr) - for cursor := int64(0); cursor < length; cursor++ { - c := src[cursor] - switch c { - case ' ', '\t', '\n', '\r': - continue - case '"': - if err := dst.WriteByte(c); err != nil { - return err - } - for { - cursor++ - if err := dst.WriteByte(src[cursor]); err != nil { - return err - } - switch src[cursor] { - case '\\': - cursor++ - if err := dst.WriteByte(src[cursor]); err != nil { - return err - } - case '"': - goto LOOP_END - case '\000': - return errors.ErrUnexpectedEndOfJSON("string", length) - } - } - case '{': - if cursor+1 < length && src[cursor+1] == '}' { - if _, err := dst.Write([]byte{'{', '}'}); err != nil { - return err - } - cursor++ - } else { - indentNum++ - b := []byte{c, '\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - if _, err := dst.Write(b); err != nil { - return err - } - } - case '}': - indentNum-- - if indentNum < 0 { - return errors.ErrInvalidCharacter('}', "}", cursor) - } - b := []byte{'\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - b = append(b, c) - if _, err := dst.Write(b); err != nil { - return err - } - case '[': - if cursor+1 < length && src[cursor+1] == ']' { - if _, err := dst.Write([]byte{'[', ']'}); err != nil { - return err - } - cursor++ - } else { - indentNum++ - b := []byte{c, '\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - if _, err := dst.Write(b); err != nil { - return err - } - } - case ']': - indentNum-- - if indentNum < 0 { - return errors.ErrInvalidCharacter(']', "]", cursor) - } - b := []byte{'\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - b = append(b, c) - if _, err := dst.Write(b); err != nil { - return err - } - case ':': - if _, err := dst.Write([]byte{':', ' '}); err != nil { - return err - } - case ',': - b := []byte{',', '\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - if _, err := dst.Write(b); err != nil { - return err - } - default: - if err := dst.WriteByte(c); err != nil { - return err - } - } - LOOP_END: +func Indent(buf *bytes.Buffer, src []byte, prefix, indentStr string) error { + if len(src) == 0 { + return errors.ErrUnexpectedEndOfJSON("", 0) + } + buf.Grow(len(src)) + dst := buf.Bytes() + newSrc := make([]byte, len(src)+1) // append nul byte to the end + copy(newSrc, src) + dst, err := doIndent(dst, newSrc, prefix, indentStr, false) + if err != nil { + return err + } + if _, err := buf.Write(dst); err != nil { + return err } return nil } + +func doIndent(dst, src []byte, prefix, indentStr string, escape bool) ([]byte, error) { + buf, _, err := indentValue(dst, src, 0, 0, []byte(prefix), []byte(indentStr), escape) + if err != nil { + return nil, err + } + return buf, nil +} + +func indentValue( + dst []byte, + src []byte, + indentNum int, + cursor int64, + prefix []byte, + indentBytes []byte, + escape bool) ([]byte, int64, error) { + for { + switch src[cursor] { + case ' ', '\t', '\n', '\r': + cursor++ + continue + case '{': + return indentObject(dst, src, indentNum, cursor, prefix, indentBytes, escape) + case '}': + return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor) + case '[': + return indentArray(dst, src, indentNum, cursor, prefix, indentBytes, escape) + case ']': + return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor) + case '"': + return compactString(dst, src, cursor, escape) + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return compactNumber(dst, src, cursor) + case 't': + return compactTrue(dst, src, cursor) + case 'f': + return compactFalse(dst, src, cursor) + case 'n': + return compactNull(dst, src, cursor) + default: + return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor) + } + } +} + +func indentObject( + dst []byte, + src []byte, + indentNum int, + cursor int64, + prefix []byte, + indentBytes []byte, + escape bool) ([]byte, int64, error) { + switch src[cursor] { + case 'n': + return compactNull(dst, src, cursor) + case '{': + dst = append(dst, '{') + default: + return nil, 0, errors.ErrExpected("expected { character for object value", cursor) + } + cursor = skipWhiteSpace(src, cursor+1) + if src[cursor] == '}' { + dst = append(dst, '}') + return dst, cursor + 1, nil + } + indentNum++ + var err error + for { + dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum)...) + cursor = skipWhiteSpace(src, cursor) + dst, cursor, err = compactString(dst, src, cursor, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + if src[cursor] != ':' { + return nil, 0, errors.ErrSyntax( + fmt.Sprintf("invalid character '%c' after object key", src[cursor]), + cursor+1, + ) + } + dst = append(dst, ':', ' ') + dst, cursor, err = indentValue(dst, src, indentNum, cursor+1, prefix, indentBytes, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + switch src[cursor] { + case '}': + dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum-1)...) + dst = append(dst, '}') + cursor++ + return dst, cursor, nil + case ',': + dst = append(dst, ',') + default: + return nil, 0, errors.ErrSyntax( + fmt.Sprintf("invalid character '%c' after object key:value pair", src[cursor]), + cursor+1, + ) + } + cursor++ + } +} + +func indentArray( + dst []byte, + src []byte, + indentNum int, + cursor int64, + prefix []byte, + indentBytes []byte, + escape bool) ([]byte, int64, error) { + switch src[cursor] { + case 'n': + return compactNull(dst, src, cursor) + case '[': + dst = append(dst, '[') + default: + return nil, 0, errors.ErrExpected("expected [ character for array value", cursor) + } + cursor = skipWhiteSpace(src, cursor+1) + if src[cursor] == ']' { + dst = append(dst, ']') + return dst, cursor + 1, nil + } + indentNum++ + var err error + for { + dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum)...) + dst, cursor, err = indentValue(dst, src, indentNum, cursor, prefix, indentBytes, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + switch src[cursor] { + case ']': + dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum-1)...) + dst = append(dst, ']') + cursor++ + return dst, cursor, nil + case ',': + dst = append(dst, ',') + default: + return nil, 0, errors.ErrSyntax( + fmt.Sprintf("invalid character '%c' after array value", src[cursor]), + cursor+1, + ) + } + cursor++ + } +} diff --git a/json_test.go b/json_test.go index cc92955..4abac57 100644 --- a/json_test.go +++ b/json_test.go @@ -197,7 +197,7 @@ func TestIndentErrors(t *testing.T) { buf := bytes.NewBuffer(slice) if err := json.Indent(buf, []uint8(tt.in), "", ""); err != nil { if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: Indent: %#v", i, err) + t.Errorf("#%d: Indent: expected %#v but got %#v", i, tt.err, err) continue } }