sa5012.go raw

   1  package sa5012
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/constant"
   7  	"go/token"
   8  	"go/types"
   9  
  10  	"honnef.co/go/tools/analysis/lint"
  11  	"honnef.co/go/tools/analysis/report"
  12  	"honnef.co/go/tools/go/ir"
  13  	"honnef.co/go/tools/go/ir/irutil"
  14  	"honnef.co/go/tools/go/types/typeutil"
  15  	"honnef.co/go/tools/internal/passes/buildir"
  16  
  17  	"golang.org/x/tools/go/analysis"
  18  )
  19  
  20  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  21  	Analyzer: &analysis.Analyzer{
  22  		Name:      "SA5012",
  23  		Run:       run,
  24  		FactTypes: []analysis.Fact{new(evenElements)},
  25  		Requires:  []*analysis.Analyzer{buildir.Analyzer},
  26  	},
  27  	Doc: &lint.RawDocumentation{
  28  		Title: "Passing odd-sized slice to function expecting even size",
  29  		Text: `Some functions that take slices as parameters expect the slices to have an even number of elements. 
  30  Often, these functions treat elements in a slice as pairs. 
  31  For example, \'strings.NewReplacer\' takes pairs of old and new strings, 
  32  and calling it with an odd number of elements would be an error.`,
  33  		Since:    "2020.2",
  34  		Severity: lint.SeverityError,
  35  		MergeIf:  lint.MergeIfAny,
  36  	},
  37  })
  38  
  39  var Analyzer = SCAnalyzer.Analyzer
  40  
  41  type evenElements struct{}
  42  
  43  func (evenElements) AFact() {}
  44  
  45  func (evenElements) String() string { return "needs even elements" }
  46  
  47  func findSliceLength(v ir.Value) int {
  48  	// TODO(dh): VRP would help here
  49  
  50  	v = irutil.Flatten(v)
  51  	val := func(v ir.Value) int {
  52  		if v, ok := v.(*ir.Const); ok {
  53  			return int(v.Int64())
  54  		}
  55  		return -1
  56  	}
  57  	switch v := v.(type) {
  58  	case *ir.Slice:
  59  		low := 0
  60  		high := -1
  61  		if v.Low != nil {
  62  			low = val(v.Low)
  63  		}
  64  		if v.High != nil {
  65  			high = val(v.High)
  66  		} else {
  67  			switch vv := v.X.(type) {
  68  			case *ir.Alloc:
  69  				high = int(typeutil.Dereference(vv.Type()).Underlying().(*types.Array).Len())
  70  			case *ir.Slice:
  71  				high = findSliceLength(vv)
  72  			}
  73  		}
  74  		if low == -1 || high == -1 {
  75  			return -1
  76  		}
  77  		return high - low
  78  	default:
  79  		return -1
  80  	}
  81  }
  82  
  83  func flagSliceLens(pass *analysis.Pass) {
  84  	var tag evenElements
  85  
  86  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  87  		for _, b := range fn.Blocks {
  88  			for _, instr := range b.Instrs {
  89  				call, ok := instr.(ir.CallInstruction)
  90  				if !ok {
  91  					continue
  92  				}
  93  				callee := call.Common().StaticCallee()
  94  				if callee == nil {
  95  					continue
  96  				}
  97  				for argi, arg := range call.Common().Args {
  98  					if callee.Signature.Recv() != nil {
  99  						if argi == 0 {
 100  							continue
 101  						}
 102  						argi--
 103  					}
 104  
 105  					_, ok := arg.Type().Underlying().(*types.Slice)
 106  					if !ok {
 107  						continue
 108  					}
 109  					param := callee.Signature.Params().At(argi)
 110  					if !pass.ImportObjectFact(param, &tag) {
 111  						continue
 112  					}
 113  
 114  					// TODO handle stubs
 115  
 116  					// we know the argument has to have even length.
 117  					// now let's try to find its length
 118  					if n := findSliceLength(arg); n > -1 && n%2 != 0 {
 119  						src := call.Source().(*ast.CallExpr).Args[argi]
 120  						sig := call.Common().Signature()
 121  						var label string
 122  						if argi == sig.Params().Len()-1 && sig.Variadic() {
 123  							label = "variadic argument"
 124  						} else {
 125  							label = "argument"
 126  						}
 127  						// Note that param.Name() is guaranteed to not
 128  						// be empty, otherwise the function couldn't
 129  						// have enforced its length.
 130  						report.Report(pass, src, fmt.Sprintf("%s %q is expected to have even number of elements, but has %d elements", label, param.Name(), n))
 131  					}
 132  				}
 133  			}
 134  		}
 135  	}
 136  }
 137  
 138  func findSliceLenChecks(pass *analysis.Pass) {
 139  	// mark all function parameters that have to be of even length
 140  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
 141  		for _, b := range fn.Blocks {
 142  			// all paths go through this block
 143  			if !b.Dominates(fn.Exit) {
 144  				continue
 145  			}
 146  
 147  			// if foo % 2 != 0
 148  			ifi, ok := b.Control().(*ir.If)
 149  			if !ok {
 150  				continue
 151  			}
 152  			cmp, ok := ifi.Cond.(*ir.BinOp)
 153  			if !ok {
 154  				continue
 155  			}
 156  			var needle uint64
 157  			switch cmp.Op {
 158  			case token.NEQ:
 159  				// look for != 0
 160  				needle = 0
 161  			case token.EQL:
 162  				// look for == 1
 163  				needle = 1
 164  			default:
 165  				continue
 166  			}
 167  
 168  			rem, ok1 := cmp.X.(*ir.BinOp)
 169  			k, ok2 := cmp.Y.(*ir.Const)
 170  			if ok1 != ok2 {
 171  				continue
 172  			}
 173  			if !ok1 {
 174  				rem, ok1 = cmp.Y.(*ir.BinOp)
 175  				k, ok2 = cmp.X.(*ir.Const)
 176  			}
 177  			if !ok1 || !ok2 || rem.Op != token.REM || k.Value.Kind() != constant.Int || k.Uint64() != needle {
 178  				continue
 179  			}
 180  			k, ok = rem.Y.(*ir.Const)
 181  			if !ok || k.Value.Kind() != constant.Int || k.Uint64() != 2 {
 182  				continue
 183  			}
 184  
 185  			// if len(foo) % 2 != 0
 186  			call, ok := rem.X.(*ir.Call)
 187  			if !ok || !irutil.IsCallTo(call.Common(), "len") {
 188  				continue
 189  			}
 190  
 191  			// we're checking the length of a parameter that is a slice
 192  			// TODO(dh): support parameters that have flown through sigmas and phis
 193  			param, ok := call.Call.Args[0].(*ir.Parameter)
 194  			if !ok {
 195  				continue
 196  			}
 197  			if !typeutil.All(param.Type(), typeutil.IsSlice) {
 198  				continue
 199  			}
 200  
 201  			// if len(foo) % 2 != 0 then panic
 202  			if _, ok := b.Succs[0].Control().(*ir.Panic); !ok {
 203  				continue
 204  			}
 205  
 206  			pass.ExportObjectFact(param.Object(), new(evenElements))
 207  		}
 208  	}
 209  }
 210  
 211  func findIndirectSliceLenChecks(pass *analysis.Pass) {
 212  	seen := map[*ir.Function]struct{}{}
 213  
 214  	var doFunction func(fn *ir.Function)
 215  	doFunction = func(fn *ir.Function) {
 216  		if _, ok := seen[fn]; ok {
 217  			return
 218  		}
 219  		seen[fn] = struct{}{}
 220  
 221  		for _, b := range fn.Blocks {
 222  			// all paths go through this block
 223  			if !b.Dominates(fn.Exit) {
 224  				continue
 225  			}
 226  
 227  			for _, instr := range b.Instrs {
 228  				call, ok := instr.(*ir.Call)
 229  				if !ok {
 230  					continue
 231  				}
 232  				callee := call.Call.StaticCallee()
 233  				if callee == nil {
 234  					continue
 235  				}
 236  
 237  				if callee.Pkg == fn.Pkg || callee.Pkg == nil {
 238  					doFunction(callee)
 239  				}
 240  
 241  				for argi, arg := range call.Call.Args {
 242  					if callee.Signature.Recv() != nil {
 243  						if argi == 0 {
 244  							continue
 245  						}
 246  						argi--
 247  					}
 248  
 249  					// TODO(dh): support parameters that have flown through length-preserving instructions
 250  					param, ok := arg.(*ir.Parameter)
 251  					if !ok {
 252  						continue
 253  					}
 254  					if !typeutil.All(param.Type(), typeutil.IsSlice) {
 255  						continue
 256  					}
 257  
 258  					// We can't use callee.Params to look up the
 259  					// parameter, because Params is not populated for
 260  					// external functions. In our modular analysis.
 261  					// any function in any package that isn't the
 262  					// current package is considered "external", as it
 263  					// has been loaded from export data only.
 264  					sigParams := callee.Signature.Params()
 265  
 266  					if !pass.ImportObjectFact(sigParams.At(argi), new(evenElements)) {
 267  						continue
 268  					}
 269  					pass.ExportObjectFact(param.Object(), new(evenElements))
 270  				}
 271  			}
 272  		}
 273  	}
 274  
 275  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
 276  		doFunction(fn)
 277  	}
 278  }
 279  
 280  func run(pass *analysis.Pass) (interface{}, error) {
 281  	findSliceLenChecks(pass)
 282  	findIndirectSliceLenChecks(pass)
 283  	flagSliceLens(pass)
 284  
 285  	return nil, nil
 286  }
 287