golang.org/x/tools@v0.21.0/go/analysis/passes/unusedwrite/unusedwrite.go (about)

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