aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/polyfloyd
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/polyfloyd')
-rw-r--r--vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go190
-rw-r--r--vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go67
-rw-r--r--vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go54
-rw-r--r--vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go131
4 files changed, 427 insertions, 15 deletions
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 // `<package>.<name>`, 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
+}