GoPLS Viewer

Home|gopls/go/analysis/passes/unusedwrite/unusedwrite.go
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.
6package unusedwrite
7
8import (
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.
18const Doc = `checks for unused writes
19
20The analyzer reports instances of writes to struct fields and
21arrays that are never read. Specifically, when a struct object
22or an array is copied, its elements are copied implicitly by
23the compiler, and any element write to this copy does nothing
24with the original object.
25
26For 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
35Another 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.
45var Analyzer = &analysis.Analyzer{
46    Name:     "unusedwrite",
47    Doc:      Doc,
48    Requires: []*analysis.Analyzer{buildssa.Analyzer},
49    Run:      run,
50}
51
52func 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 nilnil
70}
71
72// checkStores returns *Stores in fn whose address is written to but never used.
73func 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 storeok := 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(storeaddr.Xaddr) {
85                        reports = append(reportsstore)
86                    }
87                case *ssa.IndexAddr:
88                    if isDeadStore(storeaddr.Xaddr) {
89                        reports = append(reportsstore)
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.
100func isDeadStore(store *ssa.Storeobj ssa.Valueaddr ssa.Instructionbool {
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 faddrok := 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.
147func isStructOrArray(tp types.Typebool {
148    if namedok := 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.
161func hasStructOrArrayType(v ssa.Valuebool {
162    if instrok := v.(ssa.Instruction); ok {
163        if allocok := 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 tpok := 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".
182func getFieldName(tp types.Typeindex intstring {
183    if ptok := tp.(*types.Pointer); ok {
184        tp = pt.Elem()
185    }
186    if namedok := tp.(*types.Named); ok {
187        tp = named.Underlying()
188    }
189    if stpok := tp.(*types.Struct); ok {
190        return stp.Field(index).Name()
191    }
192    return fmt.Sprintf("%d"index)
193}
194
MembersX
run.RangeStmt_1426.BlockStmt.reports
checkStores.RangeStmt_2145.blk
isDeadStore
getFieldName.tp
types
run
run.pass
isDeadStore.obj
fmt
hasStructOrArrayType.v
getFieldName.index
buildssa
isDeadStore.addr
analysis
run.RangeStmt_1426.fn
checkStores.fn
isDeadStore.store
run.RangeStmt_1426.BlockStmt.RangeStmt_1596.store
isStructOrArray.tp
getFieldName
Doc
checkStores.reports
checkStores.RangeStmt_2145.BlockStmt.RangeStmt_2179.instr
isDeadStore.RangeStmt_3167.ref
isStructOrArray
hasStructOrArrayType
ssa
checkStores
Members
X