From 68d82d646fc6518249833c8224fdb6e4df55c7de Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 8 Feb 2022 17:04:02 +0100 Subject: [PATCH] Handle types that implement json.Marshaler interface Fixes #9 --- errchkjson.go | 26 +++++++++++++++++++++++--- testdata/src/nosafe/a.go | 26 +++++++++++++++++++++++++- testdata/src/standard/a.go | 26 +++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/errchkjson.go b/errchkjson.go index d3c0b71..cdb2bf5 100644 --- a/errchkjson.go +++ b/errchkjson.go @@ -154,7 +154,7 @@ func (e *errchkjson) jsonSafe(t types.Type, level int, seenTypes map[types.Type] return nil } - if types.Implements(t, textMarshalerInterface()) { + if types.Implements(t, textMarshalerInterface()) || types.Implements(t, jsonMarshalerInterface()) { return fmt.Errorf("unsafe type `%s` found", t.String()) } @@ -247,7 +247,7 @@ func (e *errchkjson) jsonSafe(t types.Type, level int, seenTypes map[types.Type] } func jsonSafeMapKey(t types.Type) error { - if types.Implements(t, textMarshalerInterface()) { + if types.Implements(t, textMarshalerInterface()) || types.Implements(t, jsonMarshalerInterface()) { return fmt.Errorf("unsafe type `%s` as map key found", t.String()) } switch ut := t.Underlying().(type) { @@ -268,7 +268,7 @@ func jsonSafeMapKey(t types.Type) error { } } -// Construct *types.Interface for interface TextMarshaler +// Construct *types.Interface for interface encoding.TextMarshaler // type TextMarshaler interface { // MarshalText() (text []byte, err error) // } @@ -287,3 +287,23 @@ func textMarshalerInterface() *types.Interface { return textMarshalerInterface } + +// Construct *types.Interface for interface json.Marshaler +// type Marshaler interface { +// MarshalJSON() ([]byte, error) +// } +// +func jsonMarshalerInterface() *types.Interface { + textMarshalerInterface := types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalJSON", types.NewSignature( + nil, nil, types.NewTuple( + types.NewVar(token.NoPos, nil, "", + types.NewSlice( + types.Universe.Lookup("byte").Type())), + types.NewVar(token.NoPos, nil, "", types.Universe.Lookup("error").Type())), + false)), + }, nil) + textMarshalerInterface.Complete() + + return textMarshalerInterface +} diff --git a/testdata/src/nosafe/a.go b/testdata/src/nosafe/a.go index e51d811..de77a7b 100644 --- a/testdata/src/nosafe/a.go +++ b/testdata/src/nosafe/a.go @@ -10,12 +10,20 @@ import ( type marshalText struct{} -func (mt marshalText) MarshalText() ([]byte, error) { +func (_ marshalText) MarshalText() ([]byte, error) { return []byte(`mt`), nil } var _ encoding.TextMarshaler = marshalText(struct{}{}) +type marshalJSON struct{} + +func (_ marshalJSON) MarshalJSON() ([]byte, error) { + return []byte(`mj`), nil +} + +var _ json.Marshaler = marshalJSON(struct{}{}) + // JSONMarshalSafeTypesWithNoSafe contains a multitude of test cases to marshal different combinations of types to JSON, // that are safe, that is, they will never return an error, if these types are marshaled to JSON. func JSONMarshalSafeTypesWithNoSafe() { @@ -266,6 +274,8 @@ type ( Stringer fmt.Stringer Mt marshalText MapMarshalTextString map[marshalText]string + Mj marshalJSON + MapMarshalJSONString map[marshalJSON]string C128 complex128 C128Ptr *complex128 @@ -301,6 +311,8 @@ func JSONMarshalSafeStructWithUnexportedFieldsWithNoSafe() { stringer fmt.Stringer // unsafe unexported mt marshalText // unsafe unexported mapMarshalTextString map[marshalText]string // unsafe unexported + mj marshalJSON // unsafe unexported + mapMarshalJSONString map[marshalJSON]string // unsafe unexported unexportedStruct ExportedUnsafeAndInvalidStruct // unsafe unexported unexportedStructPtr *ExportedUnsafeAndInvalidStruct // unsafe unexported @@ -366,6 +378,8 @@ func JSONMarshalSafeStructWithOmittedFieldsWithNoSafe() { Stringer fmt.Stringer `json:"-"` // unsafe exported but omitted Mt marshalText `json:"-"` // unsafe exported but omitted MapMarshalTextString map[marshalText]string `json:"-"` // unsafe exported but omitted + Mj marshalJSON `json:"-"` // unsafe exported but omitted + MapMarshalJSONString map[marshalJSON]string `json:"-"` // unsafe exported but omitted ExportedStruct ExportedUnsafeAndInvalidStruct `json:"-"` // unsafe exported but omitted ExportedStructPtr *ExportedUnsafeAndInvalidStruct `json:"-"` // unsafe exported but omitted @@ -506,6 +520,16 @@ func JSONMarshalUnsafeTypes() { _, _ = json.Marshal(mapMarshalTextString) // want "Error return value of `encoding/json.Marshal` is not checked: unsafe type `nosafe.marshalText` as map key found" _, err = json.Marshal(mapMarshalTextString) // err is checked _ = err + + var mj marshalJSON + _, _ = json.Marshal(mj) // want "Error return value of `encoding/json.Marshal` is not checked: unsafe type `nosafe.marshalJSON` found" + _, err = json.Marshal(mj) // err is checked + _ = err + + var mapMarshalJSONString map[marshalJSON]string + _, _ = json.Marshal(mapMarshalJSONString) // want "Error return value of `encoding/json.Marshal` is not checked: unsafe type `nosafe.marshalJSON` as map key found" + _, err = json.Marshal(mapMarshalJSONString) // err is checked + _ = err } // JSONMarshalInvalidTypes contains a multitude of test cases to marshal different combinations of types to JSON, diff --git a/testdata/src/standard/a.go b/testdata/src/standard/a.go index a9e1a7b..3780597 100644 --- a/testdata/src/standard/a.go +++ b/testdata/src/standard/a.go @@ -10,12 +10,20 @@ import ( type marshalText struct{} -func (mt marshalText) MarshalText() ([]byte, error) { +func (_ marshalText) MarshalText() ([]byte, error) { return []byte(`mt`), nil } var _ encoding.TextMarshaler = marshalText(struct{}{}) +type marshalJSON struct{} + +func (_ marshalJSON) MarshalJSON() ([]byte, error) { + return []byte(`mj`), nil +} + +var _ json.Marshaler = marshalJSON(struct{}{}) + // JSONMarshalSafeTypes contains a multitude of test cases to marshal different combinations of types to JSON, // that are safe, that is, they will never return an error, if these types are marshaled to JSON. func JSONMarshalSafeTypes() { @@ -266,6 +274,8 @@ type ( Stringer fmt.Stringer Mt marshalText MapMarshalTextString map[marshalText]string + Mj marshalJSON + MapMarshalJSONString map[marshalJSON]string C128 complex128 C128Ptr *complex128 @@ -301,6 +311,8 @@ func JSONMarshalSafeStructWithUnexportedFields() { stringer fmt.Stringer // unsafe unexported mt marshalText // unsafe unexported mapMarshalTextString map[marshalText]string // unsafe unexported + mj marshalJSON // unsafe unexported + mapMarshalJSONString map[marshalJSON]string // unsafe unexported unexportedStruct ExportedUnsafeAndInvalidStruct // unsafe unexported unexportedStructPtr *ExportedUnsafeAndInvalidStruct // unsafe unexported @@ -366,6 +378,8 @@ func JSONMarshalSafeStructWithOmittedFields() { Stringer fmt.Stringer `json:"-"` // unsafe exported but omitted Mt marshalText `json:"-"` // unsafe exported but omitted MapMarshalTextString map[marshalText]string `json:"-"` // unsafe exported but omitted + Mj marshalJSON `json:"-"` // unsafe exported but omitted + MapMarshalJSONString map[marshalJSON]string `json:"-"` // unsafe exported but omitted ExportedStruct ExportedUnsafeAndInvalidStruct `json:"-"` // unsafe exported but omitted ExportedStructPtr *ExportedUnsafeAndInvalidStruct `json:"-"` // unsafe exported but omitted @@ -506,6 +520,16 @@ func JSONMarshalUnsafeTypes() { _, _ = json.Marshal(mapMarshalTextString) // want "Error return value of `encoding/json.Marshal` is not checked: unsafe type `standard.marshalText` as map key found" _, err = json.Marshal(mapMarshalTextString) // err is checked _ = err + + var mj marshalJSON + _, _ = json.Marshal(mj) // want "Error return value of `encoding/json.Marshal` is not checked: unsafe type `standard.marshalJSON` found" + _, err = json.Marshal(mj) // err is checked + _ = err + + var mapMarshalJSONString map[marshalJSON]string + _, _ = json.Marshal(mapMarshalJSONString) // want "Error return value of `encoding/json.Marshal` is not checked: unsafe type `standard.marshalJSON` as map key found" + _, err = json.Marshal(mapMarshalJSONString) // err is checked + _ = err } // JSONMarshalInvalidTypes contains a multitude of test cases to marshal different combinations of types to JSON,