...

Source file src/golang.org/x/tools/go/analysis/passes/unusedwrite/unusedwrite.go

Documentation: golang.org/x/tools/go/analysis/passes/unusedwrite

     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  	"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.
    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  	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 nil, nil
    70  }
    71  
    72  // checkStores returns *Stores in fn whose address is written to but never used.
    73  func 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 store, ok := 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(store, addr.X, addr) {
    85  						reports = append(reports, store)
    86  					}
    87  				case *ssa.IndexAddr:
    88  					if isDeadStore(store, addr.X, addr) {
    89  						reports = append(reports, store)
    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.
   100  func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool {
   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 faddr, ok := 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.
   147  func isStructOrArray(tp types.Type) bool {
   148  	if named, ok := 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.
   161  func hasStructOrArrayType(v ssa.Value) bool {
   162  	if instr, ok := v.(ssa.Instruction); ok {
   163  		if alloc, ok := 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 tp, ok := 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".
   182  func getFieldName(tp types.Type, index int) string {
   183  	if pt, ok := tp.(*types.Pointer); ok {
   184  		tp = pt.Elem()
   185  	}
   186  	if named, ok := tp.(*types.Named); ok {
   187  		tp = named.Underlying()
   188  	}
   189  	if stp, ok := tp.(*types.Struct); ok {
   190  		return stp.Field(index).Name()
   191  	}
   192  	return fmt.Sprintf("%d", index)
   193  }
   194  

View as plain text