aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/tomarrell/wrapcheck
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2022-09-05 14:27:54 +0200
committerGitHub <noreply@github.com>2022-09-05 12:27:54 +0000
commitb2f2446b46bf02821d90ebedadae2bf7ae0e880e (patch)
tree923cf42842918d6bebca1d6bbdc08abed54d274d /vendor/github.com/tomarrell/wrapcheck
parente6654faff4bcca4be92e9a8596fd4b77f747c39e (diff)
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
Diffstat (limited to 'vendor/github.com/tomarrell/wrapcheck')
-rw-r--r--vendor/github.com/tomarrell/wrapcheck/v2/LICENSE (renamed from vendor/github.com/tomarrell/wrapcheck/LICENSE)0
-rw-r--r--vendor/github.com/tomarrell/wrapcheck/v2/wrapcheck/wrapcheck.go428
-rw-r--r--vendor/github.com/tomarrell/wrapcheck/wrapcheck/wrapcheck.go226
3 files changed, 428 insertions, 226 deletions
diff --git a/vendor/github.com/tomarrell/wrapcheck/LICENSE b/vendor/github.com/tomarrell/wrapcheck/v2/LICENSE
index b5d9d30d3..b5d9d30d3 100644
--- a/vendor/github.com/tomarrell/wrapcheck/LICENSE
+++ b/vendor/github.com/tomarrell/wrapcheck/v2/LICENSE
diff --git a/vendor/github.com/tomarrell/wrapcheck/v2/wrapcheck/wrapcheck.go b/vendor/github.com/tomarrell/wrapcheck/v2/wrapcheck/wrapcheck.go
new file mode 100644
index 000000000..3d492ee9c
--- /dev/null
+++ b/vendor/github.com/tomarrell/wrapcheck/v2/wrapcheck/wrapcheck.go
@@ -0,0 +1,428 @@
+package wrapcheck
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "regexp"
+ "strings"
+
+ "github.com/gobwas/glob"
+ "golang.org/x/tools/go/analysis"
+)
+
+var DefaultIgnoreSigs = []string{
+ ".Errorf(",
+ "errors.New(",
+ "errors.Unwrap(",
+ ".Wrap(",
+ ".Wrapf(",
+ ".WithMessage(",
+ ".WithMessagef(",
+ ".WithStack(",
+}
+
+// WrapcheckConfig is the set of configuration values which configure the
+// behaviour of the linter.
+type WrapcheckConfig struct {
+ // IgnoreSigs defines a list of substrings which if contained within the
+ // signature of the function call returning the error, will be ignored. This
+ // allows you to specify functions that wrapcheck will not report as
+ // unwrapped.
+ //
+ // For example, an ignoreSig of `[]string{"errors.New("}` will ignore errors
+ // returned from the stdlib package error's function:
+ //
+ // `func errors.New(message string) error`
+ //
+ // Due to the signature containing the substring `errors.New(`.
+ //
+ // Note: Setting this value will intentionally override the default ignored
+ // sigs. To achieve the same behaviour as default, you should add the default
+ // list to your config.
+ IgnoreSigs []string `mapstructure:"ignoreSigs" yaml:"ignoreSigs"`
+
+ // IgnoreSigRegexps defines a list of regular expressions which if matched
+ // to the signature of the function call returning the error, will be ignored. This
+ // allows you to specify functions that wrapcheck will not report as
+ // unwrapped.
+ //
+ // For example, an ignoreSigRegexp of `[]string{"\.New.*Err\("}`` will ignore errors
+ // returned from any signature whose method name starts with "New" and ends with "Err"
+ // due to the signature matching the regular expression `\.New.*Err\(`.
+ //
+ // Note that this is similar to the ignoreSigs configuration, but provides
+ // slightly more flexibility in defining rules by which signatures will be
+ // ignored.
+ IgnoreSigRegexps []string `mapstructure:"ignoreSigRegexps" yaml:"ignoreSigRegexps"`
+
+ // IgnorePackageGlobs defines a list of globs which, if matching the package
+ // of the function returning the error, will ignore the error when doing
+ // wrapcheck analysis.
+ //
+ // This is useful for broadly ignoring packages and subpackages from wrapcheck
+ // analysis. For example, to ignore all errors from all packages and
+ // subpackages of "encoding" you may include the configuration:
+ //
+ // -- .wrapcheck.yaml
+ // ignorePackageGlobs:
+ // - encoding/*
+ IgnorePackageGlobs []string `mapstructure:"ignorePackageGlobs" yaml:"ignorePackageGlobs"`
+
+ // IgnoreInterfaceRegexps defines a list of regular expressions which, if matched
+ // to a underlying interface name, will ignore unwrapped errors returned from a
+ // function whose call is defined on the given interface.
+ //
+ // For example, an ignoreInterfaceRegexps of `[]string{"Transac(tor|tion)"}`` will ignore errors
+ // returned from any function whose call is defined on a interface named 'Transactor'
+ // or 'Transaction' due to the name matching the regular expression `Transac(tor|tion)`.
+ IgnoreInterfaceRegexps []string `mapstructure:"ignoreInterfaceRegexps" yaml:"ignoreInterfaceRegexps"`
+}
+
+func NewDefaultConfig() WrapcheckConfig {
+ return WrapcheckConfig{
+ IgnoreSigs: DefaultIgnoreSigs,
+ IgnoreSigRegexps: []string{},
+ IgnorePackageGlobs: []string{},
+ IgnoreInterfaceRegexps: []string{},
+ }
+}
+
+func NewAnalyzer(cfg WrapcheckConfig) *analysis.Analyzer {
+ return &analysis.Analyzer{
+ Name: "wrapcheck",
+ Doc: "Checks that errors returned from external packages are wrapped",
+ Run: run(cfg),
+ }
+}
+
+func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
+ // Precompile the regexps, report the error
+ var (
+ ignoreSigRegexp []*regexp.Regexp
+ ignoreInterfaceRegexps []*regexp.Regexp
+ ignorePackageGlobs []glob.Glob
+ err error
+ )
+
+ ignoreSigRegexp, err = compileRegexps(cfg.IgnoreSigRegexps)
+ if err == nil {
+ ignoreInterfaceRegexps, err = compileRegexps(cfg.IgnoreInterfaceRegexps)
+ }
+ if err == nil {
+ ignorePackageGlobs, err = compileGlobs(cfg.IgnorePackageGlobs)
+
+ }
+
+ return func(pass *analysis.Pass) (interface{}, error) {
+ if err != nil {
+ return nil, err
+ }
+
+ for _, file := range pass.Files {
+ ast.Inspect(file, func(n ast.Node) bool {
+ ret, ok := n.(*ast.ReturnStmt)
+ if !ok {
+ return true
+ }
+
+ if len(ret.Results) < 1 {
+ return true
+ }
+
+ // Iterate over the values to be returned looking for errors
+ for _, expr := range ret.Results {
+ // Check if the return expression is a function call, if it is, we need
+ // to handle it by checking the return params of the function.
+ retFn, ok := expr.(*ast.CallExpr)
+ if ok {
+ // If the return type of the function is a single error. This will not
+ // match an error within multiple return values, for that, the below
+ // tuple check is required.
+
+ if isError(pass.TypesInfo.TypeOf(expr)) {
+ reportUnwrapped(pass, retFn, retFn.Pos(), cfg, ignoreSigRegexp, ignoreInterfaceRegexps, ignorePackageGlobs)
+ return true
+ }
+
+ // Check if one of the return values from the function is an error
+ tup, ok := pass.TypesInfo.TypeOf(expr).(*types.Tuple)
+ if !ok {
+ return true
+ }
+
+ // Iterate over the return values of the function looking for error
+ // types
+ for i := 0; i < tup.Len(); i++ {
+ v := tup.At(i)
+ if v == nil {
+ return true
+ }
+ if isError(v.Type()) {
+ reportUnwrapped(pass, retFn, expr.Pos(), cfg, ignoreSigRegexp, ignoreInterfaceRegexps, ignorePackageGlobs)
+ return true
+ }
+ }
+ }
+
+ if !isError(pass.TypesInfo.TypeOf(expr)) {
+ continue
+ }
+
+ ident, ok := expr.(*ast.Ident)
+ if !ok {
+ return true
+ }
+
+ var call *ast.CallExpr
+
+ // Attempt to find the most recent short assign
+ if shortAss := prevErrAssign(pass, file, ident); shortAss != nil {
+ call, ok = shortAss.Rhs[0].(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ } else if isUnresolved(file, ident) {
+ // TODO Check if the identifier is unresolved, and try to resolve it in
+ // another file.
+ return true
+ } else {
+ // Check for ValueSpec nodes in order to locate a possible var
+ // declaration.
+ if ident.Obj == nil {
+ return true
+ }
+
+ vSpec, ok := ident.Obj.Decl.(*ast.ValueSpec)
+ if !ok {
+ // We couldn't find a short or var assign for this error return.
+ // This is an error. Where did this identifier come from? Possibly a
+ // function param.
+ //
+ // TODO decide how to handle this case, whether to follow function
+ // param back, or assert wrapping at call site.
+
+ return true
+ }
+
+ if len(vSpec.Values) < 1 {
+ return true
+ }
+
+ call, ok = vSpec.Values[0].(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ }
+
+ // Make sure there is a call identified as producing the error being
+ // returned, otherwise just bail
+ if call == nil {
+ return true
+ }
+
+ reportUnwrapped(pass, call, ident.NamePos, cfg, ignoreSigRegexp, ignoreInterfaceRegexps, ignorePackageGlobs)
+ }
+
+ return true
+ })
+ }
+
+ return nil, nil
+ }
+}
+
+// Report unwrapped takes a call expression and an identifier and reports
+// if the call is unwrapped.
+func reportUnwrapped(pass *analysis.Pass, call *ast.CallExpr, tokenPos token.Pos, cfg WrapcheckConfig, regexpsSig []*regexp.Regexp, regexpsInter []*regexp.Regexp, pkgGlobs []glob.Glob) {
+ sel, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return
+ }
+
+ // Check for ignored signatures
+ fnSig := pass.TypesInfo.ObjectOf(sel.Sel).String()
+
+ if contains(cfg.IgnoreSigs, fnSig) {
+ return
+ } else if containsMatch(regexpsSig, fnSig) {
+ return
+ }
+
+ // Check if the underlying type of the "x" in x.y.z is an interface, as
+ // errors returned from interface types should be wrapped, unless ignored
+ // as per `ignoreInterfaceRegexps`
+ if isInterface(pass, sel) {
+ name := types.TypeString(pass.TypesInfo.TypeOf(sel.X), func(p *types.Package) string { return p.Name() })
+ if containsMatch(regexpsInter, name) {
+ } else {
+ pass.Reportf(tokenPos, "error returned from interface method should be wrapped: sig: %s", fnSig)
+ return
+ }
+ }
+
+ // Check whether the function being called comes from another package,
+ // as functions called across package boundaries which returns errors
+ // should be wrapped
+ if isFromOtherPkg(pass, sel, pkgGlobs) {
+ pass.Reportf(tokenPos, "error returned from external package is unwrapped: sig: %s", fnSig)
+ return
+ }
+}
+
+// isInterface returns whether the function call is one defined on an interface.
+func isInterface(pass *analysis.Pass, sel *ast.SelectorExpr) bool {
+ _, ok := pass.TypesInfo.TypeOf(sel.X).Underlying().(*types.Interface)
+
+ return ok
+}
+
+// isFromotherPkg returns whether the function is defined in the package
+// currently under analysis or is considered external. It will ignore packages
+// defined in config.IgnorePackageGlobs.
+func isFromOtherPkg(pass *analysis.Pass, sel *ast.SelectorExpr, pkgGlobs []glob.Glob) bool {
+ // The package of the function that we are calling which returns the error
+ fn := pass.TypesInfo.ObjectOf(sel.Sel)
+ if containsMatchGlob(pkgGlobs, fn.Pkg().Path()) {
+ return false
+ }
+
+ // If it's not a package name, then we should check the selector to make sure
+ // that it's an identifier from the same package
+ if pass.Pkg.Path() == fn.Pkg().Path() {
+ return false
+ }
+
+ return true
+}
+
+// prevErrAssign traverses the AST of a file looking for the most recent
+// assignment to an error declaration which is specified by the returnIdent
+// identifier.
+//
+// This only returns short form assignments and reassignments, e.g. `:=` and
+// `=`. This does not include `var` statements. This function will return nil if
+// the only declaration is a `var` (aka ValueSpec) declaration.
+func prevErrAssign(pass *analysis.Pass, file *ast.File, returnIdent *ast.Ident) *ast.AssignStmt {
+ // A slice containing all the assignments which contain an identifer
+ // referring to the source declaration of the error. This is to catch
+ // cases where err is defined once, and then reassigned multiple times
+ // within the same block. In these cases, we should check the method of
+ // the most recent call.
+ var assigns []*ast.AssignStmt
+
+ // Find all assignments which have the same declaration
+ ast.Inspect(file, func(n ast.Node) bool {
+ if ass, ok := n.(*ast.AssignStmt); ok {
+ for _, expr := range ass.Lhs {
+ if !isError(pass.TypesInfo.TypeOf(expr)) {
+ continue
+ }
+ if assIdent, ok := expr.(*ast.Ident); ok {
+ if assIdent.Obj == nil || returnIdent.Obj == nil {
+ // If we can't find the Obj for one of the identifiers, just skip
+ // it.
+ return true
+ } else if assIdent.Obj.Decl == returnIdent.Obj.Decl {
+ assigns = append(assigns, ass)
+ }
+ }
+ }
+ }
+
+ return true
+ })
+
+ // Iterate through the assignments, comparing the token positions to
+ // find the assignment that directly precedes the return position
+ var mostRecentAssign *ast.AssignStmt
+
+ for _, ass := range assigns {
+ if ass.Pos() > returnIdent.Pos() {
+ break
+ }
+ mostRecentAssign = ass
+ }
+
+ return mostRecentAssign
+}
+
+func contains(slice []string, el string) bool {
+ for _, s := range slice {
+ if strings.Contains(el, s) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func containsMatch(regexps []*regexp.Regexp, el string) bool {
+ for _, re := range regexps {
+ if re.MatchString(el) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func containsMatchGlob(globs []glob.Glob, el string) bool {
+ for _, g := range globs {
+ if g.Match(el) {
+ return true
+ }
+ }
+ return false
+}
+
+// isError returns whether or not the provided type interface is an error
+func isError(typ types.Type) bool {
+ if typ == nil {
+ return false
+ }
+
+ return typ.String() == "error"
+}
+
+func isUnresolved(file *ast.File, ident *ast.Ident) bool {
+ for _, unresolvedIdent := range file.Unresolved {
+ if unresolvedIdent.Pos() == ident.Pos() {
+ return true
+ }
+ }
+
+ return false
+}
+
+// compileRegexps compiles a set of regular expressions returning them for use,
+// or the first encountered error due to an invalid expression.
+func compileRegexps(regexps []string) ([]*regexp.Regexp, error) {
+ var compiledRegexps []*regexp.Regexp
+ for _, reg := range regexps {
+ re, err := regexp.Compile(reg)
+ if err != nil {
+ return nil, fmt.Errorf("unable to compile regexp %s: %v\n", reg, err)
+ }
+
+ compiledRegexps = append(compiledRegexps, re)
+ }
+
+ return compiledRegexps, nil
+}
+
+// compileGlobs compiles a set of globs, returning them for use,
+// or the first encountered error due to an invalid expression.
+func compileGlobs(globs []string) ([]glob.Glob, error) {
+ var compiledGlobs []glob.Glob
+ for _, globString := range globs {
+ glob, err := glob.Compile(globString)
+ if err != nil {
+ return nil, fmt.Errorf("unable to compile globs %s: %v\n", glob, err)
+ }
+
+ compiledGlobs = append(compiledGlobs, glob)
+ }
+ return compiledGlobs, nil
+}
diff --git a/vendor/github.com/tomarrell/wrapcheck/wrapcheck/wrapcheck.go b/vendor/github.com/tomarrell/wrapcheck/wrapcheck/wrapcheck.go
deleted file mode 100644
index e249a7ed2..000000000
--- a/vendor/github.com/tomarrell/wrapcheck/wrapcheck/wrapcheck.go
+++ /dev/null
@@ -1,226 +0,0 @@
-package wrapcheck
-
-import (
- "go/ast"
- "go/types"
-
- "golang.org/x/tools/go/analysis"
-)
-
-var ignoredIDs = []string{
- "func fmt.Errorf(format string, a ...interface{}) error",
- "func errors.New(text string) error",
- "func errors.Unwrap(err error) error",
-}
-
-var Analyzer = &analysis.Analyzer{
- Name: "wrapcheck",
- Doc: "Checks that errors returned from external packages are wrapped",
- Run: run,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- for _, file := range pass.Files {
- ast.Inspect(file, func(n ast.Node) bool {
- if _, ok := n.(*ast.AssignStmt); ok {
- return true
- }
-
- ret, ok := n.(*ast.ReturnStmt)
- if !ok {
- return true
- }
-
- if len(ret.Results) < 1 {
- return true
- }
-
- // Iterate over the values to be returned looking for errors
- for _, expr := range ret.Results {
- if !isError(pass.TypesInfo.TypeOf(expr)) {
- continue
- }
-
- ident, ok := expr.(*ast.Ident)
- if !ok {
- return true
- }
-
- var (
- call *ast.CallExpr
- )
-
- // Attempt to find the most recent short assign
- if shortAss := prevErrAssign(pass, file, ident); shortAss != nil {
- call, ok = shortAss.Rhs[0].(*ast.CallExpr)
- if !ok {
- return true
- }
- } else if isUnresolved(file, ident) {
- // TODO Check if the identifier is unresolved, and try to resolve it in
- // another file.
- return true
- } else {
- // Check for ValueSpec nodes in order to locate a possible var
- // declaration.
- if ident.Obj == nil {
- return true
- }
-
- vSpec, ok := ident.Obj.Decl.(*ast.ValueSpec)
- if !ok {
- // We couldn't find a short or var assign for this error return.
- // This is an error. Where did this identifier come from? Possibly a
- // function param.
- //
- // TODO decide how to handle this case, whether to follow function
- // param back, or assert wrapping at call site.
-
- return true
- }
-
- if len(vSpec.Values) < 1 {
- return true
- }
-
- call, ok = vSpec.Values[0].(*ast.CallExpr)
- if !ok {
- return true
- }
- }
-
- // Make sure there is a call identified as producing the error being
- // returned, otherwise just bail
- if call == nil {
- return true
- }
-
- sel, ok := call.Fun.(*ast.SelectorExpr)
- if !ok {
- return true
- }
-
- // Check if the underlying type of the "x" in x.y.z is an interface, as
- // errors returned from interface types should be wrapped.
- if isInterface(pass, sel, ident) {
- pass.Reportf(ident.NamePos, "error returned from interface method should be wrapped")
- return true
- }
-
- // Check whether the function being called comes from another package,
- // as functions called across package boundaries which returns errors
- // should be wrapped
- if isFromOtherPkg(pass, sel, ident) {
- pass.Reportf(ident.NamePos, "error returned from external package is unwrapped")
- return true
- }
- }
-
- return true
- })
- }
-
- return nil, nil
-}
-
-// isInterface returns whether the function call is one defined on an interface.
-func isInterface(pass *analysis.Pass, sel *ast.SelectorExpr, ident *ast.Ident) bool {
- _, ok := pass.TypesInfo.TypeOf(sel.X).Underlying().(*types.Interface)
-
- return ok
-}
-
-func isFromOtherPkg(pass *analysis.Pass, sel *ast.SelectorExpr, ident *ast.Ident) bool {
- // The package of the function that we are calling which returns the error
- fn := pass.TypesInfo.ObjectOf(sel.Sel)
-
- // If it's not a package name, then we should check the selector to make sure
- // that it's an identifier from the same package
- if pass.Pkg.Path() == fn.Pkg().Path() {
- return false
- } else if contains(ignoredIDs, fn.String()) {
- return false
- }
-
- return true
-}
-
-// prevErrAssign traverses the AST of a file looking for the most recent
-// assignment to an error declaration which is specified by the returnIdent
-// identifier.
-//
-// This only returns short form assignments and reassignments, e.g. `:=` and
-// `=`. This does not include `var` statements. This function will return nil if
-// the only declaration is a `var` (aka ValueSpec) declaration.
-func prevErrAssign(pass *analysis.Pass, file *ast.File, returnIdent *ast.Ident) *ast.AssignStmt {
- // A slice containing all the assignments which contain an identifer
- // referring to the source declaration of the error. This is to catch
- // cases where err is defined once, and then reassigned multiple times
- // within the same block. In these cases, we should check the method of
- // the most recent call.
- var assigns []*ast.AssignStmt
-
- // Find all assignments which have the same declaration
- ast.Inspect(file, func(n ast.Node) bool {
- if ass, ok := n.(*ast.AssignStmt); ok {
- for _, expr := range ass.Lhs {
- if !isError(pass.TypesInfo.TypeOf(expr)) {
- continue
- }
- if assIdent, ok := expr.(*ast.Ident); ok {
- if assIdent.Obj == nil || returnIdent.Obj == nil {
- // If we can't find the Obj for one of the identifiers, just skip
- // it.
- return true
- } else if assIdent.Obj.Decl == returnIdent.Obj.Decl {
- assigns = append(assigns, ass)
- }
- }
- }
- }
-
- return true
- })
-
- // Iterate through the assignments, comparing the token positions to
- // find the assignment that directly precedes the return position
- var mostRecentAssign *ast.AssignStmt
-
- for _, ass := range assigns {
- if ass.Pos() > returnIdent.Pos() {
- break
- }
- mostRecentAssign = ass
- }
-
- return mostRecentAssign
-}
-
-func contains(slice []string, el string) bool {
- for _, s := range slice {
- if s == el {
- return true
- }
- }
-
- return false
-}
-
-// isError returns whether or not the provided type interface is an error
-func isError(typ types.Type) bool {
- if typ == nil {
- return false
- }
-
- return typ.String() == "error"
-}
-
-func isUnresolved(file *ast.File, ident *ast.Ident) bool {
- for _, unresolvedIdent := range file.Unresolved {
- if unresolvedIdent.Pos() == ident.Pos() {
- return true
- }
- }
-
- return false
-}