From b2f2446b46bf02821d90ebedadae2bf7ae0e880e Mon Sep 17 00:00:00 2001 From: Taras Madan Date: Mon, 5 Sep 2022 14:27:54 +0200 Subject: go.mod, vendor: update (#3358) * go.mod, vendor: remove unnecessary dependencies Commands: 1. go mod tidy 2. go mod vendor * go.mod, vendor: update cloud.google.com/go Commands: 1. go get -u cloud.google.com/go 2. go mod tidy 3. go mod vendor * go.mod, vendor: update cloud.google.com/* Commands: 1. go get -u cloud.google.com/storage cloud.google.com/logging 2. go mod tidy 3. go mod vendor * go.mod, .golangci.yml, vendor: update *lint* Commands: 1. go get -u golang.org/x/tools github.com/golangci/golangci-lint@v1.47.0 2. go mod tidy 3. go mod vendor 4. edit .golangci.yml to suppress new errors (resolved in the same PR later) * all: fix lint errors hash.go: copy() recommended by gosimple parse.go: ent is never nil verifier.go: signal.Notify() with unbuffered channel is bad. Have no idea why. * .golangci.yml: adjust godot rules check-all is deprecated, but still work if you're hesitating too - I'll remove this commit --- .../polyfloyd/go-errorlint/errorlint/allowed.go | 190 +++++++++++++++++++++ .../polyfloyd/go-errorlint/errorlint/analysis.go | 67 +++++++- .../polyfloyd/go-errorlint/errorlint/lint.go | 54 +++++- .../polyfloyd/go-errorlint/errorlint/printf.go | 131 ++++++++++++++ 4 files changed, 427 insertions(+), 15 deletions(-) create mode 100644 vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go create mode 100644 vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go (limited to 'vendor/github.com/polyfloyd') diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go new file mode 100644 index 000000000..7fe4c38cc --- /dev/null +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go @@ -0,0 +1,190 @@ +package errorlint + +import ( + "fmt" + "go/ast" +) + +var allowedErrors = []struct { + err string + fun string +}{ + // pkg/archive/tar + {err: "io.EOF", fun: "(*tar.Reader).Next"}, + {err: "io.EOF", fun: "(*tar.Reader).Read"}, + // pkg/bufio + {err: "io.EOF", fun: "(*bufio.Reader).Discard"}, + {err: "io.EOF", fun: "(*bufio.Reader).Peek"}, + {err: "io.EOF", fun: "(*bufio.Reader).Read"}, + {err: "io.EOF", fun: "(*bufio.Reader).ReadByte"}, + {err: "io.EOF", fun: "(*bufio.Reader).ReadBytes"}, + {err: "io.EOF", fun: "(*bufio.Reader).ReadLine"}, + {err: "io.EOF", fun: "(*bufio.Reader).ReadSlice"}, + {err: "io.EOF", fun: "(*bufio.Reader).ReadString"}, + {err: "io.EOF", fun: "(*bufio.Scanner).Scan"}, + // pkg/bytes + {err: "io.EOF", fun: "(*bytes.Buffer).Read"}, + {err: "io.EOF", fun: "(*bytes.Buffer).ReadByte"}, + {err: "io.EOF", fun: "(*bytes.Buffer).ReadBytes"}, + {err: "io.EOF", fun: "(*bytes.Buffer).ReadRune"}, + {err: "io.EOF", fun: "(*bytes.Buffer).ReadString"}, + {err: "io.EOF", fun: "(*bytes.Reader).Read"}, + {err: "io.EOF", fun: "(*bytes.Reader).ReadAt"}, + {err: "io.EOF", fun: "(*bytes.Reader).ReadByte"}, + {err: "io.EOF", fun: "(*bytes.Reader).ReadRune"}, + {err: "io.EOF", fun: "(*bytes.Reader).ReadString"}, + // pkg/database/sql + {err: "sql.ErrNoRows", fun: "(*database/sql.Row).Scan"}, + // pkg/io + {err: "io.EOF", fun: "(io.Reader).Read"}, + {err: "io.ErrClosedPipe", fun: "(*io.PipeWriter).Write"}, + {err: "io.ErrShortBuffer", fun: "io.ReadAtLeast"}, + {err: "io.ErrUnexpectedEOF", fun: "io.ReadAtLeast"}, + {err: "io.ErrUnexpectedEOF", fun: "io.ReadFull"}, + // pkg/net/http + {err: "http.ErrServerClosed", fun: "(*net/http.Server).ListenAndServe"}, + {err: "http.ErrServerClosed", fun: "(*net/http.Server).ListenAndServeTLS"}, + {err: "http.ErrServerClosed", fun: "(*net/http.Server).Serve"}, + {err: "http.ErrServerClosed", fun: "(*net/http.Server).ServeTLS"}, + {err: "http.ErrServerClosed", fun: "http.ListenAndServe"}, + {err: "http.ErrServerClosed", fun: "http.ListenAndServeTLS"}, + {err: "http.ErrServerClosed", fun: "http.Serve"}, + {err: "http.ErrServerClosed", fun: "http.ServeTLS"}, + // pkg/os + {err: "io.EOF", fun: "(*os.File).Read"}, + {err: "io.EOF", fun: "(*os.File).ReadAt"}, + {err: "io.EOF", fun: "(*os.File).ReadDir"}, + {err: "io.EOF", fun: "(*os.File).Readdir"}, + {err: "io.EOF", fun: "(*os.File).Readdirnames"}, + // pkg/strings + {err: "io.EOF", fun: "(*strings.Reader).Read"}, + {err: "io.EOF", fun: "(*strings.Reader).ReadAt"}, + {err: "io.EOF", fun: "(*strings.Reader).ReadByte"}, + {err: "io.EOF", fun: "(*strings.Reader).ReadRune"}, +} + +func isAllowedErrAndFunc(err, fun string) bool { + for _, allow := range allowedErrors { + if allow.fun == fun && allow.err == err { + return true + } + } + return false +} + +func isAllowedErrorComparison(info *TypesInfoExt, binExpr *ast.BinaryExpr) bool { + var errName string // `.`, e.g. `io.EOF` + var callExprs []*ast.CallExpr + + // Figure out which half of the expression is the returned error and which + // half is the presumed error declaration. + for _, expr := range []ast.Expr{binExpr.X, binExpr.Y} { + switch t := expr.(type) { + case *ast.SelectorExpr: + // A selector which we assume refers to a staticaly declared error + // in a package. + errName = selectorToString(t) + case *ast.Ident: + // Identifier, most likely to be the `err` variable or whatever + // produces it. + callExprs = assigningCallExprs(info, t) + case *ast.CallExpr: + callExprs = append(callExprs, t) + } + } + + // Unimplemented or not sure, disallow the expression. + if errName == "" || len(callExprs) == 0 { + return false + } + + // Map call expressions to the function name format of the allow list. + functionNames := make([]string, len(callExprs)) + for i, callExpr := range callExprs { + functionSelector, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + // If the function is not a selector it is not an Std function that is + // allowed. + return false + } + if sel, ok := info.Selections[functionSelector]; ok { + functionNames[i] = fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name()) + } else { + // If there is no selection, assume it is a package. + functionNames[i] = selectorToString(callExpr.Fun.(*ast.SelectorExpr)) + } + } + + // All assignments done must be allowed. + for _, funcName := range functionNames { + if !isAllowedErrAndFunc(errName, funcName) { + return false + } + } + return true +} + +// assigningCallExprs finds all *ast.CallExpr nodes that are part of an +// *ast.AssignStmt that assign to the subject identifier. +func assigningCallExprs(info *TypesInfoExt, subject *ast.Ident) []*ast.CallExpr { + if subject.Obj == nil { + return nil + } + + // Find other identifiers that reference this same object. Make sure to + // exclude the subject identifier as it will cause an infinite recursion + // and is being used in a read operation anyway. + sobj := info.ObjectOf(subject) + identifiers := []*ast.Ident{} + for _, ident := range info.IdentifiersForObject[sobj] { + if subject.Pos() != ident.Pos() { + identifiers = append(identifiers, ident) + } + } + + // Find out whether the identifiers are part of an assignment statement. + var callExprs []*ast.CallExpr + for _, ident := range identifiers { + parent := info.NodeParent[ident] + switch declT := parent.(type) { + case *ast.AssignStmt: + // The identifier is LHS of an assignment. + assignment := declT + + assigningExpr := assignment.Rhs[0] + // If the assignment is comprised of multiple expressions, find out + // which LHS expression we should use by finding its index in the LHS. + if len(assignment.Rhs) > 1 { + for i, lhs := range assignment.Lhs { + if subject.Name == lhs.(*ast.Ident).Name { + assigningExpr = assignment.Rhs[i] + break + } + } + } + + switch assignT := assigningExpr.(type) { + case *ast.CallExpr: + // Found the function call. + callExprs = append(callExprs, assignT) + case *ast.Ident: + // Skip assignments here the RHS points to the same object as the subject. + if assignT.Obj == subject.Obj { + continue + } + // The subject was the result of assigning from another identifier. + callExprs = append(callExprs, assigningCallExprs(info, assignT)...) + default: + // TODO: inconclusive? + } + } + } + return callExprs +} + +func selectorToString(selExpr *ast.SelectorExpr) string { + if ident, ok := selExpr.X.(*ast.Ident); ok { + return ident.Name + "." + selExpr.Sel.Name + } + return "" +} diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go index 20d1c1e7c..58ddb2632 100644 --- a/vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go @@ -2,6 +2,8 @@ package errorlint import ( "flag" + "go/ast" + "go/types" "sort" "golang.org/x/tools/go/analysis" @@ -17,24 +19,33 @@ func NewAnalyzer() *analysis.Analyzer { } var ( - flagSet flag.FlagSet - checkErrorf bool + flagSet flag.FlagSet + checkComparison bool + checkAsserts bool + checkErrorf bool ) func init() { + flagSet.BoolVar(&checkComparison, "comparison", true, "Check for plain error comparisons") + flagSet.BoolVar(&checkAsserts, "asserts", true, "Check for plain type assertions and type switches") flagSet.BoolVar(&checkErrorf, "errorf", false, "Check whether fmt.Errorf uses the %w verb for formatting errors. See the readme for caveats") } func run(pass *analysis.Pass) (interface{}, error) { lints := []Lint{} + extInfo := newTypesInfoExt(pass.TypesInfo) + if checkComparison { + l := LintErrorComparisons(pass.Fset, extInfo) + lints = append(lints, l...) + } + if checkAsserts { + l := LintErrorTypeAssertions(pass.Fset, *pass.TypesInfo) + lints = append(lints, l...) + } if checkErrorf { l := LintFmtErrorfCalls(pass.Fset, *pass.TypesInfo) lints = append(lints, l...) } - l := LintErrorComparisons(pass.Fset, *pass.TypesInfo) - lints = append(lints, l...) - l = LintErrorTypeAssertions(pass.Fset, *pass.TypesInfo) - lints = append(lints, l...) sort.Sort(ByPosition(lints)) for _, l := range lints { @@ -42,3 +53,47 @@ func run(pass *analysis.Pass) (interface{}, error) { } return nil, nil } + +type TypesInfoExt struct { + types.Info + + // Maps AST nodes back to the node they are contain within. + NodeParent map[ast.Node]ast.Node + + // Maps an object back to all identifiers to refer to it. + IdentifiersForObject map[types.Object][]*ast.Ident +} + +func newTypesInfoExt(info *types.Info) *TypesInfoExt { + nodeParent := map[ast.Node]ast.Node{} + for node := range info.Scopes { + file, ok := node.(*ast.File) + if !ok { + continue + } + stack := []ast.Node{file} + ast.Inspect(file, func(n ast.Node) bool { + nodeParent[n] = stack[len(stack)-1] + if n == nil { + stack = stack[:len(stack)-1] + } else { + stack = append(stack, n) + } + return true + }) + } + + identifiersForObject := map[types.Object][]*ast.Ident{} + for node, obj := range info.Defs { + identifiersForObject[obj] = append(identifiersForObject[obj], node) + } + for node, obj := range info.Uses { + identifiersForObject[obj] = append(identifiersForObject[obj], node) + } + + return &TypesInfoExt{ + Info: *info, + NodeParent: nodeParent, + IdentifiersForObject: identifiersForObject, + } +} diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go index e9f889c93..5301a3f22 100644 --- a/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go @@ -6,7 +6,6 @@ import ( "go/constant" "go/token" "go/types" - "regexp" ) type Lint struct { @@ -48,11 +47,11 @@ func LintFmtErrorfCalls(fset *token.FileSet, info types.Info) []Lint { var lintArg ast.Expr args := call.Args[1:] for i := 0; i < len(args) && i < len(formatVerbs); i++ { - if info.Types[args[i]].Type.String() != "error" && !isErrorStringCall(info, args[i]) { + if !implementsError(info.Types[args[i]].Type) && !isErrorStringCall(info, args[i]) { continue } - if formatVerbs[i] == "%w" { + if formatVerbs[i] == "w" { lintArg = nil break } @@ -85,6 +84,9 @@ func isErrorStringCall(info types.Info, expr ast.Expr) bool { return false } +// printfFormatStringVerbs returns a normalized list of all the verbs that are used per argument to +// the printf function. The index of each returned element corresponds to index of the respective +// argument. func printfFormatStringVerbs(info types.Info, call *ast.CallExpr) ([]string, bool) { if len(call.Args) <= 1 { return nil, false @@ -96,10 +98,23 @@ func printfFormatStringVerbs(info types.Info, call *ast.CallExpr) ([]string, boo } formatString := constant.StringVal(info.Types[strLit].Value) - // Naive format string argument verb. This does not take modifiers such as - // padding into account... - re := regexp.MustCompile(`%[^%]`) - return re.FindAllString(formatString, -1), true + pp := printfParser{str: formatString} + verbs, err := pp.ParseAllVerbs() + if err != nil { + return nil, false + } + orderedVerbs := verbOrder(verbs, len(call.Args)-1) + + resolvedVerbs := make([]string, len(orderedVerbs)) + for i, vv := range orderedVerbs { + for _, v := range vv { + resolvedVerbs[i] = v.format + if v.format == "w" { + break + } + } + } + return resolvedVerbs, true } func isFmtErrorfCallExpr(info types.Info, expr ast.Expr) (*ast.CallExpr, bool) { @@ -121,7 +136,7 @@ func isFmtErrorfCallExpr(info types.Info, expr ast.Expr) (*ast.CallExpr, bool) { return nil, false } -func LintErrorComparisons(fset *token.FileSet, info types.Info) []Lint { +func LintErrorComparisons(fset *token.FileSet, info *TypesInfoExt) []Lint { lints := []Lint{} for expr := range info.Types { @@ -138,7 +153,11 @@ func LintErrorComparisons(fset *token.FileSet, info types.Info) []Lint { continue } // Find comparisons of which one side is a of type error. - if !isErrorComparison(info, binExpr) { + if !isErrorComparison(info.Info, binExpr) { + continue + } + + if isAllowedErrorComparison(info, binExpr) { continue } @@ -243,3 +262,20 @@ func isErrorTypeAssertion(info types.Info, typeAssert *ast.TypeAssertExpr) bool t := info.Types[typeAssert.X] return t.Type.String() == "error" } + +func implementsError(t types.Type) bool { + mset := types.NewMethodSet(t) + + for i := 0; i < mset.Len(); i++ { + if mset.At(i).Kind() != types.MethodVal { + continue + } + + obj := mset.At(i).Obj() + if obj.Name() == "Error" && obj.Type().String() == "func() string" { + return true + } + } + + return false +} diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go new file mode 100644 index 000000000..f3d81b571 --- /dev/null +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go @@ -0,0 +1,131 @@ +package errorlint + +import ( + "fmt" + "io" + "strconv" + "strings" +) + +func verbOrder(verbs []verb, numArgs int) [][]verb { + orderedVerbs := make([][]verb, numArgs) + i := 0 + for _, v := range verbs { + if v.index != -1 { + i = v.index - 1 + } + orderedVerbs[i] = append(orderedVerbs[i], v) + verbs = verbs[1:] + i++ + } + return orderedVerbs +} + +type verb struct { + format string + index int +} + +type printfParser struct { + str string +} + +func (pp *printfParser) ParseAllVerbs() ([]verb, error) { + verbs := []verb{} + for { + verb, err := pp.parseVerb() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + verbs = append(verbs, *verb) + } + return verbs, nil +} + +func (pp *printfParser) parseVerb() (*verb, error) { + if err := pp.skipToPercent(); err != nil { + return nil, err + } + if pp.next() != '%' { + return nil, fmt.Errorf("expected '%%'") + } + + index := -1 + for { + switch pp.peek() { + case '%': + pp.next() + return pp.parseVerb() + case '+', '#': + pp.next() + continue + case '[': + var err error + index, err = pp.parseIndex() + if err != nil { + return nil, err + } + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': + pp.parsePrecision() + case 0: + return nil, io.EOF + } + break + } + + format := pp.next() + + return &verb{format: string(format), index: index}, nil +} + +func (pp *printfParser) parseIndex() (int, error) { + if pp.next() != '[' { + return -1, fmt.Errorf("expected '['") + } + end := strings.Index(pp.str, "]") + if end == -1 { + return -1, fmt.Errorf("unterminated indexed verb") + } + index, err := strconv.Atoi(pp.str[:end]) + if err != nil { + return -1, err + } + pp.str = pp.str[end+1:] + return index, nil +} + +func (pp *printfParser) parsePrecision() { + for { + if r := pp.peek(); (r < '0' || '9' < r) && r != '.' { + break + } + pp.next() + } +} + +func (pp *printfParser) skipToPercent() error { + i := strings.Index(pp.str, "%") + if i == -1 { + return io.EOF + } + pp.str = pp.str[i:] + return nil +} + +func (pp *printfParser) peek() rune { + if len(pp.str) == 0 { + return 0 + } + return rune(pp.str[0]) +} + +func (pp *printfParser) next() rune { + if len(pp.str) == 0 { + return 0 + } + r := rune(pp.str[0]) + pp.str = pp.str[1:] + return r +} -- cgit mrf-deployment