...
1
2
3
4
5
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
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
44
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
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
73 func checkStores(fn *ssa.Function) []*ssa.Store {
74 var reports []*ssa.Store
75
76 for _, blk := range fn.Blocks {
77 for _, instr := range blk.Instrs {
78
79 if store, ok := instr.(*ssa.Store); ok {
80
81
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
99
100 func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool {
101
102 if !hasStructOrArrayType(obj) {
103 return false
104 }
105
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
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
123 continue
124 case *ssa.IndexAddr:
125 if ins.X == obj {
126 return false
127 }
128 continue
129 case *ssa.Lookup:
130 if ins.X == obj {
131 return false
132 }
133 continue
134 case *ssa.Store:
135 if ins.Val == obj {
136 return false
137 }
138 continue
139 default:
140 return false
141 }
142 }
143 return true
144 }
145
146
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
161 func hasStructOrArrayType(v ssa.Value) bool {
162 if instr, ok := v.(ssa.Instruction); ok {
163 if alloc, ok := instr.(*ssa.Alloc); ok {
164
165
166
167
168
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
179
180
181
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 }
194
View as plain text