| // Copyright 2019 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package deepequalerrors defines an Analyzer that checks for the use | |
| // of reflect.DeepEqual with error values. | |
| package deepequalerrors | |
| import ( | |
| "go/ast" | |
| "go/types" | |
| "golang.org/x/tools/go/analysis" | |
| "golang.org/x/tools/go/analysis/passes/inspect" | |
| "golang.org/x/tools/go/ast/inspector" | |
| "golang.org/x/tools/go/types/typeutil" | |
| "golang.org/x/tools/internal/typesinternal" | |
| ) | |
| const Doc = `check for calls of reflect.DeepEqual on error values | |
| The deepequalerrors checker looks for calls of the form: | |
| reflect.DeepEqual(err1, err2) | |
| where err1 and err2 are errors. Using reflect.DeepEqual to compare | |
| errors is discouraged.` | |
| var Analyzer = &analysis.Analyzer{ | |
| Name: "deepequalerrors", | |
| Doc: Doc, | |
| URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/deepequalerrors", | |
| Requires: []*analysis.Analyzer{inspect.Analyzer}, | |
| Run: run, | |
| } | |
| func run(pass *analysis.Pass) (any, error) { | |
| if !typesinternal.Imports(pass.Pkg, "reflect") { | |
| return nil, nil // doesn't directly import reflect | |
| } | |
| inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | |
| nodeFilter := []ast.Node{ | |
| (*ast.CallExpr)(nil), | |
| } | |
| inspect.Preorder(nodeFilter, func(n ast.Node) { | |
| call := n.(*ast.CallExpr) | |
| obj := typeutil.Callee(pass.TypesInfo, call) | |
| if typesinternal.IsFunctionNamed(obj, "reflect", "DeepEqual") && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) { | |
| pass.ReportRangef(call, "avoid using reflect.DeepEqual with errors") | |
| } | |
| }) | |
| return nil, nil | |
| } | |
| var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) | |
| // hasError reports whether the type of e contains the type error. | |
| // See containsError, below, for the meaning of "contains". | |
| func hasError(pass *analysis.Pass, e ast.Expr) bool { | |
| tv, ok := pass.TypesInfo.Types[e] | |
| if !ok { // no type info, assume good | |
| return false | |
| } | |
| return containsError(tv.Type) | |
| } | |
| // Report whether any type that typ could store and that could be compared is the | |
| // error type. This includes typ itself, as well as the types of struct field, slice | |
| // and array elements, map keys and elements, and pointers. It does not include | |
| // channel types (incomparable), arg and result types of a Signature (not stored), or | |
| // methods of a named or interface type (not stored). | |
| func containsError(typ types.Type) bool { | |
| // Track types being processed, to avoid infinite recursion. | |
| // Using types as keys here is OK because we are checking for the identical pointer, not | |
| // type identity. See analysis/passes/printf/types.go. | |
| inProgress := make(map[types.Type]bool) | |
| var check func(t types.Type) bool | |
| check = func(t types.Type) bool { | |
| if t == errorType { | |
| return true | |
| } | |
| if inProgress[t] { | |
| return false | |
| } | |
| inProgress[t] = true | |
| switch t := t.(type) { | |
| case *types.Pointer: | |
| return check(t.Elem()) | |
| case *types.Slice: | |
| return check(t.Elem()) | |
| case *types.Array: | |
| return check(t.Elem()) | |
| case *types.Map: | |
| return check(t.Key()) || check(t.Elem()) | |
| case *types.Struct: | |
| for field := range t.Fields() { | |
| if check(field.Type()) { | |
| return true | |
| } | |
| } | |
| case *types.Named, *types.Alias: | |
| return check(t.Underlying()) | |
| // We list the remaining valid type kinds for completeness. | |
| case *types.Basic: | |
| case *types.Chan: // channels store values, but they are not comparable | |
| case *types.Signature: | |
| case *types.Tuple: // tuples are only part of signatures | |
| case *types.Interface: | |
| } | |
| return false | |
| } | |
| return check(typ) | |
| } |