github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/testutils/lint/passes/hash/hash.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Package hash defines an Analyzer that detects correct use of hash.Hash.
    12  package hash
    13  
    14  import (
    15  	"go/ast"
    16  	"go/types"
    17  
    18  	"golang.org/x/tools/go/analysis"
    19  	"golang.org/x/tools/go/analysis/passes/inspect"
    20  )
    21  
    22  // Doc documents this pass.
    23  const Doc = `check for correct use of hash.Hash`
    24  
    25  // Analyzer defines this pass.
    26  var Analyzer = &analysis.Analyzer{
    27  	Name:     "hash",
    28  	Doc:      Doc,
    29  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    30  	Run:      run,
    31  }
    32  
    33  // hashChecker assures that the hash.Hash interface is not misused. A common
    34  // mistake is to assume that the Sum function returns the hash of its input,
    35  // like so:
    36  //
    37  //     hashedBytes := sha256.New().Sum(inputBytes)
    38  //
    39  // In fact, the parameter to Sum is not the bytes to be hashed, but a slice that
    40  // will be used as output in case the caller wants to avoid an allocation. In
    41  // the example above, hashedBytes is not the SHA-256 hash of inputBytes, but
    42  // the concatenation of inputBytes with the hash of the empty string.
    43  //
    44  // Correct uses of the hash.Hash interface are as follows:
    45  //
    46  //     h := sha256.New()
    47  //     h.Write(inputBytes)
    48  //     hashedBytes := h.Sum(nil)
    49  //
    50  //     h := sha256.New()
    51  //     h.Write(inputBytes)
    52  //     var hashedBytes [sha256.Size]byte
    53  //     h.Sum(hashedBytes[:0])
    54  //
    55  // To differentiate between correct and incorrect usages, hashChecker applies a
    56  // simple heuristic: it flags calls to Sum where a) the parameter is non-nil and
    57  // b) the return value is used.
    58  //
    59  // The hash.Hash interface may be remedied in Go 2. See golang/go#21070.
    60  func run(pass *analysis.Pass) (interface{}, error) {
    61  	selectorIsHash := func(s *ast.SelectorExpr) bool {
    62  		tv, ok := pass.TypesInfo.Types[s.X]
    63  		if !ok {
    64  			return false
    65  		}
    66  		named, ok := tv.Type.(*types.Named)
    67  		if !ok {
    68  			return false
    69  		}
    70  		if named.Obj().Type().String() != "hash.Hash" {
    71  			return false
    72  		}
    73  		return true
    74  	}
    75  
    76  	stack := make([]ast.Node, 0, 32)
    77  	forAllFiles(pass.Files, func(n ast.Node) bool {
    78  		if n == nil {
    79  			stack = stack[:len(stack)-1] // pop
    80  			return true
    81  		}
    82  		stack = append(stack, n) // push
    83  
    84  		// Find a call to hash.Hash.Sum.
    85  		selExpr, ok := n.(*ast.SelectorExpr)
    86  		if !ok {
    87  			return true
    88  		}
    89  		if selExpr.Sel.Name != "Sum" {
    90  			return true
    91  		}
    92  		if !selectorIsHash(selExpr) {
    93  			return true
    94  		}
    95  		callExpr, ok := stack[len(stack)-2].(*ast.CallExpr)
    96  		if !ok {
    97  			return true
    98  		}
    99  		if len(callExpr.Args) != 1 {
   100  			return true
   101  		}
   102  		// We have a valid call to hash.Hash.Sum.
   103  
   104  		// Is the argument nil?
   105  		var nilArg bool
   106  		if id, ok := callExpr.Args[0].(*ast.Ident); ok && id.Name == "nil" {
   107  			nilArg = true
   108  		}
   109  
   110  		// Is the return value unused?
   111  		var retUnused bool
   112  	Switch:
   113  		switch t := stack[len(stack)-3].(type) {
   114  		case *ast.AssignStmt:
   115  			for i := range t.Rhs {
   116  				if t.Rhs[i] == stack[len(stack)-2] {
   117  					if id, ok := t.Lhs[i].(*ast.Ident); ok && id.Name == "_" {
   118  						// Assigning to the blank identifier does not count as using the
   119  						// return value.
   120  						retUnused = true
   121  					}
   122  					break Switch
   123  				}
   124  			}
   125  			panic("unreachable")
   126  		case *ast.ExprStmt:
   127  			// An expression statement means the return value is unused.
   128  			retUnused = true
   129  		default:
   130  		}
   131  
   132  		if !nilArg && !retUnused {
   133  			pass.Reportf(callExpr.Pos(), "probable misuse of hash.Hash.Sum: "+
   134  				"provide parameter or use return value, but not both")
   135  		}
   136  		return true
   137  	})
   138  
   139  	return nil, nil
   140  }
   141  
   142  func forAllFiles(files []*ast.File, fn func(node ast.Node) bool) {
   143  	for _, f := range files {
   144  		ast.Inspect(f, fn)
   145  	}
   146  }