Skip to content

Commit b261fe9

Browse files
committed
go/tools: add vet check for direct comparion of reflect.Values
Comparing reflect.Values directly is almost certainly not what you want. That compares reflect's internal representation, not the underlying value it represents. One compares reflect.Values correctly by comparing the results of .Interface() calls. Fixes golang/go#43993 Update golang/go#18871 Change-Id: I6f441d920a5089bd346e5431032780403cefca76 Reviewed-on: https://go-review.googlesource.com/c/tools/+/308209 Trust: Keith Randall <[email protected]> Trust: Joe Tsai <[email protected]> Run-TryBot: Keith Randall <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Joe Tsai <[email protected]>
1 parent e78b40c commit b261fe9

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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.
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+
// isReflectValue reports whether the type of e is reflect.Value.
80+
func isReflectValue(pass *analysis.Pass, e ast.Expr) bool {
81+
tv, ok := 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+
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+
// 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+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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_test
6+
7+
import (
8+
"testing"
9+
10+
"golang.org/x/tools/go/analysis/analysistest"
11+
"golang.org/x/tools/go/analysis/passes/reflectvaluecompare"
12+
)
13+
14+
func TestReflectValueCompare(t *testing.T) {
15+
testdata := analysistest.TestData()
16+
analysistest.Run(t, testdata, reflectvaluecompare.Analyzer, "a")
17+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
// This file contains tests for the reflectvaluecompare checker.
6+
7+
package a
8+
9+
import (
10+
"reflect"
11+
)
12+
13+
func f() {
14+
var x, y reflect.Value
15+
var a, b interface{}
16+
_ = x == y // want `avoid using == with reflect.Value`
17+
_ = x == a // want `avoid using == with reflect.Value`
18+
_ = a == x // want `avoid using == with reflect.Value`
19+
_ = a == b
20+
21+
// Comparing to reflect.Value{} is ok.
22+
_ = a == reflect.Value{}
23+
_ = reflect.Value{} == a
24+
_ = reflect.Value{} == reflect.Value{}
25+
}
26+
func g() {
27+
var x, y reflect.Value
28+
var a, b interface{}
29+
_ = x != y // want `avoid using != with reflect.Value`
30+
_ = x != a // want `avoid using != with reflect.Value`
31+
_ = a != x // want `avoid using != with reflect.Value`
32+
_ = a != b
33+
34+
// Comparing to reflect.Value{} is ok.
35+
_ = a != reflect.Value{}
36+
_ = reflect.Value{} != a
37+
_ = reflect.Value{} != reflect.Value{}
38+
}
39+
func h() {
40+
var x, y reflect.Value
41+
var a, b interface{}
42+
reflect.DeepEqual(x, y) // want `avoid using reflect.DeepEqual with reflect.Value`
43+
reflect.DeepEqual(x, a) // want `avoid using reflect.DeepEqual with reflect.Value`
44+
reflect.DeepEqual(a, x) // want `avoid using reflect.DeepEqual with reflect.Value`
45+
reflect.DeepEqual(a, b)
46+
47+
// Comparing to reflect.Value{} is ok.
48+
reflect.DeepEqual(reflect.Value{}, a)
49+
reflect.DeepEqual(a, reflect.Value{})
50+
reflect.DeepEqual(reflect.Value{}, reflect.Value{})
51+
}

0 commit comments

Comments
 (0)