351 lines
7.5 KiB
Python
Executable File
351 lines
7.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
cppcheck addon for threadsafety detection.
|
|
|
|
This script analyses Cppcheck dump files to locate threadsafety issues.
|
|
It warns about
|
|
- static local objects
|
|
- MT-Unsafe symbols listed in the "Attributes" sections of man pages.
|
|
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
|
|
import cppcheckdata
|
|
|
|
# --------------------------------
|
|
# List of MT-Unsafe identifiers
|
|
# --------------------------------
|
|
|
|
# This is Work In Progress.
|
|
# Eventually it should contain all identifiers (types
|
|
# and functions) which are MT-Unsafe.
|
|
|
|
# The script tools/MT-Unsafe.py can help to re-generate this list.
|
|
# It reads a man-page tree and report identifiers marked as "MT-Unsafe"
|
|
# (see man 7 attributes for what this means), eg
|
|
# MT-Unsafe.py /usr/share/man/man3
|
|
|
|
id_MTunsafe_full = {
|
|
# MT-Unsafe types by definition
|
|
# 'pthread_t',
|
|
# Types marked MT-Unsafe
|
|
'const:env',
|
|
'const:hostid',
|
|
'const:mallopt',
|
|
'const:sigintr',
|
|
'race:LogMask',
|
|
'race:asctime',
|
|
'race:crypt',
|
|
'race:crypt_gensalt',
|
|
'race:cuserid/!string',
|
|
'race:dirstream',
|
|
'race:drand48',
|
|
'race:ecvt',
|
|
'race:exit',
|
|
'race:fcvt',
|
|
'race:fgetgrent',
|
|
'race:fgetpwent',
|
|
'race:fgetspent',
|
|
'race:fsent',
|
|
'race:getdate',
|
|
'race:getlogin',
|
|
'race:getopt',
|
|
'race:getspent',
|
|
'race:getspnam',
|
|
'race:grent',
|
|
'race:grgid',
|
|
'race:grnam',
|
|
'race:hostbyaddr',
|
|
'race:hostbyname',
|
|
'race:hostbyname2',
|
|
'race:hostent',
|
|
'race:hsearch',
|
|
'race:l64a',
|
|
'race:localeconv',
|
|
'race:mbrlen/!ps',
|
|
'race:mbrtowc/!ps',
|
|
'race:mbsnrtowcs/!ps',
|
|
'race:mbsrtowcs/!ps',
|
|
'race:mcheck',
|
|
'race:mntentbuf',
|
|
'race:netbyaddr',
|
|
'race:netbyname',
|
|
'race:netent',
|
|
'race:netgrent',
|
|
'race:protobyname',
|
|
'race:protobynumber',
|
|
'race:protoent',
|
|
'race:ptsname',
|
|
'race:pwent',
|
|
'race:pwnam',
|
|
'race:pwuid',
|
|
'race:qecvt',
|
|
'race:qfcvt',
|
|
'race:servbyname',
|
|
'race:servbyport',
|
|
'race:servent',
|
|
'race:sgetspent',
|
|
'race:signgam',
|
|
'race:stdin',
|
|
'race:stdout',
|
|
'race:streams',
|
|
'race:strerror',
|
|
'race:strsignal',
|
|
'race:strtok',
|
|
'race:tmbuf',
|
|
'race:tmpnam/!s',
|
|
'race:ttyent',
|
|
'race:ttyname',
|
|
'race:utent',
|
|
'race:wcrtomb/!ps',
|
|
'race:wcsnrtombs/!ps',
|
|
'race:wcsrtombs/!ps',
|
|
'sig:ALRM',
|
|
'sig:SIGCHLD/linux',
|
|
# APIs marked MT-Unsafe
|
|
'asctime',
|
|
'clearenv',
|
|
'ctime',
|
|
'cuserid',
|
|
'drand48',
|
|
'ecvt',
|
|
'encrypt',
|
|
'endfsent',
|
|
'endgrent',
|
|
'endhostent',
|
|
'endnetent',
|
|
'endnetgrent',
|
|
'endprotoent',
|
|
'endpwent',
|
|
'endservent',
|
|
'endspent',
|
|
'endttyent',
|
|
'endusershell',
|
|
'endutent',
|
|
'erand48',
|
|
'error_at_line',
|
|
'ether_aton',
|
|
'ether_ntoa',
|
|
'exit',
|
|
'fcloseall',
|
|
'fcvt',
|
|
'fgetgrent',
|
|
'fgetpwent',
|
|
'fgetspent',
|
|
'fts_children',
|
|
'fts_read',
|
|
'gamma',
|
|
'gammaf',
|
|
'gammal',
|
|
'getaliasbyname',
|
|
'getaliasent',
|
|
'getchar_unlocked',
|
|
'getdate',
|
|
'getfsent',
|
|
'getfsfile',
|
|
'getfsspec',
|
|
'getgrent',
|
|
'getgrent_r',
|
|
'getgrgid',
|
|
'getgrnam',
|
|
'gethostbyaddr',
|
|
'gethostbyname',
|
|
'gethostbyname2',
|
|
'gethostent',
|
|
'gethostent_r',
|
|
'getlogin',
|
|
'getlogin_r',
|
|
'getmntent',
|
|
'getnetbyaddr',
|
|
'getnetbyname',
|
|
'getnetent',
|
|
'getnetgrent',
|
|
'getnetgrent_r',
|
|
'getopt',
|
|
'getopt_long',
|
|
'getopt_long_only',
|
|
'getpass',
|
|
'getprotobyname',
|
|
'getprotobynumber',
|
|
'getprotoent',
|
|
'getpwent',
|
|
'getpwent_r',
|
|
'getpwnam',
|
|
'getpwuid',
|
|
'getrpcbyname',
|
|
'getrpcbynumber',
|
|
'getrpcent',
|
|
'getservbyname',
|
|
'getservbyport',
|
|
'getservent',
|
|
'getspent',
|
|
'getspent_r',
|
|
'getspnam',
|
|
'getttyent',
|
|
'getttynam',
|
|
'getusershell',
|
|
'getutent',
|
|
'getutid',
|
|
'getutline',
|
|
'getwchar_unlocked',
|
|
'glob',
|
|
'gmtime',
|
|
'hcreate',
|
|
'hdestroy',
|
|
'hsearch',
|
|
'innetgr',
|
|
'jrand48',
|
|
'l64a',
|
|
'lcong48',
|
|
'localeconv',
|
|
'localtime',
|
|
'login',
|
|
'login_tty',
|
|
'logout',
|
|
'logwtmp',
|
|
'lrand48',
|
|
'mallinfo',
|
|
'mallinfo2',
|
|
'mblen',
|
|
'mbrlen',
|
|
'mbrtowc',
|
|
'mbsnrtowcs',
|
|
'mbsrtowcs',
|
|
'mbtowc',
|
|
'mcheck',
|
|
'mcheck_check_all',
|
|
'mcheck_pedantic',
|
|
'mprobe',
|
|
'mrand48',
|
|
'mtrace',
|
|
'muntrace',
|
|
'nrand48',
|
|
'profil',
|
|
'ptsname',
|
|
'putchar_unlocked',
|
|
'putenv',
|
|
'pututline',
|
|
'putwchar_unlocked',
|
|
'pvalloc',
|
|
'qecvt',
|
|
'qfcvt',
|
|
'rcmd',
|
|
'rcmd_af',
|
|
're_comp',
|
|
're_exec',
|
|
'readdir',
|
|
'rexec',
|
|
'rexec_af',
|
|
'seed48',
|
|
'setenv',
|
|
'setfsent',
|
|
'setgrent',
|
|
'sethostent',
|
|
'sethostid',
|
|
'setkey',
|
|
'setlogmask',
|
|
'setnetent',
|
|
'setnetgrent',
|
|
'setprotoent',
|
|
'setpwent',
|
|
'setservent',
|
|
'setspent',
|
|
'setttyent',
|
|
'setusershell',
|
|
'setutent',
|
|
'sgetspent',
|
|
'siginterrupt',
|
|
'sleep',
|
|
'srand48',
|
|
'strerror',
|
|
'strsignal',
|
|
'strtok',
|
|
'tmpnam',
|
|
'ttyname',
|
|
'ttyslot',
|
|
'unsetenv',
|
|
'updwtmp',
|
|
'utmpname',
|
|
'valloc',
|
|
'wcrtomb',
|
|
'wcsnrtombs',
|
|
'wcsrtombs',
|
|
'wctomb',
|
|
'wordexp'
|
|
}
|
|
|
|
# From man 7 attributes
|
|
# the full token could be feature:function/condition - we just want function.
|
|
id_MTunsafe = [re.sub('^.*:', '', re.sub('/.*$', '', x))
|
|
for x in id_MTunsafe_full
|
|
]
|
|
|
|
|
|
def reportError(token, severity, msg, errid): # noqa: D103
|
|
cppcheckdata.reportError(token, severity, msg, 'threadsafety', errid)
|
|
|
|
|
|
def checkstatic(data): # noqa: D103
|
|
for var in data.variables:
|
|
if var.isStatic and var.isLocal:
|
|
vartype = None
|
|
if var.isClass:
|
|
vartype = 'object'
|
|
else:
|
|
vartype = 'variable'
|
|
if var.isConst:
|
|
if data.standards.cpp == 'c++03':
|
|
reportError(
|
|
var.typeStartToken,
|
|
'warning',
|
|
'Local constant static '
|
|
+ vartype + "'" + var.nameToken.str
|
|
+ "', dangerous if it is initialized"
|
|
+ ' in parallel threads',
|
|
'threadsafety-const')
|
|
else:
|
|
reportError(var.typeStartToken, 'warning',
|
|
'Local static ' + vartype + ': '
|
|
+ var.nameToken.str,
|
|
'threadsafety')
|
|
|
|
|
|
def check_MTunsafe(cfg):
|
|
"""
|
|
Look for functions marked MT-unsafe in their man pages.
|
|
|
|
The MT-unsafe functions are listed in id_MTunsafe (and id_MTunsafe_full).
|
|
That section of code can be regenerated by the external script MT-Unsafe.py
|
|
"""
|
|
for token in cfg.tokenlist:
|
|
if token.str in id_MTunsafe:
|
|
reportError(token, 'warning', token.str + ' is MT-unsafe',
|
|
'unsafe-call')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = cppcheckdata.ArgumentParser()
|
|
args = parser.parse_args()
|
|
|
|
quiet = args.quiet or args.cli
|
|
|
|
if not args.dumpfile:
|
|
if not args.quiet:
|
|
print('no input files.')
|
|
sys.exit(0)
|
|
|
|
for dumpfile in args.dumpfile:
|
|
# load XML from .dump file
|
|
data = cppcheckdata.CppcheckData(dumpfile)
|
|
|
|
for cfg in data.iterconfigurations():
|
|
if not args.quiet:
|
|
srcfile = data.files[0]
|
|
print('Checking %s, config %s...' % (srcfile, cfg.name))
|
|
check_MTunsafe(cfg)
|
|
checkstatic(cfg)
|
|
|
|
sys.exit(cppcheckdata.EXIT_CODE)
|