migraphx.py 14.2 KB
Newer Older
1
import cppcheck, itertools
Paul's avatar
Format  
Paul committed
2
from cppcheckdata import simpleMatch, match  #patterns, bind_split
3

Paul's avatar
Format  
Paul committed
4

5
6
7
8
9
10
def skipTokenMatches(tokens, skip=None):
    for tok in tokens:
        if match(tok, skip):
            continue
        yield tok

Paul's avatar
Format  
Paul committed
11

12
def isTokensEqual(xtokens, ytokens, skip=None):
Paul's avatar
Format  
Paul committed
13
14
    for x, y in itertools.zip_longest(skipTokenMatches(xtokens, skip),
                                      skipTokenMatches(ytokens, skip)):
15
16
17
18
19
20
21
22
        if not x:
            return False
        if not y:
            return False
        if x.str != y.str:
            return False
    return True

Paul's avatar
Format  
Paul committed
23

24
25
26
27
28
29
30
def getInnerLink(token):
    if not token:
        return []
    if not token.link:
        return []
    return token.next.forward(token.link)

Paul's avatar
Format  
Paul committed
31

32
33
34
35
36
37
38
39
def getVariableDecl(var):
    if not var:
        return []
    end = var.typeEndToken
    if end:
        end = end.next
    return var.typeStartToken.forward(end)

Paul's avatar
Format  
Paul committed
40

Paul's avatar
Paul committed
41
42
43
44
45
46
47
48
49
50
51
def isFunctionCall(token):
    if not token:
        return False
    if not token.isName:
        return False
    if not token.next:
        return False
    if not token.next.str == '(':
        return False
    return True

Paul's avatar
Format  
Paul committed
52

Paul's avatar
Paul committed
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# def isplit(source, sep=' '):
#     sepsize = len(sep)
#     start = 0
#     while True:
#         idx = source.find(sep, start)
#         if idx == -1:
#             yield source[start:]
#             return
#         yield source[start:idx]
#         start = idx + sepsize

# def match_atom(token, p):
#     if not token:
#         return None
#     if not p:
#         return None
#     if token.str == p:
#         return token
#     if p in ['!', '|', '||', '%', '!=', '*']:
#         return None
#     if p in patterns:
#         return patterns[p](token)
#     if '|' in p:
#         for x in isplit(p, '|'):
#             t = match_atom(token, x)
#             if t:
#                 return t
#     elif p.startswith('!!'):
#         t = match_atom(token, p[2:])
#         if not t:
#             return token
#     elif p.startswith('**'):
#         a = p[2:]
#         t = token
#         while t:
#             if match_atom(t, a):
#                 return t
#             if t.link and t.str in ['(', '[', '<', '{']:
#                 t = t.link
#             t = t.next
#     return None

# class MatchResult:
#     def __init__(self, matches, bindings=None, keys=None):
#         self.__dict__.update(bindings or {})
#         self._matches = matches
#         self._keys = keys or []

#     def __bool__(self):
#         return self._matches

#     def __nonzero__(self):
#         return self._matches

#     def __getattr__(self, k):
#         if not self._matches:
#             return None
#         if k in self._keys:
#             return None
#         else:
#             raise AttributeError

# def match(token, pattern):
#     if not pattern:
#         return MatchResult(False)
#     end = None
#     bindings = {}
#     words = (bind_split(word) for word in isplit(pattern))
#     for p, b in words:
#         t = match_atom(token, p)
#         if b:
#             bindings[b] = token
#         if not t:
#             return MatchResult(False)
#         end = t
#         token = t.next
#     bindings['end'] = end
#     return MatchResult(True, bindings=bindings)
Paul's avatar
Format  
Paul committed
131

Paul's avatar
Format  
Paul committed
132

133
134
135
@cppcheck.checker
def AvoidBranchingStatementAsLastInLoop(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
136
137
        if not token.str in ['for', 'while']:
            continue
138
139
140
141
142
143
144
145
146
        end = match(token, "for|while (*) {*}").end
        if not end:
            continue
        stmt = end.tokAt(-2)
        if not match(stmt, "%any% ; }"):
            continue
        if not match(stmt, "break|continue"):
            stmt = stmt.astTop()
        if match(stmt, "break|continue|return"):
Paul's avatar
Format  
Paul committed
147
148
149
150
151
            cppcheck.reportError(
                stmt, "style",
                "Branching statement as the last statement inside a loop is very confusing."
            )

152
153
154
155
156
157
158
159

# @cppcheck.checker
# def CollapsibleIfStatements(cfg, data):
#     for token in cfg.tokenlist:
#         if not match(token, "if (*) { if (*) {*} }"):
#             continue
#         cppcheck.reportError(token, "style", "These two if statements can be collapsed into one.")

Paul's avatar
Format  
Paul committed
160

161
162
163
@cppcheck.checker
def ConditionalAssert(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
164
        # if token.str != 'if':
Paul's avatar
Format  
Paul committed
165
        # continue
166
167
        if not match(token, "if (*) { assert (*) ; }"):
            continue
Paul's avatar
Format  
Paul committed
168
169
170
        cppcheck.reportError(token, "style",
                             "The if condition should be included in assert.")

171
172
173
174

@cppcheck.checker
def EmptyCatchStatement(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
175
        # if token.str != 'catch':
Paul's avatar
Format  
Paul committed
176
        # continue
177
178
179
180
        if not match(token, "catch (*) { }"):
            continue
        cppcheck.reportError(token, "style", "An empty catch statement.")

Paul's avatar
Format  
Paul committed
181

182
183
184
@cppcheck.checker
def EmptyDoWhileStatement(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
185
        # if token.str != 'do':
Paul's avatar
Format  
Paul committed
186
        # continue
Paul's avatar
Paul committed
187
        if not simpleMatch(token, "do { } while ("):
188
189
190
            continue
        cppcheck.reportError(token, "style", "Empty do-while.")

Paul's avatar
Format  
Paul committed
191

192
193
194
@cppcheck.checker
def EmptyElseBlock(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
195
        # if token.str != 'else':
Paul's avatar
Format  
Paul committed
196
        # continue
Paul's avatar
Paul committed
197
        if not simpleMatch(token, "else { }"):
198
            continue
Paul's avatar
Format  
Paul committed
199
200
201
        cppcheck.reportError(token, "style",
                             "Empty else statement can be safely removed.")

202
203
204
205

@cppcheck.checker
def EmptyForStatement(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
206
        # if token.str != 'for':
Paul's avatar
Format  
Paul committed
207
        # continue
208
209
210
211
        if not match(token, "for (*) { }"):
            continue
        cppcheck.reportError(token, "style", "Empty for statement.")

Paul's avatar
Format  
Paul committed
212

213
214
215
@cppcheck.checker
def EmptyIfStatement(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
216
        # if token.str != 'if':
Paul's avatar
Format  
Paul committed
217
        # continue
218
219
220
221
        if not match(token, "if (*) { }"):
            continue
        cppcheck.reportError(token, "style", "Empty if statement.")

Paul's avatar
Format  
Paul committed
222

223
224
225
@cppcheck.checker
def EmptySwitchStatement(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
226
        # if token.str != 'switch':
Paul's avatar
Format  
Paul committed
227
        # continue
228
229
230
231
        if not match(token, "switch (*) { }"):
            continue
        cppcheck.reportError(token, "style", "Empty switch statement.")

Paul's avatar
Format  
Paul committed
232

233
234
235
@cppcheck.checker
def EmptyWhileStatement(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
236
        # if token.str != 'while':
Paul's avatar
Format  
Paul committed
237
        # continue
238
239
240
241
        if not match(token, "while (*) { }"):
            continue
        cppcheck.reportError(token, "style", "Empty while statement.")

Paul's avatar
Format  
Paul committed
242

243
244
245
@cppcheck.checker
def ForLoopShouldBeWhileLoop(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
246
        # if token.str != 'for':
Paul's avatar
Format  
Paul committed
247
        # continue
248
249
250
251
252
253
254
255
        if not match(token, "for ( ; !!;"):
            continue
        # Skip empty for loops
        if match(token, "for (*) { }"):
            continue
        end = token.next.link
        if not match(end.tokAt(-1), "; )"):
            continue
Paul's avatar
Format  
Paul committed
256
257
258
        cppcheck.reportError(token, "style",
                             "For loop should be written as a while loop.")

259
260
261
262

@cppcheck.checker
def GotoStatement(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
263
        if token.str != 'goto':
264
265
266
            continue
        cppcheck.reportError(token, "style", "Goto considered harmful.")

Paul's avatar
Format  
Paul committed
267

268
269
270
271
272
273
274
275
276
277
278
279
# @cppcheck.checker
# def InvertedLogic(cfg, data):
#     for token in cfg.tokenlist:
#         cond = None
#         if match(token, "if (*) {*} else { !!if"):
#             cond = token.next.astOperand2
#         elif match(token, "?"):
#             cond = token.astOperand1
#         if not match(cond, "!|!="):
#             continue
#         cppcheck.reportError(cond, "style", "It is cleaner to invert the logic.")

Paul's avatar
Format  
Paul committed
280

281
282
283
@cppcheck.checker
def LambdaAttribute(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
284
        # if token.str != ']':
Paul's avatar
Format  
Paul committed
285
        # continue
Paul's avatar
Paul committed
286
        if not match(token, "] __device__|__host__ {|("):
287
            continue
Paul's avatar
Format  
Paul committed
288
289
290
291
        cppcheck.reportError(
            token, "style",
            "Attributes to lambdas must come after parameter list.")

292
293
294
295
296
297

@cppcheck.checker
def MultipleUnaryOperator(cfg, data):
    for token in cfg.tokenlist:
        if not token.isUnaryOp(token.str):
            continue
Paul's avatar
Paul committed
298
299
        if not token.str in ['+', '-', '~', '!']:
            continue
300
301
302
303
        if not match(token.astOperand1, "+|-|~|!"):
            continue
        if not token.astOperand1.isUnaryOp(token.astOperand1.str):
            continue
Paul's avatar
Format  
Paul committed
304
305
306
        cppcheck.reportError(token, "style",
                             "Muliple unary operators used together.")

307
308
309
310

@cppcheck.checker
def MutableVariable(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
311
        # if token.str != 'mutable':
Paul's avatar
Format  
Paul committed
312
        # continue
313
314
        if not match(token, "mutable %var%"):
            continue
Paul's avatar
Format  
Paul committed
315
316
317
        cppcheck.reportError(token, "style",
                             "Do not create mutable variables.")

318
319
320
321

@cppcheck.checker
def NestedBlocks(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
322
323
        if not token.str in ['if', 'while', 'for', 'switch']:
            continue
324
325
326
327
328
329
330
        block = match(token, "if|while|for|switch (*) { {*}@block }").block
        if not block:
            block = match(token, "; { {*}@block break ; }").block
        if not block:
            continue
        cppcheck.reportError(block, "style", "Block directly inside block.")

Paul's avatar
Format  
Paul committed
331

332
333
334
@cppcheck.checker
def RedundantCast(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
335
336
        if not token.variable:
            continue
Paul's avatar
Format  
Paul committed
337
338
        m = match(token,
                  "%var%@decl ; %var%@assign = static_cast <*>@cast (*) ;")
339
340
341
342
        if not m:
            continue
        if m.decl.varId != m.assign.varId:
            continue
Paul's avatar
Paul committed
343
        if not simpleMatch(token.previous, "auto"):
Paul's avatar
Format  
Paul committed
344
345
346
            if not isTokensEqual(getVariableDecl(m.decl.variable),
                                 getInnerLink(m.cast),
                                 skip='const|volatile|&|&&|*'):
347
348
349
                continue
        cppcheck.reportError(token, "style", "Static cast is redundant.")

Paul's avatar
Format  
Paul committed
350

351
352
353
@cppcheck.checker
def RedundantConditionalOperator(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
354
        # if token.str != '?':
Paul's avatar
Format  
Paul committed
355
        # continue
356
357
        if not match(token, "? true|false : true|false"):
            continue
Paul's avatar
Format  
Paul committed
358
359
360
        cppcheck.reportError(token, "style",
                             "Conditional operator is redundant.")

361
362
363
364

@cppcheck.checker
def RedundantIfStatement(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
365
        # if token.str != 'if':
Paul's avatar
Format  
Paul committed
366
        # continue
Paul's avatar
Format  
Paul committed
367
368
369
        if not match(
                token,
                "if (*) { return true|false ; } else { return true|false ; }"):
370
371
372
            continue
        cppcheck.reportError(token, "style", "The if statement is redundant.")

Paul's avatar
Format  
Paul committed
373

374
375
376
@cppcheck.checker
def RedundantLocalVariable(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
377
378
        if not token.variable:
            continue
Paul's avatar
Format  
Paul committed
379
380
        m = match(token,
                  "%var%@decl ; %var%@assign = **; return %var%@returned ;")
381
382
383
384
385
386
        if not m:
            continue
        if m.decl.varId != m.assign.varId:
            continue
        if m.decl.varId != m.returned.varId:
            continue
Paul's avatar
Format  
Paul committed
387
388
389
390
391
        cppcheck.reportError(
            m.returned, "style",
            "Variable is returned immediately after its declaration, can be simplified to just return expression."
        )

392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407

# @cppcheck.checker
# def UnnecessaryElseStatement(cfg, data):
#     for token in cfg.tokenlist:
#         m = match(token, "if (*) {*}@block else@else_statement {")
#         if not m:
#             continue
#         stmt = m.block.link.tokAt(-2)
#         if not match(stmt, "%any% ; }"):
#             continue
#         if not match(stmt, "break|continue"):
#             stmt = stmt.astTop()
#         if not match(stmt, "break|continue|return|throw"):
#             continue
#         cppcheck.reportError(m.else_statement, "style", "Else statement is not necessary.")

Paul's avatar
Format  
Paul committed
408

409
410
411
@cppcheck.checker
def UnnecessaryEmptyCondition(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
412
        # if token.str != 'if':
Paul's avatar
Format  
Paul committed
413
        # continue
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
        m = match(token, "if (*)@if_cond { for (*)@for_cond {*} }")
        if not m:
            continue
        cond = m.if_cond.astOperand2
        if match(cond, "!"):
            cond = cond.astOperand1
        if not match(cond.tokAt(-2), ". empty ("):
            continue
        container = cond.tokAt(-2).astOperand1
        if not container.varId:
            continue
        if not match(m.for_cond.astOperand2, ":"):
            continue
        container_iter = m.for_cond.astOperand2.astOperand2
        if container_iter.varId != container.varId:
            continue
Paul's avatar
Format  
Paul committed
430
431
432
433
        cppcheck.reportError(
            container, "style",
            "Unnecessary check for empty before for range loop.")

434
435
436
437

@cppcheck.checker
def UseDeviceLaunch(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
438
        if not simpleMatch(token, "hipLaunchKernelGGL ("):
439
440
441
            continue
        cppcheck.reportError(token, "style", "Use device::launch instead.")

Paul's avatar
Format  
Paul committed
442

443
444
@cppcheck.checker
def UseManagePointer(cfg, data):
Paul's avatar
Format  
Paul committed
445
446
447
448
449
450
    functions = {
        "fclose", "free", "hipFree", "hipHostFree", "hipFreeArray",
        "hipMemFree", "hipStreamDestroy", "hipEventDestroy", "hipArrayDestroy",
        "hipCtxDestroy", "hipDestroyTextureObject", "hipDestroySurfaceObject",
        "miirDestroyHandle"
    }
451
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
452
453
454
        if not isFunctionCall(token):
            continue
        if not token.str in functions:
455
            continue
Paul's avatar
Format  
Paul committed
456
457
458
        cppcheck.reportError(token, "style",
                             "Use manage pointer for resource management.")

459
460
461
462

@cppcheck.checker
def UseSmartPointer(cfg, data):
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
463
        # if token.str != 'new':
Paul's avatar
Format  
Paul committed
464
        # continue
465
466
        if not match(token, "new %name%"):
            continue
Paul's avatar
Format  
Paul committed
467
468
469
        cppcheck.reportError(token, "style",
                             "Use manage pointer for resource management.")

470
471
472

@cppcheck.checker
def useStlAlgorithms(cfg, data):
Paul's avatar
Paul committed
473
    copy_functions = {"memcpy", "strcpy", "strncpy", "strcat", "strncat"}
474
    for token in cfg.tokenlist:
Paul's avatar
Paul committed
475
476
477
        if not isFunctionCall(token):
            continue
        if token.str in copy_functions:
478
            cppcheck.reportError(token, "style", "Use std::copy instead.")
Paul's avatar
Paul committed
479
        elif token.str == 'memset':
480
            cppcheck.reportError(token, "style", "Use std::fill instead.")
Paul's avatar
Paul committed
481
        elif token.str == 'memcmp':
Paul's avatar
Format  
Paul committed
482
483
            cppcheck.reportError(token, "style",
                                 "Use std::equal_range instead.")
Paul's avatar
Paul committed
484
        elif token.str == 'memchr':
485
            cppcheck.reportError(token, "style", "Use std::find instead.")