// 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()) }