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 }