github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/unusedwrite/unusedwrite.go (about) 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 unusedwrite checks for unused writes to the elements of a struct or array object. 6 package unusedwrite 7 8 import ( 9 "fmt" 10 "go/types" 11 12 "golang.org/x/tools/go/analysis" 13 "golang.org/x/tools/go/analysis/passes/buildssa" 14 "golang.org/x/tools/go/ssa" 15 ) 16 17 // Doc is a documentation string. 18 const Doc = `checks for unused writes 19 20 The analyzer reports instances of writes to struct fields and 21 arrays that are never read. Specifically, when a struct object 22 or an array is copied, its elements are copied implicitly by 23 the compiler, and any element write to this copy does nothing 24 with the original object. 25 26 For example: 27 28 type T struct { x int } 29 func f(input []T) { 30 for i, v := range input { // v is a copy 31 v.x = i // unused write to field x 32 } 33 } 34 35 Another example is about non-pointer receiver: 36 37 type T struct { x int } 38 func (t T) f() { // t is a copy 39 t.x = i // unused write to field x 40 } 41 ` 42 43 // Analyzer reports instances of writes to struct fields and arrays 44 // that are never read. 45 var Analyzer = &analysis.Analyzer{ 46 Name: "unusedwrite", 47 Doc: Doc, 48 Requires: []*analysis.Analyzer{buildssa.Analyzer}, 49 Run: run, 50 } 51 52 func run(pass *analysis.Pass) (interface{}, error) { 53 ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) 54 for _, fn := range ssainput.SrcFuncs { 55 // TODO(taking): Iterate over fn._Instantiations() once exported. If so, have 1 report per Pos(). 56 reports := checkStores(fn) 57 for _, store := range reports { 58 switch addr := store.Addr.(type) { 59 case *ssa.FieldAddr: 60 pass.Reportf(store.Pos(), 61 "unused write to field %s", 62 getFieldName(addr.X.Type(), addr.Field)) 63 case *ssa.IndexAddr: 64 pass.Reportf(store.Pos(), 65 "unused write to array index %s", addr.Index) 66 } 67 } 68 } 69 return nil, nil 70 } 71 72 // checkStores returns *Stores in fn whose address is written to but never used. 73 func checkStores(fn *ssa.Function) []*ssa.Store { 74 var reports []*ssa.Store 75 // Visit each block. No need to visit fn.Recover. 76 for _, blk := range fn.Blocks { 77 for _, instr := range blk.Instrs { 78 // Identify writes. 79 if store, ok := instr.(*ssa.Store); ok { 80 // Consider field/index writes to an object whose elements are copied and not shared. 81 // MapUpdate is excluded since only the reference of the map is copied. 82 switch addr := store.Addr.(type) { 83 case *ssa.FieldAddr: 84 if isDeadStore(store, addr.X, addr) { 85 reports = append(reports, store) 86 } 87 case *ssa.IndexAddr: 88 if isDeadStore(store, addr.X, addr) { 89 reports = append(reports, store) 90 } 91 } 92 } 93 } 94 } 95 return reports 96 } 97 98 // isDeadStore determines whether a field/index write to an object is dead. 99 // Argument "obj" is the object, and "addr" is the instruction fetching the field/index. 100 func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool { 101 // Consider only struct or array objects. 102 if !hasStructOrArrayType(obj) { 103 return false 104 } 105 // Check liveness: if the value is used later, then don't report the write. 106 for _, ref := range *obj.Referrers() { 107 if ref == store || ref == addr { 108 continue 109 } 110 switch ins := ref.(type) { 111 case ssa.CallInstruction: 112 return false 113 case *ssa.FieldAddr: 114 // Check whether the same field is used. 115 if ins.X == obj { 116 if faddr, ok := addr.(*ssa.FieldAddr); ok { 117 if faddr.Field == ins.Field { 118 return false 119 } 120 } 121 } 122 // Otherwise another field is used, and this usage doesn't count. 123 continue 124 case *ssa.IndexAddr: 125 if ins.X == obj { 126 return false 127 } 128 continue // Otherwise another object is used 129 case *ssa.Lookup: 130 if ins.X == obj { 131 return false 132 } 133 continue // Otherwise another object is used 134 case *ssa.Store: 135 if ins.Val == obj { 136 return false 137 } 138 continue // Otherwise other object is stored 139 default: // consider live if the object is used in any other instruction 140 return false 141 } 142 } 143 return true 144 } 145 146 // isStructOrArray returns whether the underlying type is struct or array. 147 func isStructOrArray(tp types.Type) bool { 148 if named, ok := tp.(*types.Named); ok { 149 tp = named.Underlying() 150 } 151 switch tp.(type) { 152 case *types.Array: 153 return true 154 case *types.Struct: 155 return true 156 } 157 return false 158 } 159 160 // hasStructOrArrayType returns whether a value is of struct or array type. 161 func hasStructOrArrayType(v ssa.Value) bool { 162 if instr, ok := v.(ssa.Instruction); ok { 163 if alloc, ok := instr.(*ssa.Alloc); ok { 164 // Check the element type of an allocated register (which always has pointer type) 165 // e.g., for 166 // func (t T) f() { ...} 167 // the receiver object is of type *T: 168 // t0 = local T (t) *T 169 if tp, ok := alloc.Type().(*types.Pointer); ok { 170 return isStructOrArray(tp.Elem()) 171 } 172 return false 173 } 174 } 175 return isStructOrArray(v.Type()) 176 } 177 178 // getFieldName returns the name of a field in a struct. 179 // It the field is not found, then it returns the string format of the index. 180 // 181 // For example, for struct T {x int, y int), getFieldName(*T, 1) returns "y". 182 func getFieldName(tp types.Type, index int) string { 183 if pt, ok := tp.(*types.Pointer); ok { 184 tp = pt.Elem() 185 } 186 if named, ok := tp.(*types.Named); ok { 187 tp = named.Underlying() 188 } 189 if stp, ok := tp.(*types.Struct); ok { 190 return stp.Field(index).Name() 191 } 192 return fmt.Sprintf("%d", index) 193 }