1#!/usr/bin/env python3 2# 3# MISRA C 2012 checkers 4# 5# Example usage of this addon (scan a sourcefile main.cpp) 6# cppcheck --dump main.cpp 7# python misra.py --rule-texts=<path-to-rule-texts> main.cpp.dump 8# 9# Limitations: This addon is released as open source. Rule texts can't be freely 10# distributed. https://www.misra.org.uk/forum/viewtopic.php?f=56&t=1189 11# 12# The MISRA standard documents may be obtained from https://www.misra.org.uk 13# 14# Total number of rules: 143 15 16from __future__ import print_function 17 18import cppcheckdata 19import itertools 20import json 21import sys 22import re 23import os 24import argparse 25import codecs 26import string 27import copy 28 29try: 30 from itertools import izip as zip 31except ImportError: 32 pass 33 34import misra_9 35 36def grouped(iterable, n): 37 """s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), (s2n,s2n+1,s2n+2,...s3n-1), ...""" 38 return zip(*[iter(iterable)] * n) 39 40 41INT_TYPES = ['bool', 'char', 'short', 'int', 'long', 'long long'] 42 43 44STDINT_TYPES = ['%s%d_t' % (n, v) for n, v in itertools.product( 45 ['int', 'uint', 'int_least', 'uint_least', 'int_fast', 'uint_fast'], 46 [8, 16, 32, 64])] 47 48 49typeBits = { 50 'CHAR': None, 51 'SHORT': None, 52 'INT': None, 53 'LONG': None, 54 'LONG_LONG': None, 55 'POINTER': None 56} 57 58 59def isUnsignedType(ty): 60 return ty == 'unsigned' or ty.startswith('uint') 61 62 63def simpleMatch(token, pattern): 64 return cppcheckdata.simpleMatch(token, pattern) 65 66 67def rawlink(rawtoken): 68 if rawtoken.str == '}': 69 indent = 0 70 while rawtoken: 71 if rawtoken.str == '}': 72 indent = indent + 1 73 elif rawtoken.str == '{': 74 indent = indent - 1 75 if indent == 0: 76 break 77 rawtoken = rawtoken.previous 78 else: 79 rawtoken = None 80 return rawtoken 81 82 83# Identifiers described in Section 7 "Library" of C90 Standard 84# Based on ISO/IEC9899:1990 Annex D -- Library summary and 85# Annex E -- Implementation limits. 86C90_STDLIB_IDENTIFIERS = { 87 # D.1 Errors 88 'errno.h': ['EDOM', 'ERANGE', 'errno'], 89 # D.2 Common definitions 90 'stddef.h': ['NULL', 'offsetof', 'ptrdiff_t', 'size_t', 'wchar_t'], 91 # D.3 Diagnostics 92 'assert.h': ['NDEBUG', 'assert'], 93 # D.4 Character handling 94 'ctype.h': [ 95 'isalnum', 'isalpha', 'isblank', 'iscntrl', 'isdigit', 96 'isgraph', 'islower', 'isprint', 'ispunct', 'isspace', 97 'isupper', 'isxdigit', 'tolower', 'toupper', 98 ], 99 # D.5 Localization 100 'locale.h': [ 101 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 102 'LC_NUMERIC', 'LC_TIME', 'NULL', 'lconv', 103 'setlocale', 'localeconv', 104 ], 105 # D.6 Mathematics 106 'math.h': [ 107 'HUGE_VAL', 'acos', 'asin' , 'atan2', 'cos', 'sin', 'tan', 'cosh', 108 'sinh', 'tanh', 'exp', 'frexp', 'ldexp', 'log', 'loglO', 'modf', 109 'pow', 'sqrt', 'ceil', 'fabs', 'floor', 'fmod', 110 ], 111 # D.7 Nonlocal jumps 112 'setjmp.h': ['jmp_buf', 'setjmp', 'longjmp'], 113 # D.8 Signal handling 114 'signal.h': [ 115 'sig_atomic_t', 'SIG_DFL', 'SIG_ERR', 'SIG_IGN', 'SIGABRT', 'SIGFPE', 116 'SIGILL', 'SIGINT', 'SIGSEGV', 'SIGTERM', 'signal', 'raise', 117 ], 118 # D.9 Variable arguments 119 'stdarg.h': ['va_list', 'va_start', 'va_arg', 'va_end'], 120 # D.10 Input/output 121 'stdio.h': [ 122 '_IOFBF', '_IOLBF', '_IONBF', 'BUFSIZ', 'EOF', 'FILE', 'FILENAME_MAX', 123 'FOPEN_MAX', 'fpos_t', 'L_tmpnam', 'NULL', 'SEEK_CUR', 'SEEK_END', 124 'SEEK_SET', 'size_t', 'stderr', 'stdin', 'stdout', 'TMP_MAX', 125 'remove', 'rename', 'tmpfile', 'tmpnam', 'fclose', 'fflush', 'fopen', 126 'freopen', 'setbuf', 'setvbuf', 'fprintf', 'fscanf', 'printf', 127 'scanf', 'sprintf', 'sscanf', 'vfprintf', 'vprintf', 'vsprintf', 128 'fgetc', 'fgets', 'fputc', 'fputs', 'getc', 'getchar', 'gets', 'putc', 129 'putchar', 'puts', 'ungetc', 'fread', 'fwrite', 'fgetpos', 'fseek', 130 'fsetpos', 'rewind', 'clearerr', 'feof', 'ferror', 'perror', 131 ], 132 # D.11 General utilities 133 'stdlib.h': [ 134 'EXIT_FAILURE', 'EXIT_SUCCESS', 'MB_CUR_MAX', 'NULL', 'RAND_MAX', 135 'div_t', 'ldiv_t', 'wchar_t', 'atof', 'atoi', 'strtod', 'rand', 136 'srand', 'calloc', 'free', 'malloc', 'realloc', 'abort', 'atexit', 137 'exit', 'getenv', 'system', 'bsearch', 'qsort', 'abs', 'div', 'ldiv', 138 'mblen', 'mbtowc', 'wctomb', 'mbstowcs', 'wcstombs', 139 ], 140 # D.12 String handling 141 'string.h': [ 142 'NULL', 'size_t', 'memcpy', 'memmove', 'strcpy', 'strncpy', 'strcat', 143 'strncat', 'memcmp', 'strcmp', 'strcoll', 'strncmp', 'strxfrm', 144 'memchr', 'strchr', 'strcspn', 'strpbrk', 'strrchr', 'strspn', 145 'strstr', 'strtok', 'memset', 'strerror', 'strlen', 146 ], 147 # D.13 Date and time 148 'time.h': [ 149 'CLK_TCK', 'NULL', 'clock_t', 'time_t', 'size_t', 'tm', 'clock', 150 'difftime', 'mktime', 'time', 'asctime', 'ctime', 'gmtime', 151 'localtime', 'strftime', 152 ], 153 # Annex E: Implementation limits 154 'limits.h': [ 155 'CHAR_BIT', 'SCHAR_MIN', 'SCHAR_MAX', 'UCHAR_MAX', 'CHAR_MIN', 156 'CHAR_MAX', 'MB_LEN_MAX', 'SHRT_MIN', 'SHRT_MAX', 'USHRT_MAX', 157 'INT_MIN', 'INT_MAX', 'UINT_MAX', 'LONG_MIN', 'LONG_MAX', 'ULONG_MAX', 158 ], 159 'float.h': [ 160 'FLT_ROUNDS', 'FLT_RADIX', 'FLT_MANT_DIG', 'DBL_MANT_DIG', 161 'LDBL_MANT_DIG', 'DECIMAL_DIG', 'FLT_DIG', 'DBL_DIG', 'LDBL_DIG', 162 'DBL_MIN_EXP', 'LDBL_MIN_EXP', 'FLT_MIN_10_EXP', 'DBL_MIN_10_EXP', 163 'LDBL_MIN_10_EXP', 'FLT_MAX_EXP', 'DBL_MAX_EXP', 'LDBL_MAX_EXP', 164 'FLT_MAX_10_EXP', 'DBL_MAX_10_EXP', 'LDBL_MAX_10_EXP', 'FLT_MAX', 165 'DBL_MAX', 'LDBL_MAX', 'FLT_MIN', 'DBL_MIN', 'LDBL_MIN', 166 'FLT_EPSILON', 'DBL_EPSILON', 'LDBL_EPSILON' 167 ], 168} 169 170 171# Identifiers described in Section 7 "Library" of C99 Standard 172# Based on ISO/IEC 9899 WF14/N1256 Annex B -- Library summary 173C99_STDLIB_IDENTIFIERS = { 174 # B.1 Diagnostics 175 'assert.h': C90_STDLIB_IDENTIFIERS['assert.h'], 176 # B.2 Complex 177 'complex.h': [ 178 'complex', 'imaginary', 'I', '_Complex_I', '_Imaginary_I', 179 'CX_LIMITED_RANGE', 180 'cacos', 'cacosf', 'cacosl', 181 'casin', 'casinf', 'casinl', 182 'catan', 'catanf', 'catanl', 183 'ccos', 'ccosf', 'ccosl', 184 'csin', 'csinf', 'csinl', 185 'ctan', 'ctanf', 'ctanl', 186 'cacosh', 'cacoshf', 'cacoshl', 187 'casinh', 'casinhf', 'casinhl', 188 'catanh', 'catanhf', 'catanhl', 189 'ccosh', 'ccoshf', 'ccoshl', 190 'csinh', 'csinhf', 'csinhl', 191 'ctanh', 'ctanhf', 'ctanhl', 192 'cexp', 'cexpf', 'cexpl', 193 'clog', 'clogf', 'clogl', 194 'cabs', 'cabsf', 'cabsl', 195 'cpow', 'cpowf', 'cpowl', 196 'csqrt', 'csqrtf', 'csqrtl', 197 'carg', 'cargf', 'cargl', 198 'cimag', 'cimagf', 'cimagl', 199 'conj', 'conjf', 'conjl', 200 'cproj', 'cprojf', 'cprojl', 201 'creal', 'crealf', 'creall', 202 ], 203 # B.3 Character handling 204 'ctype.h': C90_STDLIB_IDENTIFIERS['ctype.h'], 205 # B.4 Errors 206 'errno.h': C90_STDLIB_IDENTIFIERS['errno.h'] + ['EILSEQ'], 207 # B.5 Floating-point environment 208 'fenv.h': [ 209 'fenv_t', 'FE_OVERFLOW', 'FE_TOWARDZERO', 210 'fexcept_t', 'FE_UNDERFLOW', 'FE_UPWARD', 211 'FE_DIVBYZERO', 'FE_ALL_EXCEPT', 'FE_DFL_ENV', 212 'FE_INEXACT', 'FE_DOWNWARD', 213 'FE_INVALID', 'FE_TONEAREST', 214 'FENV_ACCESS', 215 'feclearexcept', 'fegetexceptflag', 'fegetround', 216 'fesetround', 'fegetenv', 'feholdexcept', 217 'fesetenv', 'feupdateenv', 218 ], 219 # B.6 Characteristics of floating types 220 'float.h': C90_STDLIB_IDENTIFIERS['float.h'] + ['FLT_EVAL_METHOD'], 221 # B.7 Format conversion of integer types 222 'inttypes.h': [ 223 'imaxdiv_t', 'imaxabs', 'imaxdiv', 'strtoimax', 224 'strtoumax', 'wcstoimax', 'wcstoumax', 225 ], 226 # B.8 Alternative spellings 227 'iso646.h': [ 228 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', 229 'or', 'or_eq', 'xor', 'xor_eq', 230 ], 231 # B.9 Size of integer types 232 'limits.h': C90_STDLIB_IDENTIFIERS['limits.h'] + 233 ['LLONG_MIN', 'LLONG_MAX', 'ULLONG_MAX'], 234 # B.10 Localization 235 'locale.h': C90_STDLIB_IDENTIFIERS['locale.h'], 236 # B.11 Mathematics 237 'math.h': C90_STDLIB_IDENTIFIERS['math.h'] + [ 238 'float_t', 'double_t', 'HUGE_VAL', 'HUGE_VALF', 'HUGE_VALL', 239 'INFINITY', 'NAN', 'FP_INFINITE', 'FP_NAN', 'FP_NORMAL', 240 'FP_SUBNORMAL', 'FP_ZERO', 'FP_FAST_FMA', 'FP_FAST_FMAF', 241 'FP_FAST_FMAL', 'FP_ILOGB0', 'FP_ILOGBNAN', 'MATH_ERRNO', 242 'MATH_ERREXCEPT', 'math_errhandling', 'FP_CONTRACT', 'fpclassify', 243 'isfinite', 'isinf', 'isnan', 'isnormal', 'signbit', 'acosf', 'acosl', 244 'asinf', 'asinl', 'atanf', 'atanl', 'atan2', 'atan2f', 'atan2l', 245 'cosf', 'cosl', 'sinf', 'sinl', 'tanf', 'tanl', 'acosh', 'acoshf', 246 'acoshl', 'asinh', 'asinhf', 'asinhl', 'atanh', 'atanhf', 'atanhl', 247 'cosh', 'coshf', 'coshl', 'sinh', 'sinhf', 'sinhl', 'tanh', 'tanhf', 248 'tanhl', 'expf', 'expl', 'exp2', 'exp2f', 'exp2l', 'expm1', 'expm1f', 249 'expm1l', 'frexpf', 'frexpl', 'ilogb', 'ilogbf', 'ilogbl', 'float', 250 'ldexpl', 'logf', 'logl', 'log10f', 'log10l', 'log1p', 'log1pf', 251 'log1pl', 'log2', 'log2f', 'log2l', 'logb', 'logbf', 'logbl', 'modff', 252 'modfl', 'scalbn', 'scalbnf', 'scalbnl', 'scalbln', 'scalblnf', 253 'scalblnl', 'hypotl', 'powf', 'powl', 'sqrtf', 'sqrtl', 'erf', 'erff', 254 'erfl', 'erfc', 'erfcf', 'erfcl', 'lgamma', 'lgammaf', 'lgammal', 255 'tgamma', 'tgammaf', 'tgammal', 'ceilf', 'ceill', 'floorf', 'floorl', 256 'nearbyint', 'nearbyintf', 'nearbyintl', 'rint', 'rintf', 'rintl', 257 'lrint', 'lrintf', 'lrintl', 'llrint', 'llrintf', 'llrintl', 'round', 258 'roundf', 'roundl', 'lround', 'lroundf', 'lroundl', 'llround', 259 'llroundf', 'llroundl', 'trunc', 'truncf', 'truncl', 'fmodf', 'fmodl', 260 'remainder', 'remainderf', 'remainderl', 'remquo', 'remquof', 261 'remquol', 'copysign', 'copysignf', 'copysignl', 'nan', 'nanf', 262 'nanl', 'nextafter', 'nextafterf', 'nextafterl', 'nexttoward', 263 'nexttowardf', 'nexttowardl', 'fdim', 'fdimf', 'fdiml', 'fmax', 264 'fmaxf', 'fmaxl', 'fmin', 'fminf', 'fminl', 'fmal', 'isgreater', 265 'isgreaterequal', 'isless', 'islessequal', 'islessgreater', 266 'isunordered', 267 ], 268 # B.12 Nonlocal jumps 269 'setjmp.h': C90_STDLIB_IDENTIFIERS['setjmp.h'], 270 # B.13 Signal handling 271 'signal.h': C90_STDLIB_IDENTIFIERS['signal.h'], 272 # B.14 Variable arguments 273 'stdarg.h': C90_STDLIB_IDENTIFIERS['stdarg.h'] + ['va_copy'], 274 # B.15 Boolean type and values 275 'stdbool.h': ['bool', 'true', 'false', '__bool_true_false_are_defined'], 276 # B.16 Common definitions 277 'stddef.h': C90_STDLIB_IDENTIFIERS['stddef.h'], 278 # B.17 Integer types 279 'stdint.h': [ 280 'intptr_t', 'uintptr_t', 'intmax_t', 'uintmax_t', 'INTN_MIN', 281 'INTN_MAX', 'UINTN_MAX', 'INT_LEASTN_MIN', 'INT_LEASTN_MAX', 282 'UINT_LEASTN_MAX', 'INT_FASTN_MIN', 'INT_FASTN_MAX', 'UINT_FASTN_MAX', 283 'INTPTR_MIN', 'INTPTR_MAX', 'UINTPTR_MAX', 'INTMAX_MIN', 'INTMAX_MAX', 284 'UINTMAX_MAX', 'PTRDIFF_MIN', 'PTRDIFF_MAX', 'SIG_ATOMIC_MIN', 285 'SIG_ATOMIC_MAX', 'SIZE_MAX', 'WCHAR_MIN', 'WCHAR_MAX', 'WINT_MIN', 286 'WINT_MAX', 'INTN_C', 'UINTN_C', 'INTMAX_C', 'UINTMAX_C', 287 ] + STDINT_TYPES, 288 # B.18 Input/output 289 'stdio.h': C90_STDLIB_IDENTIFIERS['stdio.h'] + [ 290 'mode', 'restrict', 'snprintf', 'vfscanf', 'vscanf', 291 'vsnprintf', 'vsscanf', 292 ], 293 # B.19 General utilities 294 'stdlib.h': C90_STDLIB_IDENTIFIERS['stdlib.h'] + [ 295 '_Exit', 'labs', 'llabs', 'lldiv', 'lldiv_t', 'strtof', 'strtol', 296 'strtold', 'strtoll', 'strtoul', 'strtoull' 297 ], 298 # B.20 String handling 299 'string.h': C90_STDLIB_IDENTIFIERS['string.h'], 300 # B.21 Type-generic math 301 'tgmath.h': [ 302 'acos', 'asin', 'atan', 'acosh', 'asinh', 'atanh', 'cos', 'sin', 'tan', 303 'cosh', 'sinh', 'tanh', 'exp', 'log', 'pow', 'sqrt', 'fabs', 'atan2', 304 'cbrt', 'ceil', 'copysign', 'erf', 'erfc', 'exp2', 'expm1', 'fdim', 305 'floor', 'fma', 'fmax', 'fmin', 'fmod', 'frexp', 'hypot', 'ilogb', 306 'ldexp', 'lgamma', 'llrint', 'llround', 'log10', 'log1p', 'log2', 307 'logb', 'lrint', 'lround', 'nearbyint', 'nextafter', 'nexttoward', 308 'remainder', 'remquo', 'rint', 'round', 'scalbn', 'scalbln', 'tgamma', 309 'trunc', 'carg', 'cimag', 'conj', 'cproj', 'creal', 310 ], 311 # B.22 Date and time 312 'time.h': C90_STDLIB_IDENTIFIERS['time.h'] + ['CLOCKS_PER_SEC'], 313 # B.23 Extended multibyte/wide character utilities 314 'wchar.h': [ 315 'wchar_t', 'size_t', 'mbstate_t', 'wint_t', 'tm', 'NULL', 'WCHAR_MAX', 316 'WCHAR_MIN', 'WEOF', 'fwprintf', 'fwscanf', 'swprintf', 'swscanf', 317 'vfwprintf', 'vfwscanf', 'vswprintf', 'vswscanf', 'vwprintf', 318 'vwscanf', 'wprintf', 'wscanf', 'fgetwc', 'fgetws', 'fputwc', 'fputws', 319 'fwide', 'getwc', 'getwchar', 'putwc', 'putwchar', 'ungetwc', 'wcstod', 320 'wcstof', 'double', 'int', 'long', 'long', 'long', 'wcscpy', 'wcsncpy', 321 'wmemcpy', 'wmemmove', 'wcscat', 'wcsncat', 'wcscmp', 'wcscoll', 322 'wcsncmp', 'wcsxfrm', 'wmemcmp', 'wcschr', 'wcscspn', 'wcspbrk', 323 'wcsrchr', 'wcsspn', 'wcsstr', 'wcstok', 'wmemchr', 'wcslen', 324 'wmemset', 'wcsftime', 'btowc', 'wctob', 'mbsinit', 'mbrlen', 325 'mbrtowc', 'wcrtomb', 'mbsrtowcs', 'wcsrtombs', 326 ], 327} 328 329 330def isStdLibId(id_, standard='c99'): 331 id_lists = [] 332 if standard == 'c89': 333 id_lists = C90_STDLIB_IDENTIFIERS.values() 334 elif standard in ('c99', 'c11'): 335 id_lists = C99_STDLIB_IDENTIFIERS.values() 336 for l in id_lists: 337 if id_ in l: 338 return True 339 return False 340 341 342# Reserved keywords defined in ISO/IEC9899:1990 -- ch 6.1.1 343C90_KEYWORDS = { 344 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 345 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 346 'int', 'long', 'register', 'return', 'short', 'signed', 347 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 348 'void', 'volatile', 'while' 349} 350 351 352# Reserved keywords defined in ISO/IEC 9899 WF14/N1256 -- ch. 6.4.1 353C99_ADDED_KEYWORDS = { 354 'inline', 'restrict', '_Bool', '_Complex', '_Imaginary', 355 'bool', 'complex', 'imaginary' 356} 357 358C11_ADDED_KEYWORDS = { 359 '_Alignas', '_Alignof', '_Atomic', '_Generic', '_Noreturn', 360 '_Statis_assert', '_Thread_local' , 361 'alignas', 'alignof', 'noreturn', 'static_assert' 362} 363 364def isKeyword(keyword, standard='c99'): 365 kw_set = {} 366 if standard == 'c89': 367 kw_set = C90_KEYWORDS 368 elif standard == 'c99': 369 kw_set = copy.copy(C90_KEYWORDS) 370 kw_set.update(C99_ADDED_KEYWORDS) 371 else: 372 kw_set = copy.copy(C90_KEYWORDS) 373 kw_set.update(C99_ADDED_KEYWORDS) 374 kw_set.update(C11_ADDED_KEYWORDS) 375 return keyword in kw_set 376 377 378def is_source_file(file): 379 return file.endswith('.c') 380 381 382def is_header(file): 383 return file.endswith('.h') 384 385 386def is_errno_setting_function(function_name): 387 return function_name and \ 388 function_name in ('ftell', 'fgetpos', 'fsetpos', 'fgetwc', 'fputwc' 389 'strtoimax', 'strtoumax', 'strtol', 'strtoul', 390 'strtoll', 'strtoull', 'strtof', 'strtod', 'strtold' 391 'wcstoimax', 'wcstoumax', 'wcstol', 'wcstoul', 392 'wcstoll', 'wcstoull', 'wcstof', 'wcstod', 'wcstold' 393 'wcrtomb', 'wcsrtombs', 'mbrtowc') 394 395 396def get_type_conversion_to_from(token): 397 def get_vartok(expr): 398 while expr: 399 if isCast(expr): 400 if expr.astOperand2 is None: 401 expr = expr.astOperand1 402 else: 403 expr = expr.astOperand2 404 elif expr.str in ('.', '::'): 405 expr = expr.astOperand2 406 elif expr.str == '[': 407 expr = expr.astOperand1 408 else: 409 break 410 return expr if (expr and expr.variable) else None 411 412 if isCast(token): 413 vartok = get_vartok(token) 414 if vartok: 415 return (token.next, vartok.variable.typeStartToken) 416 417 elif token.str == '=': 418 lhs = get_vartok(token.astOperand1) 419 rhs = get_vartok(token.astOperand2) 420 if lhs and rhs: 421 return (lhs.variable.typeStartToken, rhs.variable.typeStartToken) 422 423 return None 424 425def getEssentialTypeCategory(expr): 426 if not expr: 427 return None 428 if expr.str == ',': 429 return getEssentialTypeCategory(expr.astOperand2) 430 if expr.str in ('<', '<=', '==', '!=', '>=', '>', '&&', '||', '!'): 431 return 'bool' 432 if expr.str in ('<<', '>>'): 433 # TODO this is incomplete 434 return getEssentialTypeCategory(expr.astOperand1) 435 if len(expr.str) == 1 and expr.str in '+-*/%&|^': 436 # TODO this is incomplete 437 e1 = getEssentialTypeCategory(expr.astOperand1) 438 e2 = getEssentialTypeCategory(expr.astOperand2) 439 # print('{0}: {1} {2}'.format(expr.str, e1, e2)) 440 if e1 and e2 and e1 == e2: 441 return e1 442 if expr.valueType: 443 return expr.valueType.sign 444 if expr.valueType and expr.valueType.typeScope and expr.valueType.typeScope.className: 445 return "enum<" + expr.valueType.typeScope.className + ">" 446 vartok = expr 447 while simpleMatch(vartok, '[') or (vartok and vartok.str == '*' and vartok.astOperand2 is None): 448 vartok = vartok.astOperand1 449 if vartok and vartok.variable: 450 typeToken = vartok.variable.typeStartToken 451 while typeToken and typeToken.isName: 452 if typeToken.str == 'char' and not typeToken.isSigned and not typeToken.isUnsigned: 453 return 'char' 454 if typeToken.valueType: 455 if typeToken.valueType.type == 'bool': 456 return typeToken.valueType.type 457 if typeToken.valueType.type in ('float', 'double', 'long double'): 458 return "float" 459 if typeToken.valueType.sign: 460 return typeToken.valueType.sign 461 typeToken = typeToken.next 462 463 # See Appendix D, section D.6, Character constants 464 if expr.str[0] == "'" and expr.str[-1] == "'": 465 if len(expr.str) == 3 or (len(expr.str) == 4 and expr.str[1] == '\\'): 466 return 'char' 467 return expr.valueType.sign 468 469 if expr.valueType: 470 return expr.valueType.sign 471 return None 472 473 474def getEssentialCategorylist(operand1, operand2): 475 if not operand1 or not operand2: 476 return None, None 477 if (operand1.str in ('++', '--') or 478 operand2.str in ('++', '--')): 479 return None, None 480 if ((operand1.valueType and operand1.valueType.pointer) or 481 (operand2.valueType and operand2.valueType.pointer)): 482 return None, None 483 e1 = getEssentialTypeCategory(operand1) 484 e2 = getEssentialTypeCategory(operand2) 485 return e1, e2 486 487 488def get_essential_type_from_value(value, is_signed): 489 if value is None: 490 return None 491 for t in ('char', 'short', 'int', 'long', 'long long'): 492 bits = bitsOfEssentialType(t) 493 if bits >= 64: 494 continue 495 if is_signed: 496 range_min = -(1 << (bits - 1)) 497 range_max = (1 << (bits - 1)) - 1 498 else: 499 range_min = 0 500 range_max = (1 << bits) - 1 501 sign = 'signed' if is_signed else 'unsigned' 502 if is_signed and value < 0 and value >= range_min: 503 return '%s %s' % (sign, t) 504 if value >= 0 and value <= range_max: 505 return '%s %s' % (sign, t) 506 return None 507 508def getEssentialType(expr): 509 if not expr: 510 return None 511 512 # See Appendix D, section D.6, Character constants 513 if expr.str[0] == "'" and expr.str[-1] == "'": 514 if len(expr.str) == 3 or (len(expr.str) == 4 and expr.str[1] == '\\'): 515 return 'char' 516 return '%s %s' % (expr.valueType.sign, expr.valueType.type) 517 518 if expr.variable or isCast(expr): 519 typeToken = expr.variable.typeStartToken if expr.variable else expr.next 520 while typeToken and typeToken.isName: 521 if typeToken.str == 'char' and not typeToken.isSigned and not typeToken.isUnsigned: 522 return 'char' 523 typeToken = typeToken.next 524 if expr.valueType: 525 if expr.valueType.type == 'bool': 526 return 'bool' 527 if expr.valueType.isFloat(): 528 return expr.valueType.type 529 if expr.valueType.isIntegral(): 530 if (expr.valueType.sign is None) and expr.valueType.type == 'char': 531 return 'char' 532 return '%s %s' % (expr.valueType.sign, expr.valueType.type) 533 534 elif expr.isNumber: 535 # Appendix D, D.6 The essential type of literal constants 536 # Integer constants 537 if expr.valueType.type == 'bool': 538 return 'bool' 539 if expr.valueType.isFloat(): 540 return expr.valueType.type 541 if expr.valueType.isIntegral(): 542 if expr.valueType.type != 'int': 543 return '%s %s' % (expr.valueType.sign, expr.valueType.type) 544 return get_essential_type_from_value(expr.getKnownIntValue(), expr.valueType.sign == 'signed') 545 546 elif expr.str in ('<', '<=', '>=', '>', '==', '!=', '&&', '||', '!'): 547 return 'bool' 548 549 elif expr.astOperand1 and expr.astOperand2 and expr.str in ( 550 '+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":"): 551 if expr.astOperand1.valueType and expr.astOperand1.valueType.pointer > 0: 552 return None 553 if expr.astOperand2.valueType and expr.astOperand2.valueType.pointer > 0: 554 return None 555 e1 = getEssentialType(expr.astOperand1) 556 e2 = getEssentialType(expr.astOperand2) 557 if e1 is None or e2 is None: 558 return None 559 if is_constant_integer_expression(expr): 560 sign1 = e1.split(' ')[0] 561 sign2 = e2.split(' ')[0] 562 if sign1 == sign2 and sign1 in ('signed', 'unsigned'): 563 e = get_essential_type_from_value(expr.getKnownIntValue(), sign1 == 'signed') 564 if e: 565 return e 566 if bitsOfEssentialType(e2) >= bitsOfEssentialType(e1): 567 return e2 568 else: 569 return e1 570 571 elif expr.str == "~": 572 e1 = getEssentialType(expr.astOperand1) 573 return e1 574 575 return None 576 577 578def bitsOfEssentialType(ty): 579 if ty is None: 580 return 0 581 last_type = ty.split(' ')[-1] 582 if last_type == 'Boolean': 583 return 1 584 if last_type == 'char': 585 return typeBits['CHAR'] 586 if last_type == 'short': 587 return typeBits['SHORT'] 588 if last_type == 'int': 589 return typeBits['INT'] 590 if ty.endswith('long long'): 591 return typeBits['LONG_LONG'] 592 if last_type == 'long': 593 return typeBits['LONG'] 594 for sty in STDINT_TYPES: 595 if ty == sty: 596 return int(''.join(filter(str.isdigit, sty))) 597 return 0 598 599 600def get_function_pointer_type(tok): 601 ret = '' 602 par = 0 603 while tok and (tok.isName or tok.str == '*'): 604 ret += ' ' + tok.str 605 tok = tok.next 606 if tok is None or tok.str != '(': 607 return None 608 tok = tok.link 609 if not simpleMatch(tok, ') ('): 610 return None 611 ret += '(' 612 tok = tok.next.next 613 while tok and (tok.str not in '()'): 614 ret += ' ' + tok.str 615 tok = tok.next 616 if (tok is None) or tok.str != ')': 617 return None 618 return ret[1:] + ')' 619 620def isCast(expr): 621 if not expr or expr.str != '(' or not expr.astOperand1 or expr.astOperand2: 622 return False 623 if simpleMatch(expr, '( )'): 624 return False 625 return True 626 627def is_constant_integer_expression(expr): 628 if expr is None: 629 return False 630 if expr.isInt: 631 return True 632 if not expr.isArithmeticalOp: 633 return False 634 if expr.astOperand1 and not is_constant_integer_expression(expr.astOperand1): 635 return False 636 if expr.astOperand2 and not is_constant_integer_expression(expr.astOperand2): 637 return False 638 return True 639 640def isFunctionCall(expr, std='c99'): 641 if not expr: 642 return False 643 if expr.str != '(' or not expr.astOperand1: 644 return False 645 if expr.astOperand1 != expr.previous: 646 return False 647 if isKeyword(expr.astOperand1.str, std): 648 return False 649 return True 650 651 652def hasExternalLinkage(var): 653 return var.isGlobal and not var.isStatic 654 655 656def countSideEffects(expr): 657 if not expr or expr.str in (',', ';'): 658 return 0 659 ret = 0 660 if expr.str in ('++', '--', '='): 661 ret = 1 662 return ret + countSideEffects(expr.astOperand1) + countSideEffects(expr.astOperand2) 663 664 665def getForLoopExpressions(forToken): 666 if not forToken or forToken.str != 'for': 667 return None 668 lpar = forToken.next 669 if not lpar or lpar.str != '(': 670 return None 671 if not lpar.astOperand2 or lpar.astOperand2.str != ';': 672 return None 673 if not lpar.astOperand2.astOperand2 or lpar.astOperand2.astOperand2.str != ';': 674 return None 675 return [lpar.astOperand2.astOperand1, 676 lpar.astOperand2.astOperand2.astOperand1, 677 lpar.astOperand2.astOperand2.astOperand2] 678 679 680def getForLoopCounterVariables(forToken): 681 """ Return a set of Variable objects defined in ``for`` statement and 682 satisfy requirements to loop counter term from section 8.14 of MISRA 683 document. 684 """ 685 if not forToken or forToken.str != 'for': 686 return None 687 tn = forToken.next 688 if not tn or tn.str != '(': 689 return None 690 vars_defined = set() 691 vars_exit = set() 692 vars_modified = set() 693 cur_clause = 1 694 te = tn.link 695 while tn and tn != te: 696 if tn.variable: 697 if cur_clause == 1 and tn.variable.nameToken == tn: 698 vars_defined.add(tn.variable) 699 elif cur_clause == 2: 700 vars_exit.add(tn.variable) 701 elif cur_clause == 3: 702 if tn.next and hasSideEffectsRecursive(tn.next): 703 vars_modified.add(tn.variable) 704 elif tn.previous and tn.previous.str in ('++', '--'): 705 vars_modified.add(tn.variable) 706 if tn.str == ';': 707 cur_clause += 1 708 tn = tn.next 709 return vars_defined & vars_exit & vars_modified 710 711 712def findCounterTokens(cond): 713 if not cond: 714 return [] 715 if cond.str in ['&&', '||']: 716 c = findCounterTokens(cond.astOperand1) 717 c.extend(findCounterTokens(cond.astOperand2)) 718 return c 719 ret = [] 720 if ((cond.isArithmeticalOp and cond.astOperand1 and cond.astOperand2) or 721 (cond.isComparisonOp and cond.astOperand1 and cond.astOperand2)): 722 if cond.astOperand1.isName: 723 ret.append(cond.astOperand1) 724 if cond.astOperand2.isName: 725 ret.append(cond.astOperand2) 726 if cond.astOperand1.isOp: 727 ret.extend(findCounterTokens(cond.astOperand1)) 728 if cond.astOperand2.isOp: 729 ret.extend(findCounterTokens(cond.astOperand2)) 730 return ret 731 732 733def isFloatCounterInWhileLoop(whileToken): 734 if not simpleMatch(whileToken, 'while ('): 735 return False 736 lpar = whileToken.next 737 rpar = lpar.link 738 counterTokens = findCounterTokens(lpar.astOperand2) 739 whileBodyStart = None 740 if simpleMatch(rpar, ') {'): 741 whileBodyStart = rpar.next 742 elif simpleMatch(whileToken.previous, '} while') and simpleMatch(whileToken.previous.link.previous, 'do {'): 743 whileBodyStart = whileToken.previous.link 744 else: 745 return False 746 token = whileBodyStart 747 while token != whileBodyStart.link: 748 token = token.next 749 for counterToken in counterTokens: 750 if not counterToken.valueType or not counterToken.valueType.isFloat(): 751 continue 752 if token.isAssignmentOp and token.astOperand1.str == counterToken.str: 753 return True 754 if token.str == counterToken.str and token.astParent and token.astParent.str in ('++', '--'): 755 return True 756 return False 757 758 759def hasSideEffectsRecursive(expr): 760 if not expr or expr.str == ';': 761 return False 762 if expr.str == '=' and expr.astOperand1 and expr.astOperand1.str == '[': 763 prev = expr.astOperand1.previous 764 if prev and (prev.str == '{' or prev.str == '{'): 765 return hasSideEffectsRecursive(expr.astOperand2) 766 if expr.str == '=' and expr.astOperand1 and expr.astOperand1.str == '.': 767 e = expr.astOperand1 768 while e and e.str == '.' and e.astOperand2: 769 e = e.astOperand1 770 if e and e.str == '.': 771 return False 772 if expr.isAssignmentOp or expr.str in {'++', '--'}: 773 return True 774 # Todo: Check function calls 775 return hasSideEffectsRecursive(expr.astOperand1) or hasSideEffectsRecursive(expr.astOperand2) 776 777 778def isBoolExpression(expr): 779 if not expr: 780 return False 781 if expr.valueType and (expr.valueType.type == 'bool' or expr.valueType.bits == 1): 782 return True 783 return expr.str in ['!', '==', '!=', '<', '<=', '>', '>=', '&&', '||', '0', '1', 'true', 'false'] 784 785 786def isEnumConstant(expr): 787 if not expr or not expr.values: 788 return False 789 values = expr.values 790 return len(values) == 1 and values[0].valueKind == 'known' 791 792 793def isConstantExpression(expr): 794 if expr.isNumber: 795 return True 796 if expr.isName and not isEnumConstant(expr): 797 return False 798 if simpleMatch(expr.previous, 'sizeof ('): 799 return True 800 if expr.astOperand1 and not isConstantExpression(expr.astOperand1): 801 return False 802 if expr.astOperand2 and not isConstantExpression(expr.astOperand2): 803 return False 804 return True 805 806 807def isUnsignedInt(expr): 808 return expr and expr.valueType and expr.valueType.type in ('short', 'int') and expr.valueType.sign == 'unsigned' 809 810 811def getPrecedence(expr): 812 if not expr: 813 return 16 814 if not expr.astOperand1 or not expr.astOperand2: 815 return 16 816 if expr.str in ('*', '/', '%'): 817 return 12 818 if expr.str in ('+', '-'): 819 return 11 820 if expr.str in ('<<', '>>'): 821 return 10 822 if expr.str in ('<', '>', '<=', '>='): 823 return 9 824 if expr.str in ('==', '!='): 825 return 8 826 if expr.str == '&': 827 return 7 828 if expr.str == '^': 829 return 6 830 if expr.str == '|': 831 return 5 832 if expr.str == '&&': 833 return 4 834 if expr.str == '||': 835 return 3 836 if expr.str in ('?', ':'): 837 return 2 838 if expr.isAssignmentOp: 839 return 1 840 if expr.str == ',': 841 return 0 842 return -1 843 844 845def findRawLink(token): 846 tok1 = None 847 tok2 = None 848 forward = False 849 850 if token.str in '{([': 851 tok1 = token.str 852 tok2 = '})]'['{(['.find(token.str)] 853 forward = True 854 elif token.str in '})]': 855 tok1 = token.str 856 tok2 = '{(['['})]'.find(token.str)] 857 forward = False 858 else: 859 return None 860 861 # try to find link 862 indent = 0 863 while token: 864 if token.str == tok1: 865 indent = indent + 1 866 elif token.str == tok2: 867 if indent <= 1: 868 return token 869 indent = indent - 1 870 if forward is True: 871 token = token.next 872 else: 873 token = token.previous 874 875 # raw link not found 876 return None 877 878 879def numberOfParentheses(tok1, tok2): 880 while tok1 and tok1 != tok2: 881 if tok1.str == '(' or tok1.str == ')': 882 return False 883 tok1 = tok1.next 884 return tok1 == tok2 885 886 887def findGotoLabel(gotoToken): 888 label = gotoToken.next.str 889 tok = gotoToken.next.next 890 while tok: 891 if tok.str == '}' and tok.scope.type == 'Function': 892 break 893 if tok.str == label and tok.next.str == ':': 894 return tok 895 tok = tok.next 896 return None 897 898 899def findInclude(directives, header): 900 for directive in directives: 901 if directive.str == '#include ' + header: 902 return directive 903 return None 904 905 906# Get function arguments 907def getArgumentsRecursive(tok, arguments): 908 if tok is None: 909 return 910 if tok.str == ',': 911 getArgumentsRecursive(tok.astOperand1, arguments) 912 getArgumentsRecursive(tok.astOperand2, arguments) 913 else: 914 arguments.append(tok) 915 916 917def getArguments(ftok): 918 arguments = [] 919 getArgumentsRecursive(ftok.astOperand2, arguments) 920 return arguments 921 922 923def isalnum(c): 924 return c in string.digits or c in string.ascii_letters 925 926 927def isHexEscapeSequence(symbols): 928 """Checks that given symbols are valid hex escape sequence. 929 930 hexadecimal-escape-sequence: 931 \\x hexadecimal-digit 932 hexadecimal-escape-sequence hexadecimal-digit 933 934 Reference: n1570 6.4.4.4""" 935 if len(symbols) < 3 or symbols[:2] != '\\x': 936 return False 937 return all([s in string.hexdigits for s in symbols[2:]]) 938 939 940def isOctalEscapeSequence(symbols): 941 r"""Checks that given symbols are valid octal escape sequence: 942 943 octal-escape-sequence: 944 \ octal-digit 945 \ octal-digit octal-digit 946 \ octal-digit octal-digit octal-digit 947 948 Reference: n1570 6.4.4.4""" 949 if len(symbols) not in range(2, 5) or symbols[0] != '\\': 950 return False 951 return all([s in string.octdigits for s in symbols[1:]]) 952 953 954def isSimpleEscapeSequence(symbols): 955 """Checks that given symbols are simple escape sequence. 956 Reference: n1570 6.4.4.4""" 957 if len(symbols) != 2 or symbols[0] != '\\': 958 return False 959 return symbols[1] in ("'", '"', '?', '\\', 'a', 'b', 'f', 'n', 'r', 't', 'v') 960 961 962def isTernaryOperator(token): 963 if not token: 964 return False 965 if not token.astOperand2: 966 return False 967 return token.str == '?' and token.astOperand2.str == ':' 968 969 970def getTernaryOperandsRecursive(token): 971 """Returns list of ternary operands including nested ones.""" 972 if not isTernaryOperator(token): 973 return [] 974 result = [] 975 result += getTernaryOperandsRecursive(token.astOperand2.astOperand1) 976 if token.astOperand2.astOperand1 and not isTernaryOperator(token.astOperand2.astOperand1): 977 result += [token.astOperand2.astOperand1] 978 result += getTernaryOperandsRecursive(token.astOperand2.astOperand2) 979 if token.astOperand2.astOperand2 and not isTernaryOperator(token.astOperand2.astOperand2): 980 result += [token.astOperand2.astOperand2] 981 return result 982 983 984def hasNumericEscapeSequence(symbols): 985 """Check that given string contains octal or hexadecimal escape sequences.""" 986 if '\\' not in symbols: 987 return False 988 for c, cn in grouped(symbols, 2): 989 if c == '\\' and cn in ('x' + string.octdigits): 990 return True 991 return False 992 993 994def isNoReturnScope(tok): 995 if tok is None or tok.str != '}': 996 return False 997 if tok.previous is None or tok.previous.str != ';': 998 return False 999 if simpleMatch(tok.previous.previous, 'break ;'): 1000 return True 1001 prev = tok.previous.previous 1002 while prev and prev.str not in ';{}': 1003 if prev.str in '])': 1004 prev = prev.link 1005 prev = prev.previous 1006 if prev and prev.next.str in ['throw', 'return']: 1007 return True 1008 return False 1009 1010 1011# Return the token which the value is assigned to 1012def getAssignedVariableToken(valueToken): 1013 if not valueToken: 1014 return None 1015 if not valueToken.astParent: 1016 return None 1017 operator = valueToken.astParent 1018 if operator.isAssignmentOp: 1019 return operator.astOperand1 1020 if operator.isArithmeticalOp: 1021 return getAssignedVariableToken(operator) 1022 return None 1023 1024# If the value is used as a return value, return the function definition 1025def getFunctionUsingReturnValue(valueToken): 1026 if not valueToken: 1027 return None 1028 if not valueToken.astParent: 1029 return None 1030 operator = valueToken.astParent 1031 if operator.str == 'return': 1032 return operator.scope.function 1033 if operator.isArithmeticalOp: 1034 return getFunctionUsingReturnValue(operator) 1035 return None 1036 1037# Return true if the token follows a specific sequence of token str values 1038def tokenFollowsSequence(token, sequence): 1039 if not token: 1040 return False 1041 for i in reversed(sequence): 1042 prev = token.previous 1043 if not prev: 1044 return False 1045 if prev.str != i: 1046 return False 1047 token = prev 1048 return True 1049 1050class Define: 1051 def __init__(self, directive): 1052 self.name = '' 1053 self.args = [] 1054 self.expansionList = '' 1055 1056 res = re.match(r'#define ([A-Za-z0-9_]+)\(([A-Za-z0-9_, ]+)\)[ ]+(.*)', directive.str) 1057 if res: 1058 self.name = res.group(1) 1059 self.args = res.group(2).strip().split(',') 1060 self.expansionList = res.group(3) 1061 else: 1062 res = re.match(r'#define ([A-Za-z0-9_]+)[ ]+(.*)', directive.str) 1063 if res: 1064 self.name = res.group(1) 1065 self.expansionList = res.group(2) 1066 1067 def __repr__(self): 1068 attrs = ["name", "args", "expansionList"] 1069 return "{}({})".format( 1070 "Define", 1071 ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) 1072 ) 1073 1074 1075def getAddonRules(): 1076 """Returns dict of MISRA rules handled by this addon.""" 1077 addon_rules = [] 1078 compiled = re.compile(r'.*def[ ]+misra_([0-9]+)_([0-9]+)[(].*') 1079 for line in open(__file__): 1080 res = compiled.match(line) 1081 if res is None: 1082 continue 1083 addon_rules.append(res.group(1) + '.' + res.group(2)) 1084 return addon_rules 1085 1086 1087def getCppcheckRules(): 1088 """Returns list of rules handled by cppcheck.""" 1089 return ['1.3', # <most "error"> 1090 '2.1', # alwaysFalse, duplicateBreak 1091 '2.2', # alwaysTrue, redundantCondition, redundantAssignment, redundantAssignInSwitch, unreadVariable 1092 '2.6', # unusedLabel 1093 '5.3', # shadowVariable 1094 '8.3', # funcArgNamesDifferent 1095 '8.13', # constPointer 1096 '9.1', # uninitvar 1097 '14.3', # alwaysTrue, alwaysFalse, compareValueOutOfTypeRangeError 1098 '13.2', # unknownEvaluationOrder 1099 '13.6', # sizeofCalculation 1100 '17.4', # missingReturn 1101 '17.5', # argumentSize 1102 '18.1', # pointerOutOfBounds 1103 '18.2', # comparePointers 1104 '18.3', # comparePointers 1105 '18.6', # danglingLifetime 1106 '19.1', # overlappingWriteUnion, overlappingWriteFunction 1107 '20.6', # preprocessorErrorDirective 1108 '21.13', # invalidFunctionArg 1109 '21.17', # bufferAccessOutOfBounds 1110 '21.18', # bufferAccessOutOfBounds 1111 '22.1', # memleak, resourceLeak, memleakOnRealloc, leakReturnValNotUsed, leakNoVarFunctionCall 1112 '22.2', # autovarInvalidDeallocation 1113 '22.3', # incompatibleFileOpen 1114 '22.4', # writeReadOnlyFile 1115 '22.6' # useClosedFile 1116 ] 1117 1118 1119def generateTable(): 1120 # print table 1121 numberOfRules = {} 1122 numberOfRules[1] = 3 1123 numberOfRules[2] = 7 1124 numberOfRules[3] = 2 1125 numberOfRules[4] = 2 1126 numberOfRules[5] = 9 1127 numberOfRules[6] = 2 1128 numberOfRules[7] = 4 1129 numberOfRules[8] = 14 1130 numberOfRules[9] = 5 1131 numberOfRules[10] = 8 1132 numberOfRules[11] = 9 1133 numberOfRules[12] = 4 1134 numberOfRules[13] = 6 1135 numberOfRules[14] = 4 1136 numberOfRules[15] = 7 1137 numberOfRules[16] = 7 1138 numberOfRules[17] = 8 1139 numberOfRules[18] = 8 1140 numberOfRules[19] = 2 1141 numberOfRules[20] = 14 1142 numberOfRules[21] = 21 1143 numberOfRules[22] = 10 1144 1145 # Rules that can be checked with compilers: 1146 # compiler = ['1.1', '1.2'] 1147 1148 addon = getAddonRules() 1149 cppcheck = getCppcheckRules() 1150 for i1 in range(1, 23): 1151 for i2 in range(1, numberOfRules[i1] + 1): 1152 num = str(i1) + '.' + str(i2) 1153 s = '' 1154 if num in addon: 1155 s = 'X (Addon)' 1156 elif num in cppcheck: 1157 s = 'X (Cppcheck)' 1158 num = num + ' ' 1159 print(num[:8] + s) 1160 1161 1162def remove_file_prefix(file_path, prefix): 1163 """ 1164 Remove a file path prefix from a give path. leftover 1165 directory separators at the beginning of a file 1166 after the removal are also stripped. 1167 1168 Example: 1169 '/remove/this/path/file.c' 1170 with a prefix of: 1171 '/remove/this/path' 1172 becomes: 1173 file.c 1174 """ 1175 result = None 1176 if file_path.startswith(prefix): 1177 result = file_path[len(prefix):] 1178 # Remove any leftover directory separators at the 1179 # beginning 1180 result = result.lstrip('\\/') 1181 else: 1182 result = file_path 1183 return result 1184 1185 1186class Rule(object): 1187 """Class to keep rule text and metadata""" 1188 1189 MISRA_SEVERITY_LEVELS = ['Required', 'Mandatory', 'Advisory'] 1190 1191 def __init__(self, num1, num2): 1192 self.num1 = num1 1193 self.num2 = num2 1194 self.text = '' 1195 self.misra_severity = '' 1196 1197 @property 1198 def num(self): 1199 return self.num1 * 100 + self.num2 1200 1201 @property 1202 def misra_severity(self): 1203 return self._misra_severity 1204 1205 @misra_severity.setter 1206 def misra_severity(self, val): 1207 if val in self.MISRA_SEVERITY_LEVELS: 1208 self._misra_severity = val 1209 else: 1210 self._misra_severity = '' 1211 1212 @property 1213 def cppcheck_severity(self): 1214 return 'style' 1215 1216 def __repr__(self): 1217 return "%d.%d (%s)" % (self.num1, self.num2, self.misra_severity) 1218 1219 1220class MisraSettings(object): 1221 """Hold settings for misra.py script.""" 1222 1223 __slots__ = ["verify", "quiet", "show_summary"] 1224 1225 def __init__(self, args): 1226 """ 1227 :param args: Arguments given by argparse. 1228 """ 1229 self.verify = False 1230 self.quiet = False 1231 self.show_summary = True 1232 1233 if args.verify: 1234 self.verify = True 1235 if args.cli: 1236 self.quiet = True 1237 self.show_summary = False 1238 if args.quiet: 1239 self.quiet = True 1240 if args.no_summary: 1241 self.show_summary = False 1242 1243 def __repr__(self): 1244 attrs = ["verify", "quiet", "show_summary", "verify"] 1245 return "{}({})".format( 1246 "MisraSettings", 1247 ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) 1248 ) 1249 1250 1251class MisraChecker: 1252 1253 def __init__(self, settings, stdversion="c89"): 1254 """ 1255 :param settings: misra.py script settings. 1256 """ 1257 1258 self.settings = settings 1259 1260 # Test validation rules lists 1261 self.verify_expected = list() 1262 self.verify_actual = list() 1263 1264 # List of formatted violation messages 1265 self.violations = dict() 1266 1267 # if --rule-texts is specified this dictionary 1268 # is loaded with descriptions of each rule 1269 # by rule number (in hundreds). 1270 # ie rule 1.2 becomes 102 1271 self.ruleTexts = dict() 1272 1273 # Dictionary of dictionaries for rules to suppress 1274 # Dict1 is keyed by rule number in the hundreds format of 1275 # Major * 100 + minor. ie Rule 5.2 = (5*100) + 2 1276 # Dict 2 is keyed by filename. An entry of None means suppress globally. 1277 # Each file name entry contains a list of tuples of (lineNumber, symbolName) 1278 # or an item of None which indicates suppress rule for the entire file. 1279 # The line and symbol name tuple may have None as either of its elements but 1280 # should not be None for both. 1281 self.suppressedRules = dict() 1282 1283 # Prefix to ignore when matching suppression files. 1284 self.filePrefix = None 1285 1286 # Number of all violations suppressed per rule 1287 self.suppressionStats = dict() 1288 1289 self.stdversion = stdversion 1290 1291 self.severity = None 1292 1293 self.existing_violations = set() 1294 1295 self._ctu_summary_typedefs = False 1296 self._ctu_summary_tagnames = False 1297 self._ctu_summary_identifiers = False 1298 self._ctu_summary_usage = False 1299 1300 def __repr__(self): 1301 attrs = ["settings", "verify_expected", "verify_actual", "violations", 1302 "ruleTexts", "suppressedRules", "filePrefix", 1303 "suppressionStats", "stdversion", "severity"] 1304 return "{}({})".format( 1305 "MisraChecker", 1306 ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) 1307 ) 1308 1309 def get_num_significant_naming_chars(self, cfg): 1310 if cfg.standards and cfg.standards.c == "c89": 1311 return 31 1312 else: 1313 return 63 1314 1315 def _save_ctu_summary_typedefs(self, dumpfile, typedef_info): 1316 if self._ctu_summary_typedefs: 1317 return 1318 1319 self._ctu_summary_typedefs = True 1320 1321 summary = [] 1322 for ti in typedef_info: 1323 summary.append({ 'name': ti.name, 'file': ti.file, 'line': ti.linenr, 'column': ti.column, 'used': ti.used }) 1324 if len(summary) > 0: 1325 cppcheckdata.reportSummary(dumpfile, 'MisraTypedefInfo', summary) 1326 1327 def _save_ctu_summary_tagnames(self, dumpfile, cfg): 1328 if self._ctu_summary_tagnames: 1329 return 1330 1331 self._ctu_summary_tagnames = True 1332 1333 summary = [] 1334 # structs/enums 1335 for scope in cfg.scopes: 1336 if scope.className is None: 1337 continue 1338 if scope.type not in ('Struct', 'Enum'): 1339 continue 1340 used = False 1341 tok = scope.bodyEnd 1342 while tok: 1343 if tok.str == scope.className: 1344 used = True 1345 break 1346 tok = tok.next 1347 summary.append({'name': scope.className, 'used':used, 'file': scope.bodyStart.file, 'line': scope.bodyStart.linenr, 'column': scope.bodyStart.column}) 1348 if len(summary) > 0: 1349 cppcheckdata.reportSummary(dumpfile, 'MisraTagName', summary) 1350 1351 def _save_ctu_summary_identifiers(self, dumpfile, cfg): 1352 if self._ctu_summary_identifiers: 1353 return 1354 self._ctu_summary_identifiers = True 1355 1356 external_identifiers = [] 1357 internal_identifiers = [] 1358 local_identifiers = [] 1359 1360 def identifier(nameToken): 1361 return {'name':nameToken.str, 'file':nameToken.file, 'line':nameToken.linenr, 'column':nameToken.column} 1362 1363 names = [] 1364 1365 for var in cfg.variables: 1366 if var.nameToken is None: 1367 continue 1368 if var.access != 'Global': 1369 if var.nameToken.str in names: 1370 continue 1371 names.append(var.nameToken.str) 1372 local_identifiers.append(identifier(var.nameToken)) 1373 elif var.isStatic: 1374 names.append(var.nameToken.str) 1375 internal_identifiers.append(identifier(var.nameToken)) 1376 else: 1377 names.append(var.nameToken.str) 1378 i = identifier(var.nameToken) 1379 i['decl'] = var.isExtern 1380 external_identifiers.append(i) 1381 1382 for func in cfg.functions: 1383 if func.tokenDef is None: 1384 continue 1385 if func.isStatic: 1386 internal_identifiers.append(identifier(func.tokenDef)) 1387 else: 1388 i = identifier(func.tokenDef) 1389 i['decl'] = func.token is None 1390 external_identifiers.append(i) 1391 1392 cppcheckdata.reportSummary(dumpfile, 'MisraExternalIdentifiers', external_identifiers) 1393 cppcheckdata.reportSummary(dumpfile, 'MisraInternalIdentifiers', internal_identifiers) 1394 cppcheckdata.reportSummary(dumpfile, 'MisraLocalIdentifiers', local_identifiers) 1395 1396 def _save_ctu_summary_usage(self, dumpfile, cfg): 1397 if self._ctu_summary_usage: 1398 return 1399 self._ctu_summary_usage = True 1400 1401 names = [] 1402 for token in cfg.tokenlist: 1403 if not token.isName: 1404 continue 1405 if token.function and token.scope.isExecutable: 1406 if (not token.function.isStatic) and (token.str not in names): 1407 names.append(token.str) 1408 elif token.variable: 1409 if token == token.variable.nameToken: 1410 continue 1411 if token.variable.access == 'Global' and (not token.variable.isStatic) and (token.str not in names): 1412 names.append(token.str) 1413 1414 if len(names) > 0: 1415 cppcheckdata.reportSummary(dumpfile, 'MisraUsage', names) 1416 1417 1418 def misra_1_4(self, cfg): 1419 for token in cfg.tokenlist: 1420 if token.str in ('_Atomic', '_Noreturn', '_Generic', '_Thread_local', '_Alignas', '_Alignof'): 1421 self.reportError(token, 1, 4) 1422 if token.str.endswith('_s') and isFunctionCall(token.next): 1423 # See C specification C11 - Annex K, page 578 1424 if token.str in ('tmpfile_s', 'tmpnam_s', 'fopen_s', 'freopen_s', 'fprintf_s', 'fscanf_s', 'printf_s', 'scanf_s', 1425 'snprintf_s', 'sprintf_s', 'sscanf_s', 'vfprintf_s', 'vfscanf_s', 'vprintf_s', 'vscanf_s', 1426 'vsnprintf_s', 'vsprintf_s', 'vsscanf_s', 'gets_s', 'set_constraint_handler_s', 'abort_handler_s', 1427 'ignore_handler_s', 'getenv_s', 'bsearch_s', 'qsort_s', 'wctomb_s', 'mbstowcs_s', 'wcstombs_s', 1428 'memcpy_s', 'memmove_s', 'strcpy_s', 'strncpy_s', 'strcat_s', 'strncat_s', 'strtok_s', 'memset_s', 1429 'strerror_s', 'strerrorlen_s', 'strnlen_s', 'asctime_s', 'ctime_s', 'gmtime_s', 'localtime_s', 1430 'fwprintf_s', 'fwscanf_s', 'snwprintf_s', 'swprintf_s', 'swscanf_s', 'vfwprintf_s', 'vfwscanf_s', 1431 'vsnwprintf_s', 'vswprintf_s', 'vswscanf_s', 'vwprintf_s', 'vwscanf_s', 'wprintf_s', 'wscanf_s', 1432 'wcscpy_s', 'wcsncpy_s', 'wmemcpy_s', 'wmemmove_s', 'wcscat_s', 'wcsncat_s', 'wcstok_s', 'wcsnlen_s', 1433 'wcrtomb_s', 'mbsrtowcs_s', 'wcsrtombs_s'): 1434 self.reportError(token, 1, 4) 1435 1436 def misra_2_3(self, dumpfile, typedefInfo): 1437 self._save_ctu_summary_typedefs(dumpfile, typedefInfo) 1438 1439 def misra_2_4(self, dumpfile, cfg): 1440 self._save_ctu_summary_tagnames(dumpfile, cfg) 1441 1442 def misra_2_5(self, dumpfile, cfg): 1443 used_macros = list() 1444 for m in cfg.macro_usage: 1445 used_macros.append(m.name) 1446 summary = [] 1447 for directive in cfg.directives: 1448 res = re.match(r'#define[ \t]+([a-zA-Z_][a-zA-Z_0-9]*).*', directive.str) 1449 if res: 1450 macro_name = res.group(1) 1451 summary.append({'name': macro_name, 'used': (macro_name in used_macros), 'file': directive.file, 'line': directive.linenr, 'column': directive.column}) 1452 if len(summary) > 0: 1453 cppcheckdata.reportSummary(dumpfile, 'MisraMacro', summary) 1454 1455 def misra_2_7(self, data): 1456 for func in data.functions: 1457 # Skip function with no parameter 1458 if len(func.argument) == 0: 1459 continue 1460 # Setup list of function parameters 1461 func_param_list = list() 1462 for arg in func.argument: 1463 func_arg = func.argument[arg] 1464 if func_arg.typeStartToken and func_arg.typeStartToken.str == '...': 1465 continue 1466 func_param_list.append(func_arg) 1467 # Search for scope of current function 1468 for scope in data.scopes: 1469 if (scope.type == "Function") and (scope.function == func): 1470 # Search function body: remove referenced function parameter from list 1471 token = scope.bodyStart 1472 while token.next is not None and token != scope.bodyEnd and len(func_param_list) > 0: 1473 if token.variable is not None and token.variable in func_param_list: 1474 func_param_list.remove(token.variable) 1475 token = token.next 1476 # Emit a warning for each unused variable, but no more that one warning per line 1477 reported_linenrs = set() 1478 for func_param in func_param_list: 1479 if func_param.nameToken: 1480 linenr = func_param.nameToken 1481 if linenr not in reported_linenrs: 1482 self.reportError(func_param.nameToken, 2, 7) 1483 reported_linenrs.add(linenr) 1484 else: 1485 linenr = func.tokenDef.linenr 1486 if linenr not in reported_linenrs: 1487 self.reportError(func.tokenDef, 2, 7) 1488 reported_linenrs.add(linenr) 1489 1490 def misra_3_1(self, rawTokens): 1491 for token in rawTokens: 1492 starts_with_double_slash = token.str.startswith('//') 1493 if token.str.startswith('/*') or starts_with_double_slash: 1494 s = token.str.lstrip('/') 1495 if ((not starts_with_double_slash) and '//' in s) or '/*' in s: 1496 self.reportError(token, 3, 1) 1497 1498 def misra_3_2(self, rawTokens): 1499 for token in rawTokens: 1500 if token.str.startswith('//'): 1501 # Check for comment ends with trigraph which might be replaced 1502 # by a backslash. 1503 if token.str.endswith('??/'): 1504 self.reportError(token, 3, 2) 1505 # Check for comment which has been merged with subsequent line 1506 # because it ends with backslash. 1507 # The last backslash is no more part of the comment token thus 1508 # check if next token exists and compare line numbers. 1509 elif (token.next is not None) and (token.linenr == token.next.linenr): 1510 self.reportError(token, 3, 2) 1511 1512 def misra_4_1(self, rawTokens): 1513 for token in rawTokens: 1514 if (token.str[0] != '"') and (token.str[0] != '\''): 1515 continue 1516 if len(token.str) < 3: 1517 continue 1518 1519 delimiter = token.str[0] 1520 symbols = token.str[1:-1] 1521 1522 # No closing delimiter. This will not compile. 1523 if token.str[-1] != delimiter: 1524 continue 1525 1526 if len(symbols) < 2: 1527 continue 1528 1529 if not hasNumericEscapeSequence(symbols): 1530 continue 1531 1532 # String literals that contains one or more escape sequences. All of them should be 1533 # terminated. 1534 for sequence in ['\\' + t for t in symbols.split('\\')][1:]: 1535 if (isHexEscapeSequence(sequence) or isOctalEscapeSequence(sequence) or 1536 isSimpleEscapeSequence(sequence)): 1537 continue 1538 else: 1539 self.reportError(token, 4, 1) 1540 1541 def misra_4_2(self, rawTokens): 1542 for token in rawTokens: 1543 if (token.str[0] != '"') or (token.str[-1] != '"'): 1544 continue 1545 # Check for trigraph sequence as defined by ISO/IEC 9899:1999 1546 for sequence in ['??=', '??(', '??/', '??)', '??\'', '??<', '??!', '??>', '??-']: 1547 if sequence in token.str[1:-1]: 1548 # First trigraph sequence match, report error and leave loop. 1549 self.reportError(token, 4, 2) 1550 break 1551 1552 def misra_5_1(self, data): 1553 long_vars = {} 1554 num_sign_chars = self.get_num_significant_naming_chars(data) 1555 for var in data.variables: 1556 if var.nameToken is None: 1557 continue 1558 if len(var.nameToken.str) <= num_sign_chars: 1559 continue 1560 if not hasExternalLinkage(var): 1561 continue 1562 long_vars.setdefault(var.nameToken.str[:num_sign_chars], []).append(var.nameToken) 1563 for name_prefix in long_vars: 1564 tokens = long_vars[name_prefix] 1565 if len(tokens) < 2: 1566 continue 1567 for tok in sorted(tokens, key=lambda t: (t.linenr, t.column))[1:]: 1568 self.reportError(tok, 5, 1) 1569 1570 def misra_5_2(self, data): 1571 scopeVars = {} 1572 num_sign_chars = self.get_num_significant_naming_chars(data) 1573 for var in data.variables: 1574 if var.nameToken is None: 1575 continue 1576 if len(var.nameToken.str) <= num_sign_chars: 1577 continue 1578 if var.nameToken.scope not in scopeVars: 1579 scopeVars.setdefault(var.nameToken.scope, {})["varlist"] = [] 1580 scopeVars.setdefault(var.nameToken.scope, {})["scopelist"] = [] 1581 scopeVars[var.nameToken.scope]["varlist"].append(var) 1582 for scope in data.scopes: 1583 if scope.nestedIn and scope.className: 1584 if scope.nestedIn not in scopeVars: 1585 scopeVars.setdefault(scope.nestedIn, {})["varlist"] = [] 1586 scopeVars.setdefault(scope.nestedIn, {})["scopelist"] = [] 1587 scopeVars[scope.nestedIn]["scopelist"].append(scope) 1588 for scope in scopeVars: 1589 if len(scopeVars[scope]["varlist"]) <= 1: 1590 continue 1591 for i, variable1 in enumerate(scopeVars[scope]["varlist"]): 1592 for variable2 in scopeVars[scope]["varlist"][i + 1:]: 1593 if variable1.isArgument and variable2.isArgument: 1594 continue 1595 if hasExternalLinkage(variable1) or hasExternalLinkage(variable2): 1596 continue 1597 if (variable1.nameToken.str[:num_sign_chars] == variable2.nameToken.str[:num_sign_chars] and 1598 variable1 is not variable2): 1599 if int(variable1.nameToken.linenr) > int(variable2.nameToken.linenr): 1600 self.reportError(variable1.nameToken, 5, 2) 1601 else: 1602 self.reportError(variable2.nameToken, 5, 2) 1603 for innerscope in scopeVars[scope]["scopelist"]: 1604 if variable1.nameToken.str[:num_sign_chars] == innerscope.className[:num_sign_chars]: 1605 if int(variable1.nameToken.linenr) > int(innerscope.bodyStart.linenr): 1606 self.reportError(variable1.nameToken, 5, 2) 1607 else: 1608 self.reportError(innerscope.bodyStart, 5, 2) 1609 if len(scopeVars[scope]["scopelist"]) <= 1: 1610 continue 1611 for i, scopename1 in enumerate(scopeVars[scope]["scopelist"]): 1612 for scopename2 in scopeVars[scope]["scopelist"][i + 1:]: 1613 if scopename1.className[:num_sign_chars] == scopename2.className[:num_sign_chars]: 1614 if int(scopename1.bodyStart.linenr) > int(scopename2.bodyStart.linenr): 1615 self.reportError(scopename1.bodyStart, 5, 2) 1616 else: 1617 self.reportError(scopename2.bodyStart, 5, 2) 1618 1619 def misra_5_4(self, data): 1620 num_sign_chars = self.get_num_significant_naming_chars(data) 1621 macro = {} 1622 compile_name = re.compile(r'#define ([a-zA-Z0-9_]+)') 1623 compile_param = re.compile(r'#define ([a-zA-Z0-9_]+)[(]([a-zA-Z0-9_, ]+)[)]') 1624 short_names = {} 1625 macro_w_arg = [] 1626 for dir in data.directives: 1627 res1 = compile_name.match(dir.str) 1628 if res1: 1629 if dir not in macro: 1630 macro.setdefault(dir, {})["name"] = [] 1631 macro.setdefault(dir, {})["params"] = [] 1632 full_name = res1.group(1) 1633 macro[dir]["name"] = full_name 1634 short_name = full_name[:num_sign_chars] 1635 if short_name in short_names: 1636 _dir = short_names[short_name] 1637 if full_name != macro[_dir]["name"]: 1638 self.reportError(dir, 5, 4) 1639 else: 1640 short_names[short_name] = dir 1641 res2 = compile_param.match(dir.str) 1642 if res2: 1643 res_gp2 = res2.group(2).split(",") 1644 res_gp2 = [macroname.replace(" ", "") for macroname in res_gp2] 1645 macro[dir]["params"].extend(res_gp2) 1646 macro_w_arg.append(dir) 1647 for mvar in macro_w_arg: 1648 for i, macroparam1 in enumerate(macro[mvar]["params"]): 1649 for j, macroparam2 in enumerate(macro[mvar]["params"]): 1650 if j > i and macroparam1[:num_sign_chars] == macroparam2[:num_sign_chars]: 1651 self.reportError(mvar, 5, 4) 1652 param = macroparam1 1653 if param[:num_sign_chars] in short_names: 1654 m_var1 = short_names[param[:num_sign_chars]] 1655 if m_var1.linenr > mvar.linenr: 1656 self.reportError(m_var1, 5, 4) 1657 else: 1658 self.reportError(mvar, 5, 4) 1659 1660 def misra_5_5(self, data): 1661 num_sign_chars = self.get_num_significant_naming_chars(data) 1662 macroNames = {} 1663 compiled = re.compile(r'#define ([A-Za-z0-9_]+)') 1664 for dir in data.directives: 1665 res = compiled.match(dir.str) 1666 if res: 1667 macroNames[res.group(1)[:num_sign_chars]] = dir 1668 for var in data.variables: 1669 if var.nameToken and var.nameToken.str[:num_sign_chars] in macroNames: 1670 self.reportError(var.nameToken, 5, 5) 1671 for scope in data.scopes: 1672 if scope.className and scope.className[:num_sign_chars] in macroNames: 1673 self.reportError(scope.bodyStart, 5, 5) 1674 1675 1676 def misra_5_6(self, dumpfile, typedefInfo): 1677 self._save_ctu_summary_typedefs(dumpfile, typedefInfo) 1678 1679 def misra_5_7(self, dumpfile, cfg): 1680 self._save_ctu_summary_tagnames(dumpfile, cfg) 1681 1682 def misra_5_8(self, dumpfile, cfg): 1683 self._save_ctu_summary_identifiers(dumpfile, cfg) 1684 1685 def misra_5_9(self, dumpfile, cfg): 1686 self._save_ctu_summary_identifiers(dumpfile, cfg) 1687 1688 def misra_6_1(self, data): 1689 # Bitfield type must be bool or explicitly signed/unsigned int 1690 for token in data.tokenlist: 1691 if not token.valueType: 1692 continue 1693 if token.valueType.bits == 0: 1694 continue 1695 if not token.variable: 1696 continue 1697 if not token.scope: 1698 continue 1699 if token.scope.type not in 'Struct': 1700 continue 1701 1702 if data.standards.c == 'c89': 1703 if token.valueType.type != 'int' and not isUnsignedType(token.variable.typeStartToken.str): 1704 self.reportError(token, 6, 1) 1705 elif data.standards.c == 'c99': 1706 if token.valueType.type == 'bool': 1707 continue 1708 1709 isExplicitlySignedOrUnsigned = False 1710 typeToken = token.variable.typeStartToken 1711 while typeToken: 1712 if typeToken.isUnsigned or typeToken.isSigned or isUnsignedType(typeToken.str): 1713 isExplicitlySignedOrUnsigned = True 1714 break 1715 1716 if typeToken is token.variable.typeEndToken: 1717 break 1718 1719 typeToken = typeToken.next 1720 1721 if not isExplicitlySignedOrUnsigned: 1722 self.reportError(token, 6, 1) 1723 1724 1725 def misra_6_2(self, data): 1726 # Bitfields of size 1 can not be signed 1727 for token in data.tokenlist: 1728 if not token.valueType: 1729 continue 1730 if not token.scope: 1731 continue 1732 if token.scope.type not in 'Struct': 1733 continue 1734 if token.valueType.bits == 1 and token.valueType.sign == 'signed': 1735 self.reportError(token, 6, 2) 1736 1737 1738 def misra_7_1(self, rawTokens): 1739 compiled = re.compile(r'^0[0-7]+$') 1740 for tok in rawTokens: 1741 if compiled.match(tok.str): 1742 self.reportError(tok, 7, 1) 1743 1744 def misra_7_2(self, data): 1745 # Large constant numbers that are assigned to a variable should have an 1746 # u/U suffix if the variable type is unsigned. 1747 def reportErrorIfMissingSuffix(variable, value): 1748 if 'U' in value.str.upper(): 1749 return 1750 if value and value.isNumber: 1751 if variable and variable.valueType and variable.valueType.sign == 'unsigned': 1752 if variable.valueType.type in ['char', 'short', 'int', 'long', 'long long']: 1753 limit = 1 << (bitsOfEssentialType(variable.valueType.type) -1) 1754 v = value.getKnownIntValue() 1755 if v is not None and v >= limit: 1756 self.reportError(value, 7, 2) 1757 1758 for token in data.tokenlist: 1759 # Check normal variable assignment 1760 if token.valueType and token.isNumber: 1761 variable = getAssignedVariableToken(token) 1762 reportErrorIfMissingSuffix(variable, token) 1763 1764 # Check use as function parameter 1765 if isFunctionCall(token) and token.astOperand1 and token.astOperand1.function: 1766 functionDeclaration = token.astOperand1.function 1767 1768 if functionDeclaration.tokenDef: 1769 if functionDeclaration.tokenDef is token.astOperand1: 1770 # Token is not a function call, but it is the definition of the function 1771 continue 1772 1773 parametersUsed = getArguments(token) 1774 for i in range(len(parametersUsed)): 1775 usedParameter = parametersUsed[i] 1776 if usedParameter.isNumber: 1777 parameterDefinition = functionDeclaration.argument.get(i+1) 1778 if parameterDefinition and parameterDefinition.nameToken: 1779 reportErrorIfMissingSuffix(parameterDefinition.nameToken, usedParameter) 1780 1781 def misra_7_3(self, rawTokens): 1782 compiled = re.compile(r'^[0-9.]+[Uu]*l+[Uu]*$') 1783 for tok in rawTokens: 1784 if compiled.match(tok.str): 1785 self.reportError(tok, 7, 3) 1786 1787 def misra_7_4(self, data): 1788 # A string literal shall not be assigned to an object unless the object's type 1789 # is constant. 1790 def reportErrorIfVariableIsNotConst(variable, stringLiteral): 1791 if variable.valueType: 1792 if (variable.valueType.constness % 2) != 1: 1793 self.reportError(stringLiteral, 7, 4) 1794 1795 for token in data.tokenlist: 1796 if token.isString: 1797 # Check normal variable assignment 1798 variable = getAssignedVariableToken(token) 1799 if variable: 1800 reportErrorIfVariableIsNotConst(variable, token) 1801 1802 # Check use as return value 1803 function = getFunctionUsingReturnValue(token) 1804 if function: 1805 # "Primitive" test since there is no info available on return value type 1806 if not tokenFollowsSequence(function.tokenDef, ['const', 'char', '*']): 1807 self.reportError(token, 7, 4) 1808 1809 # Check use as function parameter 1810 if isFunctionCall(token) and token.astOperand1 and token.astOperand1.function: 1811 functionDeclaration = token.astOperand1.function 1812 1813 if functionDeclaration.tokenDef: 1814 if functionDeclaration.tokenDef is token.astOperand1: 1815 # Token is not a function call, but it is the definition of the function 1816 continue 1817 1818 parametersUsed = getArguments(token) 1819 for i in range(len(parametersUsed)): 1820 usedParameter = parametersUsed[i] 1821 parameterDefinition = functionDeclaration.argument.get(i+1) 1822 1823 if usedParameter.isString and parameterDefinition.nameToken: 1824 reportErrorIfVariableIsNotConst(parameterDefinition.nameToken, usedParameter) 1825 1826 def misra_8_1(self, cfg): 1827 for token in cfg.tokenlist: 1828 if token.isImplicitInt: 1829 self.reportError(token, 8, 1) 1830 1831 def misra_8_2(self, data, rawTokens): 1832 def getFollowingRawTokens(rawTokens, token, count): 1833 following =[] 1834 for rawToken in rawTokens: 1835 if (rawToken.file == token.file and 1836 rawToken.linenr == token.linenr and 1837 rawToken.column == token.column): 1838 for _ in range(count): 1839 rawToken = rawToken.next 1840 # Skip comments 1841 while rawToken and (rawToken.str.startswith('/*') or rawToken.str.startswith('//')): 1842 rawToken = rawToken.next 1843 if rawToken is None: 1844 break 1845 following.append(rawToken) 1846 return following 1847 1848 # Zero arguments should be in form ( void ) 1849 def checkZeroArguments(func, startCall, endCall): 1850 if (len(func.argument) == 0): 1851 voidArg = startCall.next 1852 while voidArg is not endCall: 1853 if voidArg.str == 'void': 1854 break 1855 voidArg = voidArg.next 1856 if not voidArg.str == 'void': 1857 if func.tokenDef.next: 1858 self.reportError(func.tokenDef.next, 8, 2) 1859 else: 1860 self.reportError(func.tokenDef, 8, 2) 1861 1862 def checkDeclarationArgumentsViolations(func, startCall, endCall): 1863 # Collect the tokens for the arguments in function definition 1864 argNameTokens = set() 1865 for arg in func.argument: 1866 argument = func.argument[arg] 1867 typeStartToken = argument.typeStartToken 1868 if typeStartToken is None: 1869 continue 1870 nameToken = argument.nameToken 1871 if nameToken is None: 1872 continue 1873 argNameTokens.add(nameToken) 1874 1875 # Check if we have the same number of variables in both the 1876 # declaration and the definition. 1877 # 1878 # TODO: We actually need to check if the names of the arguments are 1879 # the same. But we can't do this because we have no links to 1880 # variables in the arguments in function definition in the dump file. 1881 foundVariables = 0 1882 while startCall and startCall != endCall: 1883 if startCall.varId: 1884 foundVariables += 1 1885 startCall = startCall.next 1886 1887 if len(argNameTokens) != foundVariables: 1888 if func.tokenDef.next: 1889 self.reportError(func.tokenDef.next, 8, 2) 1890 else: 1891 self.reportError(func.tokenDef, 8, 2) 1892 1893 def checkDefinitionArgumentsViolations(func, startCall, endCall): 1894 for arg in func.argument: 1895 argument = func.argument[arg] 1896 typeStartToken = argument.typeStartToken 1897 if typeStartToken is None: 1898 continue 1899 1900 # Arguments should have a name unless variable length arg 1901 nameToken = argument.nameToken 1902 if nameToken is None and typeStartToken.str != '...': 1903 self.reportError(typeStartToken, 8, 2) 1904 1905 # Type declaration on next line (old style declaration list) is not allowed 1906 if typeStartToken.linenr > endCall.linenr: 1907 self.reportError(typeStartToken, 8, 2) 1908 1909 # Check arguments in function declaration 1910 for func in data.functions: 1911 1912 # Check arguments in function definition 1913 tokenImpl = func.token 1914 if tokenImpl: 1915 startCall = tokenImpl.next 1916 if startCall is None or startCall.str != '(': 1917 continue 1918 endCall = startCall.link 1919 if endCall is None or endCall.str != ')': 1920 continue 1921 checkZeroArguments(func, startCall, endCall) 1922 checkDefinitionArgumentsViolations(func, startCall, endCall) 1923 1924 # Check arguments in function declaration 1925 tokenDef = func.tokenDef 1926 if tokenDef: 1927 startCall = func.tokenDef.next 1928 if startCall is None or startCall.str != '(': 1929 continue 1930 endCall = startCall.link 1931 if endCall is None or endCall.str != ')': 1932 continue 1933 checkZeroArguments(func, startCall, endCall) 1934 if tokenImpl: 1935 checkDeclarationArgumentsViolations(func, startCall, endCall) 1936 else: 1937 # When there is no function definition, we should execute 1938 # its checks for the declaration token. The point is that without 1939 # a known definition we have no Function.argument list required 1940 # for declaration check. 1941 checkDefinitionArgumentsViolations(func, startCall, endCall) 1942 1943 # Check arguments in pointer declarations 1944 for var in data.variables: 1945 if not var.isPointer: 1946 continue 1947 1948 if var.nameToken is None: 1949 continue 1950 1951 rawTokensFollowingPtr = getFollowingRawTokens(rawTokens, var.nameToken, 3) 1952 if len(rawTokensFollowingPtr) != 3: 1953 continue 1954 1955 # Compliant: returnType (*ptrName) ( ArgType ) 1956 # Non-compliant: returnType (*ptrName) ( ) 1957 if (rawTokensFollowingPtr[0].str == ')' and 1958 rawTokensFollowingPtr[1].str == '(' and 1959 rawTokensFollowingPtr[2].str == ')'): 1960 self.reportError(var.nameToken, 8, 2) 1961 1962 1963 def misra_8_4(self, cfg): 1964 for func in cfg.functions: 1965 if func.isStatic: 1966 continue 1967 if func.token is None: 1968 continue 1969 if not is_source_file(func.token.file): 1970 continue 1971 if func.token.file != func.tokenDef.file: 1972 continue 1973 if func.tokenDef.str == 'main': 1974 continue 1975 self.reportError(func.tokenDef, 8, 4) 1976 1977 extern_vars = [] 1978 var_defs = [] 1979 1980 for var in cfg.variables: 1981 if not var.isGlobal: 1982 continue 1983 if var.isStatic: 1984 continue 1985 if var.nameToken is None: 1986 continue 1987 if var.isExtern: 1988 extern_vars.append(var.nameToken.str) 1989 else: 1990 var_defs.append(var.nameToken) 1991 for vartok in var_defs: 1992 if vartok.str not in extern_vars: 1993 self.reportError(vartok, 8, 4) 1994 1995 def misra_8_5(self, dumpfile, cfg): 1996 self._save_ctu_summary_identifiers(dumpfile, cfg) 1997 1998 def misra_8_6(self, dumpfile, cfg): 1999 self._save_ctu_summary_identifiers(dumpfile, cfg) 2000 2001 def misra_8_7(self, dumpfile, cfg): 2002 self._save_ctu_summary_usage(dumpfile, cfg) 2003 2004 def misra_8_8(self, cfg): 2005 vars = {} 2006 for var in cfg.variables: 2007 if var.access != 'Global': 2008 continue 2009 if var.nameToken is None: 2010 continue 2011 varname = var.nameToken.str 2012 if varname in vars: 2013 vars[varname].append(var) 2014 else: 2015 vars[varname] = [var] 2016 for varname, varlist in vars.items(): 2017 static_var = None 2018 extern_var = None 2019 for var in varlist: 2020 if var.isStatic: 2021 static_var = var 2022 elif var.isExtern: 2023 extern_var = var 2024 if static_var and extern_var: 2025 self.reportError(extern_var.nameToken, 8, 8) 2026 2027 def misra_8_9(self, cfg): 2028 variables = {} 2029 for scope in cfg.scopes: 2030 if scope.type != 'Function': 2031 continue 2032 variables_used_in_scope = [] 2033 tok = scope.bodyStart 2034 while tok != scope.bodyEnd: 2035 if tok.variable and tok.variable.access == 'Global' and tok.variable.isStatic: 2036 if tok.variable not in variables_used_in_scope: 2037 variables_used_in_scope.append(tok.variable) 2038 tok = tok.next 2039 for var in variables_used_in_scope: 2040 if var in variables: 2041 variables[var] += 1 2042 else: 2043 variables[var] = 1 2044 for var, count in variables.items(): 2045 if count == 1: 2046 self.reportError(var.nameToken, 8, 9) 2047 2048 2049 def misra_8_10(self, cfg): 2050 for func in cfg.functions: 2051 if func.isInlineKeyword and not func.isStatic: 2052 self.reportError(func.tokenDef, 8, 10) 2053 2054 def misra_8_11(self, data): 2055 for var in data.variables: 2056 if var.isExtern and simpleMatch(var.nameToken.next, '[ ]') and var.nameToken.scope.type == 'Global': 2057 self.reportError(var.nameToken, 8, 11) 2058 2059 def misra_8_12(self, data): 2060 for scope in data.scopes: 2061 if scope.type != 'Enum': 2062 continue 2063 enum_values = [] 2064 implicit_enum_values = [] 2065 e_token = scope.bodyStart.next 2066 while e_token != scope.bodyEnd: 2067 if e_token.str == '(': 2068 e_token = e_token.link 2069 continue 2070 if e_token.previous.str not in ',{': 2071 e_token = e_token.next 2072 continue 2073 if e_token.isName and e_token.values and e_token.valueType and e_token.valueType.typeScope == scope: 2074 token_values = [v.intvalue for v in e_token.values] 2075 enum_values += token_values 2076 if e_token.next.str != "=": 2077 implicit_enum_values += token_values 2078 e_token = e_token.next 2079 for implicit_enum_value in implicit_enum_values: 2080 if enum_values.count(implicit_enum_value) != 1: 2081 self.reportError(scope.bodyStart, 8, 12) 2082 2083 def misra_8_14(self, rawTokens): 2084 for token in rawTokens: 2085 if token.str == 'restrict': 2086 self.reportError(token, 8, 14) 2087 2088 def misra_9_2(self, data): 2089 misra_9.misra_9_x(self, data, 902) 2090 2091 def misra_9_3(self, data): 2092 misra_9.misra_9_x(self, data, 903) 2093 2094 def misra_9_4(self, data): 2095 misra_9.misra_9_x(self, data, 904) 2096 2097 def misra_9_5(self, data, rawTokens): 2098 misra_9.misra_9_x(self, data, 905, rawTokens) 2099 #for token in rawTokens: 2100 # if simpleMatch(token, '[ ] = { ['): 2101 # self.reportError(token, 9, 5) 2102 2103 def misra_10_1(self, data): 2104 for token in data.tokenlist: 2105 if not token.isOp: 2106 continue 2107 2108 for t1, t2 in itertools.product( 2109 list(getTernaryOperandsRecursive(token.astOperand1) or [token.astOperand1]), 2110 list(getTernaryOperandsRecursive(token.astOperand2) or [token.astOperand2]), 2111 ): 2112 e1 = getEssentialTypeCategory(t1) 2113 e2 = getEssentialTypeCategory(t2) 2114 if not e1 or not e2: 2115 continue 2116 if token.str in ('<<', '>>'): 2117 if not isUnsignedType(e1): 2118 self.reportError(token, 10, 1) 2119 elif not isUnsignedType(e2) and not token.astOperand2.isNumber: 2120 self.reportError(token, 10, 1) 2121 elif token.str in ('~', '&', '|', '^'): 2122 e1_et = getEssentialType(token.astOperand1) 2123 e2_et = getEssentialType(token.astOperand2) 2124 if e1_et == 'char' or e2_et == 'char': 2125 self.reportError(token, 10, 1) 2126 2127 def misra_10_2(self, data): 2128 def isEssentiallySignedOrUnsigned(op): 2129 e = getEssentialType(op) 2130 return e and (e.split(' ')[0] in ('unsigned', 'signed')) 2131 2132 def isEssentiallyChar(op): 2133 if op is None: 2134 return False 2135 if op.str == '+': 2136 return isEssentiallyChar(op.astOperand1) or isEssentiallyChar(op.astOperand2) 2137 return op.isChar 2138 2139 for token in data.tokenlist: 2140 if token.str not in ('+', '-'): 2141 continue 2142 2143 if (not isEssentiallyChar(token.astOperand1)) and (not isEssentiallyChar(token.astOperand2)): 2144 continue 2145 2146 if token.str == '+': 2147 if isEssentiallyChar(token.astOperand1) and not isEssentiallySignedOrUnsigned(token.astOperand2): 2148 self.reportError(token, 10, 2) 2149 if isEssentiallyChar(token.astOperand2) and not isEssentiallySignedOrUnsigned(token.astOperand1): 2150 self.reportError(token, 10, 2) 2151 2152 if token.str == '-': 2153 e1 = getEssentialType(token.astOperand1) 2154 if e1 and e1.split(' ')[-1] != 'char': 2155 self.reportError(token, 10, 2) 2156 if not isEssentiallyChar(token.astOperand2) and not isEssentiallySignedOrUnsigned(token.astOperand2): 2157 self.reportError(token, 10, 2) 2158 2159 def misra_10_3(self, cfg): 2160 def get_category(essential_type): 2161 if essential_type: 2162 if essential_type in ('bool', 'char'): 2163 return essential_type 2164 if essential_type.split(' ')[-1] in ('float', 'double'): 2165 return 'floating' 2166 if essential_type.split(' ')[0] in ('unsigned', 'signed'): 2167 return essential_type.split(' ')[0] 2168 return None 2169 for tok in cfg.tokenlist: 2170 if tok.isAssignmentOp: 2171 lhs = getEssentialType(tok.astOperand1) 2172 rhs = getEssentialType(tok.astOperand2) 2173 #print(lhs) 2174 #print(rhs) 2175 if lhs is None or rhs is None: 2176 continue 2177 lhs_category = get_category(lhs) 2178 rhs_category = get_category(rhs) 2179 if lhs_category and rhs_category and lhs_category != rhs_category and rhs_category not in ('signed','unsigned'): 2180 self.reportError(tok, 10, 3) 2181 if bitsOfEssentialType(lhs) < bitsOfEssentialType(rhs): 2182 self.reportError(tok, 10, 3) 2183 2184 2185 def misra_10_4(self, data): 2186 op = {'+', '-', '*', '/', '%', '&', '|', '^', '+=', '-=', ':'} 2187 for token in data.tokenlist: 2188 if token.str not in op and not token.isComparisonOp: 2189 continue 2190 if not token.astOperand1 or not token.astOperand2: 2191 continue 2192 if not token.astOperand1.valueType or not token.astOperand2.valueType: 2193 continue 2194 if ((token.astOperand1.str in op or token.astOperand1.isComparisonOp) and 2195 (token.astOperand2.str in op or token.astOperand1.isComparisonOp)): 2196 e1, e2 = getEssentialCategorylist(token.astOperand1.astOperand2, token.astOperand2.astOperand1) 2197 elif token.astOperand1.str in op or token.astOperand1.isComparisonOp: 2198 e1, e2 = getEssentialCategorylist(token.astOperand1.astOperand2, token.astOperand2) 2199 elif token.astOperand2.str in op or token.astOperand2.isComparisonOp: 2200 e1, e2 = getEssentialCategorylist(token.astOperand1, token.astOperand2.astOperand1) 2201 else: 2202 e1, e2 = getEssentialCategorylist(token.astOperand1, token.astOperand2) 2203 if token.str == "+=" or token.str == "+": 2204 if e1 == "char" and (e2 == "signed" or e2 == "unsigned"): 2205 continue 2206 if e2 == "char" and (e1 == "signed" or e1 == "unsigned"): 2207 continue 2208 if token.str == "-=" or token.str == "-": 2209 if e1 == "char" and (e2 == "signed" or e2 == "unsigned"): 2210 continue 2211 if e1 and e2 and (e1.find('Anonymous') != -1 and (e2 == "signed" or e2 == "unsigned")): 2212 continue 2213 if e1 and e2 and (e2.find('Anonymous') != -1 and (e1 == "signed" or e1 == "unsigned")): 2214 continue 2215 if e1 and e2 and e1 != e2: 2216 self.reportError(token, 10, 4) 2217 2218 def misra_10_5(self, cfg): 2219 def _get_essential_category(token): 2220 essential_type = getEssentialType(token) 2221 #print(essential_type) 2222 if essential_type: 2223 if essential_type in ('bool', 'char'): 2224 return essential_type 2225 if essential_type.split(' ')[-1] in ('float', 'double'): 2226 return 'floating' 2227 if essential_type.split(' ')[0] in ('unsigned', 'signed'): 2228 return essential_type.split(' ')[0] 2229 return None 2230 for token in cfg.tokenlist: 2231 if not isCast(token): 2232 continue 2233 to_type = _get_essential_category(token) 2234 #print(to_type) 2235 if to_type is None: 2236 continue 2237 from_type = _get_essential_category(token.astOperand1) 2238 #print(from_type) 2239 if from_type is None: 2240 continue 2241 if to_type == from_type: 2242 continue 2243 if to_type == 'bool' or from_type == 'bool': 2244 if token.astOperand1.isInt and token.astOperand1.getKnownIntValue() == 1: 2245 # Exception 2246 continue 2247 self.reportError(token, 10, 5) 2248 continue 2249 if to_type == 'enum': 2250 self.reportError(token, 10, 5) 2251 continue 2252 if from_type == 'float' and to_type == 'char': 2253 self.reportError(token, 10, 5) 2254 continue 2255 if from_type == 'char' and to_type == 'float': 2256 self.reportError(token, 10, 5) 2257 continue 2258 2259 def misra_10_6(self, data): 2260 for token in data.tokenlist: 2261 if token.str != '=' or not token.astOperand1 or not token.astOperand2: 2262 continue 2263 if (token.astOperand2.str not in ('+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~') and 2264 not isCast(token.astOperand2)): 2265 continue 2266 vt1 = token.astOperand1.valueType 2267 vt2 = token.astOperand2.valueType 2268 if not vt1 or vt1.pointer > 0: 2269 continue 2270 if not vt2 or vt2.pointer > 0: 2271 continue 2272 try: 2273 if isCast(token.astOperand2): 2274 e = vt2.type 2275 else: 2276 e = getEssentialType(token.astOperand2) 2277 if not e: 2278 continue 2279 lhsbits = vt1.bits if vt1.bits else bitsOfEssentialType(vt1.type) 2280 if lhsbits > bitsOfEssentialType(e): 2281 self.reportError(token, 10, 6) 2282 except ValueError: 2283 pass 2284 2285 def misra_10_7(self, cfg): 2286 for token in cfg.tokenlist: 2287 if token.astOperand1 is None or token.astOperand2 is None: 2288 continue 2289 if not token.isArithmeticalOp: 2290 continue 2291 parent = token.astParent 2292 if parent is None: 2293 continue 2294 if not parent.isArithmeticalOp: 2295 if not parent.isAssignmentOp: 2296 continue 2297 if parent.str == '=': 2298 continue 2299 token_type = getEssentialType(token) 2300 if token_type is None: 2301 continue 2302 sibling = parent.astOperand1 if (token == parent.astOperand2) else parent.astOperand2 2303 sibling_type = getEssentialType(sibling) 2304 if sibling_type is None: 2305 continue 2306 b1 = bitsOfEssentialType(token_type) 2307 b2 = bitsOfEssentialType(sibling_type) 2308 if b1 > 0 and b1 < b2: 2309 self.reportError(token, 10, 7) 2310 2311 def misra_10_8(self, data): 2312 for token in data.tokenlist: 2313 if not isCast(token): 2314 continue 2315 if not token.valueType or token.valueType.pointer > 0: 2316 continue 2317 if not token.astOperand1.valueType or token.astOperand1.valueType.pointer > 0: 2318 continue 2319 if not token.astOperand1.astOperand1: 2320 continue 2321 if token.astOperand1.str not in ('+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~'): 2322 continue 2323 if token.astOperand1.str != '~' and not token.astOperand1.astOperand2: 2324 continue 2325 if token.astOperand1.str == '~': 2326 e2 = getEssentialTypeCategory(token.astOperand1.astOperand1) 2327 else: 2328 e2, e3 = getEssentialCategorylist(token.astOperand1.astOperand1, token.astOperand1.astOperand2) 2329 if e2 != e3: 2330 continue 2331 e1 = getEssentialTypeCategory(token) 2332 if e1 != e2: 2333 self.reportError(token, 10, 8) 2334 else: 2335 try: 2336 e = getEssentialType(token.astOperand1) 2337 if not e: 2338 continue 2339 if bitsOfEssentialType(token.valueType.type) > bitsOfEssentialType(e): 2340 self.reportError(token, 10, 8) 2341 except ValueError: 2342 pass 2343 2344 def misra_11_1(self, data): 2345 for token in data.tokenlist: 2346 to_from = get_type_conversion_to_from(token) 2347 if to_from is None: 2348 continue 2349 from_type = get_function_pointer_type(to_from[1]) 2350 if from_type is None: 2351 continue 2352 to_type = get_function_pointer_type(to_from[0]) 2353 if to_type is None or to_type != from_type: 2354 self.reportError(token, 11, 1) 2355 2356 def misra_11_2(self, data): 2357 def get_pointer_type(type_token): 2358 while type_token and (type_token.str in ('const', 'struct')): 2359 type_token = type_token.next 2360 if type_token is None: 2361 return None 2362 if not type_token.isName: 2363 return None 2364 return type_token if (type_token.next and type_token.next.str == '*') else None 2365 2366 incomplete_types = [] 2367 2368 for token in data.tokenlist: 2369 if token.str == 'struct' and token.next and token.next.next and token.next.isName and token.next.next.str == ';': 2370 incomplete_types.append(token.next.str) 2371 to_from = get_type_conversion_to_from(token) 2372 if to_from is None: 2373 continue 2374 to_pointer_type_token = get_pointer_type(to_from[0]) 2375 if to_pointer_type_token is None: 2376 continue 2377 from_pointer_type_token = get_pointer_type(to_from[1]) 2378 if from_pointer_type_token is None: 2379 continue 2380 if to_pointer_type_token.str == from_pointer_type_token.str: 2381 continue 2382 if from_pointer_type_token.typeScope is None and (from_pointer_type_token.str in incomplete_types): 2383 self.reportError(token, 11, 2) 2384 elif to_pointer_type_token.typeScope is None and (to_pointer_type_token.str in incomplete_types): 2385 self.reportError(token, 11, 2) 2386 2387 def misra_11_3(self, data): 2388 for token in data.tokenlist: 2389 if not isCast(token): 2390 continue 2391 vt1 = token.valueType 2392 vt2 = token.astOperand1.valueType 2393 if not vt1 or not vt2: 2394 continue 2395 if vt1.type == 'void' or vt2.type == 'void': 2396 continue 2397 if (vt1.pointer > 0 and vt1.type == 'record' and 2398 vt2.pointer > 0 and vt2.type == 'record' and 2399 vt1.typeScopeId != vt2.typeScopeId): 2400 self.reportError(token, 11, 3) 2401 elif (vt1.pointer == vt2.pointer and vt1.pointer > 0 and 2402 vt1.type != vt2.type and vt1.type != 'char'): 2403 self.reportError(token, 11, 3) 2404 2405 def misra_11_4(self, data): 2406 for token in data.tokenlist: 2407 if not isCast(token): 2408 continue 2409 vt1 = token.valueType 2410 vt2 = token.astOperand1.valueType 2411 if not vt1 or not vt2: 2412 continue 2413 if vt2.pointer > 0 and vt1.pointer == 0 and (vt1.isIntegral() or vt1.isEnum()) and vt2.type != 'void': 2414 self.reportError(token, 11, 4) 2415 elif vt1.pointer > 0 and vt2.pointer == 0 and (vt2.isIntegral() or vt2.isEnum()) and vt1.type != 'void': 2416 self.reportError(token, 11, 4) 2417 2418 def misra_11_5(self, data): 2419 for token in data.tokenlist: 2420 if not isCast(token): 2421 if token.astOperand1 and token.astOperand2 and token.str == "=" and token.next.str != "(": 2422 vt1 = token.astOperand1.valueType 2423 vt2 = token.astOperand2.valueType 2424 if not vt1 or not vt2: 2425 continue 2426 if vt1.pointer > 0 and vt1.type != 'void' and vt2.pointer == vt1.pointer and vt2.type == 'void': 2427 self.reportError(token, 11, 5) 2428 continue 2429 if token.astOperand1.astOperand1 and token.astOperand1.astOperand1.str in ( 2430 'malloc', 'calloc', 'realloc', 'free'): 2431 continue 2432 vt1 = token.valueType 2433 vt2 = token.astOperand1.valueType 2434 if not vt1 or not vt2: 2435 continue 2436 if vt1.pointer > 0 and vt1.type != 'void' and vt2.pointer == vt1.pointer and vt2.type == 'void': 2437 self.reportError(token, 11, 5) 2438 2439 def misra_11_6(self, data): 2440 for token in data.tokenlist: 2441 if not isCast(token): 2442 continue 2443 if token.astOperand1.astOperand1: 2444 continue 2445 vt1 = token.valueType 2446 vt2 = token.astOperand1.valueType 2447 if not vt1 or not vt2: 2448 continue 2449 if vt1.pointer == 1 and vt1.type == 'void' and vt2.pointer == 0 and token.astOperand1.str != "0": 2450 self.reportError(token, 11, 6) 2451 elif vt1.pointer == 0 and vt1.type != 'void' and vt2.pointer == 1 and vt2.type == 'void': 2452 self.reportError(token, 11, 6) 2453 2454 def misra_11_7(self, data): 2455 for token in data.tokenlist: 2456 if not isCast(token): 2457 continue 2458 vt1 = token.valueType 2459 vt2 = token.astOperand1.valueType 2460 if not vt1 or not vt2: 2461 continue 2462 if token.astOperand1.astOperand1: 2463 continue 2464 if (vt2.pointer > 0 and vt1.pointer == 0 and 2465 not vt1.isIntegral() and not vt1.isEnum() and 2466 vt1.type != 'void'): 2467 self.reportError(token, 11, 7) 2468 elif (vt1.pointer > 0 and vt2.pointer == 0 and 2469 not vt2.isIntegral() and not vt2.isEnum() and 2470 vt1.type != 'void'): 2471 self.reportError(token, 11, 7) 2472 2473 def misra_11_8(self, data): 2474 # TODO: reuse code in CERT-EXP05 2475 for token in data.tokenlist: 2476 if isCast(token): 2477 # C-style cast 2478 if not token.valueType: 2479 continue 2480 if not token.astOperand1.valueType: 2481 continue 2482 if token.valueType.pointer == 0: 2483 continue 2484 if token.astOperand1.valueType.pointer == 0: 2485 continue 2486 const1 = token.valueType.constness 2487 const2 = token.astOperand1.valueType.constness 2488 if (const1 % 2) < (const2 % 2): 2489 self.reportError(token, 11, 8) 2490 2491 elif token.str == '(' and token.astOperand1 and token.astOperand2 and token.astOperand1.function: 2492 # Function call 2493 function = token.astOperand1.function 2494 arguments = getArguments(token) 2495 for argnr, argvar in function.argument.items(): 2496 if argnr < 1 or argnr > len(arguments): 2497 continue 2498 if not argvar.isPointer: 2499 continue 2500 argtok = arguments[argnr - 1] 2501 if not argtok.valueType: 2502 continue 2503 if argtok.valueType.pointer == 0: 2504 continue 2505 const1 = argvar.constness 2506 const2 = arguments[argnr - 1].valueType.constness 2507 if (const1 % 2) < (const2 % 2): 2508 self.reportError(token, 11, 8) 2509 2510 def misra_11_9(self, data): 2511 for token in data.tokenlist: 2512 if token.astOperand1 and token.astOperand2 and token.str in ["=", "==", "!=", "?", ":"]: 2513 vt1 = token.astOperand1.valueType 2514 vt2 = token.astOperand2.valueType 2515 if not vt1 or not vt2: 2516 continue 2517 if vt1.pointer > 0 and vt2.pointer == 0 and token.astOperand2.str == "NULL": 2518 continue 2519 if (token.astOperand2.values and vt1.pointer > 0 and 2520 vt2.pointer == 0 and token.astOperand2.values): 2521 if token.astOperand2.getValue(0): 2522 self.reportError(token, 11, 9) 2523 2524 def misra_12_1_sizeof(self, rawTokens): 2525 state = 0 2526 compiled = re.compile(r'^[a-zA-Z_]') 2527 for tok in rawTokens: 2528 if tok.str.startswith('//') or tok.str.startswith('/*'): 2529 continue 2530 if tok.str == 'sizeof': 2531 state = 1 2532 elif state == 1: 2533 if compiled.match(tok.str): 2534 state = 2 2535 else: 2536 state = 0 2537 elif state == 2: 2538 if tok.str in ('+', '-', '*', '/', '%'): 2539 self.reportError(tok, 12, 1) 2540 else: 2541 state = 0 2542 2543 def misra_12_1(self, data): 2544 for token in data.tokenlist: 2545 p = getPrecedence(token) 2546 if p < 2 or p > 12: 2547 continue 2548 p1 = getPrecedence(token.astOperand1) 2549 if p < p1 <= 12 and numberOfParentheses(token.astOperand1, token): 2550 self.reportError(token, 12, 1) 2551 continue 2552 p2 = getPrecedence(token.astOperand2) 2553 if p < p2 <= 12 and numberOfParentheses(token, token.astOperand2): 2554 self.reportError(token, 12, 1) 2555 continue 2556 2557 def misra_12_2(self, data): 2558 for token in data.tokenlist: 2559 if not (token.str in ('<<', '>>')): 2560 continue 2561 if (not token.astOperand2) or (not token.astOperand2.values): 2562 continue 2563 maxval = 0 2564 for val in token.astOperand2.values: 2565 if val.intvalue and val.intvalue > maxval: 2566 maxval = val.intvalue 2567 if maxval == 0: 2568 continue 2569 sz = bitsOfEssentialType(getEssentialType(token.astOperand1)) 2570 if sz <= 0: 2571 continue 2572 if maxval >= sz: 2573 self.reportError(token, 12, 2) 2574 2575 def misra_12_3(self, data): 2576 for token in data.tokenlist: 2577 if token.str == ';' and (token.isSplittedVarDeclComma is True): 2578 self.reportError(token, 12, 3) 2579 if token.str == ',' and token.astParent and token.astParent.str == ';': 2580 self.reportError(token, 12, 3) 2581 if token.str == ',' and token.astParent is None: 2582 if token.scope.type in ('Class', 'Struct'): 2583 # Is this initlist.. 2584 tok = token 2585 while tok and tok.str == ',': 2586 tok = tok.next 2587 if tok and tok.next and tok.isName and tok.next.str == '(': 2588 tok = tok.next.link.next 2589 if tok.str == '{': 2590 # This comma is used in initlist, do not warn 2591 continue 2592 prev = token.previous 2593 while prev: 2594 if prev.str == ';': 2595 self.reportError(token, 12, 3) 2596 break 2597 elif prev.str in ')}]': 2598 prev = prev.link 2599 elif prev.str in '({[': 2600 break 2601 prev = prev.previous 2602 2603 def misra_12_4(self, cfg): 2604 for expr in cfg.tokenlist: 2605 if not expr.astOperand2 or not expr.astOperand1: 2606 continue 2607 if expr.valueType is None: 2608 continue 2609 if expr.valueType.sign is None or expr.valueType.sign != 'unsigned': 2610 continue 2611 if expr.valueType.pointer > 0: 2612 continue 2613 if not expr.valueType.isIntegral(): 2614 continue 2615 op1 = expr.astOperand1.getKnownIntValue() 2616 if op1 is None: 2617 continue 2618 op2 = expr.astOperand2.getKnownIntValue() 2619 if op2 is None: 2620 continue 2621 bits = bitsOfEssentialType('unsigned ' + expr.valueType.type) 2622 if bits <= 0 or bits >= 64: 2623 continue 2624 max_value = (1 << bits) - 1 2625 if not is_constant_integer_expression(expr): 2626 continue 2627 if expr.str == '+' and op1 + op2 > max_value: 2628 self.reportError(expr, 12, 4) 2629 elif expr.str == '-' and op1 - op2 < 0: 2630 self.reportError(expr, 12, 4) 2631 elif expr.str == '*' and op1 * op2 > max_value: 2632 self.reportError(expr, 12, 4) 2633 2634 2635 def misra_13_1(self, data): 2636 for token in data.tokenlist: 2637 if simpleMatch(token, ") {") and token.next.astParent == token.link: 2638 pass 2639 elif not simpleMatch(token, '= {'): 2640 continue 2641 init = token.next 2642 end = init.link 2643 if not end: 2644 continue # syntax is broken 2645 2646 tn = init 2647 while tn and tn != end: 2648 if tn.str == '[' and tn.link: 2649 tn = tn.link 2650 if tn and tn.next and tn.next.str == '=': 2651 tn = tn.next.next 2652 continue 2653 else: 2654 break 2655 if tn.str == '.' and tn.next and tn.next.isName: 2656 tn = tn.next 2657 if tn.next and tn.next.str == '=': 2658 tn = tn.next.next 2659 continue 2660 if tn.str in {'++', '--'} or tn.isAssignmentOp: 2661 self.reportError(init, 13, 1) 2662 tn = tn.next 2663 2664 def misra_13_3(self, data): 2665 for token in data.tokenlist: 2666 if token.str not in ('++', '--'): 2667 continue 2668 astTop = token 2669 while astTop.astParent and astTop.astParent.str not in (',', ';'): 2670 astTop = astTop.astParent 2671 if countSideEffects(astTop) >= 2: 2672 self.reportError(astTop, 13, 3) 2673 2674 def misra_13_4(self, data): 2675 for token in data.tokenlist: 2676 if token.str != '=': 2677 continue 2678 if not token.astParent: 2679 continue 2680 if token.astOperand1.str == '[' and token.astOperand1.previous.str in ('{', ','): 2681 continue 2682 if not (token.astParent.str in [',', ';', '{']): 2683 self.reportError(token, 13, 4) 2684 2685 def misra_13_5(self, data): 2686 for token in data.tokenlist: 2687 if token.isLogicalOp and hasSideEffectsRecursive(token.astOperand2): 2688 self.reportError(token, 13, 5) 2689 2690 def misra_13_6(self, data): 2691 for token in data.tokenlist: 2692 if token.str == 'sizeof' and hasSideEffectsRecursive(token.next): 2693 self.reportError(token, 13, 6) 2694 2695 def misra_14_1(self, data): 2696 for token in data.tokenlist: 2697 if token.str == 'for': 2698 exprs = getForLoopExpressions(token) 2699 if not exprs: 2700 continue 2701 for counter in findCounterTokens(exprs[1]): 2702 if counter.valueType and counter.valueType.isFloat(): 2703 self.reportError(token, 14, 1) 2704 elif token.str == 'while': 2705 if isFloatCounterInWhileLoop(token): 2706 self.reportError(token, 14, 1) 2707 2708 def misra_14_2(self, data): 2709 for token in data.tokenlist: 2710 expressions = getForLoopExpressions(token) 2711 if not expressions: 2712 continue 2713 if expressions[0] and not expressions[0].isAssignmentOp: 2714 self.reportError(token, 14, 2) 2715 elif hasSideEffectsRecursive(expressions[1]): 2716 self.reportError(token, 14, 2) 2717 2718 # Inspect modification of loop counter in loop body 2719 counter_vars = getForLoopCounterVariables(token) 2720 outer_scope = token.scope 2721 body_scope = None 2722 tn = token.next 2723 while tn and tn.next != outer_scope.bodyEnd: 2724 if tn.scope and tn.scope.nestedIn == outer_scope: 2725 body_scope = tn.scope 2726 break 2727 tn = tn.next 2728 if not body_scope: 2729 continue 2730 tn = body_scope.bodyStart 2731 while tn and tn != body_scope.bodyEnd: 2732 if tn.variable and tn.variable in counter_vars: 2733 if tn.next: 2734 # TODO: Check modifications in function calls 2735 if hasSideEffectsRecursive(tn.next): 2736 self.reportError(tn, 14, 2) 2737 tn = tn.next 2738 2739 def misra_14_4(self, data): 2740 for token in data.tokenlist: 2741 if token.str != '(': 2742 continue 2743 if not token.astOperand1 or not (token.astOperand1.str in ['if', 'while']): 2744 continue 2745 if not isBoolExpression(token.astOperand2): 2746 self.reportError(token, 14, 4) 2747 2748 def misra_15_1(self, data): 2749 for token in data.tokenlist: 2750 if token.str == "goto": 2751 self.reportError(token, 15, 1) 2752 2753 def misra_15_2(self, data): 2754 for token in data.tokenlist: 2755 if token.str != 'goto': 2756 continue 2757 if (not token.next) or (not token.next.isName): 2758 continue 2759 if not findGotoLabel(token): 2760 self.reportError(token, 15, 2) 2761 2762 def misra_15_3(self, data): 2763 for token in data.tokenlist: 2764 if token.str != 'goto': 2765 continue 2766 if (not token.next) or (not token.next.isName): 2767 continue 2768 tok = findGotoLabel(token) 2769 if not tok: 2770 continue 2771 scope = token.scope 2772 while scope and scope != tok.scope: 2773 scope = scope.nestedIn 2774 if not scope: 2775 self.reportError(token, 15, 3) 2776 # Jump crosses from one switch-clause to another is non-compliant 2777 elif scope.type == 'Switch': 2778 # Search for start of a current case block 2779 tcase_start = token 2780 while tcase_start and tcase_start.str not in ('case', 'default'): 2781 tcase_start = tcase_start.previous 2782 # Make sure that goto label doesn't occurs in the other 2783 # switch-clauses 2784 if tcase_start: 2785 t = scope.bodyStart 2786 in_this_case = False 2787 while t and t != scope.bodyEnd: 2788 if t == tcase_start: 2789 in_this_case = True 2790 if in_this_case and t.str not in ('case', 'default'): 2791 in_this_case = False 2792 if t == tok and not in_this_case: 2793 self.reportError(token, 15, 3) 2794 break 2795 t = t.next 2796 2797 def misra_15_4(self, data): 2798 # Return a list of scopes affected by a break or goto 2799 def getLoopsAffectedByBreak(knownLoops, scope, isGoto): 2800 if scope and scope.type and scope.type not in ['Global', 'Function']: 2801 if not isGoto and scope.type == 'Switch': 2802 return 2803 if scope.type in ['For', 'While', 'Do']: 2804 knownLoops.append(scope) 2805 if not isGoto: 2806 return 2807 getLoopsAffectedByBreak(knownLoops, scope.nestedIn, isGoto) 2808 2809 loopWithBreaks = {} 2810 for token in data.tokenlist: 2811 if token.str not in ['break', 'goto']: 2812 continue 2813 2814 affectedLoopScopes = [] 2815 getLoopsAffectedByBreak(affectedLoopScopes, token.scope, token.str == 'goto') 2816 for scope in affectedLoopScopes: 2817 if scope in loopWithBreaks: 2818 loopWithBreaks[scope] += 1 2819 else: 2820 loopWithBreaks[scope] = 1 2821 2822 for scope, breakCount in loopWithBreaks.items(): 2823 if breakCount > 1: 2824 self.reportError(scope.bodyStart, 15, 4) 2825 2826 def misra_15_5(self, data): 2827 for token in data.tokenlist: 2828 if token.str == 'return' and token.scope.type != 'Function': 2829 self.reportError(token, 15, 5) 2830 2831 def misra_15_6(self, rawTokens): 2832 state = 0 2833 indent = 0 2834 tok1 = None 2835 for token in rawTokens: 2836 if token.str in ['if', 'for', 'while']: 2837 if simpleMatch(token.previous, '# if'): 2838 continue 2839 if simpleMatch(token.previous, "} while"): 2840 # is there a 'do { .. } while'? 2841 start = rawlink(token.previous) 2842 if start and simpleMatch(start.previous, 'do {'): 2843 continue 2844 if state == 2: 2845 self.reportError(tok1, 15, 6) 2846 state = 1 2847 indent = 0 2848 tok1 = token 2849 elif token.str == 'else': 2850 if simpleMatch(token.previous, '# else'): 2851 continue 2852 if simpleMatch(token, 'else if'): 2853 continue 2854 if state == 2: 2855 self.reportError(tok1, 15, 6) 2856 state = 2 2857 indent = 0 2858 tok1 = token 2859 elif state == 1: 2860 if indent == 0 and token.str != '(': 2861 state = 0 2862 continue 2863 if token.str == '(': 2864 indent = indent + 1 2865 elif token.str == ')': 2866 if indent == 0: 2867 state = 0 2868 elif indent == 1: 2869 state = 2 2870 indent = indent - 1 2871 elif state == 2: 2872 if token.str.startswith('//') or token.str.startswith('/*'): 2873 continue 2874 state = 0 2875 if token.str not in ('{', '#'): 2876 self.reportError(tok1, 15, 6) 2877 2878 def misra_15_7(self, data): 2879 for scope in data.scopes: 2880 if scope.type != 'Else': 2881 continue 2882 if not simpleMatch(scope.bodyStart, '{ if ('): 2883 continue 2884 if scope.bodyStart.column > 0: 2885 continue 2886 tok = scope.bodyStart.next.next.link 2887 if not simpleMatch(tok, ') {'): 2888 continue 2889 tok = tok.next.link 2890 if not simpleMatch(tok, '} else'): 2891 self.reportError(tok, 15, 7) 2892 2893 def misra_16_1(self, cfg): 2894 for scope in cfg.scopes: 2895 if scope.type != 'Switch': 2896 continue 2897 in_case_or_default = False 2898 tok = scope.bodyStart.next 2899 while tok != scope.bodyEnd: 2900 if not in_case_or_default: 2901 if tok.str not in ('case', 'default'): 2902 self.reportError(tok, 16, 1) 2903 else: 2904 in_case_or_default = True 2905 else: 2906 if simpleMatch(tok, 'break ;'): 2907 in_case_or_default = False 2908 tok = tok.next 2909 if tok.str == '{': 2910 tok = tok.link 2911 if tok.scope.type == 'Unconditional' and simpleMatch(tok.previous.previous, 'break ;'): 2912 in_case_or_default = False 2913 tok = tok.next 2914 2915 def misra_16_2(self, data): 2916 for token in data.tokenlist: 2917 if token.str == 'case' and token.scope.type != 'Switch': 2918 self.reportError(token, 16, 2) 2919 2920 def misra_16_3(self, rawTokens): 2921 STATE_NONE = 0 # default state, not in switch case/default block 2922 STATE_BREAK = 1 # break/comment is seen but not its ';' 2923 STATE_OK = 2 # a case/default is allowed (we have seen 'break;'/'comment'/'{'/attribute) 2924 STATE_SWITCH = 3 # walking through switch statement scope 2925 2926 state = STATE_NONE 2927 end_swtich_token = None # end '}' for the switch scope 2928 for token in rawTokens: 2929 # Find switch scope borders 2930 if token.str == 'switch': 2931 state = STATE_SWITCH 2932 if state == STATE_SWITCH: 2933 if token.str == '{': 2934 end_swtich_token = findRawLink(token) 2935 else: 2936 continue 2937 2938 if token.str == 'break' or token.str == 'return' or token.str == 'throw': 2939 state = STATE_BREAK 2940 elif token.str == ';': 2941 if state == STATE_BREAK: 2942 state = STATE_OK 2943 elif token.next and token.next == end_swtich_token: 2944 self.reportError(token.next, 16, 3) 2945 else: 2946 state = STATE_NONE 2947 elif token.str.startswith('/*') or token.str.startswith('//'): 2948 if 'fallthrough' in token.str.lower(): 2949 state = STATE_OK 2950 elif simpleMatch(token, '[ [ fallthrough ] ] ;'): 2951 state = STATE_BREAK 2952 elif token.str == '{': 2953 state = STATE_OK 2954 elif token.str == '}' and state == STATE_OK: 2955 # is this {} an unconditional block of code? 2956 prev = findRawLink(token) 2957 if prev: 2958 prev = prev.previous 2959 while prev and prev.str[:2] in ('//', '/*'): 2960 prev = prev.previous 2961 if (prev is None) or (prev.str not in ':;{}'): 2962 state = STATE_NONE 2963 elif token.str == 'case' or token.str == 'default': 2964 if state != STATE_OK: 2965 self.reportError(token, 16, 3) 2966 state = STATE_OK 2967 2968 def misra_16_4(self, data): 2969 for token in data.tokenlist: 2970 if token.str != 'switch': 2971 continue 2972 if not simpleMatch(token, 'switch ('): 2973 continue 2974 if not simpleMatch(token.next.link, ') {'): 2975 continue 2976 startTok = token.next.link.next 2977 tok = startTok.next 2978 while tok and tok.str != '}': 2979 if tok.str == '{': 2980 tok = tok.link 2981 elif tok.str == 'default': 2982 break 2983 tok = tok.next 2984 if tok and tok.str != 'default': 2985 self.reportError(token, 16, 4) 2986 2987 def misra_16_5(self, data): 2988 for token in data.tokenlist: 2989 if token.str != 'default': 2990 continue 2991 if token.previous and token.previous.str == '{': 2992 continue 2993 tok2 = token 2994 while tok2: 2995 if tok2.str in ('}', 'case'): 2996 break 2997 if tok2.str == '{': 2998 tok2 = tok2.link 2999 tok2 = tok2.next 3000 if tok2 and tok2.str == 'case': 3001 self.reportError(token, 16, 5) 3002 3003 def misra_16_6(self, data): 3004 for token in data.tokenlist: 3005 if not (simpleMatch(token, 'switch (') and simpleMatch(token.next.link, ') {')): 3006 continue 3007 tok = token.next.link.next.next 3008 count = 0 3009 while tok: 3010 if tok.str in ['break', 'return', 'throw']: 3011 count = count + 1 3012 elif tok.str == '{': 3013 tok = tok.link 3014 if isNoReturnScope(tok): 3015 count = count + 1 3016 elif tok.str == '}': 3017 break 3018 tok = tok.next 3019 if count < 2: 3020 self.reportError(token, 16, 6) 3021 3022 def misra_16_7(self, data): 3023 for token in data.tokenlist: 3024 if simpleMatch(token, 'switch (') and isBoolExpression(token.next.astOperand2): 3025 self.reportError(token, 16, 7) 3026 3027 def misra_17_1(self, data): 3028 for token in data.tokenlist: 3029 if isFunctionCall(token) and token.astOperand1.str in ( 3030 'va_list', 'va_arg', 'va_start', 'va_end', 'va_copy'): 3031 self.reportError(token, 17, 1) 3032 elif token.str == 'va_list': 3033 self.reportError(token, 17, 1) 3034 3035 def misra_17_2(self, data): 3036 # find recursions.. 3037 def find_recursive_call(search_for_function, direct_call, calls_map, visited=None): 3038 if visited is None: 3039 visited = set() 3040 if direct_call == search_for_function: 3041 return True 3042 for indirect_call in calls_map.get(direct_call, []): 3043 if indirect_call == search_for_function: 3044 return True 3045 if indirect_call in visited: 3046 # This has already been handled 3047 continue 3048 visited.add(indirect_call) 3049 if find_recursive_call(search_for_function, indirect_call, calls_map, visited): 3050 return True 3051 return False 3052 3053 # List functions called in each function 3054 function_calls = {} 3055 for scope in data.scopes: 3056 if scope.type != 'Function': 3057 continue 3058 calls = [] 3059 tok = scope.bodyStart 3060 while tok != scope.bodyEnd: 3061 tok = tok.next 3062 if not isFunctionCall(tok, data.standards.c): 3063 continue 3064 f = tok.astOperand1.function 3065 if f is not None and f not in calls: 3066 calls.append(f) 3067 function_calls[scope.function] = calls 3068 3069 # Report warnings for all recursions.. 3070 for func in function_calls: 3071 for call in function_calls[func]: 3072 if not find_recursive_call(func, call, function_calls): 3073 # Function call is not recursive 3074 continue 3075 # Warn about all functions calls.. 3076 for scope in data.scopes: 3077 if scope.type != 'Function' or scope.function != func: 3078 continue 3079 tok = scope.bodyStart 3080 while tok != scope.bodyEnd: 3081 if tok.function and tok.function == call: 3082 self.reportError(tok, 17, 2) 3083 tok = tok.next 3084 3085 def misra_17_6(self, rawTokens): 3086 for token in rawTokens: 3087 if simpleMatch(token, '[ static'): 3088 self.reportError(token, 17, 6) 3089 3090 def misra_17_7(self, data): 3091 for token in data.tokenlist: 3092 if not token.scope.isExecutable: 3093 continue 3094 if token.str != '(' or token.astParent: 3095 continue 3096 if not token.previous.isName or token.previous.varId: 3097 continue 3098 if token.valueType is None: 3099 continue 3100 if token.valueType.type == 'void' and token.valueType.pointer == 0: 3101 continue 3102 self.reportError(token, 17, 7) 3103 3104 def misra_17_8(self, data): 3105 for token in data.tokenlist: 3106 if not (token.isAssignmentOp or (token.str in ('++', '--'))): 3107 continue 3108 if not token.astOperand1: 3109 continue 3110 var = token.astOperand1.variable 3111 if var and var.isArgument: 3112 self.reportError(token, 17, 8) 3113 3114 def misra_18_4(self, data): 3115 for token in data.tokenlist: 3116 if token.str not in ('+', '-', '+=', '-='): 3117 continue 3118 if token.astOperand1 is None or token.astOperand2 is None: 3119 continue 3120 vt1 = token.astOperand1.valueType 3121 vt2 = token.astOperand2.valueType 3122 if vt1 and vt1.pointer > 0: 3123 self.reportError(token, 18, 4) 3124 elif vt2 and vt2.pointer > 0: 3125 self.reportError(token, 18, 4) 3126 3127 def misra_18_5(self, data): 3128 for var in data.variables: 3129 if not var.isPointer: 3130 continue 3131 typetok = var.nameToken 3132 count = 0 3133 while typetok: 3134 if typetok.str == '*': 3135 count = count + 1 3136 elif not typetok.isName: 3137 break 3138 typetok = typetok.previous 3139 if count > 2: 3140 self.reportError(var.nameToken, 18, 5) 3141 3142 def misra_18_7(self, data): 3143 for scope in data.scopes: 3144 if scope.type != 'Struct': 3145 continue 3146 3147 token = scope.bodyStart.next 3148 while token != scope.bodyEnd and token is not None: 3149 # Handle nested structures to not duplicate an error. 3150 if token.str == '{': 3151 token = token.link 3152 3153 if cppcheckdata.simpleMatch(token, "[ ]"): 3154 self.reportError(token, 18, 7) 3155 break 3156 token = token.next 3157 3158 def misra_18_8(self, data): 3159 for var in data.variables: 3160 if not var.isArray or not var.isLocal: 3161 continue 3162 # TODO Array dimensions are not available in dump, must look in tokens 3163 typetok = var.nameToken.next 3164 if not typetok or typetok.str != '[': 3165 continue 3166 # Unknown define or syntax error 3167 if not typetok.astOperand2: 3168 continue 3169 if not isConstantExpression(typetok.astOperand2): 3170 self.reportError(var.nameToken, 18, 8) 3171 3172 def misra_19_2(self, data): 3173 for token in data.tokenlist: 3174 if token.str == 'union': 3175 self.reportError(token, 19, 2) 3176 3177 def misra_20_1(self, data): 3178 token_in_file = {} 3179 for token in data.tokenlist: 3180 if token.file not in token_in_file: 3181 token_in_file[token.file] = int(token.linenr) 3182 else: 3183 token_in_file[token.file] = min(token_in_file[token.file], int(token.linenr)) 3184 3185 for directive in data.directives: 3186 if not directive.str.startswith('#include'): 3187 continue 3188 if directive.file not in token_in_file: 3189 continue 3190 if token_in_file[directive.file] < int(directive.linenr): 3191 self.reportError(directive, 20, 1) 3192 3193 def misra_20_2(self, data): 3194 for directive in data.directives: 3195 if not directive.str.startswith('#include '): 3196 continue 3197 for pattern in ('\\', '//', '/*', ',', "'"): 3198 if pattern in directive.str: 3199 self.reportError(directive, 20, 2) 3200 break 3201 3202 def misra_20_3(self, data): 3203 for directive in data.directives: 3204 if not directive.str.startswith('#include '): 3205 continue 3206 3207 words = directive.str.split(' ') 3208 3209 # If include directive contains more than two words, here would be 3210 # violation anyway. 3211 if len(words) > 2: 3212 self.reportError(directive, 20, 3) 3213 3214 # Handle include directives with not quoted argument 3215 elif len(words) > 1: 3216 filename = words[1] 3217 if not ((filename.startswith('"') and 3218 filename.endswith('"')) or 3219 (filename.startswith('<') and 3220 filename.endswith('>'))): 3221 # We are handle only directly included files in the 3222 # following format: #include file.h 3223 # Cases with macro expansion provided by MISRA document are 3224 # skipped because we don't always have access to directive 3225 # definition. 3226 if '.' in filename: 3227 self.reportError(directive, 20, 3) 3228 3229 def misra_20_4(self, data): 3230 for directive in data.directives: 3231 res = re.search(r'#define ([a-z][a-z0-9_]+)', directive.str) 3232 if res and isKeyword(res.group(1), data.standards.c): 3233 self.reportError(directive, 20, 4) 3234 3235 def misra_20_5(self, data): 3236 for directive in data.directives: 3237 if directive.str.startswith('#undef '): 3238 self.reportError(directive, 20, 5) 3239 3240 def misra_20_7(self, data): 3241 def find_string_concat(exp, arg, directive_args): 3242 # Handle concatenation of string literals, e.g.: 3243 # #define MACRO(A, B) (A " " B) 3244 # Addon should not report errors for both macro arguments. 3245 arg_pos = exp.find(arg, 0) 3246 need_check = False 3247 skip_next = False 3248 state_in_string = False 3249 pos_search = arg_pos + 1 3250 directive_args = [a.strip() for a in directive_args if a != arg] 3251 arg = arg.strip() 3252 while pos_search < len(exp): 3253 if exp[pos_search] == '"': 3254 if state_in_string: 3255 state_in_string = False 3256 else: 3257 state_in_string = True 3258 pos_search += 1 3259 elif exp[pos_search].isalnum(): 3260 word = "" 3261 while pos_search < len(exp) and exp[pos_search].isalnum(): 3262 word += exp[pos_search] 3263 pos_search += 1 3264 if word == arg: 3265 pos_search += 1 3266 elif word in directive_args: 3267 skip_next = True 3268 break 3269 elif exp[pos_search] == ' ': 3270 pos_search += 1 3271 elif state_in_string: 3272 pos_search += 1 3273 else: 3274 need_check = True 3275 break 3276 return need_check, skip_next 3277 3278 for directive in data.directives: 3279 d = Define(directive) 3280 exp = '(' + d.expansionList + ')' 3281 skip_next = False 3282 for arg in d.args: 3283 if skip_next: 3284 _, skip_next = find_string_concat(exp, arg, d.args) 3285 continue 3286 need_check, skip_next = find_string_concat(exp, arg, d.args) 3287 if not need_check: 3288 continue 3289 3290 pos = 0 3291 while pos < len(exp): 3292 pos = exp.find(arg, pos) 3293 if pos < 0: 3294 break 3295 # is 'arg' used at position pos 3296 pos1 = pos - 1 3297 pos2 = pos + len(arg) 3298 pos = pos2 3299 if pos1 >= 0 and (isalnum(exp[pos1]) or exp[pos1] == '_'): 3300 continue 3301 if pos2 < len(exp) and (isalnum(exp[pos2]) or exp[pos2] == '_'): 3302 continue 3303 3304 while pos1 >= 0 and exp[pos1] == ' ': 3305 pos1 -= 1 3306 if exp[pos1] == '#': 3307 continue 3308 if exp[pos1] not in '([,.': 3309 self.reportError(directive, 20, 7) 3310 break 3311 while pos2 < len(exp) and exp[pos2] == ' ': 3312 pos2 += 1 3313 if pos2 < len(exp) and exp[pos2] not in ')]#,': 3314 self.reportError(directive, 20, 7) 3315 break 3316 3317 def misra_20_8(self, cfg): 3318 for cond in cfg.preprocessor_if_conditions: 3319 #print(cond) 3320 if cond.result and cond.result not in (0,1): 3321 self.reportError(cond, 20, 8) 3322 3323 def misra_20_9(self, cfg): 3324 for cond in cfg.preprocessor_if_conditions: 3325 if cond.E is None: 3326 continue 3327 defined = [] 3328 for directive in cfg.directives: 3329 if directive.file == cond.file and directive.linenr == cond.linenr: 3330 for name in re.findall(r'[^_a-zA-Z0-9]defined[ ]*\([ ]*([_a-zA-Z0-9]+)[ ]*\)', directive.str): 3331 defined.append(name) 3332 for name in re.findall(r'[^_a-zA-Z0-9]defined[ ]*([_a-zA-Z0-9]+)', directive.str): 3333 defined.append(name) 3334 break 3335 for s in cond.E.split(' '): 3336 if (s[0] >= 'A' and s[0] <= 'Z') or (s[0] >= 'a' and s[0] <= 'z'): 3337 if isKeyword(s): 3338 continue 3339 if s in defined: 3340 continue 3341 self.reportError(cond, 20, 9) 3342 3343 def misra_20_10(self, data): 3344 for directive in data.directives: 3345 d = Define(directive) 3346 if d.expansionList.find('#') >= 0: 3347 self.reportError(directive, 20, 10) 3348 3349 def misra_20_11(self, cfg): 3350 for directive in cfg.directives: 3351 d = Define(directive) 3352 for arg in d.args: 3353 res = re.search(r'[^#]#[ ]*%s[ ]*##' % arg, ' ' + d.expansionList) 3354 if res: 3355 self.reportError(directive, 20, 11) 3356 3357 def misra_20_12(self, cfg): 3358 def _is_hash_hash_op(expansion_list, arg): 3359 return re.search(r'##[ ]*%s[^a-zA-Z0-9_]' % arg, expansion_list) or \ 3360 re.search(r'[^a-zA-Z0-9_]%s[ ]*##' % arg, expansion_list) 3361 3362 def _is_other_op(expansion_list, arg): 3363 pos = expansion_list.find(arg) 3364 while pos >= 0: 3365 pos1 = pos - 1 3366 pos2 = pos + len(arg) 3367 pos = expansion_list.find(arg, pos2) 3368 if isalnum(expansion_list[pos1]) or expansion_list[pos1] == '_': 3369 continue 3370 if isalnum(expansion_list[pos2]) or expansion_list[pos2] == '_': 3371 continue 3372 while expansion_list[pos1] == ' ': 3373 pos1 = pos1 - 1 3374 if expansion_list[pos1] == '#': 3375 continue 3376 while expansion_list[pos2] == ' ': 3377 pos2 = pos2 + 1 3378 if expansion_list[pos2] == '#': 3379 continue 3380 return True 3381 return False 3382 3383 def _is_arg_macro_usage(directive, arg): 3384 for macro_usage in cfg.macro_usage: 3385 if macro_usage.file == directive.file and macro_usage.linenr == directive.linenr: 3386 for macro_usage_arg in cfg.macro_usage: 3387 if macro_usage_arg == macro_usage: 3388 continue 3389 if (macro_usage.usefile == macro_usage_arg.usefile and 3390 macro_usage.uselinenr == macro_usage_arg.uselinenr and 3391 macro_usage.usecolumn == macro_usage_arg.usecolumn): 3392 # TODO: check arg better 3393 return True 3394 return False 3395 3396 for directive in cfg.directives: 3397 define = Define(directive) 3398 expansion_list = '(%s)' % define.expansionList 3399 for arg in define.args: 3400 if not _is_hash_hash_op(expansion_list, arg): 3401 continue 3402 if not _is_other_op(expansion_list, arg): 3403 continue 3404 if _is_arg_macro_usage(directive, arg): 3405 self.reportError(directive, 20, 12) 3406 break 3407 3408 def misra_20_13(self, data): 3409 dir_pattern = re.compile(r'#[ ]*([^ (<]*)') 3410 for directive in data.directives: 3411 dir = directive.str 3412 mo = dir_pattern.match(dir) 3413 if mo: 3414 dir = mo.group(1) 3415 if dir not in ['define', 'elif', 'else', 'endif', 'error', 'if', 'ifdef', 'ifndef', 'include', 3416 'pragma', 'undef', 'warning']: 3417 self.reportError(directive, 20, 13) 3418 3419 def misra_20_14(self, data): 3420 # stack for #if blocks. contains the #if directive until the corresponding #endif is seen. 3421 # the size increases when there are inner #if directives. 3422 ifStack = [] 3423 for directive in data.directives: 3424 if directive.str.startswith('#if ') or directive.str.startswith('#ifdef ') or directive.str.startswith( 3425 '#ifndef '): 3426 ifStack.append(directive) 3427 elif directive.str == '#else' or directive.str.startswith('#elif '): 3428 if len(ifStack) == 0: 3429 self.reportError(directive, 20, 14) 3430 ifStack.append(directive) 3431 elif directive.file != ifStack[-1].file: 3432 self.reportError(directive, 20, 14) 3433 elif directive.str == '#endif': 3434 if len(ifStack) == 0: 3435 self.reportError(directive, 20, 14) 3436 elif directive.file != ifStack[-1].file: 3437 self.reportError(directive, 20, 14) 3438 ifStack.pop() 3439 3440 def misra_21_1(self, data): 3441 re_forbidden_macro = re.compile(r'#(?:define|undef) _[_A-Z]+') 3442 re_macro_name = re.compile(r'#(?:define|undef) (.+)[ $]') 3443 3444 for d in data.directives: 3445 # Search for forbidden identifiers 3446 m = re.search(re_forbidden_macro, d.str) 3447 if m: 3448 self.reportError(d, 21, 1) 3449 continue 3450 3451 # Search standard library identifiers in macro names 3452 m = re.search(re_macro_name, d.str) 3453 if not m: 3454 continue 3455 name = m.group(1) 3456 if isStdLibId(name, data.standards.c): 3457 self.reportError(d, 21, 1) 3458 3459 def misra_21_2(self, cfg): 3460 for directive in cfg.directives: 3461 define = Define(directive) 3462 if re.match(r'_+BUILTIN_.*', define.name.upper()): 3463 self.reportError(directive, 21, 2) 3464 for func in cfg.functions: 3465 if isStdLibId(func.name, cfg.standards.c): 3466 tok = func.tokenDef if func.tokenDef else func.token 3467 self.reportError(tok, 21, 2) 3468 3469 def misra_21_3(self, data): 3470 for token in data.tokenlist: 3471 if isFunctionCall(token) and (token.astOperand1.str in ('malloc', 'calloc', 'realloc', 'free')): 3472 self.reportError(token, 21, 3) 3473 3474 def misra_21_4(self, data): 3475 directive = findInclude(data.directives, '<setjmp.h>') 3476 if directive: 3477 self.reportError(directive, 21, 4) 3478 3479 def misra_21_5(self, data): 3480 directive = findInclude(data.directives, '<signal.h>') 3481 if directive: 3482 self.reportError(directive, 21, 5) 3483 3484 def misra_21_6(self, data): 3485 dir_stdio = findInclude(data.directives, '<stdio.h>') 3486 dir_wchar = findInclude(data.directives, '<wchar.h>') 3487 if dir_stdio: 3488 self.reportError(dir_stdio, 21, 6) 3489 if dir_wchar: 3490 self.reportError(dir_wchar, 21, 6) 3491 3492 def misra_21_7(self, data): 3493 for token in data.tokenlist: 3494 if isFunctionCall(token) and (token.astOperand1.str in ('atof', 'atoi', 'atol', 'atoll')): 3495 self.reportError(token, 21, 7) 3496 3497 def misra_21_8(self, data): 3498 for token in data.tokenlist: 3499 if isFunctionCall(token) and (token.astOperand1.str in ('abort', 'exit', 'getenv')): 3500 self.reportError(token, 21, 8) 3501 3502 def misra_21_9(self, data): 3503 for token in data.tokenlist: 3504 if (token.str in ('bsearch', 'qsort')) and token.next and token.next.str == '(': 3505 self.reportError(token, 21, 9) 3506 3507 def misra_21_10(self, data): 3508 directive = findInclude(data.directives, '<time.h>') 3509 if directive: 3510 self.reportError(directive, 21, 10) 3511 3512 for token in data.tokenlist: 3513 if (token.str == 'wcsftime') and token.next and token.next.str == '(': 3514 self.reportError(token, 21, 10) 3515 3516 def misra_21_11(self, data): 3517 directive = findInclude(data.directives, '<tgmath.h>') 3518 if directive: 3519 self.reportError(directive, 21, 11) 3520 3521 def misra_21_12(self, data): 3522 if findInclude(data.directives, '<fenv.h>'): 3523 for token in data.tokenlist: 3524 if token.str == 'fexcept_t' and token.isName: 3525 self.reportError(token, 21, 12) 3526 if isFunctionCall(token) and (token.astOperand1.str in ( 3527 'feclearexcept', 3528 'fegetexceptflag', 3529 'feraiseexcept', 3530 'fesetexceptflag', 3531 'fetestexcept')): 3532 self.reportError(token, 21, 12) 3533 3534 def misra_21_14(self, data): 3535 # buffers used in strcpy/strlen/etc function calls 3536 string_buffers = [] 3537 for token in data.tokenlist: 3538 if token.str[0] == 's' and isFunctionCall(token.next): 3539 name, args = cppcheckdata.get_function_call_name_args(token) 3540 if name is None: 3541 continue 3542 def _get_string_buffers(match, args, argnum): 3543 if not match: 3544 return [] 3545 ret = [] 3546 for a in argnum: 3547 if a < len(args): 3548 arg = args[a] 3549 while arg and arg.str in ('.', '::'): 3550 arg = arg.astOperand2 3551 if arg and arg.varId != 0 and arg.varId not in ret: 3552 ret.append(arg.varId) 3553 return ret 3554 string_buffers += _get_string_buffers(name == 'strcpy', args, [0, 1]) 3555 string_buffers += _get_string_buffers(name == 'strncpy', args, [0, 1]) 3556 string_buffers += _get_string_buffers(name == 'strlen', args, [0]) 3557 string_buffers += _get_string_buffers(name == 'strcmp', args, [0, 1]) 3558 string_buffers += _get_string_buffers(name == 'sprintf', args, [0]) 3559 string_buffers += _get_string_buffers(name == 'snprintf', args, [0, 3]) 3560 3561 for token in data.tokenlist: 3562 if token.str != 'memcmp': 3563 continue 3564 name, args = cppcheckdata.get_function_call_name_args(token) 3565 if name is None: 3566 continue 3567 if len(args) != 3: 3568 continue 3569 for arg in args[:2]: 3570 if arg.str[-1] == '\"': 3571 self.reportError(arg, 21, 14) 3572 continue 3573 while arg and arg.str in ('.', '::'): 3574 arg = arg.astOperand2 3575 if arg and arg.varId and arg.varId in string_buffers: 3576 self.reportError(arg, 21, 14) 3577 3578 def misra_21_15(self, data): 3579 for token in data.tokenlist: 3580 if token.str not in ('memcpy', 'memmove', 'memcmp'): 3581 continue 3582 name, args = cppcheckdata.get_function_call_name_args(token) 3583 if name is None: 3584 continue 3585 if len(args) != 3: 3586 continue 3587 if args[0].valueType is None or args[1].valueType is None: 3588 continue 3589 if args[0].valueType.type == args[1].valueType.type: 3590 continue 3591 if args[0].valueType.type == 'void' or args[1].valueType.type == 'void': 3592 continue 3593 self.reportError(token, 21, 15) 3594 3595 def misra_21_16(self, cfg): 3596 for token in cfg.tokenlist: 3597 if token.str != 'memcmp': 3598 continue 3599 name, args = cppcheckdata.get_function_call_name_args(token) 3600 if name is None: 3601 continue 3602 if len(args) != 3: 3603 continue 3604 for arg in args[:2]: 3605 if arg.valueType is None: 3606 continue 3607 if arg.valueType.pointer > 1: 3608 continue 3609 if arg.valueType.sign in ('unsigned', 'signed'): 3610 continue 3611 if arg.valueType.isEnum(): 3612 continue 3613 self.reportError(token, 21, 16) 3614 3615 def misra_21_19(self, cfg): 3616 for token in cfg.tokenlist: 3617 if token.str in ('localeconv', 'getenv', 'setlocale', 'strerror') and simpleMatch(token.next, '('): 3618 name, _ = cppcheckdata.get_function_call_name_args(token) 3619 if name is None or name != token.str: 3620 continue 3621 parent = token.next 3622 while simpleMatch(parent.astParent, '+'): 3623 parent = parent.astParent 3624 # x = f() 3625 if simpleMatch(parent.astParent, '=') and parent == parent.astParent.astOperand2: 3626 lhs = parent.astParent.astOperand1 3627 if lhs and lhs.valueType and lhs.valueType.pointer > 0 and lhs.valueType.constness == 0: 3628 self.reportError(token, 21, 19) 3629 if token.str == '=': 3630 lhs = token.astOperand1 3631 while simpleMatch(lhs, '*') and lhs.astOperand2 is None: 3632 lhs = lhs.astOperand1 3633 if not simpleMatch(lhs, '.'): 3634 continue 3635 while simpleMatch(lhs, '.'): 3636 lhs = lhs.astOperand1 3637 if lhs and lhs.variable and simpleMatch(lhs.variable.typeStartToken, 'lconv'): 3638 self.reportError(token, 21, 19) 3639 3640 def misra_21_20(self, cfg): 3641 assigned = {} 3642 invalid = [] 3643 for token in cfg.tokenlist: 3644 # No sophisticated data flow analysis, bail out if control flow is "interrupted" 3645 if token.str in ('{', '}', 'break', 'continue', 'return'): 3646 assigned = {} 3647 invalid = [] 3648 continue 3649 3650 # When pointer is assigned, remove it from 'assigned' and 'invalid' 3651 if token.varId and token.varId > 0 and simpleMatch(token.next, '='): 3652 for name in assigned.keys(): 3653 while token.varId in assigned[name]: 3654 assigned[name].remove(token.varId) 3655 while token.varId in invalid: 3656 invalid.remove(token.varId) 3657 continue 3658 3659 # Calling dangerous function 3660 if token.str in ('asctime', 'ctime', 'gmtime', 'localtime', 'localeconv', 'getenv', 'setlocale', 'strerror'): 3661 name, args = cppcheckdata.get_function_call_name_args(token) 3662 if name and name == token.str: 3663 # make assigned pointers invalid 3664 for varId in assigned.get(name, ()): 3665 if varId not in invalid: 3666 invalid.append(varId) 3667 3668 # assign pointer 3669 parent = token.next 3670 while parent.astParent and (parent.astParent.str == '+' or isCast(parent.astParent)): 3671 parent = parent.astParent 3672 if simpleMatch(parent.astParent, '='): 3673 eq = parent.astParent 3674 vartok = eq.previous 3675 if vartok and vartok.varId and vartok.varId > 0: 3676 if name not in assigned: 3677 assigned[name] = [vartok.varId] 3678 elif vartok.varId not in assigned[name]: 3679 assigned[name].append(vartok.varId) 3680 continue 3681 3682 # taking value of invalid pointer.. 3683 if token.astParent and token.varId: 3684 if token.varId in invalid: 3685 self.reportError(token, 21, 20) 3686 3687 def misra_21_21(self, cfg): 3688 for token in cfg.tokenlist: 3689 if token.str == 'system': 3690 name, args = cppcheckdata.get_function_call_name_args(token) 3691 if name == 'system' and len(args) == 1: 3692 self.reportError(token, 21, 21) 3693 3694 def misra_22_5(self, cfg): 3695 for token in cfg.tokenlist: 3696 if token.isUnaryOp("*") or (token.isBinaryOp() and token.str == '.'): 3697 fileptr = token.astOperand1 3698 if fileptr.variable and cppcheckdata.simpleMatch(fileptr.variable.typeStartToken, 'FILE *'): 3699 self.reportError(token, 22, 5) 3700 3701 def misra_22_7(self, cfg): 3702 for eofToken in cfg.tokenlist: 3703 if eofToken.str != 'EOF': 3704 continue 3705 if eofToken.astParent is None or not eofToken.astParent.isComparisonOp: 3706 continue 3707 if eofToken.astParent.astOperand1 == eofToken: 3708 eofTokenSibling = eofToken.astParent.astOperand2 3709 else: 3710 eofTokenSibling = eofToken.astParent.astOperand1 3711 while isCast(eofTokenSibling) and eofTokenSibling.valueType and eofTokenSibling.valueType.type and eofTokenSibling.valueType.type == 'int': 3712 eofTokenSibling = eofTokenSibling.astOperand2 if eofTokenSibling.astOperand2 else eofTokenSibling.astOperand1 3713 if eofTokenSibling is not None and eofTokenSibling.valueType and eofTokenSibling.valueType and eofTokenSibling.valueType.type in ('bool', 'char', 'short'): 3714 self.reportError(eofToken, 22, 7) 3715 3716 def misra_22_8(self, cfg): 3717 is_zero = False 3718 for token in cfg.tokenlist: 3719 if simpleMatch(token, 'errno = 0'): 3720 is_zero = True 3721 if token.str == '(' and not simpleMatch(token.link, ') {'): 3722 name, _ = cppcheckdata.get_function_call_name_args(token.previous) 3723 if name is None: 3724 continue 3725 if is_errno_setting_function(name): 3726 if not is_zero: 3727 self.reportError(token, 22, 8) 3728 else: 3729 is_zero = False 3730 3731 def misra_22_9(self, cfg): 3732 errno_is_set = False 3733 for token in cfg.tokenlist: 3734 if token.str == '(' and not simpleMatch(token.link, ') {'): 3735 name, args = cppcheckdata.get_function_call_name_args(token.previous) 3736 if name is None: 3737 continue 3738 errno_is_set = is_errno_setting_function(name) 3739 if errno_is_set and token.str in '{};': 3740 errno_is_set = False 3741 tok = token.next 3742 while tok and tok.str not in ('{','}',';','errno'): 3743 tok = tok.next 3744 if tok is None or tok.str != 'errno': 3745 self.reportError(token, 22, 9) 3746 elif (tok.astParent is None) or (not tok.astParent.isComparisonOp): 3747 self.reportError(token, 22, 9) 3748 3749 def misra_22_10(self, cfg): 3750 last_function_call = None 3751 for token in cfg.tokenlist: 3752 if token.str == '(' and not simpleMatch(token.link, ') {'): 3753 name, args = cppcheckdata.get_function_call_name_args(token.previous) 3754 last_function_call = name 3755 if token.str == '}': 3756 last_function_call = None 3757 if token.str == 'errno' and token.astParent and token.astParent.isComparisonOp: 3758 if last_function_call is None: 3759 self.reportError(token, 22, 10) 3760 elif not is_errno_setting_function(last_function_call): 3761 self.reportError(token, 22, 10) 3762 3763 3764 def get_verify_expected(self): 3765 """Return the list of expected violations in the verify test""" 3766 return self.verify_expected 3767 3768 def get_verify_actual(self): 3769 """Return the list of actual violations in for the verify test""" 3770 return self.verify_actual 3771 3772 def get_violations(self, violation_type=None): 3773 """Return the list of violations for a normal checker run""" 3774 if violation_type is None: 3775 return self.violations.items() 3776 else: 3777 return self.violations[violation_type] 3778 3779 def get_violation_types(self): 3780 """Return the list of violations for a normal checker run""" 3781 return self.violations.keys() 3782 3783 def addSuppressedRule(self, ruleNum, 3784 fileName=None, 3785 lineNumber=None, 3786 symbolName=None): 3787 """ 3788 Add a suppression to the suppressions data structure 3789 3790 Suppressions are stored in a dictionary of dictionaries that 3791 contains a list of tuples. 3792 3793 The first dictionary is keyed by the MISRA rule in hundreds 3794 format. The value of that dictionary is a dictionary of filenames. 3795 If the value is None then the rule is assumed to be suppressed for 3796 all files. 3797 If the filename exists then the value of that dictionary contains a list 3798 with the scope of the suppression. If the list contains an item of None 3799 then the rule is assumed to be suppressed for the entire file. Otherwise 3800 the list contains line number, symbol name tuples. 3801 For each tuple either line number or symbol name can can be none. 3802 3803 """ 3804 normalized_filename = None 3805 3806 if fileName is not None: 3807 normalized_filename = os.path.expanduser(fileName) 3808 normalized_filename = os.path.normpath(normalized_filename) 3809 3810 if lineNumber is not None or symbolName is not None: 3811 line_symbol = (lineNumber, symbolName) 3812 else: 3813 line_symbol = None 3814 3815 # If the rule is not in the dict already then add it 3816 if ruleNum not in self.suppressedRules: 3817 ruleItemList = list() 3818 ruleItemList.append(line_symbol) 3819 3820 fileDict = dict() 3821 fileDict[normalized_filename] = ruleItemList 3822 3823 self.suppressedRules[ruleNum] = fileDict 3824 3825 # Rule is added. Done. 3826 return 3827 3828 # Rule existed in the dictionary. Check for 3829 # filename entries. 3830 3831 # Get the dictionary for the rule number 3832 fileDict = self.suppressedRules[ruleNum] 3833 3834 # If the filename is not in the dict already add it 3835 if normalized_filename not in fileDict: 3836 ruleItemList = list() 3837 ruleItemList.append(line_symbol) 3838 3839 fileDict[normalized_filename] = ruleItemList 3840 3841 # Rule is added with a file scope. Done 3842 return 3843 3844 # Rule has a matching filename. Get the rule item list. 3845 3846 # Check the lists of rule items 3847 # to see if this (lineNumber, symbolName) combination 3848 # or None already exists. 3849 ruleItemList = fileDict[normalized_filename] 3850 3851 if line_symbol is None: 3852 # is it already in the list? 3853 if line_symbol not in ruleItemList: 3854 ruleItemList.append(line_symbol) 3855 else: 3856 # Check the list looking for matches 3857 matched = False 3858 for each in ruleItemList: 3859 if each is not None: 3860 if (each[0] == line_symbol[0]) and (each[1] == line_symbol[1]): 3861 matched = True 3862 3863 # Append the rule item if it was not already found 3864 if not matched: 3865 ruleItemList.append(line_symbol) 3866 3867 def isRuleSuppressed(self, file_path, linenr, ruleNum): 3868 """ 3869 Check to see if a rule is suppressed. 3870 3871 :param ruleNum: is the rule number in hundreds format 3872 :param file_path: File path of checked location 3873 :param linenr: Line number of checked location 3874 3875 If the rule exists in the dict then check for a filename 3876 If the filename is None then rule is suppressed globally 3877 for all files. 3878 If the filename exists then look for list of 3879 line number, symbol name tuples. If the list is None then 3880 the rule is suppressed for the entire file 3881 If the list of tuples exists then search the list looking for 3882 matching line numbers. Symbol names are currently ignored 3883 because they can include regular expressions. 3884 TODO: Support symbol names and expression matching. 3885 3886 """ 3887 ruleIsSuppressed = False 3888 3889 # Remove any prefix listed in command arguments from the filename. 3890 filename = None 3891 if file_path is not None: 3892 if self.filePrefix is not None: 3893 filename = remove_file_prefix(file_path, self.filePrefix) 3894 else: 3895 filename = os.path.basename(file_path) 3896 3897 if ruleNum in self.suppressedRules: 3898 fileDict = self.suppressedRules[ruleNum] 3899 3900 # a file name entry of None means that the rule is suppressed 3901 # globally 3902 if None in fileDict: 3903 ruleIsSuppressed = True 3904 else: 3905 # Does the filename match one of the names in 3906 # the file list 3907 if filename in fileDict: 3908 # Get the list of ruleItems 3909 ruleItemList = fileDict[filename] 3910 3911 if None in ruleItemList: 3912 # Entry of None in the ruleItemList means the rule is 3913 # suppressed for all lines in the filename 3914 ruleIsSuppressed = True 3915 else: 3916 # Iterate though the the list of line numbers 3917 # and symbols looking for a match of the line 3918 # number. Matching the symbol is a TODO: 3919 for each in ruleItemList: 3920 if each is not None: 3921 if each[0] == linenr: 3922 ruleIsSuppressed = True 3923 3924 return ruleIsSuppressed 3925 3926 def isRuleGloballySuppressed(self, rule_num): 3927 """ 3928 Check to see if a rule is globally suppressed. 3929 :param rule_num: is the rule number in hundreds format 3930 """ 3931 if rule_num not in self.suppressedRules: 3932 return False 3933 return None in self.suppressedRules[rule_num] 3934 3935 def showSuppressedRules(self): 3936 """ 3937 Print out rules in suppression list sorted by Rule Number 3938 """ 3939 print("Suppressed Rules List:") 3940 outlist = list() 3941 3942 for ruleNum in self.suppressedRules: 3943 fileDict = self.suppressedRules[ruleNum] 3944 3945 for fname in fileDict: 3946 ruleItemList = fileDict[fname] 3947 3948 for item in ruleItemList: 3949 if item is None: 3950 item_str = "None" 3951 else: 3952 item_str = str(item[0]) 3953 3954 outlist.append("%s: %s: %s (%d locations suppressed)" % ( 3955 float(ruleNum) / 100, fname, item_str, self.suppressionStats.get(ruleNum, 0))) 3956 3957 for line in sorted(outlist, reverse=True): 3958 print(" %s" % line) 3959 3960 def setFilePrefix(self, prefix): 3961 """ 3962 Set the file prefix to ignore from files when matching 3963 suppression files 3964 """ 3965 self.filePrefix = prefix 3966 3967 def setSeverity(self, severity): 3968 """ 3969 Set the severity for all errors. 3970 """ 3971 self.severity = severity 3972 3973 def setSuppressionList(self, suppressionlist): 3974 num1 = 0 3975 num2 = 0 3976 rule_pattern = re.compile(r'([0-9]+).([0-9]+)') 3977 strlist = suppressionlist.split(",") 3978 3979 # build ignore list 3980 for item in strlist: 3981 res = rule_pattern.match(item) 3982 if res: 3983 num1 = int(res.group(1)) 3984 num2 = int(res.group(2)) 3985 ruleNum = (num1 * 100) + num2 3986 3987 self.addSuppressedRule(ruleNum) 3988 3989 def reportError(self, location, num1, num2): 3990 ruleNum = num1 * 100 + num2 3991 3992 if self.isRuleGloballySuppressed(ruleNum): 3993 return 3994 3995 if self.settings.verify: 3996 self.verify_actual.append('%s:%d %d.%d' % (location.file, location.linenr, num1, num2)) 3997 elif self.isRuleSuppressed(location.file, location.linenr, ruleNum): 3998 # Error is suppressed. Ignore 3999 self.suppressionStats.setdefault(ruleNum, 0) 4000 self.suppressionStats[ruleNum] += 1 4001 return 4002 else: 4003 errorId = 'c2012-' + str(num1) + '.' + str(num2) 4004 misra_severity = 'Undefined' 4005 cppcheck_severity = 'style' 4006 if ruleNum in self.ruleTexts: 4007 errmsg = self.ruleTexts[ruleNum].text 4008 if self.ruleTexts[ruleNum].misra_severity: 4009 misra_severity = self.ruleTexts[ruleNum].misra_severity 4010 cppcheck_severity = self.ruleTexts[ruleNum].cppcheck_severity 4011 elif len(self.ruleTexts) == 0: 4012 errmsg = 'misra violation (use --rule-texts=<file> to get proper output)' 4013 else: 4014 errmsg = 'misra violation %s with no text in the supplied rule-texts-file' % (ruleNum) 4015 4016 if self.severity: 4017 cppcheck_severity = self.severity 4018 4019 this_violation = '{}-{}-{}-{}'.format(location.file, location.linenr, location.column, ruleNum) 4020 4021 # If this is new violation then record it and show it. If not then 4022 # skip it since it has already been displayed. 4023 if not this_violation in self.existing_violations: 4024 self.existing_violations.add(this_violation) 4025 cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', errorId, misra_severity) 4026 4027 if misra_severity not in self.violations: 4028 self.violations[misra_severity] = [] 4029 self.violations[misra_severity].append('misra-' + errorId) 4030 4031 def loadRuleTexts(self, filename): 4032 num1 = 0 4033 num2 = 0 4034 appendixA = False 4035 ruleText = False 4036 expect_more = False 4037 4038 Rule_pattern = re.compile(r'^Rule ([0-9]+).([0-9]+)') 4039 severity_pattern = re.compile(r'.*[ ]*(Advisory|Required|Mandatory)$') 4040 xA_Z_pattern = re.compile(r'^[#A-Z].*') 4041 a_z_pattern = re.compile(r'^[a-z].*') 4042 # Try to detect the file encoding 4043 file_stream = None 4044 encodings = ['ascii', 'utf-8', 'windows-1250', 'windows-1252'] 4045 for e in encodings: 4046 try: 4047 file_stream = codecs.open(filename, 'r', encoding=e) 4048 file_stream.readlines() 4049 file_stream.seek(0) 4050 except UnicodeDecodeError: 4051 file_stream = None 4052 else: 4053 break 4054 if not file_stream: 4055 print('Could not find a suitable codec for "' + filename + '".') 4056 print('If you know the codec please report it to the developers so the list can be enhanced.') 4057 print('Trying with default codec now and ignoring errors if possible ...') 4058 try: 4059 file_stream = open(filename, 'rt', errors='ignore') 4060 except TypeError: 4061 # Python 2 does not support the errors parameter 4062 file_stream = open(filename, 'rt') 4063 4064 rule = None 4065 have_severity = False 4066 severity_loc = 0 4067 4068 for line in file_stream: 4069 4070 line = line.replace('\r', '').replace('\n', '') 4071 4072 if not appendixA: 4073 if line.find('Appendix A') >= 0 and line.find('Summary of guidelines') >= 10: 4074 appendixA = True 4075 continue 4076 if line.find('Appendix B') >= 0: 4077 break 4078 if len(line) == 0: 4079 continue 4080 4081 # Parse rule declaration. 4082 res = Rule_pattern.match(line) 4083 4084 if res: 4085 have_severity = False 4086 expect_more = False 4087 severity_loc = 0 4088 num1 = int(res.group(1)) 4089 num2 = int(res.group(2)) 4090 rule = Rule(num1, num2) 4091 4092 if not have_severity and rule is not None: 4093 res = severity_pattern.match(line) 4094 4095 if res: 4096 rule.misra_severity = res.group(1) 4097 have_severity = True 4098 else: 4099 severity_loc += 1 4100 4101 # Only look for severity on the Rule line 4102 # or the next non-blank line after 4103 # If it's not in either of those locations then 4104 # assume a severity was not provided. 4105 4106 if severity_loc < 2: 4107 continue 4108 else: 4109 rule.misra_severity = '' 4110 have_severity = True 4111 4112 if rule is None: 4113 continue 4114 4115 # Parse continuing of rule text. 4116 if expect_more: 4117 if a_z_pattern.match(line): 4118 self.ruleTexts[rule.num].text += ' ' + line 4119 continue 4120 4121 expect_more = False 4122 continue 4123 4124 # Parse beginning of rule text. 4125 if xA_Z_pattern.match(line): 4126 rule.text = line 4127 self.ruleTexts[rule.num] = rule 4128 expect_more = True 4129 4130 def verifyRuleTexts(self): 4131 """Prints rule numbers without rule text.""" 4132 rule_texts_rules = [] 4133 for rule_num in self.ruleTexts: 4134 rule = self.ruleTexts[rule_num] 4135 rule_texts_rules.append(str(rule.num1) + '.' + str(rule.num2)) 4136 4137 all_rules = list(getAddonRules() + getCppcheckRules()) 4138 4139 missing_rules = list(set(all_rules) - set(rule_texts_rules)) 4140 if len(missing_rules) == 0: 4141 print("Rule texts are correct.") 4142 else: 4143 print("Missing rule texts: " + ', '.join(missing_rules)) 4144 4145 def printStatus(self, *args, **kwargs): 4146 if not self.settings.quiet: 4147 print(*args, **kwargs) 4148 4149 def executeCheck(self, rule_num, check_function, *args): 4150 """Execute check function for a single MISRA rule. 4151 4152 :param rule_num: Number of rule in hundreds format 4153 :param check_function: Check function to execute 4154 :param args: Check function arguments 4155 """ 4156 if not self.isRuleGloballySuppressed(rule_num): 4157 check_function(*args) 4158 4159 def parseDump(self, dumpfile): 4160 def fillVerifyExpected(verify_expected, tok): 4161 """Add expected suppressions to verify_expected list.""" 4162 rule_re = re.compile(r'[0-9]+\.[0-9]+') 4163 if tok.str.startswith('//') and 'TODO' not in tok.str: 4164 for word in tok.str[2:].split(' '): 4165 if rule_re.match(word): 4166 verify_expected.append('%s:%d %s' % (tok.file, tok.linenr, word)) 4167 4168 data = cppcheckdata.parsedump(dumpfile) 4169 4170 typeBits['CHAR'] = data.platform.char_bit 4171 typeBits['SHORT'] = data.platform.short_bit 4172 typeBits['INT'] = data.platform.int_bit 4173 typeBits['LONG'] = data.platform.long_bit 4174 typeBits['LONG_LONG'] = data.platform.long_long_bit 4175 typeBits['POINTER'] = data.platform.pointer_bit 4176 4177 if self.settings.verify: 4178 # Add suppressions from the current file 4179 for tok in data.rawTokens: 4180 fillVerifyExpected(self.verify_expected, tok) 4181 # Add suppressions from the included headers 4182 include_re = re.compile(r'^#include [<"]([a-zA-Z0-9]+[a-zA-Z\-_./\\0-9]*)[">]$') 4183 dump_dir = os.path.dirname(data.filename) 4184 for conf in data.configurations: 4185 for directive in conf.directives: 4186 m = re.match(include_re, directive.str) 4187 if not m: 4188 continue 4189 header_dump_path = os.path.join(dump_dir, m.group(1) + '.dump') 4190 if not os.path.exists(header_dump_path): 4191 continue 4192 header_data = cppcheckdata.parsedump(header_dump_path) 4193 for tok in header_data.rawTokens: 4194 fillVerifyExpected(self.verify_expected, tok) 4195 else: 4196 self.printStatus('Checking ' + dumpfile + '...') 4197 4198 for cfgNumber, cfg in enumerate(data.iterconfigurations()): 4199 if not self.settings.quiet: 4200 self.printStatus('Checking %s, config %s...' % (dumpfile, cfg.name)) 4201 4202 self.executeCheck(104, self.misra_1_4, cfg) 4203 self.executeCheck(203, self.misra_2_3, dumpfile, cfg.typedefInfo) 4204 self.executeCheck(204, self.misra_2_4, dumpfile, cfg) 4205 self.executeCheck(205, self.misra_2_5, dumpfile, cfg) 4206 self.executeCheck(207, self.misra_2_7, cfg) 4207 # data.rawTokens is same for all configurations 4208 if cfgNumber == 0: 4209 self.executeCheck(301, self.misra_3_1, data.rawTokens) 4210 self.executeCheck(302, self.misra_3_2, data.rawTokens) 4211 self.executeCheck(401, self.misra_4_1, data.rawTokens) 4212 self.executeCheck(402, self.misra_4_2, data.rawTokens) 4213 self.executeCheck(501, self.misra_5_1, cfg) 4214 self.executeCheck(502, self.misra_5_2, cfg) 4215 self.executeCheck(504, self.misra_5_4, cfg) 4216 self.executeCheck(505, self.misra_5_5, cfg) 4217 self.executeCheck(506, self.misra_5_6, dumpfile, cfg.typedefInfo) 4218 self.executeCheck(507, self.misra_5_7, dumpfile, cfg) 4219 self.executeCheck(508, self.misra_5_8, dumpfile, cfg) 4220 self.executeCheck(509, self.misra_5_9, dumpfile, cfg) 4221 self.executeCheck(601, self.misra_6_1, cfg) 4222 self.executeCheck(602, self.misra_6_2, cfg) 4223 if cfgNumber == 0: 4224 self.executeCheck(701, self.misra_7_1, data.rawTokens) 4225 self.executeCheck(702, self.misra_7_2, cfg) 4226 if cfgNumber == 0: 4227 self.executeCheck(703, self.misra_7_3, data.rawTokens) 4228 self.executeCheck(704, self.misra_7_4, cfg) 4229 self.executeCheck(801, self.misra_8_1, cfg) 4230 if cfgNumber == 0: 4231 self.executeCheck(802, self.misra_8_2, cfg, data.rawTokens) 4232 self.executeCheck(804, self.misra_8_4, cfg) 4233 self.executeCheck(805, self.misra_8_5, dumpfile, cfg) 4234 self.executeCheck(806, self.misra_8_6, dumpfile, cfg) 4235 self.executeCheck(807, self.misra_8_7, dumpfile, cfg) 4236 self.executeCheck(808, self.misra_8_8, cfg) 4237 self.executeCheck(809, self.misra_8_9, cfg) 4238 self.executeCheck(810, self.misra_8_10, cfg) 4239 self.executeCheck(811, self.misra_8_11, cfg) 4240 self.executeCheck(812, self.misra_8_12, cfg) 4241 if cfgNumber == 0: 4242 self.executeCheck(814, self.misra_8_14, data.rawTokens) 4243 self.executeCheck(902, self.misra_9_2, cfg) 4244 self.executeCheck(903, self.misra_9_3, cfg) 4245 self.executeCheck(904, self.misra_9_4, cfg) 4246 if cfgNumber == 0: 4247 self.executeCheck(905, self.misra_9_5, cfg, data.rawTokens) 4248 self.executeCheck(1001, self.misra_10_1, cfg) 4249 self.executeCheck(1002, self.misra_10_2, cfg) 4250 self.executeCheck(1003, self.misra_10_3, cfg) 4251 self.executeCheck(1004, self.misra_10_4, cfg) 4252 self.executeCheck(1005, self.misra_10_5, cfg) 4253 self.executeCheck(1006, self.misra_10_6, cfg) 4254 self.executeCheck(1007, self.misra_10_7, cfg) 4255 self.executeCheck(1008, self.misra_10_8, cfg) 4256 self.executeCheck(1101, self.misra_11_1, cfg) 4257 self.executeCheck(1102, self.misra_11_2, cfg) 4258 self.executeCheck(1103, self.misra_11_3, cfg) 4259 self.executeCheck(1104, self.misra_11_4, cfg) 4260 self.executeCheck(1105, self.misra_11_5, cfg) 4261 self.executeCheck(1106, self.misra_11_6, cfg) 4262 self.executeCheck(1107, self.misra_11_7, cfg) 4263 self.executeCheck(1108, self.misra_11_8, cfg) 4264 self.executeCheck(1109, self.misra_11_9, cfg) 4265 if cfgNumber == 0: 4266 self.executeCheck(1201, self.misra_12_1_sizeof, data.rawTokens) 4267 self.executeCheck(1201, self.misra_12_1, cfg) 4268 self.executeCheck(1202, self.misra_12_2, cfg) 4269 self.executeCheck(1203, self.misra_12_3, cfg) 4270 self.executeCheck(1204, self.misra_12_4, cfg) 4271 self.executeCheck(1301, self.misra_13_1, cfg) 4272 self.executeCheck(1303, self.misra_13_3, cfg) 4273 self.executeCheck(1304, self.misra_13_4, cfg) 4274 self.executeCheck(1305, self.misra_13_5, cfg) 4275 self.executeCheck(1306, self.misra_13_6, cfg) 4276 self.executeCheck(1401, self.misra_14_1, cfg) 4277 self.executeCheck(1402, self.misra_14_2, cfg) 4278 self.executeCheck(1404, self.misra_14_4, cfg) 4279 self.executeCheck(1501, self.misra_15_1, cfg) 4280 self.executeCheck(1502, self.misra_15_2, cfg) 4281 self.executeCheck(1503, self.misra_15_3, cfg) 4282 self.executeCheck(1504, self.misra_15_4, cfg) 4283 self.executeCheck(1505, self.misra_15_5, cfg) 4284 if cfgNumber == 0: 4285 self.executeCheck(1506, self.misra_15_6, data.rawTokens) 4286 self.executeCheck(1507, self.misra_15_7, cfg) 4287 self.executeCheck(1601, self.misra_16_1, cfg) 4288 self.executeCheck(1602, self.misra_16_2, cfg) 4289 if cfgNumber == 0: 4290 self.executeCheck(1603, self.misra_16_3, data.rawTokens) 4291 self.executeCheck(1604, self.misra_16_4, cfg) 4292 self.executeCheck(1605, self.misra_16_5, cfg) 4293 self.executeCheck(1606, self.misra_16_6, cfg) 4294 self.executeCheck(1607, self.misra_16_7, cfg) 4295 self.executeCheck(1701, self.misra_17_1, cfg) 4296 self.executeCheck(1702, self.misra_17_2, cfg) 4297 if cfgNumber == 0: 4298 self.executeCheck(1706, self.misra_17_6, data.rawTokens) 4299 self.executeCheck(1707, self.misra_17_7, cfg) 4300 self.executeCheck(1708, self.misra_17_8, cfg) 4301 self.executeCheck(1804, self.misra_18_4, cfg) 4302 self.executeCheck(1805, self.misra_18_5, cfg) 4303 self.executeCheck(1807, self.misra_18_7, cfg) 4304 self.executeCheck(1808, self.misra_18_8, cfg) 4305 self.executeCheck(1902, self.misra_19_2, cfg) 4306 self.executeCheck(2001, self.misra_20_1, cfg) 4307 self.executeCheck(2002, self.misra_20_2, cfg) 4308 self.executeCheck(2003, self.misra_20_3, cfg) 4309 self.executeCheck(2004, self.misra_20_4, cfg) 4310 self.executeCheck(2005, self.misra_20_5, cfg) 4311 self.executeCheck(2007, self.misra_20_7, cfg) 4312 self.executeCheck(2008, self.misra_20_8, cfg) 4313 self.executeCheck(2009, self.misra_20_9, cfg) 4314 self.executeCheck(2010, self.misra_20_10, cfg) 4315 self.executeCheck(2011, self.misra_20_11, cfg) 4316 self.executeCheck(2012, self.misra_20_12, cfg) 4317 self.executeCheck(2013, self.misra_20_13, cfg) 4318 self.executeCheck(2014, self.misra_20_14, cfg) 4319 self.executeCheck(2101, self.misra_21_1, cfg) 4320 self.executeCheck(2102, self.misra_21_2, cfg) 4321 self.executeCheck(2103, self.misra_21_3, cfg) 4322 self.executeCheck(2104, self.misra_21_4, cfg) 4323 self.executeCheck(2105, self.misra_21_5, cfg) 4324 self.executeCheck(2106, self.misra_21_6, cfg) 4325 self.executeCheck(2107, self.misra_21_7, cfg) 4326 self.executeCheck(2108, self.misra_21_8, cfg) 4327 self.executeCheck(2109, self.misra_21_9, cfg) 4328 self.executeCheck(2110, self.misra_21_10, cfg) 4329 self.executeCheck(2111, self.misra_21_11, cfg) 4330 self.executeCheck(2112, self.misra_21_12, cfg) 4331 self.executeCheck(2114, self.misra_21_14, cfg) 4332 self.executeCheck(2115, self.misra_21_15, cfg) 4333 self.executeCheck(2116, self.misra_21_16, cfg) 4334 self.executeCheck(2119, self.misra_21_19, cfg) 4335 self.executeCheck(2120, self.misra_21_20, cfg) 4336 self.executeCheck(2121, self.misra_21_21, cfg) 4337 # 22.4 is already covered by Cppcheck writeReadOnlyFile 4338 self.executeCheck(2205, self.misra_22_5, cfg) 4339 self.executeCheck(2207, self.misra_22_7, cfg) 4340 self.executeCheck(2208, self.misra_22_8, cfg) 4341 self.executeCheck(2209, self.misra_22_9, cfg) 4342 self.executeCheck(2210, self.misra_22_10, cfg) 4343 4344 def analyse_ctu_info(self, ctu_info_files): 4345 all_typedef_info = [] 4346 all_tagname_info = [] 4347 all_macro_info = [] 4348 all_external_identifiers_decl = {} 4349 all_external_identifiers_def = {} 4350 all_internal_identifiers = {} 4351 all_local_identifiers = {} 4352 all_usage_count = {} 4353 4354 from cppcheckdata import Location 4355 4356 def is_different_location(loc1, loc2): 4357 return loc1['file'] != loc2['file'] or loc1['line'] != loc2['line'] 4358 4359 for filename in ctu_info_files: 4360 for line in open(filename, 'rt'): 4361 if not line.startswith('{'): 4362 continue 4363 4364 s = json.loads(line) 4365 summary_type = s['summary'] 4366 summary_data = s['data'] 4367 4368 if summary_type == 'MisraTypedefInfo': 4369 for new_typedef_info in summary_data: 4370 found = False 4371 for old_typedef_info in all_typedef_info: 4372 if old_typedef_info['name'] == new_typedef_info['name']: 4373 found = True 4374 if is_different_location(old_typedef_info, new_typedef_info): 4375 self.reportError(Location(old_typedef_info), 5, 6) 4376 self.reportError(Location(new_typedef_info), 5, 6) 4377 else: 4378 if new_typedef_info['used']: 4379 old_typedef_info['used'] = True 4380 break 4381 if not found: 4382 all_typedef_info.append(new_typedef_info) 4383 4384 if summary_type == 'MisraTagName': 4385 for new_tagname_info in summary_data: 4386 found = False 4387 for old_tagname_info in all_tagname_info: 4388 if old_tagname_info['name'] == new_tagname_info['name']: 4389 found = True 4390 if is_different_location(old_tagname_info, new_tagname_info): 4391 self.reportError(Location(old_tagname_info), 5, 7) 4392 self.reportError(Location(new_tagname_info), 5, 7) 4393 else: 4394 if new_tagname_info['used']: 4395 old_tagname_info['used'] = True 4396 break 4397 if not found: 4398 all_tagname_info.append(new_tagname_info) 4399 4400 if summary_type == 'MisraMacro': 4401 for new_macro in summary_data: 4402 found = False 4403 for old_macro in all_macro_info: 4404 if old_macro['name'] == new_macro['name']: 4405 found = True 4406 if new_macro['used']: 4407 old_macro['used'] = True 4408 break 4409 if not found: 4410 all_macro_info.append(new_macro) 4411 4412 if summary_type == 'MisraExternalIdentifiers': 4413 for s in summary_data: 4414 is_declaration = s['decl'] 4415 if is_declaration: 4416 all_external_identifiers = all_external_identifiers_decl 4417 else: 4418 all_external_identifiers = all_external_identifiers_def 4419 4420 name = s['name'] 4421 if name in all_external_identifiers and is_different_location(s, all_external_identifiers[name]): 4422 num = 5 if is_declaration else 6 4423 self.reportError(Location(s), 8, num) 4424 self.reportError(Location(all_external_identifiers[name]), 8, num) 4425 all_external_identifiers[name] = s 4426 4427 if summary_type == 'MisraInternalIdentifiers': 4428 for s in summary_data: 4429 if s['name'] in all_internal_identifiers: 4430 self.reportError(Location(s), 5, 9) 4431 self.reportError(Location(all_internal_identifiers[s['name']]), 5, 9) 4432 all_internal_identifiers[s['name']] = s 4433 4434 if summary_type == 'MisraLocalIdentifiers': 4435 for s in summary_data: 4436 all_local_identifiers[s['name']] = s 4437 4438 if summary_type == 'MisraUsage': 4439 for s in summary_data: 4440 if s in all_usage_count: 4441 all_usage_count[s] += 1 4442 else: 4443 all_usage_count[s] = 1 4444 4445 for ti in all_typedef_info: 4446 if not ti['used']: 4447 self.reportError(Location(ti), 2, 3) 4448 4449 for ti in all_tagname_info: 4450 if not ti['used']: 4451 self.reportError(Location(ti), 2, 4) 4452 4453 for m in all_macro_info: 4454 if not m['used']: 4455 self.reportError(Location(m), 2, 5) 4456 4457 all_external_identifiers = all_external_identifiers_decl 4458 all_external_identifiers.update(all_external_identifiers_def) 4459 for name, external_identifier in all_external_identifiers.items(): 4460 internal_identifier = all_internal_identifiers.get(name) 4461 if internal_identifier: 4462 self.reportError(Location(internal_identifier), 5, 8) 4463 self.reportError(Location(external_identifier), 5, 8) 4464 4465 local_identifier = all_local_identifiers.get(name) 4466 if local_identifier: 4467 self.reportError(Location(local_identifier), 5, 8) 4468 self.reportError(Location(external_identifier), 5, 8) 4469 4470 for name, count in all_usage_count.items(): 4471 #print('%s:%i' % (name, count)) 4472 if count != 1: 4473 continue 4474 if name in all_external_identifiers: 4475 self.reportError(Location(all_external_identifiers[name]), 8, 7) 4476 4477RULE_TEXTS_HELP = '''Path to text file of MISRA rules 4478 4479If you have the tool 'pdftotext' you might be able 4480to generate this textfile with such command: 4481 4482 pdftotext MISRA_C_2012.pdf MISRA_C_2012.txt 4483 4484Otherwise you can more or less copy/paste the chapter 4485Appendix A Summary of guidelines 4486from the MISRA pdf. You can buy the MISRA pdf from 4487http://www.misra.org.uk/ 4488 4489Format: 4490 4491<..arbitrary text..> 4492Appendix A Summary of guidelines 4493Rule 1.1 Required 4494Rule text for 1.1 4495continuation of rule text for 1.1 4496Rule 1.2 Mandatory 4497Rule text for 1.2 4498continuation of rule text for 1.2 4499<...> 4500 4501''' 4502 4503SUPPRESS_RULES_HELP = '''MISRA rules to suppress (comma-separated) 4504 4505For example, if you'd like to suppress rules 15.1, 11.3, 4506and 20.13, run: 4507 4508 python misra.py --suppress-rules 15.1,11.3,20.13 ... 4509 4510''' 4511 4512 4513def get_args_parser(): 4514 """Generates list of command-line arguments acceptable by misra.py script.""" 4515 parser = cppcheckdata.ArgumentParser() 4516 parser.add_argument("--rule-texts", type=str, help=RULE_TEXTS_HELP) 4517 parser.add_argument("--verify-rule-texts", 4518 help="Verify that all supported rules texts are present in given file and exit.", 4519 action="store_true") 4520 parser.add_argument("--suppress-rules", type=str, help=SUPPRESS_RULES_HELP) 4521 parser.add_argument("--no-summary", help="Hide summary of violations", action="store_true") 4522 parser.add_argument("--show-suppressed-rules", help="Print rule suppression list", action="store_true") 4523 parser.add_argument("-P", "--file-prefix", type=str, help="Prefix to strip when matching suppression file rules") 4524 parser.add_argument("-generate-table", help=argparse.SUPPRESS, action="store_true") 4525 parser.add_argument("-verify", help=argparse.SUPPRESS, action="store_true") 4526 parser.add_argument("--severity", type=str, help="Set a custom severity string, for example 'error' or 'warning'. ") 4527 return parser 4528 4529 4530def main(): 4531 parser = get_args_parser() 4532 args = parser.parse_args() 4533 settings = MisraSettings(args) 4534 checker = MisraChecker(settings) 4535 4536 if args.generate_table: 4537 generateTable() 4538 sys.exit(0) 4539 4540 if args.rule_texts: 4541 filename = os.path.expanduser(args.rule_texts) 4542 filename = os.path.normpath(filename) 4543 if not os.path.isfile(filename): 4544 print('Fatal error: file is not found: ' + filename) 4545 sys.exit(1) 4546 checker.loadRuleTexts(filename) 4547 if args.verify_rule_texts: 4548 checker.verifyRuleTexts() 4549 sys.exit(0) 4550 4551 if args.verify_rule_texts and not args.rule_texts: 4552 print("Error: Please specify rule texts file with --rule-texts=<file>") 4553 sys.exit(1) 4554 4555 if args.suppress_rules: 4556 checker.setSuppressionList(args.suppress_rules) 4557 4558 if args.file_prefix: 4559 checker.setFilePrefix(args.file_prefix) 4560 4561 dump_files, ctu_info_files = cppcheckdata.get_files(args) 4562 4563 if (not dump_files) and (not ctu_info_files): 4564 if not args.quiet: 4565 print("No input files.") 4566 sys.exit(0) 4567 4568 if args.severity: 4569 checker.setSeverity(args.severity) 4570 4571 for item in dump_files: 4572 checker.parseDump(item) 4573 4574 if settings.verify: 4575 verify_expected = checker.get_verify_expected() 4576 verify_actual = checker.get_verify_actual() 4577 4578 exitCode = 0 4579 for expected in verify_expected: 4580 if expected not in verify_actual: 4581 print('Expected but not seen: ' + expected) 4582 exitCode = 1 4583 for actual in verify_actual: 4584 if actual not in verify_expected: 4585 print('Not expected: ' + actual) 4586 exitCode = 1 4587 4588 # Existing behavior of verify mode is to exit 4589 # on the first un-expected output. 4590 # TODO: Is this required? or can it be moved to after 4591 # all input files have been processed 4592 if exitCode != 0: 4593 sys.exit(exitCode) 4594 4595 checker.analyse_ctu_info(ctu_info_files) 4596 4597 if settings.verify: 4598 sys.exit(exitCode) 4599 4600 number_of_violations = len(checker.get_violations()) 4601 if number_of_violations > 0: 4602 if settings.show_summary: 4603 print("\nMISRA rules violations found:\n\t%s\n" % ( 4604 "\n\t".join(["%s: %d" % (viol, len(checker.get_violations(viol))) for viol in 4605 checker.get_violation_types()]))) 4606 4607 rules_violated = {} 4608 for severity, ids in checker.get_violations(): 4609 for misra_id in ids: 4610 rules_violated[misra_id] = rules_violated.get(misra_id, 0) + 1 4611 print("MISRA rules violated:") 4612 convert = lambda text: int(text) if text.isdigit() else text 4613 misra_sort = lambda key: [convert(c) for c in re.split(r'[\.-]([0-9]*)', key)] 4614 for misra_id in sorted(rules_violated.keys(), key=misra_sort): 4615 res = re.match(r'misra-c2012-([0-9]+)\\.([0-9]+)', misra_id) 4616 if res is None: 4617 num = 0 4618 else: 4619 num = int(res.group(1)) * 100 + int(res.group(2)) 4620 severity = '-' 4621 if num in checker.ruleTexts: 4622 severity = checker.ruleTexts[num].cppcheck_severity 4623 print("\t%15s (%s): %d" % (misra_id, severity, rules_violated[misra_id])) 4624 4625 if args.show_suppressed_rules: 4626 checker.showSuppressedRules() 4627 4628 4629if __name__ == '__main__': 4630 main() 4631 sys.exit(cppcheckdata.EXIT_CODE) 4632