src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa5012.go (6913B)


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