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