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