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 }