From 95bb00d1088767ed14e3bd1a5f533a690d619a5f Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Sun, 13 Sep 2020 02:12:02 +0000 Subject: encoding/json: implement Is on all errors Allows users to check: errors.Is(err, &UnmarshalTypeError{}) errors.Is(err, &UnmarshalFieldError{}) errors.Is(err, &InvalidUnmarshalError{}) errors.Is(err, &UnsupportedValueError{}) errors.Is(err, &MarshalerError{}) which is the recommended way of checking for kinds of errors. SyntaxError.Is was implemented in CL 253037. As and Unwrap relevant methods will be added in future CLs. Change-Id: I1f8a503b8fdc0f3afdfe9669a91f3af8d960e028 GitHub-Last-Rev: 930cda5384c987a0b31f277ba3b4ab690ea74ac3 GitHub-Pull-Request: golang/go#41360 Reviewed-on: https://go-review.googlesource.com/c/go/+/254537 Run-TryBot: Emmanuel Odeke TryBot-Result: Gobot Gobot Reviewed-by: Emmanuel Odeke Trust: Emmanuel Odeke --- src/encoding/json/encode.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/encoding/json/encode.go') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 578d551102..8e6b342b59 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -245,6 +245,12 @@ func (e *UnsupportedValueError) Error() string { return "json: unsupported value: " + e.Str } +// Is returns true if target is a UnsupportedValueError. +func (e *UnsupportedValueError) Is(target error) bool { + _, ok := target.(*UnsupportedValueError) + return ok +} + // Before Go 1.2, an InvalidUTF8Error was returned by Marshal when // attempting to encode a string value with invalid UTF-8 sequences. // As of Go 1.2, Marshal instead coerces the string to valid UTF-8 by @@ -279,6 +285,12 @@ func (e *MarshalerError) Error() string { // Unwrap returns the underlying error. func (e *MarshalerError) Unwrap() error { return e.Err } +// Is returns true if target is a MarshalerError. +func (e *MarshalerError) Is(target error) bool { + _, ok := target.(*MarshalerError) + return ok +} + var hex = "0123456789abcdef" // An encodeState encodes JSON into a bytes.Buffer. -- cgit v1.3 From 114719e16e9681bd1001326598ededa719c17944 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Mon, 14 Sep 2020 21:00:52 +0000 Subject: Revert "encoding/json: implement Is on all errors" This reverts CL 254537. Reason for revert: Reason for revert: The recommended way to check for a type of error is errors.As. API changes should also start with a proposal. Change-Id: I07c37428575e99c80b17525833a61831d10963bb Reviewed-on: https://go-review.googlesource.com/c/go/+/254857 Trust: Damien Neil Reviewed-by: Emmanuel Odeke --- src/encoding/json/decode.go | 18 ------------------ src/encoding/json/decode_test.go | 31 ------------------------------- src/encoding/json/encode.go | 12 ------------ src/encoding/json/encode_test.go | 24 +----------------------- 4 files changed, 1 insertion(+), 84 deletions(-) (limited to 'src/encoding/json/encode.go') diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 1b006ffb17..86d8a69db7 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -136,12 +136,6 @@ func (e *UnmarshalTypeError) Error() string { return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() } -// Is returns true if target is a UnmarshalTypeError. -func (e *UnmarshalTypeError) Is(target error) bool { - _, ok := target.(*UnmarshalTypeError) - return ok -} - // An UnmarshalFieldError describes a JSON object key that // led to an unexported (and therefore unwritable) struct field. // @@ -156,24 +150,12 @@ func (e *UnmarshalFieldError) Error() string { return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() } -// Is returns true if target is a UnmarshalFieldError. -func (e *UnmarshalFieldError) Is(target error) bool { - _, ok := target.(*UnmarshalFieldError) - return ok -} - // An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. // (The argument to Unmarshal must be a non-nil pointer.) type InvalidUnmarshalError struct { Type reflect.Type } -// Is returns true if target is a InvalidUnmarshalError. -func (e *InvalidUnmarshalError) Is(target error) bool { - _, ok := target.(*InvalidUnmarshalError) - return ok -} - func (e *InvalidUnmarshalError) Error() string { if e.Type == nil { return "json: Unmarshal(nil)" diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index b707dcfa99..219e845c7b 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -2572,34 +2572,3 @@ func TestUnmarshalMaxDepth(t *testing.T) { } } } - -func TestInvalidUnmarshalErrorIs(t *testing.T) { - err := fmt.Errorf("apackage: %w: failed to parse struct", &InvalidUnmarshalError{reflect.TypeOf("a")}) - if !errors.Is(err, &InvalidUnmarshalError{}) { - t.Fatalf("%v should be unwrapped to a InvalidUnmarshalError", err) - } -} - -func TestUnmarshalFieldErrorIs(t *testing.T) { - err := fmt.Errorf("apackage: %w: failed to parse struct", &UnmarshalFieldError{ - Key: "foo", - Type: reflect.TypeOf("a"), - Field: reflect.StructField{Name: "b"}, - }) - if !errors.Is(err, &UnmarshalFieldError{}) { - t.Fatalf("%v should be unwrapped to a UnmarshalFieldError", err) - } -} - -func TestUnmarshalTypeErrorIs(t *testing.T) { - err := fmt.Errorf("apackage: %w: failed to parse struct", &UnmarshalTypeError{ - Value: "foo", - Type: reflect.TypeOf("a"), - Offset: 1, - Struct: "Foo", - Field: "Bar", - }) - if !errors.Is(err, &UnmarshalTypeError{}) { - t.Fatalf("%v should be unwrapped to a UnmarshalTypeError", err) - } -} diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 8e6b342b59..578d551102 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -245,12 +245,6 @@ func (e *UnsupportedValueError) Error() string { return "json: unsupported value: " + e.Str } -// Is returns true if target is a UnsupportedValueError. -func (e *UnsupportedValueError) Is(target error) bool { - _, ok := target.(*UnsupportedValueError) - return ok -} - // Before Go 1.2, an InvalidUTF8Error was returned by Marshal when // attempting to encode a string value with invalid UTF-8 sequences. // As of Go 1.2, Marshal instead coerces the string to valid UTF-8 by @@ -285,12 +279,6 @@ func (e *MarshalerError) Error() string { // Unwrap returns the underlying error. func (e *MarshalerError) Unwrap() error { return e.Err } -// Is returns true if target is a MarshalerError. -func (e *MarshalerError) Is(target error) bool { - _, ok := target.(*MarshalerError) - return ok -} - var hex = "0123456789abcdef" // An encodeState encodes JSON into a bytes.Buffer. diff --git a/src/encoding/json/encode_test.go b/src/encoding/json/encode_test.go index 90826a7f47..7290eca06f 100644 --- a/src/encoding/json/encode_test.go +++ b/src/encoding/json/encode_test.go @@ -7,7 +7,6 @@ package json import ( "bytes" "encoding" - "errors" "fmt" "log" "math" @@ -212,7 +211,7 @@ var unsupportedValues = []interface{}{ func TestUnsupportedValues(t *testing.T) { for _, v := range unsupportedValues { if _, err := Marshal(v); err != nil { - if !errors.Is(err, &UnsupportedValueError{}) { + if _, ok := err.(*UnsupportedValueError); !ok { t.Errorf("for %v, got %T want UnsupportedValueError", v, err) } } else { @@ -1156,24 +1155,3 @@ func TestMarshalerError(t *testing.T) { } } } - -func TestMarshalerErrorIs(t *testing.T) { - err := fmt.Errorf("apackage: %w: failed to parse struct", &MarshalerError{ - reflect.TypeOf("a"), - fmt.Errorf("something"), - "TestMarshalerErrorIs", - }) - if !errors.Is(err, &MarshalerError{}) { - t.Fatalf("%v should be unwrapped to a MarshalerError", err) - } -} - -func TestUnsupportedValueErrorIs(t *testing.T) { - err := fmt.Errorf("apackage: %w: failed to parse struct", &UnsupportedValueError{ - Value: reflect.Value{}, - Str: "Foo", - }) - if !errors.Is(err, &UnsupportedValueError{}) { - t.Fatalf("%v should be unwrapped to a UnsupportedValueError", err) - } -} -- cgit v1.3 From 25a33daa2b7e7bda773705215113450923ae4815 Mon Sep 17 00:00:00 2001 From: Sean Liao Date: Thu, 21 May 2020 17:52:33 +0200 Subject: encoding/json: allow semicolon in field key / struct tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow ';' as a valid character for json field keys and struct tags. Fixes #39189 Change-Id: I4b602a1b0674ff028db07623682f0d1e8e9fd6c9 Reviewed-on: https://go-review.googlesource.com/c/go/+/234818 Run-TryBot: Daniel Martí TryBot-Result: Go Bot Trust: Giovanni Bajo Trust: Daniel Martí Reviewed-by: Daniel Martí --- src/encoding/json/encode.go | 2 +- src/encoding/json/tagkey_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/encoding/json/encode.go') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 578d551102..c2d191442c 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -946,7 +946,7 @@ func isValidTag(s string) bool { } for _, c := range s { switch { - case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c): // Backslash and quote chars are reserved, but // otherwise any punctuation chars are allowed // in a tag name. diff --git a/src/encoding/json/tagkey_test.go b/src/encoding/json/tagkey_test.go index f77c49c764..bbb4e6a28d 100644 --- a/src/encoding/json/tagkey_test.go +++ b/src/encoding/json/tagkey_test.go @@ -41,7 +41,7 @@ type percentSlashTag struct { } type punctuationTag struct { - V string `json:"!#$%&()*+-./:<=>?@[]^_{|}~"` // https://golang.org/issue/3546 + V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` // https://golang.org/issue/3546 } type dashTag struct { @@ -90,7 +90,7 @@ var structTagObjectKeyTests = []struct { {badFormatTag{"Orfevre"}, "Orfevre", "Y"}, {badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, {percentSlashTag{"brut"}, "brut", "text/html%"}, - {punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:<=>?@[]^_{|}~"}, + {punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, {spaceTag{"Perreddu"}, "Perreddu", "With space"}, {unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, } -- cgit v1.3 From 428509402b03c608e625a4844ab0cce75e4bead2 Mon Sep 17 00:00:00 2001 From: lujjjh Date: Thu, 17 Sep 2020 14:39:13 +0000 Subject: encoding/json: detect cyclic maps and slices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now reports an error if cyclic maps and slices are to be encoded instead of an infinite recursion. This case wasn't handled in CL 187920. Fixes #40745. Change-Id: Ia34b014ecbb71fd2663bb065ba5355a307dbcc15 GitHub-Last-Rev: 6f874944f4065b5237babbb0fdce14c1c74a3c97 GitHub-Pull-Request: golang/go#40756 Reviewed-on: https://go-review.googlesource.com/c/go/+/248358 Reviewed-by: Daniel Martí Trust: Daniel Martí Trust: Bryan C. Mills Run-TryBot: Daniel Martí TryBot-Result: Go Bot --- src/encoding/json/encode.go | 27 +++++++++++++++++++++++++++ src/encoding/json/encode_test.go | 27 ++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) (limited to 'src/encoding/json/encode.go') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index c2d191442c..ea5eca51ef 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -779,6 +779,16 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { e.WriteString("null") return } + if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter { + // We're a large number of nested ptrEncoder.encode calls deep; + // start checking if we've run into a pointer cycle. + ptr := v.Pointer() + if _, ok := e.ptrSeen[ptr]; ok { + e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())}) + } + e.ptrSeen[ptr] = struct{}{} + defer delete(e.ptrSeen, ptr) + } e.WriteByte('{') // Extract and sort the keys. @@ -801,6 +811,7 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { me.elemEnc(e, v.MapIndex(kv.v), opts) } e.WriteByte('}') + e.ptrLevel-- } func newMapEncoder(t reflect.Type) encoderFunc { @@ -857,7 +868,23 @@ func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { e.WriteString("null") return } + if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter { + // We're a large number of nested ptrEncoder.encode calls deep; + // start checking if we've run into a pointer cycle. + // Here we use a struct to memorize the pointer to the first element of the slice + // and its length. + ptr := struct { + ptr uintptr + len int + }{v.Pointer(), v.Len()} + if _, ok := e.ptrSeen[ptr]; ok { + e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())}) + } + e.ptrSeen[ptr] = struct{}{} + defer delete(e.ptrSeen, ptr) + } se.arrayEnc(e, v, opts) + e.ptrLevel-- } func newSliceEncoder(t reflect.Type) encoderFunc { diff --git a/src/encoding/json/encode_test.go b/src/encoding/json/encode_test.go index 7290eca06f..42bb09d5cd 100644 --- a/src/encoding/json/encode_test.go +++ b/src/encoding/json/encode_test.go @@ -183,7 +183,15 @@ type PointerCycleIndirect struct { Ptrs []interface{} } -var pointerCycleIndirect = &PointerCycleIndirect{} +type RecursiveSlice []RecursiveSlice + +var ( + pointerCycleIndirect = &PointerCycleIndirect{} + mapCycle = make(map[string]interface{}) + sliceCycle = []interface{}{nil} + sliceNoCycle = []interface{}{nil, nil} + recursiveSliceCycle = []RecursiveSlice{nil} +) func init() { ptr := &SamePointerNoCycle{} @@ -192,6 +200,14 @@ func init() { pointerCycle.Ptr = pointerCycle pointerCycleIndirect.Ptrs = []interface{}{pointerCycleIndirect} + + mapCycle["x"] = mapCycle + sliceCycle[0] = sliceCycle + sliceNoCycle[1] = sliceNoCycle[:1] + for i := startDetectingCyclesAfter; i > 0; i-- { + sliceNoCycle = []interface{}{sliceNoCycle} + } + recursiveSliceCycle[0] = recursiveSliceCycle } func TestSamePointerNoCycle(t *testing.T) { @@ -200,12 +216,21 @@ func TestSamePointerNoCycle(t *testing.T) { } } +func TestSliceNoCycle(t *testing.T) { + if _, err := Marshal(sliceNoCycle); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + var unsupportedValues = []interface{}{ math.NaN(), math.Inf(-1), math.Inf(1), pointerCycle, pointerCycleIndirect, + mapCycle, + sliceCycle, + recursiveSliceCycle, } func TestUnsupportedValues(t *testing.T) { -- cgit v1.3 From 8266570ba71fd6af9c07d8fac945b3710010dfc7 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Sun, 13 Sep 2020 14:40:51 +0000 Subject: encoding/json: added docs to UnsupportedValueError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added godoc to UnsupportedValueError. Change-Id: I5fc13bac0b6e14b3a6eba27c9d3331ff5c5269aa GitHub-Last-Rev: 516cd7a92903e1048caa4d560abf5d66339e5a8f GitHub-Pull-Request: golang/go#41364 Reviewed-on: https://go-review.googlesource.com/c/go/+/254540 Reviewed-by: Daniel Martí Trust: Daniel Martí Trust: Heschi Kreinick Run-TryBot: Daniel Martí TryBot-Result: Go Bot --- src/encoding/json/encode.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/encoding/json/encode.go') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index ea5eca51ef..483b9d8f2d 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -236,6 +236,8 @@ func (e *UnsupportedTypeError) Error() string { return "json: unsupported type: " + e.Type.String() } +// An UnsupportedValueError is returned by Marshal when attempting +// to encode an unsupported value. type UnsupportedValueError struct { Value reflect.Value Str string -- cgit v1.3