github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/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 "github.com/jhump/golang-x-tools/go/analysis" 13 "github.com/jhump/golang-x-tools/go/analysis/passes/buildssa" 14 "github.com/jhump/golang-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 // Check the writes to struct and array objects. 54 checkStore := func(store *ssa.Store) { 55 // Consider field/index writes to an object whose elements are copied and not shared. 56 // MapUpdate is excluded since only the reference of the map is copied. 57 switch addr := store.Addr.(type) { 58 case *ssa.FieldAddr: 59 if isDeadStore(store, addr.X, addr) { 60 // Report the bug. 61 pass.Reportf(store.Pos(), 62 "unused write to field %s", 63 getFieldName(addr.X.Type(), addr.Field)) 64 } 65 case *ssa.IndexAddr: 66 if isDeadStore(store, addr.X, addr) { 67 // Report the bug. 68 pass.Reportf(store.Pos(), 69 "unused write to array index %s", addr.Index) 70 } 71 } 72 } 73 74 ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) 75 for _, fn := range ssainput.SrcFuncs { 76 // Visit each block. No need to visit fn.Recover. 77 for _, blk := range fn.Blocks { 78 for _, instr := range blk.Instrs { 79 // Identify writes. 80 if store, ok := instr.(*ssa.Store); ok { 81 checkStore(store) 82 } 83 } 84 } 85 } 86 return nil, nil 87 } 88 89 // isDeadStore determines whether a field/index write to an object is dead. 90 // Argument "obj" is the object, and "addr" is the instruction fetching the field/index. 91 func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool { 92 // Consider only struct or array objects. 93 if !hasStructOrArrayType(obj) { 94 return false 95 } 96 // Check liveness: if the value is used later, then don't report the write. 97 for _, ref := range *obj.Referrers() { 98 if ref == store || ref == addr { 99 continue 100 } 101 switch ins := ref.(type) { 102 case ssa.CallInstruction: 103 return false 104 case *ssa.FieldAddr: 105 // Check whether the same field is used. 106 if ins.X == obj { 107 if faddr, ok := addr.(*ssa.FieldAddr); ok { 108 if faddr.Field == ins.Field { 109 return false 110 } 111 } 112 } 113 // Otherwise another field is used, and this usage doesn't count. 114 continue 115 case *ssa.IndexAddr: 116 if ins.X == obj { 117 return false 118 } 119 continue // Otherwise another object is used 120 case *ssa.Lookup: 121 if ins.X == obj { 122 return false 123 } 124 continue // Otherwise another object is used 125 case *ssa.Store: 126 if ins.Val == obj { 127 return false 128 } 129 continue // Otherwise other object is stored 130 default: // consider live if the object is used in any other instruction 131 return false 132 } 133 } 134 return true 135 } 136 137 // isStructOrArray returns whether the underlying type is struct or array. 138 func isStructOrArray(tp types.Type) bool { 139 if named, ok := tp.(*types.Named); ok { 140 tp = named.Underlying() 141 } 142 switch tp.(type) { 143 case *types.Array: 144 return true 145 case *types.Struct: 146 return true 147 } 148 return false 149 } 150 151 // hasStructOrArrayType returns whether a value is of struct or array type. 152 func hasStructOrArrayType(v ssa.Value) bool { 153 if instr, ok := v.(ssa.Instruction); ok { 154 if alloc, ok := instr.(*ssa.Alloc); ok { 155 // Check the element type of an allocated register (which always has pointer type) 156 // e.g., for 157 // func (t T) f() { ...} 158 // the receiver object is of type *T: 159 // t0 = local T (t) *T 160 if tp, ok := alloc.Type().(*types.Pointer); ok { 161 return isStructOrArray(tp.Elem()) 162 } 163 return false 164 } 165 } 166 return isStructOrArray(v.Type()) 167 } 168 169 // getFieldName returns the name of a field in a struct. 170 // It the field is not found, then it returns the string format of the index. 171 // 172 // For example, for struct T {x int, y int), getFieldName(*T, 1) returns "y". 173 func getFieldName(tp types.Type, index int) string { 174 if pt, ok := tp.(*types.Pointer); ok { 175 tp = pt.Elem() 176 } 177 if named, ok := tp.(*types.Named); ok { 178 tp = named.Underlying() 179 } 180 if stp, ok := tp.(*types.Struct); ok { 181 return stp.Field(index).Name() 182 } 183 return fmt.Sprintf("%d", index) 184 }