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.
5 // Package unusedwrite checks for unused writes to the elements of a struct or array object.
12 "golang.org/x/tools/go/analysis"
13 "golang.org/x/tools/go/analysis/passes/buildssa"
14 "golang.org/x/tools/go/ssa"
17 // Doc is a documentation string.
18 const Doc = `checks for unused writes
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.
28 type T struct { x int }
30 for i, v := range input { // v is a copy
31 v.x = i // unused write to field x
35 Another example is about non-pointer receiver:
37 type T struct { x int }
38 func (t T) f() { // t is a copy
39 t.x = i // unused write to field x
43 // Analyzer reports instances of writes to struct fields and arrays
44 //that are never read.
45 var Analyzer = &analysis.Analyzer{
48 Requires: []*analysis.Analyzer{buildssa.Analyzer},
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) {
59 if isDeadStore(store, addr.X, addr) {
61 pass.Reportf(store.Pos(),
62 "unused write to field %s",
63 getFieldName(addr.X.Type(), addr.Field))
66 if isDeadStore(store, addr.X, addr) {
68 pass.Reportf(store.Pos(),
69 "unused write to array index %s", addr.Index)
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 {
80 if store, ok := instr.(*ssa.Store); ok {
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) {
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 {
101 switch ins := ref.(type) {
102 case ssa.CallInstruction:
105 // Check whether the same field is used.
107 if faddr, ok := addr.(*ssa.FieldAddr); ok {
108 if faddr.Field == ins.Field {
113 // Otherwise another field is used, and this usage doesn't count.
119 continue // Otherwise another object is used
124 continue // Otherwise another object is used
129 continue // Otherwise other object is stored
130 default: // consider live if the object is used in any other instruction
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()
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)
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())
166 return isStructOrArray(v.Type())
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.
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 {
177 if named, ok := tp.(*types.Named); ok {
178 tp = named.Underlying()
180 if stp, ok := tp.(*types.Struct); ok {
181 return stp.Field(index).Name()
183 return fmt.Sprintf("%d", index)