...

Source file src/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go

Documentation: golang.org/x/tools/go/analysis/passes/reflectvaluecompare

     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  }
   100  

View as plain text