github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/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 checks for unused writes to the elements of a struct or array object.
     6  package unusedwrite
     7  
     8  import (
     9  	"fmt"
    10  	"go/types"
    11  
    12  	"github.com/jhump/golang-x-tools/go/analysis"
    13  	"github.com/jhump/golang-x-tools/go/analysis/passes/buildssa"
    14  	"github.com/jhump/golang-x-tools/go/ssa"
    15  )
    16  
    17  // Doc is a documentation string.
    18  const Doc = `checks for unused writes
    19  
    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.
    25  
    26  For 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  
    35  Another 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.
    45  var Analyzer = &analysis.Analyzer{
    46  	Name:     "unusedwrite",
    47  	Doc:      Doc,
    48  	Requires: []*analysis.Analyzer{buildssa.Analyzer},
    49  	Run:      run,
    50  }
    51  
    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) {
    58  		case *ssa.FieldAddr:
    59  			if isDeadStore(store, addr.X, addr) {
    60  				// Report the bug.
    61  				pass.Reportf(store.Pos(),
    62  					"unused write to field %s",
    63  					getFieldName(addr.X.Type(), addr.Field))
    64  			}
    65  		case *ssa.IndexAddr:
    66  			if isDeadStore(store, addr.X, addr) {
    67  				// Report the bug.
    68  				pass.Reportf(store.Pos(),
    69  					"unused write to array index %s", addr.Index)
    70  			}
    71  		}
    72  	}
    73  
    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 {
    79  				// Identify writes.
    80  				if store, ok := instr.(*ssa.Store); ok {
    81  					checkStore(store)
    82  				}
    83  			}
    84  		}
    85  	}
    86  	return nil, nil
    87  }
    88  
    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) {
    94  		return false
    95  	}
    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 {
    99  			continue
   100  		}
   101  		switch ins := ref.(type) {
   102  		case ssa.CallInstruction:
   103  			return false
   104  		case *ssa.FieldAddr:
   105  			// Check whether the same field is used.
   106  			if ins.X == obj {
   107  				if faddr, ok := addr.(*ssa.FieldAddr); ok {
   108  					if faddr.Field == ins.Field {
   109  						return false
   110  					}
   111  				}
   112  			}
   113  			// Otherwise another field is used, and this usage doesn't count.
   114  			continue
   115  		case *ssa.IndexAddr:
   116  			if ins.X == obj {
   117  				return false
   118  			}
   119  			continue // Otherwise another object is used
   120  		case *ssa.Lookup:
   121  			if ins.X == obj {
   122  				return false
   123  			}
   124  			continue // Otherwise another object is used
   125  		case *ssa.Store:
   126  			if ins.Val == obj {
   127  				return false
   128  			}
   129  			continue // Otherwise other object is stored
   130  		default: // consider live if the object is used in any other instruction
   131  			return false
   132  		}
   133  	}
   134  	return true
   135  }
   136  
   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()
   141  	}
   142  	switch tp.(type) {
   143  	case *types.Array:
   144  		return true
   145  	case *types.Struct:
   146  		return true
   147  	}
   148  	return false
   149  }
   150  
   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)
   156  			// e.g., for
   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())
   162  			}
   163  			return false
   164  		}
   165  	}
   166  	return isStructOrArray(v.Type())
   167  }
   168  
   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.
   171  //
   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 {
   175  		tp = pt.Elem()
   176  	}
   177  	if named, ok := tp.(*types.Named); ok {
   178  		tp = named.Underlying()
   179  	}
   180  	if stp, ok := tp.(*types.Struct); ok {
   181  		return stp.Field(index).Name()
   182  	}
   183  	return fmt.Sprintf("%d", index)
   184  }