...
1
2
3
4
5
6
7
8 package reflectvaluecompare
9
10 import (
11 "go/ast"
12 "go/token"
13 "go/types"
14
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/inspect"
17 "golang.org/x/tools/go/ast/inspector"
18 "golang.org/x/tools/go/types/typeutil"
19 )
20
21 const Doc = `check for comparing reflect.Value values with == or reflect.DeepEqual
22
23 The reflectvaluecompare checker looks for expressions of the form:
24
25 v1 == v2
26 v1 != v2
27 reflect.DeepEqual(v1, v2)
28
29 where v1 or v2 are reflect.Values. Comparing reflect.Values directly
30 is almost certainly not correct, as it compares the reflect package's
31 internal representation, not the underlying value.
32 Likely what is intended is:
33
34 v1.Interface() == v2.Interface()
35 v1.Interface() != v2.Interface()
36 reflect.DeepEqual(v1.Interface(), v2.Interface())
37 `
38
39 var Analyzer = &analysis.Analyzer{
40 Name: "reflectvaluecompare",
41 Doc: Doc,
42 Requires: []*analysis.Analyzer{inspect.Analyzer},
43 Run: run,
44 }
45
46 func run(pass *analysis.Pass) (interface{}, error) {
47 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
48
49 nodeFilter := []ast.Node{
50 (*ast.BinaryExpr)(nil),
51 (*ast.CallExpr)(nil),
52 }
53 inspect.Preorder(nodeFilter, func(n ast.Node) {
54 switch n := n.(type) {
55 case *ast.BinaryExpr:
56 if n.Op != token.EQL && n.Op != token.NEQ {
57 return
58 }
59 if isReflectValue(pass, n.X) || isReflectValue(pass, n.Y) {
60 if n.Op == token.EQL {
61 pass.ReportRangef(n, "avoid using == with reflect.Value")
62 } else {
63 pass.ReportRangef(n, "avoid using != with reflect.Value")
64 }
65 }
66 case *ast.CallExpr:
67 fn, ok := typeutil.Callee(pass.TypesInfo, n).(*types.Func)
68 if !ok {
69 return
70 }
71 if fn.FullName() == "reflect.DeepEqual" && (isReflectValue(pass, n.Args[0]) || isReflectValue(pass, n.Args[1])) {
72 pass.ReportRangef(n, "avoid using reflect.DeepEqual with reflect.Value")
73 }
74 }
75 })
76 return nil, nil
77 }
78
79
80 func isReflectValue(pass *analysis.Pass, e ast.Expr) bool {
81 tv, ok := pass.TypesInfo.Types[e]
82 if !ok {
83 return false
84 }
85
86 named, ok := tv.Type.(*types.Named)
87 if !ok {
88 return false
89 }
90 if obj := named.Obj(); obj == nil || obj.Pkg() == nil || obj.Pkg().Path() != "reflect" || obj.Name() != "Value" {
91 return false
92 }
93 if _, ok := e.(*ast.CompositeLit); ok {
94
95
96 return false
97 }
98 return true
99 }
100
View as plain text