From caae93c18f3f190a20bd05843f3d976f0d2c4188 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Sat, 6 Nov 2021 11:25:10 +0100 Subject: [PATCH 1/4] Add errchkjson linter --- .golangci.example.yml | 18 + go.mod | 1 + go.sum | 2 + pkg/config/linters_settings.go | 6 + pkg/golinters/errchkjson.go | 34 + pkg/lint/lintersdb/manager.go | 7 + test/testdata/configs/errchkjson.yml | 2 + .../errchkjson_check_error_free_encoding.yml | 5 + .../configs/errchkjson_no_exported.yml | 3 + test/testdata/errchkjson.go | 641 ++++++++++++++++++ .../errchkjson_check_error_free_encoding.go | 616 +++++++++++++++++ test/testdata/errchkjson_no_exported.go | 28 + 12 files changed, 1363 insertions(+) create mode 100644 pkg/golinters/errchkjson.go create mode 100644 test/testdata/configs/errchkjson.yml create mode 100644 test/testdata/configs/errchkjson_check_error_free_encoding.yml create mode 100644 test/testdata/configs/errchkjson_no_exported.yml create mode 100644 test/testdata/errchkjson.go create mode 100644 test/testdata/errchkjson_check_error_free_encoding.go create mode 100644 test/testdata/errchkjson_no_exported.go diff --git a/.golangci.example.yml b/.golangci.example.yml index a79b718ce947..18cbde45b2f4 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -103,6 +103,24 @@ linters-settings: # should ignore tests (default false) skip-tests: false + errchkjson: + # with check-error-free-encoding set to true, errchkjson does warn about errors + # from json encoding functions that are safe to be ignored, + # because they are not possible to happen (default false) + # + # if check-error-free-encoding is set to true and errcheck linter is enabled, + # it is recommended to add the following exceptions to prevent from false positives: + # + # linters-settings: + # errcheck: + # exclude-functions: + # - encoding/json.Marshal + # - encoding/json.MarshalIndent + # - (*encoding/json.Encoder).Encode + check-error-free-encoding: false + # if report-no-exported is true, encoding a struct without exported fields is reported as issue (default false) + report-no-exported: false + dogsled: # checks assignments with too many blank identifiers; default is 2 max-blank-identifiers: 2 diff --git a/go.mod b/go.mod index b312036ba7aa..b79320a2cd5b 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/blizzy78/varnamelen v0.5.0 github.com/bombsimon/wsl/v3 v3.3.0 github.com/breml/bidichk v0.2.1 + github.com/breml/errchkjson v0.2.0 github.com/butuzov/ireturn v0.1.1 github.com/charithe/durationcheck v0.0.9 github.com/daixiang0/gci v0.2.9 diff --git a/go.sum b/go.sum index 6980b71bdbac..6e1dc88f22b7 100644 --- a/go.sum +++ b/go.sum @@ -108,6 +108,8 @@ github.com/bombsimon/wsl/v3 v3.3.0 h1:Mka/+kRLoQJq7g2rggtgQsjuI/K5Efd87WX96EWFxj github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= github.com/breml/bidichk v0.2.1 h1:SRNtZuLdfkxtocj+xyHXKC1Uv3jVi6EPYx+NHSTNQvE= github.com/breml/bidichk v0.2.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso= +github.com/breml/errchkjson v0.2.0 h1:5XK9tXXqahYiPHuJ5Asx9a5ucpASxLMxq3EvQyLb26c= +github.com/breml/errchkjson v0.2.0/go.mod h1:jZEATw/jF69cL1iy7//Yih8yp/mXp2CBoBr9GJwCAsY= github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY= github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 9a26ca06a4ba..9b783348bbb0 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -89,6 +89,7 @@ type LintersSettings struct { Dogsled DogsledSettings Dupl DuplSettings Errcheck ErrcheckSettings + ErrChkJSON ErrChkJSONSettings ErrorLint ErrorLintSettings Exhaustive ExhaustiveSettings ExhaustiveStruct ExhaustiveStructSettings @@ -165,6 +166,11 @@ type Cyclop struct { SkipTests bool `mapstructure:"skip-tests"` } +type ErrChkJSONSettings struct { + CheckErrorFreeEncoding bool `mapstructure:"check-error-free-encoding"` + ReportNoExported bool `mapstructure:"report-no-exported"` +} + type DepGuardSettings struct { ListType string `mapstructure:"list-type"` Packages []string diff --git a/pkg/golinters/errchkjson.go b/pkg/golinters/errchkjson.go new file mode 100644 index 000000000000..e88d0510c3f3 --- /dev/null +++ b/pkg/golinters/errchkjson.go @@ -0,0 +1,34 @@ +package golinters + +import ( + "golang.org/x/tools/go/analysis" + + "github.com/breml/errchkjson" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewErrChkJSONFuncName(cfg *config.ErrChkJSONSettings) *goanalysis.Linter { + a := errchkjson.NewAnalyzer() + + cfgMap := map[string]map[string]interface{}{} + cfgMap[a.Name] = map[string]interface{}{ + "omit-safe": true, + } + if cfg != nil { + cfgMap[a.Name] = map[string]interface{}{ + "omit-safe": !cfg.CheckErrorFreeEncoding, + "report-no-exported": cfg.ReportNoExported, + } + } + + return goanalysis.NewLinter( + "errchkjson", + "Checks types passed to the json encoding functions. "+ + "Reports unsupported types and optionally reports occations, "+ + "where the check for the returned error can be omitted.", + []*analysis.Analyzer{a}, + cfgMap, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 7d3e2130ae2d..b380d3786176 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -102,6 +102,7 @@ func enableLinterConfigs(lcs []*linter.Config, isEnabled func(lc *linter.Config) func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { var bidichkCfg *config.BiDiChkSettings var cyclopCfg *config.Cyclop + var errchkjsonCfg *config.ErrChkJSONSettings var errorlintCfg *config.ErrorLintSettings var exhaustiveCfg *config.ExhaustiveSettings var exhaustiveStructCfg *config.ExhaustiveStructSettings @@ -129,6 +130,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { if m.cfg != nil { bidichkCfg = &m.cfg.LintersSettings.BiDiChk cyclopCfg = &m.cfg.LintersSettings.Cyclop + errchkjsonCfg = &m.cfg.LintersSettings.ErrChkJSON errorlintCfg = &m.cfg.LintersSettings.ErrorLint exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive exhaustiveStructCfg = &m.cfg.LintersSettings.ExhaustiveStruct @@ -548,6 +550,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithSince("1.43.0"). WithPresets(linter.PresetBugs). WithURL("https://github.com/breml/bidichk"), + linter.NewConfig(golinters.NewErrChkJSONFuncName(errchkjsonCfg)). + WithSince("1.44.0"). + WithPresets(linter.PresetBugs, linter.PresetUnused). + WithLoadForGoAnalysis(). + WithURL("https://github.com/breml/errchkjson"), // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives linter.NewConfig(golinters.NewNoLintLint()). diff --git a/test/testdata/configs/errchkjson.yml b/test/testdata/configs/errchkjson.yml new file mode 100644 index 000000000000..d7d534574241 --- /dev/null +++ b/test/testdata/configs/errchkjson.yml @@ -0,0 +1,2 @@ +issues: + max-issues-per-linter: 100 diff --git a/test/testdata/configs/errchkjson_check_error_free_encoding.yml b/test/testdata/configs/errchkjson_check_error_free_encoding.yml new file mode 100644 index 000000000000..0186bef7de95 --- /dev/null +++ b/test/testdata/configs/errchkjson_check_error_free_encoding.yml @@ -0,0 +1,5 @@ +issues: + max-issues-per-linter: 100 +linters-settings: + errchkjson: + check-error-free-encoding: true diff --git a/test/testdata/configs/errchkjson_no_exported.yml b/test/testdata/configs/errchkjson_no_exported.yml new file mode 100644 index 000000000000..b62e297c6160 --- /dev/null +++ b/test/testdata/configs/errchkjson_no_exported.yml @@ -0,0 +1,3 @@ +linters-settings: + errchkjson: + report-no-exported: true diff --git a/test/testdata/errchkjson.go b/test/testdata/errchkjson.go new file mode 100644 index 000000000000..e35a29454320 --- /dev/null +++ b/test/testdata/errchkjson.go @@ -0,0 +1,641 @@ +// args: -Eerrchkjson +// config_path: testdata/configs/errchkjson.yml +package testdata + +import ( + "encoding" + "encoding/json" + "fmt" + "io/ioutil" + "unsafe" +) + +type marshalText struct{} + +func (mt marshalText) MarshalText() ([]byte, error) { + return []byte(`mt`), nil +} + +var _ encoding.TextMarshaler = marshalText(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() { + var err error + + _, _ = json.Marshal(nil) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + json.Marshal(nil) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(nil) // nil is safe and check-error-free-encoding is false + _ = err + + _, _ = json.MarshalIndent(nil, "", " ") // ERROR "Error return value of `encoding/json.MarshalIndent` is not checked" + json.MarshalIndent(nil, "", " ") // ERROR "Error return value of `encoding/json.MarshalIndent` is not checked" + _, err = json.MarshalIndent(nil, "", " ") // nil is safe and check-error-free-encoding is false + _ = err + + enc := json.NewEncoder(ioutil.Discard) + _ = enc.Encode(nil) // ERROR "Error return value of `\\([*]encoding/json.Encoder\\).Encode` is not checked" + enc.Encode(nil) // ERROR "Error return value of `\\([*]encoding/json.Encoder\\).Encode` is not checked" + err = enc.Encode(nil) // nil is safe and check-error-free-encoding is false + _ = err + + var b bool + _, _ = json.Marshal(b) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(b) // bool is safe and check-error-free-encoding is false + _ = err + + var i int + _, _ = json.Marshal(i) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(i) // int is safe and check-error-free-encoding is false + _ = err + + var i8 int8 + _, _ = json.Marshal(i8) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(i8) // int8 is safe and check-error-free-encoding is false + _ = err + + var i16 int16 + _, _ = json.Marshal(i16) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(i16) // int16 is safe and check-error-free-encoding is false + _ = err + + var i32 int32 + _, _ = json.Marshal(i32) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(i32) // int32 / rune is safe and check-error-free-encoding is false + _ = err + + var i64 int64 + _, _ = json.Marshal(i64) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(i64) // int64 is safe and check-error-free-encoding is false + _ = err + + var ui uint + _, _ = json.Marshal(ui) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(ui) // uint is safe and check-error-free-encoding is false + _ = err + + var ui8 uint8 + _, _ = json.Marshal(ui8) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(ui8) // uint8 is safe and check-error-free-encoding is false + _ = err + + var ui16 uint16 + _, _ = json.Marshal(ui16) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(ui16) // uint16 is safe and check-error-free-encoding is false + _ = err + + var ui32 uint32 + _, _ = json.Marshal(ui32) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(ui32) // uint32 is safe and check-error-free-encoding is false + _ = err + + var ui64 uint64 + _, _ = json.Marshal(ui64) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(ui64) // uint64 is safe and check-error-free-encoding is false + _ = err + + var uiptr uintptr + _, _ = json.Marshal(uiptr) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(uiptr) // uintptr is safe and check-error-free-encoding is false + _ = err + + var str string + _, _ = json.Marshal(str) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(str) // string is safe and check-error-free-encoding is false + _ = err + + var strSlice []string + _, _ = json.Marshal(strSlice) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(strSlice) // []string is safe and check-error-free-encoding is false + _ = err + + var intSlice []int + _, _ = json.Marshal(intSlice) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(intSlice) // []int is safe and check-error-free-encoding is false + _ = err + + var boolSlice []bool + _, _ = json.Marshal(boolSlice) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(boolSlice) // []bool is safe and check-error-free-encoding is false + _ = err + + var strArray [10]string + _, _ = json.Marshal(strArray) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(strArray) // [10]string is safe and check-error-free-encoding is false + _ = err + + var intArray [10]int + _, _ = json.Marshal(intArray) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(intArray) // [10]int is safe and check-error-free-encoding is false + _ = err + + var boolArray [10]bool + _, _ = json.Marshal(boolArray) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(boolArray) // [10]bool is safe and check-error-free-encoding is false + _ = err + + var basicStruct struct { + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 // also rune + Int64 int64 + Uint uint + Uint8 uint8 // also byte + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Uintptr uintptr + String string + } + _, _ = json.Marshal(basicStruct) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(basicStruct) // struct containing only safe basic types is safe and check-error-free-encoding is false + _ = err + + var ptrStruct struct { + Bool *bool + Int *int + Int8 *int8 + Int16 *int16 + Int32 *int32 + Int64 *int64 + Uint *uint + Uint8 *uint8 + Uint16 *uint16 + Uint32 *uint32 + Uint64 *uint64 + Uintptr *uintptr + String *string + } + _, _ = json.Marshal(ptrStruct) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(ptrStruct) // struct containing pointer to only safe basic types is safe and check-error-free-encoding is false + _ = err + + var mapStrStr map[string]string + _, _ = json.Marshal(mapStrStr) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(mapStrStr) // map[string]string is safe and check-error-free-encoding is false + _ = err + + var mapStrInt map[string]int + _, _ = json.Marshal(mapStrInt) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(mapStrInt) // map[string]int is safe and check-error-free-encoding is false + _ = err + + var mapStrBool map[string]bool + _, _ = json.Marshal(mapStrBool) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(mapStrBool) // map[string]bool is safe and check-error-free-encoding is false + _ = err + + var mapIntStr map[int]string + _, _ = json.Marshal(mapIntStr) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(mapIntStr) // map[int]string is safe and check-error-free-encoding is false + _ = err + + var mapIntInt map[int]int + _, _ = json.Marshal(mapIntInt) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(mapIntInt) // map[int]int is safe and check-error-free-encoding is false + _ = err + + var mapIntBool map[int]bool + _, _ = json.Marshal(mapIntBool) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(mapIntBool) // map[int]bool is safe and check-error-free-encoding is false + _ = err + + type innerStruct struct { + Bool bool + Int int + String string + + StrSlice []string + IntSlice []int + BoolSlice []bool + + StrArray [10]string + IntArray [10]int + BoolArray [10]bool + + MapStrStr map[string]string + MapStrInt map[string]int + MapStrBool map[string]bool + + MapIntStr map[int]string + MapIntInt map[int]int + MapIntBool map[int]bool + } + var outerStruct struct { + Bool bool + Int int + String string + + StrSlice []string + IntSlice []int + BoolSlice []bool + + StrArray [10]string + IntArray [10]int + BoolArray [10]bool + + MapStrStr map[string]string + MapStrInt map[string]int + MapStrBool map[string]bool + + MapIntStr map[int]string + MapIntInt map[int]int + MapIntBool map[int]bool + + InnerStruct innerStruct + } + _, _ = json.Marshal(outerStruct) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(outerStruct) // struct with only safe types is safe and check-error-free-encoding is false + _ = err +} + +type ( + structKey struct{ id int } + ExportedUnsafeAndInvalidStruct struct { // unsafe unexported but omitted + F64 float64 + F64Ptr *float64 + F64Slice []float64 + F64Array [10]float64 + MapStrF64 map[string]float64 + MapEIStr map[interface{}]string + Number json.Number + NumberPtr *json.Number + NumberSlice []json.Number + MapNumberStr map[json.Number]string + Ei interface{} + Stringer fmt.Stringer + Mt marshalText + MapMarshalTextString map[marshalText]string + + C128 complex128 + C128Ptr *complex128 + C128Slice []complex128 + C128Array [10]complex128 + MapBoolStr map[bool]string + MapF64Str map[float64]string + F func() + Ch chan struct{} + UnsafePtr unsafe.Pointer + MapStructStr map[structKey]string + } +) + +// JSONMarshalSafeStructWithUnexportedFieldsWithNoSafe contains a struct with unexported, unsafe fields. +func JSONMarshalSaveStructWithUnexportedFieldsWithNoSafe() { + var err error + + var unexportedInStruct struct { + Bool bool // safe exported + + f64 float64 // unsafe unexported + f64Ptr *float64 // unsafe unexported + f64Slice []float64 // unsafe unexported + f64Array [10]float64 // unsafe unexported + mapStrF64 map[string]float64 // unsafe unexported + mapEIStr map[interface{}]string // unsafe unexported + number json.Number // unsafe unexported + numberPtr *json.Number // unsafe unexported + numberSlice []json.Number // unsafe unexported + mapNumberStr map[json.Number]string // unsafe unexported + ei interface{} // unsafe unexported + stringer fmt.Stringer // unsafe unexported + mt marshalText // unsafe unexported + mapMarshalTextString map[marshalText]string // unsafe unexported + unexportedStruct ExportedUnsafeAndInvalidStruct // unsafe unexported + unexportedStructPtr *ExportedUnsafeAndInvalidStruct // unsafe unexported + + c128 complex128 // invalid unexported + c128Slice []complex128 // invalid unexported + c128Array [10]complex128 // invalid unexported + mapBoolStr map[bool]string // invalid unexported + mapF64Str map[float64]string // invalid unexported + f func() // invalid unexported + ch chan struct{} // invalid unexported + unsafePtr unsafe.Pointer // invalid unexported + mapStructStr map[structKey]string // invalid unexported + } + _ = unexportedInStruct.f64 + _ = unexportedInStruct.f64Ptr + _ = unexportedInStruct.f64Slice + _ = unexportedInStruct.f64Array + _ = unexportedInStruct.mapStrF64 + _ = unexportedInStruct.mapEIStr + _ = unexportedInStruct.number + _ = unexportedInStruct.numberPtr + _ = unexportedInStruct.numberSlice + _ = unexportedInStruct.mapNumberStr + _ = unexportedInStruct.ei + _ = unexportedInStruct.stringer + _ = unexportedInStruct.mt + _ = unexportedInStruct.mapMarshalTextString + _ = unexportedInStruct.unexportedStruct + _ = unexportedInStruct.unexportedStructPtr + + _ = unexportedInStruct.c128 + _ = unexportedInStruct.c128Slice + _ = unexportedInStruct.c128Array + _ = unexportedInStruct.mapBoolStr + _ = unexportedInStruct.mapF64Str + _ = unexportedInStruct.f + _ = unexportedInStruct.ch + _ = unexportedInStruct.unsafePtr + _ = unexportedInStruct.mapStructStr[structKey{1}] + _, _ = json.Marshal(unexportedInStruct) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(unexportedInStruct) // struct containing unsafe but unexported fields is safe + _ = err +} + +// JSONMarshalSafeStructWithOmittedFieldsWithNoSafe contains a struct with omitted, unsafe fields. +func JSONMarshalSaveStructWithOmittedFieldsWithNoSafe() { + var err error + + var omitInStruct struct { + Bool bool // safe exported + + F64 float64 `json:"-"` // unsafe exported but omitted + F64Ptr *float64 `json:"-"` // unsafe exported but omitted + F64Slice []float64 `json:"-"` // unsafe exported but omitted + F64Array [10]float64 `json:"-"` // unsafe exported but omitted + MapStrF64 map[string]float64 `json:"-"` // unsafe exported but omitted + MapEIStr map[interface{}]string `json:"-"` // unsafe exported but omitted + Number json.Number `json:"-"` // unsafe exported but omitted + NumberPtr *json.Number `json:"-"` // unsafe exported but omitted + NumberSlice []json.Number `json:"-"` // unsafe exported but omitted + MapNumberStr map[json.Number]string `json:"-"` // unsafe exported but omitted + Ei interface{} `json:"-"` // unsafe exported but omitted + Stringer fmt.Stringer `json:"-"` // unsafe exported but omitted + Mt marshalText `json:"-"` // unsafe exported but omitted + MapMarshalTextString map[marshalText]string `json:"-"` // unsafe exported but omitted + ExportedStruct ExportedUnsafeAndInvalidStruct `json:"-"` // unsafe exported but omitted + ExportedStructPtr *ExportedUnsafeAndInvalidStruct `json:"-"` // unsafe exported but omitted + + C128 complex128 `json:"-"` // invalid exported but omitted + C128Slice []complex128 `json:"-"` // invalid exported but omitted + C128Array [10]complex128 `json:"-"` // invalid exported but omitted + MapBoolStr map[bool]string `json:"-"` // invalid exported but omitted + MapF64Str map[float64]string `json:"-"` // invalid exported but omitted + F func() `json:"-"` // invalid exported but omitted + Ch chan struct{} `json:"-"` // invalid exported but omitted + UnsafePtr unsafe.Pointer `json:"-"` // invalid exported but omitted + MapStructStr map[structKey]string `json:"-"` // invalid exported but omitted + } + _ = omitInStruct.MapStructStr[structKey{1}] + _, _ = json.Marshal(omitInStruct) // ERROR "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(omitInStruct) // struct containing unsafe but omitted, exported fields is safe and check-error-free-encoding is false + _ = err +} + +// JSONMarshalUnsafeTypes contains a multitude of test cases to marshal different combinations of types to JSON, +// that can potentially lead to json.Marshal returning an error. +func JSONMarshalUnsafeTypes() { + var err error + + var f32 float32 + json.Marshal(f32) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, _ = json.Marshal(f32) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(f32) // err is checked + _ = err + + var f64 float64 + _, _ = json.Marshal(f64) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(f64) // err is checked + _ = err + + var f32Slice []float32 + _, _ = json.Marshal(f32Slice) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(f32Slice) // err is checked + _ = err + + var f64Slice []float64 + _, _ = json.Marshal(f64Slice) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(f64Slice) // err is checked + _ = err + + var f32Array [10]float32 + _, _ = json.Marshal(f32Array) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(f32Array) // err is checked + _ = err + + var f64Array [10]float64 + _, _ = json.Marshal(f64Array) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(f64Array) // err is checked + _ = err + + var structPtrF32 struct { + F32 *float32 + } + _, _ = json.Marshal(structPtrF32) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(structPtrF32) // err is checked + _ = err + + var structPtrF64 struct { + F64 *float64 + } + _, _ = json.Marshal(structPtrF64) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(structPtrF64) // err is checked + _ = err + + var mapStrF32 map[string]float32 + _, _ = json.Marshal(mapStrF32) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(mapStrF32) // err is checked + _ = err + + var mapStrF64 map[string]float64 + _, _ = json.Marshal(mapStrF64) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(mapStrF64) // err is checked + _ = err + + var mapEIStr map[interface{}]string + _, _ = json.Marshal(mapEIStr) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `interface{}` as map key found" + _, err = json.Marshal(mapEIStr) // err is checked + _ = err + + var mapStringerStr map[fmt.Stringer]string + _, _ = json.Marshal(mapStringerStr) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `fmt.Stringer` as map key found" + _, err = json.Marshal(mapStringerStr) // err is checked + _ = err + + var number json.Number + _, _ = json.Marshal(number) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `encoding/json.Number` found" + _, err = json.Marshal(number) // err is checked + _ = err + + var numberSlice []json.Number + _, _ = json.Marshal(numberSlice) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `encoding/json.Number` found" + _, err = json.Marshal(numberSlice) // err is checked + _ = err + + var mapNumberStr map[json.Number]string + _, _ = json.Marshal(mapNumberStr) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `encoding/json.Number` as map key found" + _, err = json.Marshal(mapNumberStr) // err is checked + _ = err + + var mapStrNumber map[string]json.Number + _, _ = json.Marshal(mapStrNumber) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `encoding/json.Number` found" + _, err = json.Marshal(mapStrNumber) // err is checked + _ = err + + var ei interface{} + _, _ = json.Marshal(ei) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `interface{}` found" + _, err = json.Marshal(ei) // err is checked + _ = err + + var eiptr *interface{} + _, _ = json.Marshal(eiptr) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `*interface{}` found" + _, err = json.Marshal(eiptr) // err is checked + _ = err + + var stringer fmt.Stringer + _, _ = json.Marshal(stringer) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `fmt.Stringer` found" + _, err = json.Marshal(stringer) // err is checked + _ = err + + var structWithEmptyInterface struct { + EmptyInterface interface{} + } + _, _ = json.Marshal(structWithEmptyInterface) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `interface{}` found" + _, err = json.Marshal(structWithEmptyInterface) // err is checked + _ = err + + var mt marshalText + _, _ = json.Marshal(mt) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `[a-z-]+.marshalText` found" + _, err = json.Marshal(mt) // err is checked + _ = err + + var mapMarshalTextString map[marshalText]string + _, _ = json.Marshal(mapMarshalTextString) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `[a-z-]+.marshalText` as map key found" + _, err = json.Marshal(mapMarshalTextString) // err is checked + _ = err +} + +// JSONMarshalInvalidTypes contains a multitude of test cases to marshal different combinations of types to JSON, +// that are invalid and not supported by json.Marshal, that is they will always return an error, if these types used +// with json.Marshal. +func JSONMarshalInvalidTypes() { + var err error + + var c64 complex64 + json.Marshal(c64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, _ = json.Marshal(c64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, err = json.Marshal(c64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _ = err + + var c128 complex128 + _, _ = json.Marshal(c128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _, err = json.Marshal(c128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _ = err + + var sliceC64 []complex64 + _, _ = json.Marshal(sliceC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, err = json.Marshal(sliceC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _ = err + + var sliceC128 []complex128 + _, _ = json.Marshal(sliceC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _, err = json.Marshal(sliceC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _ = err + + var arrayC64 []complex64 + _, _ = json.Marshal(arrayC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, err = json.Marshal(arrayC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _ = err + + var arrayC128 []complex128 + _, _ = json.Marshal(arrayC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _, err = json.Marshal(arrayC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _ = err + + var structPtrC64 struct { + C64 *complex64 + } + _, _ = json.Marshal(structPtrC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, err = json.Marshal(structPtrC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _ = err + + var structPtrC128 struct { + C128 *complex128 + } + _, _ = json.Marshal(structPtrC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _, err = json.Marshal(structPtrC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _ = err + + var mapBoolStr map[bool]string + _, _ = json.Marshal(mapBoolStr) // ERROR "`encoding/json.Marshal` for unsupported type `bool` as map key found" + _, err = json.Marshal(mapBoolStr) // ERROR "`encoding/json.Marshal` for unsupported type `bool` as map key found" + _ = err + + var mapF32Str map[float32]string + _, _ = json.Marshal(mapF32Str) // ERROR "`encoding/json.Marshal` for unsupported type `float32` as map key found" + _, err = json.Marshal(mapF32Str) // ERROR "`encoding/json.Marshal` for unsupported type `float32` as map key found" + _ = err + + var mapF64Str map[float64]string + _, _ = json.Marshal(mapF64Str) // ERROR "`encoding/json.Marshal` for unsupported type `float64` as map key found" + _, err = json.Marshal(mapF64Str) // ERROR "`encoding/json.Marshal` for unsupported type `float64` as map key found" + _ = err + + var mapC64Str map[complex64]string + _, _ = json.Marshal(mapC64Str) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` as map key found" + _, err = json.Marshal(mapC64Str) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` as map key found" + _ = err + + var mapC128Str map[complex128]string + _, _ = json.Marshal(mapC128Str) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` as map key found" + _, err = json.Marshal(mapC128Str) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` as map key found" + _ = err + + mapStructStr := map[structKey]string{structKey{1}: "str"} + _, _ = json.Marshal(mapStructStr) // ERROR "`encoding/json.Marshal` for unsupported type `[a-z-]+.structKey` as map key found" + _, err = json.Marshal(mapStructStr) // ERROR "`encoding/json.Marshal` for unsupported type `[a-z-]+.structKey` as map key found" + _ = err + + f := func() {} + _, _ = json.Marshal(f) // ERROR "`encoding/json.Marshal` for unsupported type `func\\(\\)` found" + _, err = json.Marshal(f) // ERROR "`encoding/json.Marshal` for unsupported type `func\\(\\)` found" + _ = err + + var ch chan struct{} = make(chan struct{}) + _, _ = json.Marshal(ch) // ERROR "`encoding/json.Marshal` for unsupported type `chan struct{}` found" + _, err = json.Marshal(ch) // ERROR "`encoding/json.Marshal` for unsupported type `chan struct{}` found" + _ = err + + var unsafePtr unsafe.Pointer + _, _ = json.Marshal(unsafePtr) // ERROR "`encoding/json.Marshal` for unsupported type `unsafe.Pointer` found" + _, err = json.Marshal(unsafePtr) // ERROR "`encoding/json.Marshal` for unsupported type `unsafe.Pointer` found" + _ = err +} + +// NotJSONMarshal contains other go ast node types, that are not considered by errchkjson +func NotJSONMarshal() { + s := fmt.Sprintln("I am not considered by errchkjson") + _ = s + f := func() bool { return false } + _ = f() +} + +// JSONMarshalStructWithoutExportedFields contains a struct without exported fields. +func JSONMarshalStructWithoutExportedFields() { + var err error + + var withoutExportedFields struct { + privateField bool + ExportedButOmittedField bool `json:"-"` + } + _, err = json.Marshal(withoutExportedFields) // want "Error argument passed to `encoding/json.Marshal` does not contain any exported field" + _ = err +} + +// JSONMarshalStructWithoutExportedFields contains a struct without exported fields. +func JSONMarshalStructWithNestedStructWithoutExportedFields() { + var err error + + var withNestedStructWithoutExportedFields struct { + ExportedStruct struct { + privatField bool + } + } + _, err = json.Marshal(withNestedStructWithoutExportedFields) + _ = err +} diff --git a/test/testdata/errchkjson_check_error_free_encoding.go b/test/testdata/errchkjson_check_error_free_encoding.go new file mode 100644 index 000000000000..b16a4dcee541 --- /dev/null +++ b/test/testdata/errchkjson_check_error_free_encoding.go @@ -0,0 +1,616 @@ +// args: -Eerrchkjson +// config_path: testdata/configs/errchkjson_check_error_free_encoding.yml +package testdata + +import ( + "encoding" + "encoding/json" + "fmt" + "io/ioutil" + "unsafe" +) + +type marshalText struct{} + +func (mt marshalText) MarshalText() ([]byte, error) { + return []byte(`mt`), nil +} + +var _ encoding.TextMarshaler = marshalText(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() { + var err error + + _, _ = json.Marshal(nil) // nil is safe + json.Marshal(nil) // nil is safe + _, err = json.Marshal(nil) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + _, _ = json.MarshalIndent(nil, "", " ") // nil is safe + json.MarshalIndent(nil, "", " ") // nil is safe + _, err = json.MarshalIndent(nil, "", " ") // ERROR "Error return value of `encoding/json.MarshalIndent` is checked but passed argument is safe" + _ = err + + enc := json.NewEncoder(ioutil.Discard) + _ = enc.Encode(nil) // nil is safe + enc.Encode(nil) // nil is safe + err = enc.Encode(nil) // ERROR "Error return value of `\\([*]encoding/json.Encoder\\).Encode` is checked but passed argument is safe" + _ = err + + var b bool + _, _ = json.Marshal(b) // bool is safe + _, err = json.Marshal(b) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var i int + _, _ = json.Marshal(i) // int is safe + _, err = json.Marshal(i) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var i8 int8 + _, _ = json.Marshal(i8) // int8 is safe + _, err = json.Marshal(i8) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var i16 int16 + _, _ = json.Marshal(i16) // int16 is safe + _, err = json.Marshal(i16) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var i32 int32 + _, _ = json.Marshal(i32) // int32 / rune is safe + _, err = json.Marshal(i32) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var i64 int64 + _, _ = json.Marshal(i64) // int64 is safe + _, err = json.Marshal(i64) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var ui uint + _, _ = json.Marshal(ui) // uint is safe + _, err = json.Marshal(ui) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var ui8 uint8 + _, _ = json.Marshal(ui8) // uint8 / byte is safe + _, err = json.Marshal(ui8) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var ui16 uint16 + _, _ = json.Marshal(ui16) // uint16 is safe + _, err = json.Marshal(ui16) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var ui32 uint32 + _, _ = json.Marshal(ui32) // uint32 / rune is safe + _, err = json.Marshal(ui32) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var ui64 uint64 + _, _ = json.Marshal(ui64) // uint64 is safe + _, err = json.Marshal(ui64) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var uiptr uintptr + _, _ = json.Marshal(uiptr) // uintptr is safe + _, err = json.Marshal(uiptr) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var str string + _, _ = json.Marshal(str) // string is safe + _, err = json.Marshal(str) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var strSlice []string + _, _ = json.Marshal(strSlice) // []string is safe + _, err = json.Marshal(strSlice) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var intSlice []int + _, _ = json.Marshal(intSlice) // []int is safe + _, err = json.Marshal(intSlice) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var boolSlice []bool + _, _ = json.Marshal(boolSlice) // []bool is safe + _, err = json.Marshal(boolSlice) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var strArray [10]string + _, _ = json.Marshal(strArray) // [10]string is safe + _, err = json.Marshal(strArray) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var intArray [10]int + _, _ = json.Marshal(intArray) // [10]int is safe + _, err = json.Marshal(intArray) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var boolArray [10]bool + _, _ = json.Marshal(boolArray) // [10]bool is safe + _, err = json.Marshal(boolArray) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var basicStruct struct { + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 // also rune + Int64 int64 + Uint uint + Uint8 uint8 // also byte + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Uintptr uintptr + String string + } + _, _ = json.Marshal(basicStruct) // struct containing only safe basic types is safe + _, err = json.Marshal(basicStruct) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var ptrStruct struct { + Bool *bool + Int *int + Int8 *int8 + Int16 *int16 + Int32 *int32 + Int64 *int64 + Uint *uint + Uint8 *uint8 + Uint16 *uint16 + Uint32 *uint32 + Uint64 *uint64 + Uintptr *uintptr + String *string + } + _, _ = json.Marshal(ptrStruct) // struct containing pointer to only safe basic types is safe + _, err = json.Marshal(ptrStruct) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var mapStrStr map[string]string + _, _ = json.Marshal(mapStrStr) // map[string]string is safe + _, err = json.Marshal(mapStrStr) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var mapStrInt map[string]int + _, _ = json.Marshal(mapStrInt) // map[string]int is safe + _, err = json.Marshal(mapStrInt) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var mapStrBool map[string]bool + _, _ = json.Marshal(mapStrBool) // map[string]bool is safe + _, err = json.Marshal(mapStrBool) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var mapIntStr map[int]string + _, _ = json.Marshal(mapIntStr) // map[int]string is safe + _, err = json.Marshal(mapIntStr) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var mapIntInt map[int]int + _, _ = json.Marshal(mapIntInt) // map[int]int is safe + _, err = json.Marshal(mapIntInt) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + var mapIntBool map[int]bool + _, _ = json.Marshal(mapIntBool) // map[int]bool is safe + _, err = json.Marshal(mapIntBool) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err + + type innerStruct struct { + Bool bool + Int int + String string + + StrSlice []string + IntSlice []int + BoolSlice []bool + + StrArray [10]string + IntArray [10]int + BoolArray [10]bool + + MapStrStr map[string]string + MapStrInt map[string]int + MapStrBool map[string]bool + + MapIntStr map[int]string + MapIntInt map[int]int + MapIntBool map[int]bool + } + var outerStruct struct { + Bool bool + Int int + String string + + StrSlice []string + IntSlice []int + BoolSlice []bool + + StrArray [10]string + IntArray [10]int + BoolArray [10]bool + + MapStrStr map[string]string + MapStrInt map[string]int + MapStrBool map[string]bool + + MapIntStr map[int]string + MapIntInt map[int]int + MapIntBool map[int]bool + + InnerStruct innerStruct + } + _, _ = json.Marshal(outerStruct) // struct with only safe types + _, err = json.Marshal(outerStruct) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err +} + +type ( + structKey struct{ id int } + ExportedUnsafeAndInvalidStruct struct { // unsafe unexported but omitted + F64 float64 + F64Ptr *float64 + F64Slice []float64 + F64Array [10]float64 + MapStrF64 map[string]float64 + MapEIStr map[interface{}]string + Number json.Number + NumberPtr *json.Number + NumberSlice []json.Number + MapNumberStr map[json.Number]string + Ei interface{} + Stringer fmt.Stringer + Mt marshalText + MapMarshalTextString map[marshalText]string + + C128 complex128 + C128Ptr *complex128 + C128Slice []complex128 + C128Array [10]complex128 + MapBoolStr map[bool]string + MapF64Str map[float64]string + F func() + Ch chan struct{} + UnsafePtr unsafe.Pointer + MapStructStr map[structKey]string + } +) + +// JSONMarshalSaveStructWithUnexportedFields contains a struct with unexported, unsafe fields. +func JSONMarshalSaveStructWithUnexportedFields() { + var err error + + var unexportedInStruct struct { + Bool bool // safe exported + + f64 float64 // unsafe unexported + f64Ptr *float64 // unsafe unexported + f64Slice []float64 // unsafe unexported + f64Array [10]float64 // unsafe unexported + mapStrF64 map[string]float64 // unsafe unexported + mapEIStr map[interface{}]string // unsafe unexported + number json.Number // unsafe unexported + numberPtr *json.Number // unsafe unexported + numberSlice []json.Number // unsafe unexported + mapNumberStr map[json.Number]string // unsafe unexported + ei interface{} // unsafe unexported + stringer fmt.Stringer // unsafe unexported + mt marshalText // unsafe unexported + mapMarshalTextString map[marshalText]string // unsafe unexported + unexportedStruct ExportedUnsafeAndInvalidStruct // unsafe unexported + unexportedStructPtr *ExportedUnsafeAndInvalidStruct // unsafe unexported + + c128 complex128 // invalid unexported + c128Slice []complex128 // invalid unexported + c128Array [10]complex128 // invalid unexported + mapBoolStr map[bool]string // invalid unexported + mapF64Str map[float64]string // invalid unexported + f func() // invalid unexported + ch chan struct{} // invalid unexported + unsafePtr unsafe.Pointer // invalid unexported + mapStructStr map[structKey]string // invalid unexported + } + _ = unexportedInStruct.f64 + _ = unexportedInStruct.f64Ptr + _ = unexportedInStruct.f64Slice + _ = unexportedInStruct.f64Array + _ = unexportedInStruct.mapStrF64 + _ = unexportedInStruct.mapEIStr + _ = unexportedInStruct.number + _ = unexportedInStruct.numberPtr + _ = unexportedInStruct.numberSlice + _ = unexportedInStruct.mapNumberStr + _ = unexportedInStruct.ei + _ = unexportedInStruct.stringer + _ = unexportedInStruct.mt + _ = unexportedInStruct.mapMarshalTextString + _ = unexportedInStruct.unexportedStruct + _ = unexportedInStruct.unexportedStructPtr + + _ = unexportedInStruct.c128 + _ = unexportedInStruct.c128Slice + _ = unexportedInStruct.c128Array + _ = unexportedInStruct.mapBoolStr + _ = unexportedInStruct.mapF64Str + _ = unexportedInStruct.f + _ = unexportedInStruct.ch + _ = unexportedInStruct.unsafePtr + _ = unexportedInStruct.mapStructStr[structKey{1}] + _, _ = json.Marshal(unexportedInStruct) // struct containing unsafe but unexported fields is safe + _, err = json.Marshal(unexportedInStruct) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err +} + +// JSONMarshalSaveStructWithOmittedFields contains a struct with omitted, unsafe fields. +func JSONMarshalSaveStructWithOmittedFields() { + var err error + + var omitInStruct struct { + Bool bool // safe exported + + F64 float64 `json:"-"` // unsafe exported but omitted + F64Ptr *float64 `json:"-"` // unsafe exported but omitted + F64Slice []float64 `json:"-"` // unsafe exported but omitted + F64Array [10]float64 `json:"-"` // unsafe exported but omitted + MapStrF64 map[string]float64 `json:"-"` // unsafe exported but omitted + MapEIStr map[interface{}]string `json:"-"` // unsafe exported but omitted + Number json.Number `json:"-"` // unsafe exported but omitted + NumberPtr *json.Number `json:"-"` // unsafe exported but omitted + NumberSlice []json.Number `json:"-"` // unsafe exported but omitted + MapNumberStr map[json.Number]string `json:"-"` // unsafe exported but omitted + Ei interface{} `json:"-"` // unsafe exported but omitted + Stringer fmt.Stringer `json:"-"` // unsafe exported but omitted + Mt marshalText `json:"-"` // unsafe exported but omitted + MapMarshalTextString map[marshalText]string `json:"-"` // unsafe exported but omitted + ExportedStruct ExportedUnsafeAndInvalidStruct `json:"-"` // unsafe exported but omitted + ExportedStructPtr *ExportedUnsafeAndInvalidStruct `json:"-"` // unsafe exported but omitted + + C128 complex128 `json:"-"` // invalid exported but omitted + C128Slice []complex128 `json:"-"` // invalid exported but omitted + C128Array [10]complex128 `json:"-"` // invalid exported but omitted + MapBoolStr map[bool]string `json:"-"` // invalid exported but omitted + MapF64Str map[float64]string `json:"-"` // invalid exported but omitted + F func() `json:"-"` // invalid exported but omitted + Ch chan struct{} `json:"-"` // invalid exported but omitted + UnsafePtr unsafe.Pointer `json:"-"` // invalid exported but omitted + MapStructStr map[structKey]string `json:"-"` // invalid exported but omitted + } + _ = omitInStruct.MapStructStr[structKey{1}] + _, _ = json.Marshal(omitInStruct) // struct containing unsafe but omitted, exported fields is safe + _, err = json.Marshal(omitInStruct) // ERROR "Error return value of `encoding/json.Marshal` is checked but passed argument is safe" + _ = err +} + +// JSONMarshalUnsafeTypes contains a multitude of test cases to marshal different combinations of types to JSON, +// that can potentially lead to json.Marshal returning an error. +func JSONMarshalUnsafeTypes() { + var err error + + var f32 float32 + json.Marshal(f32) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, _ = json.Marshal(f32) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(f32) // err is checked + _ = err + + var f64 float64 + _, _ = json.Marshal(f64) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(f64) // err is checked + _ = err + + var f32Slice []float32 + _, _ = json.Marshal(f32Slice) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(f32Slice) // err is checked + _ = err + + var f64Slice []float64 + _, _ = json.Marshal(f64Slice) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(f64Slice) // err is checked + _ = err + + var f32Array [10]float32 + _, _ = json.Marshal(f32Array) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(f32Array) // err is checked + _ = err + + var f64Array [10]float64 + _, _ = json.Marshal(f64Array) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(f64Array) // err is checked + _ = err + + var structPtrF32 struct { + F32 *float32 + } + _, _ = json.Marshal(structPtrF32) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(structPtrF32) // err is checked + _ = err + + var structPtrF64 struct { + F64 *float64 + } + _, _ = json.Marshal(structPtrF64) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(structPtrF64) // err is checked + _ = err + + var mapStrF32 map[string]float32 + _, _ = json.Marshal(mapStrF32) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float32` found" + _, err = json.Marshal(mapStrF32) // err is checked + _ = err + + var mapStrF64 map[string]float64 + _, _ = json.Marshal(mapStrF64) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `float64` found" + _, err = json.Marshal(mapStrF64) // err is checked + _ = err + + var mapEIStr map[interface{}]string + _, _ = json.Marshal(mapEIStr) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `interface{}` as map key found" + _, err = json.Marshal(mapEIStr) // err is checked + _ = err + + var mapStringerStr map[fmt.Stringer]string + _, _ = json.Marshal(mapStringerStr) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `fmt.Stringer` as map key found" + _, err = json.Marshal(mapStringerStr) // err is checked + _ = err + + var number json.Number + _, _ = json.Marshal(number) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `encoding/json.Number` found" + _, err = json.Marshal(number) // err is checked + _ = err + + var numberSlice []json.Number + _, _ = json.Marshal(numberSlice) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `encoding/json.Number` found" + _, err = json.Marshal(numberSlice) // err is checked + _ = err + + var mapNumberStr map[json.Number]string + _, _ = json.Marshal(mapNumberStr) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `encoding/json.Number` as map key found" + _, err = json.Marshal(mapNumberStr) // err is checked + _ = err + + var mapStrNumber map[string]json.Number + _, _ = json.Marshal(mapStrNumber) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `encoding/json.Number` found" + _, err = json.Marshal(mapStrNumber) // err is checked + _ = err + + var ei interface{} + _, _ = json.Marshal(ei) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `interface{}` found" + _, err = json.Marshal(ei) // err is checked + _ = err + + var eiptr *interface{} + _, _ = json.Marshal(eiptr) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `*interface{}` found" + _, err = json.Marshal(eiptr) // err is checked + _ = err + + var stringer fmt.Stringer + _, _ = json.Marshal(stringer) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `fmt.Stringer` found" + _, err = json.Marshal(stringer) // err is checked + _ = err + + var structWithEmptyInterface struct { + EmptyInterface interface{} + } + _, _ = json.Marshal(structWithEmptyInterface) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `interface{}` found" + _, err = json.Marshal(structWithEmptyInterface) // err is checked + _ = err + + var mt marshalText + _, _ = json.Marshal(mt) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `[a-z-]+.marshalText` found" + _, err = json.Marshal(mt) // err is checked + _ = err + + var mapMarshalTextString map[marshalText]string + _, _ = json.Marshal(mapMarshalTextString) // ERROR "Error return value of `encoding/json.Marshal` is not checked: unsafe type `[a-z-]+.marshalText` as map key found" + _, err = json.Marshal(mapMarshalTextString) // err is checked + _ = err +} + +// JSONMarshalInvalidTypes contains a multitude of test cases to marshal different combinations of types to JSON, +// that are invalid and not supported by json.Marshal, that is they will always return an error, if these types used +// with json.Marshal. +func JSONMarshalInvalidTypes() { + var err error + + var c64 complex64 + json.Marshal(c64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, _ = json.Marshal(c64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, err = json.Marshal(c64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _ = err + + var c128 complex128 + _, _ = json.Marshal(c128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _, err = json.Marshal(c128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _ = err + + var sliceC64 []complex64 + _, _ = json.Marshal(sliceC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, err = json.Marshal(sliceC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _ = err + + var sliceC128 []complex128 + _, _ = json.Marshal(sliceC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _, err = json.Marshal(sliceC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _ = err + + var arrayC64 []complex64 + _, _ = json.Marshal(arrayC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, err = json.Marshal(arrayC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _ = err + + var arrayC128 []complex128 + _, _ = json.Marshal(arrayC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _, err = json.Marshal(arrayC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _ = err + + var structPtrC64 struct { + C64 *complex64 + } + _, _ = json.Marshal(structPtrC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _, err = json.Marshal(structPtrC64) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` found" + _ = err + + var structPtrC128 struct { + C128 *complex128 + } + _, _ = json.Marshal(structPtrC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _, err = json.Marshal(structPtrC128) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` found" + _ = err + + var mapBoolStr map[bool]string + _, _ = json.Marshal(mapBoolStr) // ERROR "`encoding/json.Marshal` for unsupported type `bool` as map key found" + _, err = json.Marshal(mapBoolStr) // ERROR "`encoding/json.Marshal` for unsupported type `bool` as map key found" + _ = err + + var mapF32Str map[float32]string + _, _ = json.Marshal(mapF32Str) // ERROR "`encoding/json.Marshal` for unsupported type `float32` as map key found" + _, err = json.Marshal(mapF32Str) // ERROR "`encoding/json.Marshal` for unsupported type `float32` as map key found" + _ = err + + var mapF64Str map[float64]string + _, _ = json.Marshal(mapF64Str) // ERROR "`encoding/json.Marshal` for unsupported type `float64` as map key found" + _, err = json.Marshal(mapF64Str) // ERROR "`encoding/json.Marshal` for unsupported type `float64` as map key found" + _ = err + + var mapC64Str map[complex64]string + _, _ = json.Marshal(mapC64Str) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` as map key found" + _, err = json.Marshal(mapC64Str) // ERROR "`encoding/json.Marshal` for unsupported type `complex64` as map key found" + _ = err + + var mapC128Str map[complex128]string + _, _ = json.Marshal(mapC128Str) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` as map key found" + _, err = json.Marshal(mapC128Str) // ERROR "`encoding/json.Marshal` for unsupported type `complex128` as map key found" + _ = err + + mapStructStr := map[structKey]string{structKey{1}: "str"} + _, _ = json.Marshal(mapStructStr) // ERROR "`encoding/json.Marshal` for unsupported type `[a-z-]+.structKey` as map key found" + _, err = json.Marshal(mapStructStr) // ERROR "`encoding/json.Marshal` for unsupported type `[a-z-]+.structKey` as map key found" + _ = err + + f := func() {} + _, _ = json.Marshal(f) // ERROR "`encoding/json.Marshal` for unsupported type `func\\(\\)` found" + _, err = json.Marshal(f) // ERROR "`encoding/json.Marshal` for unsupported type `func\\(\\)` found" + _ = err + + var ch chan struct{} = make(chan struct{}) + _, _ = json.Marshal(ch) // ERROR "`encoding/json.Marshal` for unsupported type `chan struct{}` found" + _, err = json.Marshal(ch) // ERROR "`encoding/json.Marshal` for unsupported type `chan struct{}` found" + _ = err + + var unsafePtr unsafe.Pointer + _, _ = json.Marshal(unsafePtr) // ERROR "`encoding/json.Marshal` for unsupported type `unsafe.Pointer` found" + _, err = json.Marshal(unsafePtr) // ERROR "`encoding/json.Marshal` for unsupported type `unsafe.Pointer` found" + _ = err +} + +// NotJSONMarshal contains other go ast node types, that are not considered by errchkjson +func NotJSONMarshal() { + s := fmt.Sprintln("I am not considered by errchkjson") + _ = s + f := func() bool { return false } + _ = f() +} diff --git a/test/testdata/errchkjson_no_exported.go b/test/testdata/errchkjson_no_exported.go new file mode 100644 index 000000000000..97880f502899 --- /dev/null +++ b/test/testdata/errchkjson_no_exported.go @@ -0,0 +1,28 @@ +// args: -Eerrchkjson +// config_path: testdata/configs/errchkjson_no_exported.yml +package testdata + +import ( + "encoding/json" +) + +// JSONMarshalStructWithoutExportedFields contains a struct without exported fields. +func JSONMarshalStructWithoutExportedFields() { + var withoutExportedFields struct { + privateField bool + ExportedButOmittedField bool `json:"-"` + } + _, err := json.Marshal(withoutExportedFields) // ERROR "Error argument passed to `encoding/json.Marshal` does not contain any exported field" + _ = err +} + +// JSONMarshalStructWithNestedStructWithoutExportedFields contains a struct without exported fields. +func JSONMarshalStructWithNestedStructWithoutExportedFields() { + var withNestedStructWithoutExportedFields struct { + ExportedStruct struct { + privatField bool + } + } + _, err := json.Marshal(withNestedStructWithoutExportedFields) + _ = err +} From 2a3a39c34b485c21dc43cc7bd16b10bf10cf9b12 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 23 Nov 2021 21:08:32 +0100 Subject: [PATCH 2/4] Update pkg/golinters/errchkjson.go Co-authored-by: Ludovic Fernandez --- pkg/golinters/errchkjson.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/golinters/errchkjson.go b/pkg/golinters/errchkjson.go index e88d0510c3f3..a01e0f0d37e6 100644 --- a/pkg/golinters/errchkjson.go +++ b/pkg/golinters/errchkjson.go @@ -2,7 +2,6 @@ package golinters import ( "golang.org/x/tools/go/analysis" - "github.com/breml/errchkjson" "github.com/golangci/golangci-lint/pkg/config" From 10249ca498aadcd772eb52cf3320bd505fc3969e Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 23 Nov 2021 21:20:42 +0100 Subject: [PATCH 3/4] Apply review findings --- .golangci.example.yml | 36 +++++++++++++++++------------------ pkg/lint/lintersdb/manager.go | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.golangci.example.yml b/.golangci.example.yml index 18cbde45b2f4..ddfc00cb4897 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -103,24 +103,6 @@ linters-settings: # should ignore tests (default false) skip-tests: false - errchkjson: - # with check-error-free-encoding set to true, errchkjson does warn about errors - # from json encoding functions that are safe to be ignored, - # because they are not possible to happen (default false) - # - # if check-error-free-encoding is set to true and errcheck linter is enabled, - # it is recommended to add the following exceptions to prevent from false positives: - # - # linters-settings: - # errcheck: - # exclude-functions: - # - encoding/json.Marshal - # - encoding/json.MarshalIndent - # - (*encoding/json.Encoder).Encode - check-error-free-encoding: false - # if report-no-exported is true, encoding a struct without exported fields is reported as issue (default false) - report-no-exported: false - dogsled: # checks assignments with too many blank identifiers; default is 2 max-blank-identifiers: 2 @@ -155,6 +137,24 @@ linters-settings: - io.Copy(*bytes.Buffer) - io.Copy(os.Stdout) + errchkjson: + # with check-error-free-encoding set to true, errchkjson does warn about errors + # from json encoding functions that are safe to be ignored, + # because they are not possible to happen (default false) + # + # if check-error-free-encoding is set to true and errcheck linter is enabled, + # it is recommended to add the following exceptions to prevent from false positives: + # + # linters-settings: + # errcheck: + # exclude-functions: + # - encoding/json.Marshal + # - encoding/json.MarshalIndent + # - (*encoding/json.Encoder).Encode + check-error-free-encoding: false + # if report-no-exported is true, encoding a struct without exported fields is reported as issue (default false) + report-no-exported: false + errorlint: # Check whether fmt.Errorf uses the %w verb for formatting errors. See the readme for caveats errorf: true diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index b380d3786176..b853f3ab6dfa 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -552,7 +552,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithURL("https://github.com/breml/bidichk"), linter.NewConfig(golinters.NewErrChkJSONFuncName(errchkjsonCfg)). WithSince("1.44.0"). - WithPresets(linter.PresetBugs, linter.PresetUnused). + WithPresets(linter.PresetBugs). WithLoadForGoAnalysis(). WithURL("https://github.com/breml/errchkjson"), From 0a6c8d1fdac6982039e18b1225b14b364992b510 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 23 Nov 2021 21:38:17 +0100 Subject: [PATCH 4/4] Fix order of imports --- pkg/golinters/errchkjson.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/golinters/errchkjson.go b/pkg/golinters/errchkjson.go index a01e0f0d37e6..6dc2b20046ff 100644 --- a/pkg/golinters/errchkjson.go +++ b/pkg/golinters/errchkjson.go @@ -1,8 +1,8 @@ package golinters import ( - "golang.org/x/tools/go/analysis" "github.com/breml/errchkjson" + "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis"