GoPLS Viewer

Home|gopls/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go
1// Copyright 2021 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 reflectvaluecompare defines an Analyzer that checks for accidentally
6// using == or reflect.DeepEqual to compare reflect.Value values.
7// See issues 43993 and 18871.
8package reflectvaluecompare
9
10import (
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
21const Doc = `check for comparing reflect.Value values with == or reflect.DeepEqual
22
23The reflectvaluecompare checker looks for expressions of the form:
24
25    v1 == v2
26    v1 != v2
27    reflect.DeepEqual(v1, v2)
28
29where v1 or v2 are reflect.Values. Comparing reflect.Values directly
30is almost certainly not correct, as it compares the reflect package's
31internal representation, not the underlying value.
32Likely what is intended is:
33
34    v1.Interface() == v2.Interface()
35    v1.Interface() != v2.Interface()
36    reflect.DeepEqual(v1.Interface(), v2.Interface())
37`
38
39var Analyzer = &analysis.Analyzer{
40    Name:     "reflectvaluecompare",
41    Doc:      Doc,
42    Requires: []*analysis.Analyzer{inspect.Analyzer},
43    Run:      run,
44}
45
46func 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(passn.X) || isReflectValue(passn.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            fnok := typeutil.Callee(pass.TypesInfon).(*types.Func)
68            if !ok {
69                return
70            }
71            if fn.FullName() == "reflect.DeepEqual" && (isReflectValue(passn.Args[0]) || isReflectValue(passn.Args[1])) {
72                pass.ReportRangef(n"avoid using reflect.DeepEqual with reflect.Value")
73            }
74        }
75    })
76    return nilnil
77}
78
79// isReflectValue reports whether the type of e is reflect.Value.
80func isReflectValue(pass *analysis.Passe ast.Exprbool {
81    tvok := pass.TypesInfo.Types[e]
82    if !ok { // no type info, something else is wrong
83        return false
84    }
85    // See if the type is reflect.Value
86    namedok := 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        // This is reflect.Value{}. Don't treat that as an error.
95        // Users should probably use x.IsValid() rather than x == reflect.Value{}, but the latter isn't wrong.
96        return false
97    }
98    return true
99}
100
MembersX
inspect
inspector
typeutil
run.nodeFilter
types
analysis
run.pass
isReflectValue.obj
token
Doc
run
isReflectValue
isReflectValue.pass
ast
isReflectValue.e
Members
X