diff options
Diffstat (limited to 'vendor/github.com/breml')
| -rw-r--r-- | vendor/github.com/breml/bidichk/LICENSE | 21 | ||||
| -rw-r--r-- | vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go | 180 | ||||
| -rw-r--r-- | vendor/github.com/breml/bidichk/pkg/bidichk/version.go | 19 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/.gitignore | 29 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/.goreleaser.yml | 33 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/LICENSE | 21 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/README.md | 131 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/errchkjson.go | 348 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/go.mod | 11 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/go.sum | 30 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/noexported_error.go | 23 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/unsupported_error.go | 23 | ||||
| -rw-r--r-- | vendor/github.com/breml/errchkjson/version.go | 19 |
13 files changed, 888 insertions, 0 deletions
diff --git a/vendor/github.com/breml/bidichk/LICENSE b/vendor/github.com/breml/bidichk/LICENSE new file mode 100644 index 000000000..47a8419ce --- /dev/null +++ b/vendor/github.com/breml/bidichk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Lucas Bremgartner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go b/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go new file mode 100644 index 000000000..2e1e89934 --- /dev/null +++ b/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go @@ -0,0 +1,180 @@ +package bidichk + +import ( + "bytes" + "flag" + "fmt" + "go/token" + "os" + "sort" + "strings" + "unicode/utf8" + + "golang.org/x/tools/go/analysis" +) + +const ( + doc = "bidichk detects dangerous unicode character sequences" + disallowedDoc = `coma separated list of disallowed runes (full name or short name) + +Supported runes + +LEFT-TO-RIGHT-EMBEDDING, LRE (u+202A) +RIGHT-TO-LEFT-EMBEDDING, RLE (u+202B) +POP-DIRECTIONAL-FORMATTING, PDF (u+202C) +LEFT-TO-RIGHT-OVERRIDE, LRO (u+202D) +RIGHT-TO-LEFT-OVERRIDE, RLO (u+202E) +LEFT-TO-RIGHT-ISOLATE, LRI (u+2066) +RIGHT-TO-LEFT-ISOLATE, RLI (u+2067) +FIRST-STRONG-ISOLATE, FSI (u+2068) +POP-DIRECTIONAL-ISOLATE, PDI (u+2069) +` +) + +type disallowedRunes map[string]rune + +func (m disallowedRunes) String() string { + ss := make([]string, 0, len(m)) + for s := range m { + ss = append(ss, s) + } + sort.Strings(ss) + return strings.Join(ss, ",") +} + +func (m disallowedRunes) Set(s string) error { + ss := strings.FieldsFunc(s, func(c rune) bool { return c == ',' }) + if len(ss) == 0 { + return nil + } + + for k := range m { + delete(m, k) + } + + for _, v := range ss { + switch v { + case runeShortNameLRE, runeShortNameRLE, runeShortNamePDF, + runeShortNameLRO, runeShortNameRLO, runeShortNameLRI, + runeShortNameRLI, runeShortNameFSI, runeShortNamePDI: + v = shortNameLookup[v] + fallthrough + case runeNameLRE, runeNameRLE, runeNamePDF, + runeNameLRO, runeNameRLO, runeNameLRI, + runeNameRLI, runeNameFSI, runeNamePDI: + m[v] = runeLookup[v] + default: + return fmt.Errorf("unknown check name %q (see help for full list)", v) + } + } + return nil +} + +const ( + runeNameLRE = "LEFT-TO-RIGHT-EMBEDDING" + runeNameRLE = "RIGHT-TO-LEFT-EMBEDDING" + runeNamePDF = "POP-DIRECTIONAL-FORMATTING" + runeNameLRO = "LEFT-TO-RIGHT-OVERRIDE" + runeNameRLO = "RIGHT-TO-LEFT-OVERRIDE" + runeNameLRI = "LEFT-TO-RIGHT-ISOLATE" + runeNameRLI = "RIGHT-TO-LEFT-ISOLATE" + runeNameFSI = "FIRST-STRONG-ISOLATE" + runeNamePDI = "POP-DIRECTIONAL-ISOLATE" + + runeShortNameLRE = "LRE" // LEFT-TO-RIGHT-EMBEDDING + runeShortNameRLE = "RLE" // RIGHT-TO-LEFT-EMBEDDING + runeShortNamePDF = "PDF" // POP-DIRECTIONAL-FORMATTING + runeShortNameLRO = "LRO" // LEFT-TO-RIGHT-OVERRIDE + runeShortNameRLO = "RLO" // RIGHT-TO-LEFT-OVERRIDE + runeShortNameLRI = "LRI" // LEFT-TO-RIGHT-ISOLATE + runeShortNameRLI = "RLI" // RIGHT-TO-LEFT-ISOLATE + runeShortNameFSI = "FSI" // FIRST-STRONG-ISOLATE + runeShortNamePDI = "PDI" // POP-DIRECTIONAL-ISOLATE +) + +var runeLookup = map[string]rune{ + runeNameLRE: '\u202A', // LEFT-TO-RIGHT-EMBEDDING + runeNameRLE: '\u202B', // RIGHT-TO-LEFT-EMBEDDING + runeNamePDF: '\u202C', // POP-DIRECTIONAL-FORMATTING + runeNameLRO: '\u202D', // LEFT-TO-RIGHT-OVERRIDE + runeNameRLO: '\u202E', // RIGHT-TO-LEFT-OVERRIDE + runeNameLRI: '\u2066', // LEFT-TO-RIGHT-ISOLATE + runeNameRLI: '\u2067', // RIGHT-TO-LEFT-ISOLATE + runeNameFSI: '\u2068', // FIRST-STRONG-ISOLATE + runeNamePDI: '\u2069', // POP-DIRECTIONAL-ISOLATE +} + +var shortNameLookup = map[string]string{ + runeShortNameLRE: runeNameLRE, + runeShortNameRLE: runeNameRLE, + runeShortNamePDF: runeNamePDF, + runeShortNameLRO: runeNameLRO, + runeShortNameRLO: runeNameRLO, + runeShortNameLRI: runeNameLRI, + runeShortNameRLI: runeNameRLI, + runeShortNameFSI: runeNameFSI, + runeShortNamePDI: runeNamePDI, +} + +type bidichk struct { + disallowedRunes disallowedRunes +} + +// NewAnalyzer return a new bidichk analyzer. +func NewAnalyzer() *analysis.Analyzer { + bidichk := bidichk{} + bidichk.disallowedRunes = make(map[string]rune, len(runeLookup)) + for k, v := range runeLookup { + bidichk.disallowedRunes[k] = v + } + + a := &analysis.Analyzer{ + Name: "bidichk", + Doc: doc, + Run: bidichk.run, + } + + a.Flags.Init("bidichk", flag.ExitOnError) + a.Flags.Var(&bidichk.disallowedRunes, "disallowed-runes", disallowedDoc) + a.Flags.Var(versionFlag{}, "V", "print version and exit") + + return a +} + +func (b bidichk) run(pass *analysis.Pass) (interface{}, error) { + var err error + + pass.Fset.Iterate(func(f *token.File) bool { + if strings.HasPrefix(f.Name(), "$GOROOT") { + return true + } + + return b.check(f.Name(), f.Pos(0), pass) == nil + }) + + return nil, err +} + +func (b bidichk) check(filename string, pos token.Pos, pass *analysis.Pass) error { + body, err := os.ReadFile(filename) + if err != nil { + return err + } + + for name, r := range b.disallowedRunes { + start := 0 + for { + idx := bytes.IndexRune(body[start:], r) + if idx == -1 { + break + } + start += idx + + pass.Reportf(pos+token.Pos(start), "found dangerous unicode character sequence %s", name) + + start += utf8.RuneLen(r) + } + } + + return nil +} diff --git a/vendor/github.com/breml/bidichk/pkg/bidichk/version.go b/vendor/github.com/breml/bidichk/pkg/bidichk/version.go new file mode 100644 index 000000000..4cfc57dd1 --- /dev/null +++ b/vendor/github.com/breml/bidichk/pkg/bidichk/version.go @@ -0,0 +1,19 @@ +package bidichk + +import ( + "fmt" + "os" +) + +var Version = "bidichk version dev" + +type versionFlag struct{} + +func (versionFlag) IsBoolFlag() bool { return true } +func (versionFlag) Get() interface{} { return nil } +func (versionFlag) String() string { return "" } +func (versionFlag) Set(s string) error { + fmt.Println(Version) + os.Exit(0) + return nil +} diff --git a/vendor/github.com/breml/errchkjson/.gitignore b/vendor/github.com/breml/errchkjson/.gitignore new file mode 100644 index 000000000..0362de301 --- /dev/null +++ b/vendor/github.com/breml/errchkjson/.gitignore @@ -0,0 +1,29 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +/errchkjson +/cmd/errchkjson/errchkjson + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +coverage.html + +# Log files +*.log + +# Env files +.env + +# Exclude todo +TODO.md + +# Exclude IDE settings +.idea/ +*.iml +.vscode/ diff --git a/vendor/github.com/breml/errchkjson/.goreleaser.yml b/vendor/github.com/breml/errchkjson/.goreleaser.yml new file mode 100644 index 000000000..5f23690f1 --- /dev/null +++ b/vendor/github.com/breml/errchkjson/.goreleaser.yml @@ -0,0 +1,33 @@ +# This is an example .goreleaser.yml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy +builds: + - main: ./cmd/errchkjson + binary: errchkjson + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin +archives: + - name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + skip: true +release: + github: + owner: breml + name: errchkjson +gomod: + proxy: true diff --git a/vendor/github.com/breml/errchkjson/LICENSE b/vendor/github.com/breml/errchkjson/LICENSE new file mode 100644 index 000000000..08db5cb6f --- /dev/null +++ b/vendor/github.com/breml/errchkjson/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Lucas Bremgartner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/breml/errchkjson/README.md b/vendor/github.com/breml/errchkjson/README.md new file mode 100644 index 000000000..197959738 --- /dev/null +++ b/vendor/github.com/breml/errchkjson/README.md @@ -0,0 +1,131 @@ +# errchkjson + +[](https://github.com/breml/errchkjson/actions/workflows/ci.yml) [](https://goreportcard.com/report/github.com/breml/errchkjson) [](LICENSE) + +Checks types passed to the json encoding functions. Reports unsupported types and reports occurrences where the check for the returned error can be omitted. + +Consider this [http.Handler](https://pkg.go.dev/net/http#Handler): + +```Go +func JSONHelloWorld(w http.ResponseWriter, r *http.Request) { + response := struct { + Message string + Code int + }{ + Message: "Hello World", + Code: 200, + } + + body, err := json.Marshal(response) + if err != nil { + panic(err) // unreachable, because json encoding of a struct with just a string and an int will never return an error. + } + + w.Write(body) +} +``` + +Because the `panic` is not possible to happen, one might refactor the code like this: + +```Go +func JSONHelloWorld(w http.ResponseWriter, r *http.Request) { + response := struct { + Message string + Code int + }{ + Message: "Hello World", + Code: 200, + } + + body, _ := json.Marshal(response) + + w.Write(body) +} +``` + +This is ok, as long as the struct is not altered in such a way, that could potentially lead +to `json.Marshal` returning an error. + +`errchkjson` allows you to lint your code such that the above error returned from `json.Marshal` +can be omitted while still staying safe, because as soon as an unsafe type is added to the +response type, the linter will warn you. + +## Installation + +Download `errchkjson` from the [releases](https://github.com/breml/errchkjson/releases) or get the latest version from source with: + +```shell +go get github.com/breml/errchkjson/cmd/errchkjson +``` + +## Usage + +### Shell + +Check everything: + +```shell +errchkjson ./... +``` + +`errchkjson` also recognizes the following command-line options: + +The `-omit-safe` flag disables checking for safe returns of errors from json.Marshal + +## Types + +### Safe + +The following types are safe to use with [json encoding functions](https://pkg.go.dev/encoding/json), that is, the encoding to JSON can not fail: + +Safe basic types: + +* `bool` +* `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `uintptr` +* `string` +* Pointer type of the above listed basic types + +Composed types (struct, map, slice, array) are safe, if the type of the value is +safe. For structs, only exported fields are relevant. For maps, the key needs to be either an integer type or a string. + +### Unsafe + +The following types are unsafe to use with [json encoding functions](https://pkg.go.dev/encoding/json), that is, the encoding to JSON can fail (return an error): + +Unsafe basic types: + +* `float32`, `float64` +* `interface{}` +* Pointer type of the above listed basic types + +Any composed types (struct, map, slice, array) containing an unsafe basic type. + +If a type implements the `json.Marshaler` or `encoding.TextMarshaler` interface (e.g. `json.Number`). + +### Forbidden + +Forbidden basic types: + +* `complex64`, `complex128` +* `chan` +* `func` +* `unsafe.Pointer` + +Any composed types (struct, map, slice, array) containing a forbidden basic type. Any map +using a key with a forbidden type (`bool`, `float32`, `float64`, `struct`). + +## Accepted edge case + +For `encoding/json.MarshalIndent`, there is a (pathological) edge case, where this +function could [return an error](https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/encoding/json/scanner.go;drc=refs%2Ftags%2Fgo1.18;l=181) for an otherwise safe argument, if the argument has +a nesting depth larger than [`10000`](https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/encoding/json/scanner.go;drc=refs%2Ftags%2Fgo1.18;l=144) (as of Go 1.18). + +## Bugs found during development + +During the development of `errcheckjson`, the following issues in package `encoding/json` of the Go standard library have been found and PR have been merged: + +* [Issue #34154: encoding/json: string option (struct tag) on string field with SetEscapeHTML(false) escapes anyway](https://github.com/golang/go/issues/34154) +* [PR #34127: encoding/json: fix and optimize marshal for quoted string](https://github.com/golang/go/pull/34127) +* [Issue #34268: encoding/json: wrong encoding for json.Number field with string option (struct tag)](https://github.com/golang/go/issues/34268) +* [PR #34269: encoding/json: make Number with the ,string option marshal with quotes](https://github.com/golang/go/pull/34269) +* [PR #34272: encoding/json: validate strings when decoding into Number](https://github.com/golang/go/pull/34272) diff --git a/vendor/github.com/breml/errchkjson/errchkjson.go b/vendor/github.com/breml/errchkjson/errchkjson.go new file mode 100644 index 000000000..746709c76 --- /dev/null +++ b/vendor/github.com/breml/errchkjson/errchkjson.go @@ -0,0 +1,348 @@ +// Package errchkjson defines an Analyzer that finds places, where it is +// safe to omit checking the error returned from json.Marshal. +package errchkjson + +import ( + "flag" + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/types/typeutil" +) + +type errchkjson struct { + omitSafe bool // -omit-safe flag + reportNoExported bool // -report-no-exported flag +} + +// NewAnalyzer returns a new errchkjson analyzer. +func NewAnalyzer() *analysis.Analyzer { + errchkjson := &errchkjson{} + + a := &analysis.Analyzer{ + Name: "errchkjson", + Doc: "Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omitted.", + Run: errchkjson.run, + } + + a.Flags.Init("errchkjson", flag.ExitOnError) + a.Flags.BoolVar(&errchkjson.omitSafe, "omit-safe", false, "if omit-safe is true, checking of safe returns is omitted") + a.Flags.BoolVar(&errchkjson.reportNoExported, "report-no-exported", false, "if report-no-exported is true, encoding a struct without exported fields is reported as issue") + a.Flags.Var(versionFlag{}, "V", "print version and exit") + + return a +} + +func (e *errchkjson) run(pass *analysis.Pass) (interface{}, error) { + for _, file := range pass.Files { + ast.Inspect(file, func(n ast.Node) bool { + if n == nil { + return true + } + + // if the error is returned, it is the caller's responsibility to check + // the return value. + if _, ok := n.(*ast.ReturnStmt); ok { + return false + } + + ce, ok := n.(*ast.CallExpr) + if ok { + fn, _ := typeutil.Callee(pass.TypesInfo, ce).(*types.Func) + if fn == nil { + return true + } + + switch fn.FullName() { + case "encoding/json.Marshal", "encoding/json.MarshalIndent": + e.handleJSONMarshal(pass, ce, fn.FullName(), blankIdentifier, e.omitSafe) + case "(*encoding/json.Encoder).Encode": + e.handleJSONMarshal(pass, ce, fn.FullName(), blankIdentifier, true) + default: + e.inspectArgs(pass, ce.Args) + } + return false + } + + as, ok := n.(*ast.AssignStmt) + if !ok { + return true + } + + ce, ok = as.Rhs[0].(*ast.CallExpr) + if !ok { + return true + } + + fn, _ := typeutil.Callee(pass.TypesInfo, ce).(*types.Func) + if fn == nil { + return true + } + + switch fn.FullName() { + case "encoding/json.Marshal", "encoding/json.MarshalIndent": + e.handleJSONMarshal(pass, ce, fn.FullName(), evaluateMarshalErrorTarget(as.Lhs[1]), e.omitSafe) + case "(*encoding/json.Encoder).Encode": + e.handleJSONMarshal(pass, ce, fn.FullName(), evaluateMarshalErrorTarget(as.Lhs[0]), true) + default: + return true + } + return false + }) + } + + return nil, nil +} + +func evaluateMarshalErrorTarget(n ast.Expr) marshalErrorTarget { + if errIdent, ok := n.(*ast.Ident); ok { + if errIdent.Name == "_" { + return blankIdentifier + } + } + return variableAssignment +} + +type marshalErrorTarget int + +const ( + blankIdentifier = iota // the returned error from the JSON marshal function is assigned to the blank identifier "_". + variableAssignment // the returned error from the JSON marshal function is assigned to a variable. + functionArgument // the returned error from the JSON marshal function is passed to an other function as argument. +) + +func (e *errchkjson) handleJSONMarshal(pass *analysis.Pass, ce *ast.CallExpr, fnName string, errorTarget marshalErrorTarget, omitSafe bool) { + t := pass.TypesInfo.TypeOf(ce.Args[0]) + if t == nil { + // Not sure, if this is at all possible + if errorTarget == blankIdentifier { + pass.Reportf(ce.Pos(), "Type of argument to `%s` could not be evaluated and error return value is not checked", fnName) + } + return + } + + if _, ok := t.(*types.Pointer); ok { + t = t.(*types.Pointer).Elem() + } + + err := e.jsonSafe(t, 0, map[types.Type]struct{}{}) + if err != nil { + if _, ok := err.(unsupported); ok { + pass.Reportf(ce.Pos(), "`%s` for %v", fnName, err) + return + } + if _, ok := err.(noexported); ok { + pass.Reportf(ce.Pos(), "Error argument passed to `%s` does not contain any exported field", fnName) + } + // Only care about unsafe types if they are assigned to the blank identifier. + if errorTarget == blankIdentifier { + pass.Reportf(ce.Pos(), "Error return value of `%s` is not checked: %v", fnName, err) + } + } + if err == nil && errorTarget == variableAssignment && !omitSafe { + pass.Reportf(ce.Pos(), "Error return value of `%s` is checked but passed argument is safe", fnName) + } + // Report an error, if err for json.Marshal is not checked and safe types are omitted + if err == nil && errorTarget == blankIdentifier && omitSafe { + pass.Reportf(ce.Pos(), "Error return value of `%s` is not checked", fnName) + } +} + +const ( + allowedBasicTypes = types.IsBoolean | types.IsInteger | types.IsString + allowedMapKeyBasicTypes = types.IsInteger | types.IsString + unsupportedBasicTypes = types.IsComplex +) + +func (e *errchkjson) jsonSafe(t types.Type, level int, seenTypes map[types.Type]struct{}) error { + if _, ok := seenTypes[t]; ok { + return nil + } + + if types.Implements(t, textMarshalerInterface()) || types.Implements(t, jsonMarshalerInterface()) { + return fmt.Errorf("unsafe type `%s` found", t.String()) + } + + switch ut := t.Underlying().(type) { + case *types.Basic: + if ut.Info()&allowedBasicTypes > 0 { // bool, int-family, string + if ut.Info()&types.IsString > 0 && t.String() == "encoding/json.Number" { + return fmt.Errorf("unsafe type `%s` found", t.String()) + } + return nil + } + if ut.Info()&unsupportedBasicTypes > 0 { // complex64, complex128 + return newUnsupportedError(fmt.Errorf("unsupported type `%s` found", ut.String())) + } + switch ut.Kind() { + case types.UntypedNil: + return nil + case types.UnsafePointer: + return newUnsupportedError(fmt.Errorf("unsupported type `%s` found", ut.String())) + default: + // E.g. float32, float64 + return fmt.Errorf("unsafe type `%s` found", ut.String()) + } + + case *types.Array: + err := e.jsonSafe(ut.Elem(), level+1, seenTypes) + if err != nil { + return err + } + return nil + + case *types.Slice: + err := e.jsonSafe(ut.Elem(), level+1, seenTypes) + if err != nil { + return err + } + return nil + + case *types.Struct: + seenTypes[t] = struct{}{} + exported := 0 + for i := 0; i < ut.NumFields(); i++ { + if !ut.Field(i).Exported() { + // Unexported fields can be ignored + continue + } + if tag, ok := reflect.StructTag(ut.Tag(i)).Lookup("json"); ok { + if tag == "-" { + // Fields omitted in json can be ignored + continue + } + } + err := e.jsonSafe(ut.Field(i).Type(), level+1, seenTypes) + if err != nil { + return err + } + exported++ + } + if e.reportNoExported && level == 0 && exported == 0 { + return newNoexportedError(fmt.Errorf("struct does not export any field")) + } + return nil + + case *types.Pointer: + err := e.jsonSafe(ut.Elem(), level+1, seenTypes) + if err != nil { + return err + } + return nil + + case *types.Map: + err := jsonSafeMapKey(ut.Key()) + if err != nil { + return err + } + err = e.jsonSafe(ut.Elem(), level+1, seenTypes) + if err != nil { + return err + } + return nil + + case *types.Chan, *types.Signature: + // Types that are not supported for encoding to json: + return newUnsupportedError(fmt.Errorf("unsupported type `%s` found", ut.String())) + + default: + // Types that are not supported for encoding to json or are not completely safe, like: interfaces + return fmt.Errorf("unsafe type `%s` found", t.String()) + } +} + +func jsonSafeMapKey(t types.Type) error { + 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) { + case *types.Basic: + if ut.Info()&types.IsString > 0 && t.String() == "encoding/json.Number" { + return fmt.Errorf("unsafe type `%s` as map key found", t.String()) + } + if ut.Info()&allowedMapKeyBasicTypes > 0 { // bool, int-family, string + return nil + } + // E.g. bool, float32, float64, complex64, complex128 + return newUnsupportedError(fmt.Errorf("unsupported type `%s` as map key found", t.String())) + case *types.Interface: + return fmt.Errorf("unsafe type `%s` as map key found", t.String()) + default: + // E.g. struct composed solely of basic types, that are comparable + return newUnsupportedError(fmt.Errorf("unsupported type `%s` as map key found", t.String())) + } +} + +func (e *errchkjson) inspectArgs(pass *analysis.Pass, args []ast.Expr) { + for _, a := range args { + ast.Inspect(a, func(n ast.Node) bool { + if n == nil { + return true + } + + ce, ok := n.(*ast.CallExpr) + if !ok { + return false + } + + fn, _ := typeutil.Callee(pass.TypesInfo, ce).(*types.Func) + if fn == nil { + return true + } + + switch fn.FullName() { + case "encoding/json.Marshal", "encoding/json.MarshalIndent": + e.handleJSONMarshal(pass, ce, fn.FullName(), functionArgument, e.omitSafe) + case "(*encoding/json.Encoder).Encode": + e.handleJSONMarshal(pass, ce, fn.FullName(), functionArgument, true) + default: + e.inspectArgs(pass, ce.Args) + } + return false + }) + } +} + +// Construct *types.Interface for interface encoding.TextMarshaler +// type TextMarshaler interface { +// MarshalText() (text []byte, err error) +// } +// +func textMarshalerInterface() *types.Interface { + textMarshalerInterface := types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalText", types.NewSignature( + nil, nil, types.NewTuple( + types.NewVar(token.NoPos, nil, "text", + types.NewSlice( + types.Universe.Lookup("byte").Type())), + types.NewVar(token.NoPos, nil, "err", types.Universe.Lookup("error").Type())), + false)), + }, nil) + textMarshalerInterface.Complete() + + 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/vendor/github.com/breml/errchkjson/go.mod b/vendor/github.com/breml/errchkjson/go.mod new file mode 100644 index 000000000..75704c4bb --- /dev/null +++ b/vendor/github.com/breml/errchkjson/go.mod @@ -0,0 +1,11 @@ +module github.com/breml/errchkjson + +go 1.17 + +require golang.org/x/tools v0.1.10 + +require ( + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) diff --git a/vendor/github.com/breml/errchkjson/go.sum b/vendor/github.com/breml/errchkjson/go.sum new file mode 100644 index 000000000..0e4a1ee18 --- /dev/null +++ b/vendor/github.com/breml/errchkjson/go.sum @@ -0,0 +1,30 @@ +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/breml/errchkjson/noexported_error.go b/vendor/github.com/breml/errchkjson/noexported_error.go new file mode 100644 index 000000000..07b7a07d2 --- /dev/null +++ b/vendor/github.com/breml/errchkjson/noexported_error.go @@ -0,0 +1,23 @@ +package errchkjson + +type noexported interface { + noexported() +} + +var _ noexported = noexportedError{} + +type noexportedError struct { + err error +} + +func newNoexportedError(err error) error { + return noexportedError{ + err: err, + } +} + +func (u noexportedError) noexported() {} + +func (u noexportedError) Error() string { + return u.err.Error() +} diff --git a/vendor/github.com/breml/errchkjson/unsupported_error.go b/vendor/github.com/breml/errchkjson/unsupported_error.go new file mode 100644 index 000000000..1a38c3f53 --- /dev/null +++ b/vendor/github.com/breml/errchkjson/unsupported_error.go @@ -0,0 +1,23 @@ +package errchkjson + +type unsupported interface { + unsupported() +} + +var _ unsupported = unsupportedError{} + +type unsupportedError struct { + err error +} + +func newUnsupportedError(err error) error { + return unsupportedError{ + err: err, + } +} + +func (u unsupportedError) unsupported() {} + +func (u unsupportedError) Error() string { + return u.err.Error() +} diff --git a/vendor/github.com/breml/errchkjson/version.go b/vendor/github.com/breml/errchkjson/version.go new file mode 100644 index 000000000..77d8ef8bb --- /dev/null +++ b/vendor/github.com/breml/errchkjson/version.go @@ -0,0 +1,19 @@ +package errchkjson + +import ( + "fmt" + "os" +) + +var Version = "errchkjson version dev" + +type versionFlag struct{} + +func (versionFlag) IsBoolFlag() bool { return true } +func (versionFlag) Get() interface{} { return nil } +func (versionFlag) String() string { return "" } +func (versionFlag) Set(s string) error { + fmt.Println(Version) + os.Exit(0) + return nil +} |
