aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/golangci/errcheck/internal
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-07-04 11:12:55 +0200
committerDmitry Vyukov <dvyukov@google.com>2020-07-04 15:05:30 +0200
commitc7d7f10bdff703e4a3c0414e8a33d4e45c91eb35 (patch)
tree0dff0ee1f98dbfa3ad8776112053a450d176592b /vendor/github.com/golangci/errcheck/internal
parent9573094ce235bd9afe88f5da27a47dd6bcc1e13b (diff)
go.mod: vendor golangci-lint
Diffstat (limited to 'vendor/github.com/golangci/errcheck/internal')
-rw-r--r--vendor/github.com/golangci/errcheck/internal/errcheck/errcheck.go538
1 files changed, 538 insertions, 0 deletions
diff --git a/vendor/github.com/golangci/errcheck/internal/errcheck/errcheck.go b/vendor/github.com/golangci/errcheck/internal/errcheck/errcheck.go
new file mode 100644
index 000000000..2a698fcee
--- /dev/null
+++ b/vendor/github.com/golangci/errcheck/internal/errcheck/errcheck.go
@@ -0,0 +1,538 @@
+// Package errcheck is the library used to implement the errcheck command-line tool.
+//
+// Note: The API of this package has not been finalized and may change at any point.
+package errcheck
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/build"
+ "go/token"
+ "go/types"
+ "os"
+ "regexp"
+ "sort"
+ "strings"
+ "sync"
+
+ "golang.org/x/tools/go/loader"
+)
+
+var errorType *types.Interface
+
+func init() {
+ errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
+
+}
+
+var (
+ // ErrNoGoFiles is returned when CheckPackage is run on a package with no Go source files
+ ErrNoGoFiles = errors.New("package contains no go source files")
+)
+
+// UncheckedError indicates the position of an unchecked error return.
+type UncheckedError struct {
+ Pos token.Position
+ Line string
+ FuncName string
+}
+
+// UncheckedErrors is returned from the CheckPackage function if the package contains
+// any unchecked errors.
+// Errors should be appended using the Append method, which is safe to use concurrently.
+type UncheckedErrors struct {
+ mu sync.Mutex
+
+ // Errors is a list of all the unchecked errors in the package.
+ // Printing an error reports its position within the file and the contents of the line.
+ Errors []UncheckedError
+}
+
+func (e *UncheckedErrors) Append(errors ...UncheckedError) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.Errors = append(e.Errors, errors...)
+}
+
+func (e *UncheckedErrors) Error() string {
+ return fmt.Sprintf("%d unchecked errors", len(e.Errors))
+}
+
+// Len is the number of elements in the collection.
+func (e *UncheckedErrors) Len() int { return len(e.Errors) }
+
+// Swap swaps the elements with indexes i and j.
+func (e *UncheckedErrors) Swap(i, j int) { e.Errors[i], e.Errors[j] = e.Errors[j], e.Errors[i] }
+
+type byName struct{ *UncheckedErrors }
+
+// Less reports whether the element with index i should sort before the element with index j.
+func (e byName) Less(i, j int) bool {
+ ei, ej := e.Errors[i], e.Errors[j]
+
+ pi, pj := ei.Pos, ej.Pos
+
+ if pi.Filename != pj.Filename {
+ return pi.Filename < pj.Filename
+ }
+ if pi.Line != pj.Line {
+ return pi.Line < pj.Line
+ }
+ if pi.Column != pj.Column {
+ return pi.Column < pj.Column
+ }
+
+ return ei.Line < ej.Line
+}
+
+type Checker struct {
+ // ignore is a map of package names to regular expressions. Identifiers from a package are
+ // checked against its regular expressions and if any of the expressions match the call
+ // is not checked.
+ Ignore map[string]*regexp.Regexp
+
+ // If blank is true then assignments to the blank identifier are also considered to be
+ // ignored errors.
+ Blank bool
+
+ // If asserts is true then ignored type assertion results are also checked
+ Asserts bool
+
+ // build tags
+ Tags []string
+
+ Verbose bool
+
+ // If true, checking of _test.go files is disabled
+ WithoutTests bool
+
+ exclude map[string]bool
+}
+
+func NewChecker() *Checker {
+ c := Checker{}
+ c.SetExclude(map[string]bool{})
+ return &c
+}
+
+func (c *Checker) SetExclude(l map[string]bool) {
+ // Default exclude for stdlib functions
+ c.exclude = map[string]bool{
+ "math/rand.Read": true,
+ "(*math/rand.Rand).Read": true,
+
+ "(*bytes.Buffer).Write": true,
+ "(*bytes.Buffer).WriteByte": true,
+ "(*bytes.Buffer).WriteRune": true,
+ "(*bytes.Buffer).WriteString": true,
+
+ "(*strings.Builder).Write": true,
+ "(*strings.Builder).WriteByte": true,
+ "(*strings.Builder).WriteRune": true,
+ "(*strings.Builder).WriteString": true,
+ }
+ for k := range l {
+ c.exclude[k] = true
+ }
+}
+
+func (c *Checker) logf(msg string, args ...interface{}) {
+ if c.Verbose {
+ fmt.Fprintf(os.Stderr, msg+"\n", args...)
+ }
+}
+
+func (c *Checker) load(paths ...string) (*loader.Program, error) {
+ ctx := build.Default
+ for _, tag := range c.Tags {
+ ctx.BuildTags = append(ctx.BuildTags, tag)
+ }
+ loadcfg := loader.Config{
+ Build: &ctx,
+ }
+ rest, err := loadcfg.FromArgs(paths, !c.WithoutTests)
+ if err != nil {
+ return nil, fmt.Errorf("could not parse arguments: %s", err)
+ }
+ if len(rest) > 0 {
+ return nil, fmt.Errorf("unhandled extra arguments: %v", rest)
+ }
+
+ return loadcfg.Load()
+}
+
+func (c *Checker) CheckProgram(program *loader.Program) error {
+ var wg sync.WaitGroup
+ u := &UncheckedErrors{}
+ for _, pkgInfo := range program.InitialPackages() {
+ if pkgInfo.Pkg.Path() == "unsafe" { // not a real package
+ continue
+ }
+
+ wg.Add(1)
+
+ go func(pkgInfo *loader.PackageInfo) {
+ defer wg.Done()
+ c.logf("Checking %s", pkgInfo.Pkg.Path())
+
+ v := &visitor{
+ prog: program,
+ pkg: pkgInfo,
+ ignore: c.Ignore,
+ blank: c.Blank,
+ asserts: c.Asserts,
+ lines: make(map[string][]string),
+ exclude: c.exclude,
+ errors: []UncheckedError{},
+ }
+
+ for _, astFile := range v.pkg.Files {
+ ast.Walk(v, astFile)
+ }
+ u.Append(v.errors...)
+ }(pkgInfo)
+ }
+
+ wg.Wait()
+ if u.Len() > 0 {
+ sort.Sort(byName{u})
+ return u
+ }
+ return nil
+}
+
+// CheckPackages checks packages for errors.
+func (c *Checker) CheckPackages(paths ...string) error {
+ program, err := c.load(paths...)
+ if err != nil {
+ return fmt.Errorf("could not type check: %s", err)
+ }
+
+ return c.CheckProgram(program)
+}
+
+// visitor implements the errcheck algorithm
+type visitor struct {
+ prog *loader.Program
+ pkg *loader.PackageInfo
+ ignore map[string]*regexp.Regexp
+ blank bool
+ asserts bool
+ lines map[string][]string
+ exclude map[string]bool
+
+ errors []UncheckedError
+}
+
+func getSelName(sel *ast.SelectorExpr) string {
+ if ident, ok := sel.X.(*ast.Ident); ok {
+ return fmt.Sprintf("%s.%s", ident.Name, sel.Sel.Name)
+ }
+
+ if s, ok := sel.X.(*ast.SelectorExpr); ok {
+ return fmt.Sprintf("%s.%s", getSelName(s), sel.Sel.Name)
+ }
+
+ return ""
+}
+
+func (v *visitor) fullNameForPrinting(call *ast.CallExpr) (string, bool) {
+ if ident, ok := call.Fun.(*ast.Ident); ok {
+ return ident.Name, true
+ }
+
+ sel, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return "", false
+ }
+
+ name := getSelName(sel)
+ if name != "" {
+ return name, true
+ }
+
+ fn, ok := v.pkg.ObjectOf(sel.Sel).(*types.Func)
+ if !ok {
+ // Shouldn't happen, but be paranoid
+ return "", false
+ }
+ // The name is fully qualified by the import path, possible type,
+ // function/method name and pointer receiver.
+ //
+ // TODO(dh): vendored packages will have /vendor/ in their name,
+ // thus not matching vendored standard library packages. If we
+ // want to support vendored stdlib packages, we need to implement
+ // FullName with our own logic.
+ return fn.FullName(), true
+}
+
+func (v *visitor) fullName(call *ast.CallExpr) (string, bool) {
+ if ident, ok := call.Fun.(*ast.Ident); ok {
+ return ident.Name, true
+ }
+
+ sel, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return "", false
+ }
+
+ fn, ok := v.pkg.ObjectOf(sel.Sel).(*types.Func)
+ if !ok {
+ // Shouldn't happen, but be paranoid
+ return "", false
+ }
+ // The name is fully qualified by the import path, possible type,
+ // function/method name and pointer receiver.
+ //
+ // TODO(dh): vendored packages will have /vendor/ in their name,
+ // thus not matching vendored standard library packages. If we
+ // want to support vendored stdlib packages, we need to implement
+ // FullName with our own logic.
+ return fn.FullName(), true
+}
+
+func (v *visitor) excludeCall(call *ast.CallExpr) bool {
+ if name, ok := v.fullName(call); ok {
+ return v.exclude[name]
+ }
+
+ return false
+}
+
+func (v *visitor) ignoreCall(call *ast.CallExpr) bool {
+ if v.excludeCall(call) {
+ return true
+ }
+
+ // Try to get an identifier.
+ // Currently only supports simple expressions:
+ // 1. f()
+ // 2. x.y.f()
+ var id *ast.Ident
+ switch exp := call.Fun.(type) {
+ case (*ast.Ident):
+ id = exp
+ case (*ast.SelectorExpr):
+ id = exp.Sel
+ default:
+ // eg: *ast.SliceExpr, *ast.IndexExpr
+ }
+
+ if id == nil {
+ return false
+ }
+
+ // If we got an identifier for the function, see if it is ignored
+ if re, ok := v.ignore[""]; ok && re.MatchString(id.Name) {
+ return true
+ }
+
+ if obj := v.pkg.Uses[id]; obj != nil {
+ if pkg := obj.Pkg(); pkg != nil {
+ if re, ok := v.ignore[pkg.Path()]; ok {
+ return re.MatchString(id.Name)
+ }
+
+ // if current package being considered is vendored, check to see if it should be ignored based
+ // on the unvendored path.
+ if nonVendoredPkg, ok := nonVendoredPkgPath(pkg.Path()); ok {
+ if re, ok := v.ignore[nonVendoredPkg]; ok {
+ return re.MatchString(id.Name)
+ }
+ }
+ }
+ }
+
+ return false
+}
+
+// nonVendoredPkgPath returns the unvendored version of the provided package path (or returns the provided path if it
+// does not represent a vendored path). The second return value is true if the provided package was vendored, false
+// otherwise.
+func nonVendoredPkgPath(pkgPath string) (string, bool) {
+ lastVendorIndex := strings.LastIndex(pkgPath, "/vendor/")
+ if lastVendorIndex == -1 {
+ return pkgPath, false
+ }
+ return pkgPath[lastVendorIndex+len("/vendor/"):], true
+}
+
+// errorsByArg returns a slice s such that
+// len(s) == number of return types of call
+// s[i] == true iff return type at position i from left is an error type
+func (v *visitor) errorsByArg(call *ast.CallExpr) []bool {
+ switch t := v.pkg.Types[call].Type.(type) {
+ case *types.Named:
+ // Single return
+ return []bool{isErrorType(t)}
+ case *types.Pointer:
+ // Single return via pointer
+ return []bool{isErrorType(t)}
+ case *types.Tuple:
+ // Multiple returns
+ s := make([]bool, t.Len())
+ for i := 0; i < t.Len(); i++ {
+ switch et := t.At(i).Type().(type) {
+ case *types.Named:
+ // Single return
+ s[i] = isErrorType(et)
+ case *types.Pointer:
+ // Single return via pointer
+ s[i] = isErrorType(et)
+ default:
+ s[i] = false
+ }
+ }
+ return s
+ }
+ return []bool{false}
+}
+
+func (v *visitor) callReturnsError(call *ast.CallExpr) bool {
+ if v.isRecover(call) {
+ return true
+ }
+ for _, isError := range v.errorsByArg(call) {
+ if isError {
+ return true
+ }
+ }
+ return false
+}
+
+// isRecover returns true if the given CallExpr is a call to the built-in recover() function.
+func (v *visitor) isRecover(call *ast.CallExpr) bool {
+ if fun, ok := call.Fun.(*ast.Ident); ok {
+ if _, ok := v.pkg.Uses[fun].(*types.Builtin); ok {
+ return fun.Name == "recover"
+ }
+ }
+ return false
+}
+
+func (v *visitor) addErrorAtPosition(position token.Pos, call *ast.CallExpr) {
+ pos := v.prog.Fset.Position(position)
+ lines, ok := v.lines[pos.Filename]
+ if !ok {
+ lines = readfile(pos.Filename)
+ v.lines[pos.Filename] = lines
+ }
+
+ line := "??"
+ if pos.Line-1 < len(lines) {
+ line = strings.TrimSpace(lines[pos.Line-1])
+ }
+
+ var name string
+ if call != nil {
+ name, _ = v.fullNameForPrinting(call)
+ }
+
+ v.errors = append(v.errors, UncheckedError{pos, line, name})
+}
+
+func readfile(filename string) []string {
+ var f, err = os.Open(filename)
+ if err != nil {
+ return nil
+ }
+ defer f.Close()
+
+ var lines []string
+ var scanner = bufio.NewScanner(f)
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ }
+ return lines
+}
+
+func (v *visitor) Visit(node ast.Node) ast.Visitor {
+ switch stmt := node.(type) {
+ case *ast.ExprStmt:
+ if call, ok := stmt.X.(*ast.CallExpr); ok {
+ if !v.ignoreCall(call) && v.callReturnsError(call) {
+ v.addErrorAtPosition(call.Lparen, call)
+ }
+ }
+ case *ast.GoStmt:
+ if !v.ignoreCall(stmt.Call) && v.callReturnsError(stmt.Call) {
+ v.addErrorAtPosition(stmt.Call.Lparen, stmt.Call)
+ }
+ case *ast.DeferStmt:
+ if !v.ignoreCall(stmt.Call) && v.callReturnsError(stmt.Call) {
+ v.addErrorAtPosition(stmt.Call.Lparen, stmt.Call)
+ }
+ case *ast.AssignStmt:
+ if len(stmt.Rhs) == 1 {
+ // single value on rhs; check against lhs identifiers
+ if call, ok := stmt.Rhs[0].(*ast.CallExpr); ok {
+ if !v.blank {
+ break
+ }
+ if v.ignoreCall(call) {
+ break
+ }
+ isError := v.errorsByArg(call)
+ for i := 0; i < len(stmt.Lhs); i++ {
+ if id, ok := stmt.Lhs[i].(*ast.Ident); ok {
+ // We shortcut calls to recover() because errorsByArg can't
+ // check its return types for errors since it returns interface{}.
+ if id.Name == "_" && (v.isRecover(call) || isError[i]) {
+ v.addErrorAtPosition(id.NamePos, call)
+ }
+ }
+ }
+ } else if assert, ok := stmt.Rhs[0].(*ast.TypeAssertExpr); ok {
+ if !v.asserts {
+ break
+ }
+ if assert.Type == nil {
+ // type switch
+ break
+ }
+ if len(stmt.Lhs) < 2 {
+ // assertion result not read
+ v.addErrorAtPosition(stmt.Rhs[0].Pos(), nil)
+ } else if id, ok := stmt.Lhs[1].(*ast.Ident); ok && v.blank && id.Name == "_" {
+ // assertion result ignored
+ v.addErrorAtPosition(id.NamePos, nil)
+ }
+ }
+ } else {
+ // multiple value on rhs; in this case a call can't return
+ // multiple values. Assume len(stmt.Lhs) == len(stmt.Rhs)
+ for i := 0; i < len(stmt.Lhs); i++ {
+ if id, ok := stmt.Lhs[i].(*ast.Ident); ok {
+ if call, ok := stmt.Rhs[i].(*ast.CallExpr); ok {
+ if !v.blank {
+ continue
+ }
+ if v.ignoreCall(call) {
+ continue
+ }
+ if id.Name == "_" && v.callReturnsError(call) {
+ v.addErrorAtPosition(id.NamePos, call)
+ }
+ } else if assert, ok := stmt.Rhs[i].(*ast.TypeAssertExpr); ok {
+ if !v.asserts {
+ continue
+ }
+ if assert.Type == nil {
+ // Shouldn't happen anyway, no multi assignment in type switches
+ continue
+ }
+ v.addErrorAtPosition(id.NamePos, nil)
+ }
+ }
+ }
+ }
+ default:
+ }
+ return v
+}
+
+func isErrorType(t types.Type) bool {
+ return types.Implements(t, errorType)
+}