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 | } |
194 |
Members