golang.org/x/tools@v0.21.0/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 6 7 import ( 8 _ "embed" 9 "go/types" 10 11 "golang.org/x/tools/go/analysis" 12 "golang.org/x/tools/go/analysis/passes/buildssa" 13 "golang.org/x/tools/go/analysis/passes/internal/analysisutil" 14 "golang.org/x/tools/go/ssa" 15 "golang.org/x/tools/internal/aliases" 16 "golang.org/x/tools/internal/typeparams" 17 ) 18 19 //go:embed doc.go 20 var doc string 21 22 // Analyzer reports instances of writes to struct fields and arrays 23 // that are never read. 24 var Analyzer = &analysis.Analyzer{ 25 Name: "unusedwrite", 26 Doc: analysisutil.MustExtractDoc(doc, "unusedwrite"), 27 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedwrite", 28 Requires: []*analysis.Analyzer{buildssa.Analyzer}, 29 Run: run, 30 } 31 32 func run(pass *analysis.Pass) (interface{}, error) { 33 ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) 34 for _, fn := range ssainput.SrcFuncs { 35 // TODO(taking): Iterate over fn._Instantiations() once exported. If so, have 1 report per Pos(). 36 reports := checkStores(fn) 37 for _, store := range reports { 38 switch addr := store.Addr.(type) { 39 case *ssa.FieldAddr: 40 field := typeparams.CoreType(typeparams.MustDeref(addr.X.Type())).(*types.Struct).Field(addr.Field) 41 pass.Reportf(store.Pos(), 42 "unused write to field %s", field.Name()) 43 case *ssa.IndexAddr: 44 pass.Reportf(store.Pos(), 45 "unused write to array index %s", addr.Index) 46 } 47 } 48 } 49 return nil, nil 50 } 51 52 // checkStores returns *Stores in fn whose address is written to but never used. 53 func checkStores(fn *ssa.Function) []*ssa.Store { 54 var reports []*ssa.Store 55 // Visit each block. No need to visit fn.Recover. 56 for _, blk := range fn.Blocks { 57 for _, instr := range blk.Instrs { 58 // Identify writes. 59 if store, ok := instr.(*ssa.Store); ok { 60 // Consider field/index writes to an object whose elements are copied and not shared. 61 // MapUpdate is excluded since only the reference of the map is copied. 62 switch addr := store.Addr.(type) { 63 case *ssa.FieldAddr: 64 if isDeadStore(store, addr.X, addr) { 65 reports = append(reports, store) 66 } 67 case *ssa.IndexAddr: 68 if isDeadStore(store, addr.X, addr) { 69 reports = append(reports, store) 70 } 71 } 72 } 73 } 74 } 75 return reports 76 } 77 78 // isDeadStore determines whether a field/index write to an object is dead. 79 // Argument "obj" is the object, and "addr" is the instruction fetching the field/index. 80 func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool { 81 // Consider only struct or array objects. 82 if !hasStructOrArrayType(obj) { 83 return false 84 } 85 // Check liveness: if the value is used later, then don't report the write. 86 for _, ref := range *obj.Referrers() { 87 if ref == store || ref == addr { 88 continue 89 } 90 switch ins := ref.(type) { 91 case ssa.CallInstruction: 92 return false 93 case *ssa.FieldAddr: 94 // Check whether the same field is used. 95 if ins.X == obj { 96 if faddr, ok := addr.(*ssa.FieldAddr); ok { 97 if faddr.Field == ins.Field { 98 return false 99 } 100 } 101 } 102 // Otherwise another field is used, and this usage doesn't count. 103 continue 104 case *ssa.IndexAddr: 105 if ins.X == obj { 106 return false 107 } 108 continue // Otherwise another object is used 109 case *ssa.Lookup: 110 if ins.X == obj { 111 return false 112 } 113 continue // Otherwise another object is used 114 case *ssa.Store: 115 if ins.Val == obj { 116 return false 117 } 118 continue // Otherwise other object is stored 119 default: // consider live if the object is used in any other instruction 120 return false 121 } 122 } 123 return true 124 } 125 126 // isStructOrArray returns whether the underlying type is struct or array. 127 func isStructOrArray(tp types.Type) bool { 128 switch tp.Underlying().(type) { 129 case *types.Array: 130 return true 131 case *types.Struct: 132 return true 133 } 134 return false 135 } 136 137 // hasStructOrArrayType returns whether a value is of struct or array type. 138 func hasStructOrArrayType(v ssa.Value) bool { 139 if instr, ok := v.(ssa.Instruction); ok { 140 if alloc, ok := instr.(*ssa.Alloc); ok { 141 // Check the element type of an allocated register (which always has pointer type) 142 // e.g., for 143 // func (t T) f() { ...} 144 // the receiver object is of type *T: 145 // t0 = local T (t) *T 146 if tp, ok := aliases.Unalias(alloc.Type()).(*types.Pointer); ok { 147 return isStructOrArray(tp.Elem()) 148 } 149 return false 150 } 151 } 152 return isStructOrArray(v.Type()) 153 }