1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package deepequalerrors defines an Analyzer that checks for the use
6// of reflect.DeepEqual with error values.
7package deepequalerrors
8
9import (
10	"go/ast"
11	"go/types"
12
13	"golang.org/x/tools/go/analysis"
14	"golang.org/x/tools/go/analysis/passes/inspect"
15	"golang.org/x/tools/go/ast/inspector"
16	"golang.org/x/tools/go/types/typeutil"
17)
18
19const Doc = `check for calls of reflect.DeepEqual on error values
20
21The deepequalerrors checker looks for calls of the form:
22
23    reflect.DeepEqual(err1, err2)
24
25where err1 and err2 are errors. Using reflect.DeepEqual to compare
26errors is discouraged.`
27
28var Analyzer = &analysis.Analyzer{
29	Name:     "deepequalerrors",
30	Doc:      Doc,
31	Requires: []*analysis.Analyzer{inspect.Analyzer},
32	Run:      run,
33}
34
35func run(pass *analysis.Pass) (interface{}, error) {
36	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
37
38	nodeFilter := []ast.Node{
39		(*ast.CallExpr)(nil),
40	}
41	inspect.Preorder(nodeFilter, func(n ast.Node) {
42		call := n.(*ast.CallExpr)
43		fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
44		if !ok {
45			return
46		}
47		if fn.FullName() == "reflect.DeepEqual" && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) {
48			pass.ReportRangef(call, "avoid using reflect.DeepEqual with errors")
49		}
50	})
51	return nil, nil
52}
53
54var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
55
56// hasError reports whether the type of e contains the type error.
57// See containsError, below, for the meaning of "contains".
58func hasError(pass *analysis.Pass, e ast.Expr) bool {
59	tv, ok := pass.TypesInfo.Types[e]
60	if !ok { // no type info, assume good
61		return false
62	}
63	return containsError(tv.Type)
64}
65
66// Report whether any type that typ could store and that could be compared is the
67// error type. This includes typ itself, as well as the types of struct field, slice
68// and array elements, map keys and elements, and pointers. It does not include
69// channel types (incomparable), arg and result types of a Signature (not stored), or
70// methods of a named or interface type (not stored).
71func containsError(typ types.Type) bool {
72	// Track types being processed, to avoid infinite recursion.
73	// Using types as keys here is OK because we are checking for the identical pointer, not
74	// type identity. See analysis/passes/printf/types.go.
75	inProgress := make(map[types.Type]bool)
76
77	var check func(t types.Type) bool
78	check = func(t types.Type) bool {
79		if t == errorType {
80			return true
81		}
82		if inProgress[t] {
83			return false
84		}
85		inProgress[t] = true
86		switch t := t.(type) {
87		case *types.Pointer:
88			return check(t.Elem())
89		case *types.Slice:
90			return check(t.Elem())
91		case *types.Array:
92			return check(t.Elem())
93		case *types.Map:
94			return check(t.Key()) || check(t.Elem())
95		case *types.Struct:
96			for i := 0; i < t.NumFields(); i++ {
97				if check(t.Field(i).Type()) {
98					return true
99				}
100			}
101		case *types.Named:
102			return check(t.Underlying())
103
104		// We list the remaining valid type kinds for completeness.
105		case *types.Basic:
106		case *types.Chan: // channels store values, but they are not comparable
107		case *types.Signature:
108		case *types.Tuple: // tuples are only part of signatures
109		case *types.Interface:
110		}
111		return false
112	}
113
114	return check(typ)
115}
116