2019-10-23 09:12:15 +02:00
|
|
|
# Donate CPU client library
|
|
|
|
|
|
|
|
import shutil
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import socket
|
|
|
|
import time
|
|
|
|
import re
|
2020-02-25 11:49:56 +01:00
|
|
|
import signal
|
2019-10-23 09:12:15 +02:00
|
|
|
import tarfile
|
2020-01-25 15:20:06 +01:00
|
|
|
import shlex
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
# Version scheme (MAJOR.MINOR.PATCH) should orientate on "Semantic Versioning" https://semver.org/
|
|
|
|
# Every change in this script should result in increasing the version number accordingly (exceptions may be cosmetic
|
|
|
|
# changes)
|
2022-08-21 17:08:58 +02:00
|
|
|
CLIENT_VERSION = "1.3.31"
|
2020-01-24 12:33:51 +01:00
|
|
|
|
|
|
|
# Timeout for analysis with Cppcheck in seconds
|
2021-02-20 13:44:14 +01:00
|
|
|
CPPCHECK_TIMEOUT = 30 * 60
|
2020-01-24 12:33:51 +01:00
|
|
|
|
2021-12-19 18:52:36 +01:00
|
|
|
CPPCHECK_REPO_URL = "https://github.com/danmar/cppcheck.git"
|
|
|
|
|
2020-01-24 12:33:51 +01:00
|
|
|
# Return code that is used to mark a timed out analysis
|
|
|
|
RETURN_CODE_TIMEOUT = -999
|
2019-10-23 09:12:15 +02:00
|
|
|
|
2022-08-21 17:08:58 +02:00
|
|
|
__make_cmd = None
|
|
|
|
|
|
|
|
def detect_make():
|
|
|
|
make_cmds = ['make', 'mingw32-make']
|
|
|
|
if sys.platform == 'win32':
|
|
|
|
make_cmds.append('msbuild.exe')
|
|
|
|
|
|
|
|
for m in make_cmds:
|
|
|
|
try:
|
|
|
|
print('{} --version'.format(m))
|
|
|
|
subprocess.call([m, '--version'])
|
|
|
|
except OSError as e:
|
|
|
|
print("'{}' not found ({})".format(m, e))
|
|
|
|
continue
|
|
|
|
|
|
|
|
print("using '{}'".format(m))
|
|
|
|
return m
|
|
|
|
|
|
|
|
print("Error: a make command ({}) is required".format(','.join(make_cmds)))
|
|
|
|
return None
|
|
|
|
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
def check_requirements():
|
|
|
|
result = True
|
2022-08-21 17:08:58 +02:00
|
|
|
|
|
|
|
global __make_cmd
|
|
|
|
__make_cmd = detect_make()
|
|
|
|
if __make_cmd is None:
|
|
|
|
result = False
|
|
|
|
|
|
|
|
apps = ['git', 'wget']
|
|
|
|
if __make_cmd in ['make', 'mingw32-make']:
|
|
|
|
apps.append('g++')
|
|
|
|
apps.append('gdb')
|
|
|
|
|
|
|
|
for app in apps:
|
2019-10-23 09:12:15 +02:00
|
|
|
try:
|
2022-08-21 17:08:58 +02:00
|
|
|
print('{} --version'.format(app))
|
2019-10-23 09:12:15 +02:00
|
|
|
subprocess.call([app, '--version'])
|
|
|
|
except OSError:
|
2021-03-06 12:34:12 +01:00
|
|
|
print("Error: '{}' is required".format(app))
|
2019-10-23 09:12:15 +02:00
|
|
|
result = False
|
2022-08-21 17:08:58 +02:00
|
|
|
|
2022-01-02 22:16:00 +01:00
|
|
|
try:
|
|
|
|
import psutil
|
|
|
|
except ImportError as e:
|
2022-03-19 20:06:11 +01:00
|
|
|
print("Error: {}. Module is required.".format(e))
|
2022-01-02 22:16:00 +01:00
|
|
|
result = False
|
2022-08-21 17:08:58 +02:00
|
|
|
|
2019-10-23 09:12:15 +02:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
2021-12-19 18:52:36 +01:00
|
|
|
# Try and retry with exponential backoff if an exception is raised
|
|
|
|
def try_retry(fun, fargs=(), max_tries=5):
|
|
|
|
sleep_duration = 5.0
|
|
|
|
for i in range(max_tries):
|
2019-10-23 09:12:15 +02:00
|
|
|
try:
|
2021-12-19 18:52:36 +01:00
|
|
|
return fun(*fargs)
|
|
|
|
except KeyboardInterrupt as e:
|
|
|
|
# Do not retry in case of user abort
|
|
|
|
raise e
|
|
|
|
except BaseException as e:
|
|
|
|
if i < max_tries - 1:
|
|
|
|
print("{} in {}: {}".format(type(e).__name__, fun.__name__, str(e)))
|
|
|
|
print("Trying {} again in {} seconds".format(fun.__name__, sleep_duration))
|
|
|
|
time.sleep(sleep_duration)
|
|
|
|
sleep_duration *= 2.0
|
|
|
|
else:
|
|
|
|
print("Maximum number of tries reached for {}".format(fun.__name__))
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
|
|
|
def clone_cppcheck(repo_path, migrate_from_path):
|
|
|
|
repo_git_dir = os.path.join(repo_path, '.git')
|
|
|
|
if os.path.exists(repo_git_dir):
|
|
|
|
return
|
|
|
|
# Attempt to migrate clone directory used prior to 1.3.17
|
|
|
|
if os.path.exists(migrate_from_path):
|
|
|
|
os.rename(migrate_from_path, repo_path)
|
|
|
|
else:
|
|
|
|
# A shallow git clone (depth = 1) is enough for building and scanning.
|
2022-03-19 20:06:11 +01:00
|
|
|
# Do not checkout until fetch_cppcheck_version.
|
2021-12-19 18:52:36 +01:00
|
|
|
subprocess.check_call(['git', 'clone', '--depth=1', '--no-checkout', CPPCHECK_REPO_URL, repo_path])
|
|
|
|
# Checkout an empty branch to allow "git worktree add" for main later on
|
|
|
|
try:
|
|
|
|
# git >= 2.27
|
|
|
|
subprocess.check_call(['git', 'switch', '--orphan', 'empty'], cwd=repo_path)
|
|
|
|
except subprocess.CalledProcessError:
|
2022-03-19 20:06:11 +01:00
|
|
|
subprocess.check_call(['git', 'checkout', '--orphan', 'empty'], cwd=repo_path)
|
2021-12-19 18:52:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
def checkout_cppcheck_version(repo_path, version, cppcheck_path):
|
|
|
|
if not os.path.isabs(cppcheck_path):
|
|
|
|
raise ValueError("cppcheck_path is not an absolute path")
|
|
|
|
if os.path.exists(cppcheck_path):
|
2022-03-21 18:46:19 +01:00
|
|
|
print('Checking out {}'.format(version))
|
2022-03-19 20:06:11 +01:00
|
|
|
subprocess.check_call(['git', 'checkout', '-f', version], cwd=cppcheck_path)
|
2022-03-21 18:46:19 +01:00
|
|
|
|
2021-12-19 18:52:36 +01:00
|
|
|
# It is possible to pull branches, not tags
|
2022-03-21 18:46:19 +01:00
|
|
|
if version != 'main':
|
|
|
|
return
|
|
|
|
|
|
|
|
print('Pulling {}'.format(version))
|
|
|
|
subprocess.check_call(['git', 'pull'], cwd=cppcheck_path)
|
2021-12-19 18:52:36 +01:00
|
|
|
else:
|
|
|
|
if version != 'main':
|
2022-03-21 18:46:19 +01:00
|
|
|
print('Fetching {}'.format(version))
|
2021-12-19 18:52:36 +01:00
|
|
|
# Since this is a shallow clone, explicitly fetch the remote version tag
|
|
|
|
refspec = 'refs/tags/' + version + ':ref/tags/' + version
|
|
|
|
subprocess.check_call(['git', 'fetch', '--depth=1', 'origin', refspec], cwd=repo_path)
|
2022-03-21 18:46:19 +01:00
|
|
|
print('Adding worktree \'{}\' for {}'.format(cppcheck_path, version))
|
2021-12-19 18:52:36 +01:00
|
|
|
subprocess.check_call(['git', 'worktree', 'add', cppcheck_path, version], cwd=repo_path)
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
def get_cppcheck_info(cppcheck_path):
|
|
|
|
try:
|
2022-07-13 21:09:29 +02:00
|
|
|
return subprocess.check_output(['git', 'show', "--pretty=%h (%ci)", 'HEAD', '--no-patch', '--no-notes'], cwd=cppcheck_path).decode('utf-8').strip()
|
2019-10-23 09:12:15 +02:00
|
|
|
except:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
|
2021-12-19 18:52:36 +01:00
|
|
|
def compile_version(cppcheck_path, jobs):
|
2022-08-21 17:08:58 +02:00
|
|
|
if __make_cmd == "msbuild.exe":
|
|
|
|
if os.path.isfile(os.path.join(cppcheck_path, 'bin', 'cppcheck.exe')):
|
|
|
|
return True
|
|
|
|
elif __make_cmd == 'mingw32-make':
|
|
|
|
if os.path.isfile(os.path.join(cppcheck_path, 'cppcheck.exe')):
|
|
|
|
return True
|
|
|
|
elif os.path.isfile(os.path.join(cppcheck_path, 'cppcheck')):
|
2019-10-23 09:12:15 +02:00
|
|
|
return True
|
2021-12-19 18:52:36 +01:00
|
|
|
# Build
|
|
|
|
ret = compile_cppcheck(cppcheck_path, jobs)
|
|
|
|
# Clean intermediate build files
|
2022-08-21 17:08:58 +02:00
|
|
|
if __make_cmd == "msbuild.exe":
|
|
|
|
exclude_bin = 'bin'
|
|
|
|
elif __make_cmd == 'mingw32-make':
|
|
|
|
exclude_bin = 'cppcheck.exe'
|
|
|
|
else:
|
|
|
|
exclude_bin = 'cppcheck'
|
|
|
|
# TODO: how to support multiple compiler on the same machine? this will clean msbuild.exe files in a mingw32-make build and vice versa
|
|
|
|
subprocess.call(['git', 'clean', '-f', '-d', '-x', '--exclude', exclude_bin], cwd=cppcheck_path)
|
2021-12-19 18:52:36 +01:00
|
|
|
return ret
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
|
2020-01-21 14:27:39 +01:00
|
|
|
def compile_cppcheck(cppcheck_path, jobs):
|
2021-12-19 18:52:36 +01:00
|
|
|
print('Compiling {}'.format(os.path.basename(cppcheck_path)))
|
2019-10-23 09:12:15 +02:00
|
|
|
try:
|
2022-08-21 17:08:58 +02:00
|
|
|
if __make_cmd == 'msbuild.exe':
|
|
|
|
# TODO: run matchcompiler
|
|
|
|
# TODO: always uses all available threads
|
|
|
|
subprocess.check_call([__make_cmd, jobs.replace('j', 'm:', 1), '-t:cli', os.path.join(cppcheck_path, 'cppcheck.sln'), '/property:Configuration=Release;Platform=x64'], cwd=cppcheck_path)
|
|
|
|
subprocess.check_call([os.path.join(cppcheck_path, 'bin', 'cppcheck.exe'), '--version'], cwd=os.path.join(cppcheck_path, 'bin'))
|
2022-04-10 22:47:27 +02:00
|
|
|
else:
|
2022-08-21 17:08:58 +02:00
|
|
|
rdynamic = ''
|
|
|
|
build_env = os.environ
|
|
|
|
if __make_cmd == 'mingw32-make':
|
|
|
|
# TODO: MinGW will always link even if no changes are present
|
|
|
|
# assume Python is in PATH for now
|
|
|
|
build_env['PYTHON_INTERPRETER'] = 'python3'
|
|
|
|
# TODO: MinGW is not detected by Makefile - so work around it for now
|
|
|
|
rdynamic = 'RDYNAMIC=-lshlwapi'
|
|
|
|
subprocess.check_call([__make_cmd, jobs, 'MATCHCOMPILER=yes', 'CXXFLAGS=-O2 -g -w', rdynamic], cwd=cppcheck_path, env=build_env)
|
2022-04-10 22:47:27 +02:00
|
|
|
subprocess.check_call([os.path.join(cppcheck_path, 'cppcheck'), '--version'], cwd=cppcheck_path)
|
2022-03-19 20:06:11 +01:00
|
|
|
except:
|
2019-10-23 09:12:15 +02:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def get_cppcheck_versions(server_address):
|
|
|
|
print('Connecting to server to get Cppcheck versions..')
|
|
|
|
try:
|
2020-02-03 10:27:32 +01:00
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
|
|
sock.connect(server_address)
|
|
|
|
sock.send(b'GetCppcheckVersions\n')
|
|
|
|
versions = sock.recv(256)
|
2019-10-23 09:12:15 +02:00
|
|
|
except socket.error as err:
|
|
|
|
print('Failed to get cppcheck versions: ' + str(err))
|
|
|
|
return None
|
|
|
|
return versions.decode('utf-8').split()
|
|
|
|
|
|
|
|
|
|
|
|
def get_packages_count(server_address):
|
|
|
|
print('Connecting to server to get count of packages..')
|
|
|
|
try:
|
2020-02-03 10:27:32 +01:00
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
|
|
sock.connect(server_address)
|
|
|
|
sock.send(b'getPackagesCount\n')
|
|
|
|
packages = int(sock.recv(64))
|
2019-10-23 09:12:15 +02:00
|
|
|
except socket.error as err:
|
|
|
|
print('Failed to get count of packages: ' + str(err))
|
|
|
|
return None
|
|
|
|
return packages
|
|
|
|
|
|
|
|
|
2020-01-21 14:27:39 +01:00
|
|
|
def get_package(server_address, package_index=None):
|
2019-12-12 15:04:25 +01:00
|
|
|
package = b''
|
2020-02-02 18:00:36 +01:00
|
|
|
while not package:
|
|
|
|
print('Connecting to server to get assigned work..')
|
|
|
|
try:
|
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
|
|
sock.connect(server_address)
|
|
|
|
if package_index is None:
|
|
|
|
sock.send(b'get\n')
|
|
|
|
else:
|
|
|
|
request = 'getPackageIdx:' + str(package_index) + '\n'
|
|
|
|
sock.send(request.encode())
|
|
|
|
package = sock.recv(256)
|
|
|
|
except socket.error:
|
|
|
|
print("network or server might be temporarily down.. will try again in 30 seconds..")
|
|
|
|
time.sleep(30)
|
2019-10-23 09:12:15 +02:00
|
|
|
return package.decode('utf-8')
|
|
|
|
|
|
|
|
|
2022-03-19 20:06:11 +01:00
|
|
|
def __handle_remove_readonly(func, path, exc):
|
2019-10-23 09:12:15 +02:00
|
|
|
import stat
|
|
|
|
if not os.access(path, os.W_OK):
|
|
|
|
# Is the error an access error ?
|
|
|
|
os.chmod(path, stat.S_IWUSR)
|
|
|
|
func(path)
|
|
|
|
|
|
|
|
|
2022-03-19 20:06:11 +01:00
|
|
|
def __remove_tree(folder_name):
|
2021-10-09 14:51:24 +02:00
|
|
|
if not os.path.exists(folder_name):
|
2019-10-23 09:12:15 +02:00
|
|
|
return
|
|
|
|
count = 5
|
|
|
|
while count > 0:
|
|
|
|
count -= 1
|
|
|
|
try:
|
2022-03-19 20:06:11 +01:00
|
|
|
shutil.rmtree(folder_name, onerror=__handle_remove_readonly)
|
2019-10-23 09:12:15 +02:00
|
|
|
break
|
|
|
|
except OSError as err:
|
|
|
|
time.sleep(30)
|
|
|
|
if count == 0:
|
2021-10-09 14:51:24 +02:00
|
|
|
print('Failed to cleanup {}: {}'.format(folder_name, err))
|
2019-10-23 09:12:15 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
2022-03-19 20:06:11 +01:00
|
|
|
def __wget(url, destfile, bandwidth_limit):
|
2019-10-23 09:12:15 +02:00
|
|
|
if os.path.exists(destfile):
|
|
|
|
if os.path.isfile(destfile):
|
|
|
|
os.remove(destfile)
|
|
|
|
else:
|
|
|
|
print('Error: ' + destfile + ' exists but it is not a file! Please check the path and delete it manually.')
|
|
|
|
sys.exit(1)
|
|
|
|
wget_call = ['wget', '--tries=10', '--timeout=300', '-O', destfile, url]
|
|
|
|
if bandwidth_limit and isinstance(bandwidth_limit, str):
|
|
|
|
wget_call.append('--limit-rate=' + bandwidth_limit)
|
|
|
|
exitcode = subprocess.call(wget_call)
|
|
|
|
if exitcode != 0:
|
|
|
|
print('wget failed with ' + str(exitcode))
|
|
|
|
os.remove(destfile)
|
|
|
|
return False
|
|
|
|
if not os.path.isfile(destfile):
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def download_package(work_path, package, bandwidth_limit):
|
|
|
|
print('Download package ' + package)
|
2022-03-21 18:46:19 +01:00
|
|
|
destfile = os.path.join(work_path, 'temp.tgz')
|
2022-03-19 20:06:11 +01:00
|
|
|
if not __wget(package, destfile, bandwidth_limit):
|
2019-10-23 09:12:15 +02:00
|
|
|
return None
|
|
|
|
return destfile
|
|
|
|
|
|
|
|
|
2022-05-20 23:20:16 +02:00
|
|
|
def unpack_package(work_path, tgz, cpp_only=False, skip_files=None):
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Unpacking..')
|
2022-03-21 18:46:19 +01:00
|
|
|
temp_path = os.path.join(work_path, 'temp')
|
2022-03-19 20:06:11 +01:00
|
|
|
__remove_tree(temp_path)
|
2019-10-23 09:12:15 +02:00
|
|
|
os.mkdir(temp_path)
|
2022-07-13 21:09:29 +02:00
|
|
|
|
|
|
|
header_endings = ('.hpp', '.h++', '.hxx', '.hh', '.h')
|
|
|
|
source_endings = ('.cpp', '.c++', '.cxx', '.cc', '.tpp', '.txx', '.ipp', '.ixx', '.qml')
|
|
|
|
c_source_endings = ('.c',)
|
|
|
|
|
|
|
|
if not cpp_only:
|
|
|
|
source_endings = source_endings + c_source_endings
|
|
|
|
|
|
|
|
source_found = False
|
2019-10-23 09:12:15 +02:00
|
|
|
if tarfile.is_tarfile(tgz):
|
2020-02-03 10:27:32 +01:00
|
|
|
with tarfile.open(tgz) as tf:
|
2022-07-13 21:09:29 +02:00
|
|
|
total = 0
|
|
|
|
extracted = 0
|
|
|
|
skipped = 0
|
2020-02-03 10:27:32 +01:00
|
|
|
for member in tf:
|
2022-07-13 21:09:29 +02:00
|
|
|
total += 1
|
2020-02-03 10:27:32 +01:00
|
|
|
if member.name.startswith(('/', '..')):
|
|
|
|
# Skip dangerous file names
|
2022-07-13 21:09:29 +02:00
|
|
|
# print('skipping dangerous file: ' + member.name)
|
|
|
|
skipped += 1
|
2020-02-03 10:27:32 +01:00
|
|
|
continue
|
2022-07-13 21:09:29 +02:00
|
|
|
|
|
|
|
is_source = member.name.lower().endswith(source_endings)
|
|
|
|
if is_source or member.name.lower().endswith(header_endings):
|
2022-05-20 23:20:16 +02:00
|
|
|
if skip_files is not None:
|
|
|
|
skip = False
|
|
|
|
for skip_file in skip_files:
|
|
|
|
if member.name.endswith('/' + skip_file):
|
2022-07-13 21:09:29 +02:00
|
|
|
# print('found file to skip: ' + member.name)
|
2022-05-20 23:20:16 +02:00
|
|
|
skip = True
|
|
|
|
break
|
|
|
|
if skip:
|
2022-07-13 21:09:29 +02:00
|
|
|
skipped += 1
|
2022-05-20 23:20:16 +02:00
|
|
|
continue
|
2020-02-03 10:27:32 +01:00
|
|
|
try:
|
2021-10-09 14:51:24 +02:00
|
|
|
tf.extract(member.name, temp_path)
|
2022-07-13 21:09:29 +02:00
|
|
|
if is_source:
|
|
|
|
source_found = True
|
|
|
|
extracted += 1
|
2020-02-03 10:27:32 +01:00
|
|
|
except OSError:
|
|
|
|
pass
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
2022-07-13 21:09:29 +02:00
|
|
|
print('extracted {} of {} files (skipped {}{})'.format(extracted, total, skipped, (' / only headers' if (extracted and not source_found) else '')))
|
|
|
|
return temp_path, source_found
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
|
2022-03-19 20:06:11 +01:00
|
|
|
def __run_command(cmd, print_cmd=True):
|
|
|
|
if print_cmd:
|
|
|
|
print(cmd)
|
2021-10-09 14:51:24 +02:00
|
|
|
start_time = time.time()
|
2021-02-27 03:19:08 +01:00
|
|
|
comm = None
|
2022-04-10 22:47:27 +02:00
|
|
|
if sys.platform == 'win32':
|
|
|
|
p = subprocess.Popen(shlex.split(cmd, comments=False, posix=False), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
else:
|
|
|
|
p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
|
2020-01-24 12:33:51 +01:00
|
|
|
try:
|
|
|
|
comm = p.communicate(timeout=CPPCHECK_TIMEOUT)
|
|
|
|
return_code = p.returncode
|
2021-02-27 03:19:08 +01:00
|
|
|
p = None
|
2020-01-24 12:33:51 +01:00
|
|
|
except subprocess.TimeoutExpired:
|
2021-10-09 14:51:24 +02:00
|
|
|
import psutil
|
2020-01-24 12:33:51 +01:00
|
|
|
return_code = RETURN_CODE_TIMEOUT
|
2021-02-27 03:19:08 +01:00
|
|
|
# terminate all the child processes so we get messages about which files were hanging
|
|
|
|
child_procs = psutil.Process(p.pid).children(recursive=True)
|
|
|
|
if len(child_procs) > 0:
|
|
|
|
for child in child_procs:
|
|
|
|
child.terminate()
|
2021-03-01 22:17:00 +01:00
|
|
|
try:
|
|
|
|
# call with timeout since it might get stuck e.g. gcc-arm-none-eabi
|
|
|
|
comm = p.communicate(timeout=5)
|
2021-03-02 10:05:49 +01:00
|
|
|
p = None
|
2021-03-01 22:17:00 +01:00
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
pass
|
2021-02-27 03:19:08 +01:00
|
|
|
finally:
|
|
|
|
if p:
|
|
|
|
os.killpg(os.getpgid(p.pid), signal.SIGTERM) # Send the signal to all the process groups
|
|
|
|
comm = p.communicate()
|
2019-10-23 09:12:15 +02:00
|
|
|
stop_time = time.time()
|
|
|
|
stdout = comm[0].decode(encoding='utf-8', errors='ignore')
|
|
|
|
stderr = comm[1].decode(encoding='utf-8', errors='ignore')
|
2021-10-09 14:51:24 +02:00
|
|
|
elapsed_time = stop_time - start_time
|
2020-01-24 12:33:51 +01:00
|
|
|
return return_code, stdout, stderr, elapsed_time
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
|
2022-07-13 21:09:29 +02:00
|
|
|
def scan_package(cppcheck_path, source_path, jobs, libraries, capture_callstack=True):
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Analyze..')
|
|
|
|
libs = ''
|
|
|
|
for library in libraries:
|
|
|
|
if os.path.exists(os.path.join(cppcheck_path, 'cfg', library + '.cfg')):
|
2019-11-12 15:34:04 +01:00
|
|
|
libs += '--library=' + library + ' '
|
2019-10-23 09:12:15 +02:00
|
|
|
|
2022-07-13 21:09:29 +02:00
|
|
|
dir_to_scan = source_path
|
2021-10-09 14:51:24 +02:00
|
|
|
|
2019-10-23 09:12:15 +02:00
|
|
|
# Reference for GNU C: https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
|
2022-06-21 16:18:12 +02:00
|
|
|
options = libs + ' --showtime=top5 --check-library --inconclusive --enable=style,information --inline-suppr --template=daca2'
|
2021-10-09 14:51:24 +02:00
|
|
|
options += ' -D__GNUC__ --platform=unix64'
|
2022-07-19 07:52:23 +02:00
|
|
|
options_rp = options + ' -rp={}'.format(dir_to_scan)
|
2022-08-21 17:08:58 +02:00
|
|
|
if __make_cmd == 'msbuild.exe':
|
2022-07-19 07:52:23 +02:00
|
|
|
cppcheck_cmd = os.path.join(cppcheck_path, 'bin', 'cppcheck.exe') + ' ' + options_rp
|
2022-04-10 22:47:27 +02:00
|
|
|
cmd = cppcheck_cmd + ' ' + jobs + ' ' + dir_to_scan
|
|
|
|
else:
|
2022-08-21 17:08:58 +02:00
|
|
|
nice_cmd = 'nice'
|
|
|
|
if __make_cmd == 'mingw32-make':
|
|
|
|
nice_cmd = ''
|
2022-07-19 07:52:23 +02:00
|
|
|
cppcheck_cmd = os.path.join(cppcheck_path, 'cppcheck') + ' ' + options_rp
|
2022-08-21 17:08:58 +02:00
|
|
|
cmd = nice_cmd + ' ' + cppcheck_cmd + ' ' + jobs + ' ' + dir_to_scan
|
2022-03-19 20:06:11 +01:00
|
|
|
returncode, stdout, stderr, elapsed_time = __run_command(cmd)
|
2021-02-27 03:19:08 +01:00
|
|
|
|
|
|
|
# collect messages
|
|
|
|
information_messages_list = []
|
|
|
|
issue_messages_list = []
|
|
|
|
internal_error_messages_list = []
|
|
|
|
count = 0
|
2022-07-13 21:09:29 +02:00
|
|
|
re_obj = None
|
2021-02-27 03:19:08 +01:00
|
|
|
for line in stderr.split('\n'):
|
|
|
|
if ': information: ' in line:
|
|
|
|
information_messages_list.append(line + '\n')
|
|
|
|
elif line:
|
|
|
|
issue_messages_list.append(line + '\n')
|
2022-07-13 21:09:29 +02:00
|
|
|
if re_obj is None:
|
|
|
|
re_obj = re.compile(r'.*:[0-9]+:.*\]$')
|
|
|
|
if re_obj.match(line):
|
2021-02-27 03:19:08 +01:00
|
|
|
count += 1
|
|
|
|
if ': error: Internal error: ' in line:
|
|
|
|
internal_error_messages_list.append(line + '\n')
|
|
|
|
print('Number of issues: ' + str(count))
|
|
|
|
# Collect timing information
|
|
|
|
stdout_lines = stdout.split('\n')
|
|
|
|
timing_info_list = []
|
|
|
|
overall_time_found = False
|
|
|
|
max_timing_lines = 6
|
|
|
|
current_timing_lines = 0
|
|
|
|
for reverse_line in reversed(stdout_lines):
|
|
|
|
if reverse_line.startswith('Overall time:'):
|
|
|
|
overall_time_found = True
|
|
|
|
if overall_time_found:
|
|
|
|
if not reverse_line or current_timing_lines >= max_timing_lines:
|
|
|
|
break
|
|
|
|
timing_info_list.insert(0, ' ' + reverse_line + '\n')
|
|
|
|
current_timing_lines += 1
|
|
|
|
timing_str = ''.join(timing_info_list)
|
|
|
|
|
|
|
|
# detect errors
|
2021-10-11 19:07:19 +02:00
|
|
|
sig_file = None
|
2019-10-23 09:12:15 +02:00
|
|
|
sig_num = -1
|
2021-10-11 19:07:19 +02:00
|
|
|
for ie_line in internal_error_messages_list:
|
|
|
|
# temp/dlib-19.10/dlib/test/dnn.cpp:0:0: error: Internal error: Child process crashed with signal 11 [cppcheckError]
|
|
|
|
if 'Child process crashed with signal' in ie_line:
|
|
|
|
sig_file = ie_line.split(':')[0]
|
|
|
|
sig_msg = 'signal '
|
2021-10-13 14:51:56 +02:00
|
|
|
sig_pos = ie_line.find(sig_msg)
|
2021-10-11 19:07:19 +02:00
|
|
|
if sig_pos != -1:
|
|
|
|
sig_start_pos = sig_pos + len(sig_msg)
|
|
|
|
sig_num = int(ie_line[sig_start_pos:ie_line.find(' ', sig_start_pos)])
|
|
|
|
# break on the first signalled file for now
|
|
|
|
break
|
2019-10-23 09:12:15 +02:00
|
|
|
print('cppcheck finished with ' + str(returncode) + ('' if sig_num == -1 else ' (signal ' + str(sig_num) + ')'))
|
2021-02-27 03:19:08 +01:00
|
|
|
|
2022-03-21 18:46:19 +01:00
|
|
|
options_j = options + ' ' + jobs
|
|
|
|
|
2020-02-28 08:12:10 +01:00
|
|
|
if returncode == RETURN_CODE_TIMEOUT:
|
|
|
|
print('Timeout!')
|
2022-03-21 18:46:19 +01:00
|
|
|
return returncode, ''.join(internal_error_messages_list), '', elapsed_time, options_j, ''
|
2021-02-27 03:19:08 +01:00
|
|
|
|
2019-10-23 09:12:15 +02:00
|
|
|
# generate stack trace for SIGSEGV, SIGABRT, SIGILL, SIGFPE, SIGBUS
|
2021-10-11 19:07:19 +02:00
|
|
|
has_error = returncode in (-11, -6, -4, -8, -7)
|
|
|
|
has_sig = sig_num in (11, 6, 4, 8, 7)
|
|
|
|
if has_error or has_sig:
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Crash!')
|
2021-10-11 19:07:19 +02:00
|
|
|
# make sure we have the actual error code set
|
|
|
|
if has_sig:
|
|
|
|
returncode = -sig_num
|
2019-10-23 09:12:15 +02:00
|
|
|
stacktrace = ''
|
2022-01-02 22:16:00 +01:00
|
|
|
if capture_callstack:
|
2019-10-23 09:12:15 +02:00
|
|
|
# re-run within gdb to get a stacktrace
|
2021-10-11 19:07:19 +02:00
|
|
|
cmd = 'gdb --batch --eval-command=run --eval-command="bt 50" --return-child-result --args ' + cppcheck_cmd + " -j1 "
|
|
|
|
if sig_file is not None:
|
|
|
|
cmd += sig_file
|
|
|
|
else:
|
|
|
|
cmd += dir_to_scan
|
2022-03-19 20:06:11 +01:00
|
|
|
_, st_stdout, _, _ = __run_command(cmd)
|
2020-12-26 17:59:19 +01:00
|
|
|
gdb_pos = st_stdout.find(" received signal")
|
2019-10-23 09:12:15 +02:00
|
|
|
if not gdb_pos == -1:
|
2020-12-26 17:59:19 +01:00
|
|
|
last_check_pos = st_stdout.rfind('Checking ', 0, gdb_pos)
|
2019-10-23 09:12:15 +02:00
|
|
|
if last_check_pos == -1:
|
2020-12-26 17:59:19 +01:00
|
|
|
stacktrace = st_stdout[gdb_pos:]
|
2019-10-23 09:12:15 +02:00
|
|
|
else:
|
2020-12-26 17:59:19 +01:00
|
|
|
stacktrace = st_stdout[last_check_pos:]
|
2021-10-11 19:07:19 +02:00
|
|
|
# if no stacktrace was generated return the original stdout or internal errors list
|
2020-12-26 17:59:19 +01:00
|
|
|
if not stacktrace:
|
2021-10-11 19:07:19 +02:00
|
|
|
if has_sig:
|
|
|
|
stacktrace = ''.join(internal_error_messages_list)
|
|
|
|
else:
|
|
|
|
stacktrace = stdout
|
2022-03-21 18:46:19 +01:00
|
|
|
return returncode, stacktrace, '', returncode, options_j, ''
|
2021-02-27 03:19:08 +01:00
|
|
|
|
2019-10-23 09:12:15 +02:00
|
|
|
if returncode != 0:
|
2021-10-24 11:02:51 +02:00
|
|
|
# returncode is always 1 when this message is written
|
|
|
|
thr_pos = stderr.find('#### ThreadExecutor')
|
|
|
|
if thr_pos != -1:
|
|
|
|
print('Thread!')
|
2022-03-21 18:46:19 +01:00
|
|
|
return -222, stderr[thr_pos:], '', -222, options_j, ''
|
2021-10-24 11:02:51 +02:00
|
|
|
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Error!')
|
|
|
|
if returncode > 0:
|
|
|
|
returncode = -100-returncode
|
2022-03-21 18:46:19 +01:00
|
|
|
return returncode, stdout, '', returncode, options_j, ''
|
2021-02-27 03:19:08 +01:00
|
|
|
|
2021-03-06 12:34:12 +01:00
|
|
|
if sig_num != -1:
|
2021-10-24 11:02:51 +02:00
|
|
|
print('Signal!')
|
2022-03-21 18:46:19 +01:00
|
|
|
return -sig_num, ''.join(internal_error_messages_list), '', -sig_num, options_j, ''
|
2021-02-27 03:19:08 +01:00
|
|
|
|
2022-03-21 18:46:19 +01:00
|
|
|
return count, ''.join(issue_messages_list), ''.join(information_messages_list), elapsed_time, options_j, timing_str
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
|
2022-03-19 20:06:11 +01:00
|
|
|
def __split_results(results):
|
2019-10-23 09:12:15 +02:00
|
|
|
ret = []
|
|
|
|
w = None
|
2022-07-13 21:09:29 +02:00
|
|
|
re_obj = None
|
2019-10-23 09:12:15 +02:00
|
|
|
for line in results.split('\n'):
|
2022-07-13 21:09:29 +02:00
|
|
|
if line.endswith(']'):
|
|
|
|
if re_obj is None:
|
|
|
|
re_obj = re.compile(r': (error|warning|style|performance|portability|information|debug):')
|
|
|
|
if re_obj.search(line):
|
|
|
|
if w is not None:
|
|
|
|
ret.append(w.strip())
|
|
|
|
w = ''
|
2019-10-23 09:12:15 +02:00
|
|
|
if w is not None:
|
|
|
|
w += ' ' * 5 + line + '\n'
|
|
|
|
if w is not None:
|
|
|
|
ret.append(w.strip())
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
2021-03-06 12:34:12 +01:00
|
|
|
def diff_results(ver1, results1, ver2, results2):
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Diff results..')
|
|
|
|
ret = ''
|
2022-03-19 20:06:11 +01:00
|
|
|
r1 = sorted(__split_results(results1))
|
|
|
|
r2 = sorted(__split_results(results2))
|
2019-10-23 09:12:15 +02:00
|
|
|
i1 = 0
|
|
|
|
i2 = 0
|
|
|
|
while i1 < len(r1) and i2 < len(r2):
|
|
|
|
if r1[i1] == r2[i2]:
|
|
|
|
i1 += 1
|
|
|
|
i2 += 1
|
|
|
|
elif r1[i1] < r2[i2]:
|
|
|
|
ret += ver1 + ' ' + r1[i1] + '\n'
|
|
|
|
i1 += 1
|
|
|
|
else:
|
|
|
|
ret += ver2 + ' ' + r2[i2] + '\n'
|
|
|
|
i2 += 1
|
|
|
|
while i1 < len(r1):
|
|
|
|
ret += ver1 + ' ' + r1[i1] + '\n'
|
|
|
|
i1 += 1
|
|
|
|
while i2 < len(r2):
|
|
|
|
ret += ver2 + ' ' + r2[i2] + '\n'
|
|
|
|
i2 += 1
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
2022-03-19 20:06:11 +01:00
|
|
|
def __send_all(connection, data):
|
2020-01-21 14:27:39 +01:00
|
|
|
bytes_ = data.encode('ascii', 'ignore')
|
|
|
|
while bytes_:
|
|
|
|
num = connection.send(bytes_)
|
|
|
|
if num < len(bytes_):
|
|
|
|
bytes_ = bytes_[num:]
|
2019-10-23 09:12:15 +02:00
|
|
|
else:
|
2020-01-21 14:27:39 +01:00
|
|
|
bytes_ = None
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
def upload_results(package, results, server_address):
|
2022-08-21 17:08:58 +02:00
|
|
|
if not __make_cmd == 'make':
|
|
|
|
print('Error: Information upload not performed - only make build binaries are currently fully supported')
|
|
|
|
return False
|
|
|
|
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Uploading results.. ' + str(len(results)) + ' bytes')
|
|
|
|
max_retries = 4
|
|
|
|
for retry in range(max_retries):
|
|
|
|
try:
|
2020-02-03 10:27:32 +01:00
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
|
|
sock.connect(server_address)
|
|
|
|
cmd = 'write\n'
|
2022-03-19 20:06:11 +01:00
|
|
|
__send_all(sock, cmd + package + '\n' + results + '\nDONE')
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Results have been successfully uploaded.')
|
|
|
|
return True
|
|
|
|
except socket.error as err:
|
|
|
|
print('Upload error: ' + str(err))
|
|
|
|
if retry < (max_retries - 1):
|
|
|
|
print('Retrying upload in 30 seconds')
|
|
|
|
time.sleep(30)
|
|
|
|
print('Upload permanently failed!')
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def upload_info(package, info_output, server_address):
|
2022-08-21 17:08:58 +02:00
|
|
|
if not __make_cmd == 'make':
|
|
|
|
print('Error: Information upload not performed - only make build binaries are currently fully supported')
|
|
|
|
return False
|
|
|
|
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Uploading information output.. ' + str(len(info_output)) + ' bytes')
|
|
|
|
max_retries = 3
|
|
|
|
for retry in range(max_retries):
|
|
|
|
try:
|
2020-02-03 10:27:32 +01:00
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
|
|
sock.connect(server_address)
|
2022-03-19 20:06:11 +01:00
|
|
|
__send_all(sock, 'write_info\n' + package + '\n' + info_output + '\nDONE')
|
2019-10-23 09:12:15 +02:00
|
|
|
print('Information output has been successfully uploaded.')
|
|
|
|
return True
|
|
|
|
except socket.error as err:
|
|
|
|
print('Upload error: ' + str(err))
|
|
|
|
if retry < (max_retries - 1):
|
|
|
|
print('Retrying upload in 30 seconds')
|
|
|
|
time.sleep(30)
|
|
|
|
print('Upload permanently failed!')
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2022-07-13 21:09:29 +02:00
|
|
|
class LibraryIncludes:
|
|
|
|
def __init__(self):
|
|
|
|
include_mappings = {'boost': ['<boost/'],
|
|
|
|
'bsd': ['<sys/queue.h>', '<sys/tree.h>', '<sys/uio.h>','<bsd/', '<fts.h>', '<db.h>', '<err.h>', '<vis.h>'],
|
|
|
|
'cairo': ['<cairo.h>'],
|
|
|
|
'cppunit': ['<cppunit/'],
|
|
|
|
'icu': ['<unicode/', '"unicode/'],
|
|
|
|
'ginac': ['<ginac/', '"ginac/'],
|
|
|
|
'googletest': ['<gtest/gtest.h>'],
|
|
|
|
'gtk': ['<gtk', '<glib.h>', '<glib-', '<glib/', '<gnome'],
|
|
|
|
'kde': ['<KGlobal>', '<KApplication>', '<KDE/'],
|
|
|
|
'libcerror': ['<libcerror.h>'],
|
|
|
|
'libcurl': ['<curl/curl.h>'],
|
|
|
|
'libsigc++': ['<sigc++/'],
|
|
|
|
'lua': ['<lua.h>', '"lua.h"'],
|
|
|
|
'mfc': ['<afx.h>', '<afxwin.h>', '<afxext.h>'],
|
|
|
|
'microsoft_atl': ['<atlbase.h>'],
|
|
|
|
'microsoft_sal': ['<sal.h>'],
|
|
|
|
'motif': ['<X11/', '<Xm/'],
|
|
|
|
'nspr': ['<prtypes.h>', '"prtypes.h"'],
|
|
|
|
'ntl': ['<ntl/', '"ntl/'],
|
|
|
|
'opencv2': ['<opencv2/', '"opencv2/'],
|
|
|
|
'opengl': ['<GL/gl.h>', '<GL/glu.h>', '<GL/glut.h>'],
|
|
|
|
'openmp': ['<omp.h>'],
|
|
|
|
'openssl': ['<openssl/'],
|
|
|
|
'pcre': ['<pcre.h>', '"pcre.h"'],
|
|
|
|
'python': ['<Python.h>', '"Python.h"'],
|
|
|
|
'qt': ['<QApplication>', '<QList>', '<QKeyEvent>', '<qlist.h>', '<QObject>', '<QFlags>', '<QFileDialog>', '<QTest>', '<QMessageBox>', '<QMetaType>', '<QString>', '<qobjectdefs.h>', '<qstring.h>', '<QWidget>', '<QtWidgets>', '<QtGui'],
|
|
|
|
'ruby': ['<ruby.h>', '<ruby/', '"ruby.h"'],
|
|
|
|
'sdl': ['<SDL.h>', '<SDL/SDL.h>', '<SDL2/SDL.h>'],
|
|
|
|
'sqlite3': ['<sqlite3.h>', '"sqlite3.h"'],
|
|
|
|
'tinyxml2': ['<tinyxml2', '"tinyxml2'],
|
|
|
|
'wxsqlite3': ['<wx/wxsqlite3', '"wx/wxsqlite3'],
|
|
|
|
'wxwidgets': ['<wx/', '"wx/'],
|
|
|
|
'zlib': ['<zlib.h>'],
|
|
|
|
}
|
|
|
|
|
|
|
|
self.__library_includes_re = {}
|
|
|
|
|
|
|
|
for library, includes in include_mappings.items():
|
|
|
|
re_includes = [re.escape(inc) for inc in includes]
|
|
|
|
re_expr = '^[ \t]*#[ \t]*include[ \t]*(' + '|'.join(re_includes) + ')'
|
|
|
|
re_obj = re.compile(re_expr, re.MULTILINE)
|
|
|
|
self.__library_includes_re[library] = re_obj
|
|
|
|
|
|
|
|
def __iterate_files(self, path, has_include_cb):
|
|
|
|
for root, _, files in os.walk(path):
|
|
|
|
for name in files:
|
|
|
|
filename = os.path.join(root, name)
|
|
|
|
try:
|
|
|
|
with open(filename, 'rt', errors='ignore') as f:
|
|
|
|
filedata = f.read()
|
|
|
|
has_include_cb(filedata)
|
|
|
|
except IOError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def get_libraries(self, folder):
|
|
|
|
print('Detecting library usage...')
|
|
|
|
libraries = ['posix', 'gnu']
|
|
|
|
|
|
|
|
library_includes_re = self.__library_includes_re
|
|
|
|
|
|
|
|
def has_include(filedata):
|
|
|
|
lib_del = []
|
|
|
|
for library, includes_re in library_includes_re.items():
|
|
|
|
if includes_re.search(filedata):
|
|
|
|
libraries.append(library)
|
|
|
|
lib_del.append(library)
|
|
|
|
|
|
|
|
for lib_d in lib_del:
|
|
|
|
del library_includes_re[lib_d]
|
|
|
|
|
|
|
|
self.__iterate_files(folder, has_include)
|
|
|
|
print('Found libraries: {}'.format(libraries))
|
|
|
|
return libraries
|
2019-10-23 09:12:15 +02:00
|
|
|
|
|
|
|
|
2021-03-06 12:34:12 +01:00
|
|
|
def get_compiler_version():
|
2022-08-21 17:08:58 +02:00
|
|
|
if __make_cmd == 'msbuild.exe':
|
|
|
|
# TODO: shorted version string
|
|
|
|
_, _, stderr, _ = __run_command('cl.exe', False)
|
|
|
|
return stderr.split('\n')[0]
|
|
|
|
|
2022-03-19 20:06:11 +01:00
|
|
|
_, stdout, _, _ = __run_command('g++ --version', False)
|
2021-03-06 12:34:12 +01:00
|
|
|
return stdout.split('\n')[0]
|
|
|
|
|
|
|
|
|
2022-07-18 16:24:04 +02:00
|
|
|
def get_client_version():
|
|
|
|
return CLIENT_VERSION
|
|
|
|
|
|
|
|
|
2019-10-24 21:54:22 +02:00
|
|
|
my_script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
|
2019-10-23 09:12:15 +02:00
|
|
|
jobs = '-j1'
|
|
|
|
stop_time = None
|
2022-07-13 21:09:29 +02:00
|
|
|
work_path = os.path.expanduser(os.path.join('~', 'cppcheck-' + my_script_name + '-workfolder'))
|
2019-10-23 09:12:15 +02:00
|
|
|
package_url = None
|
|
|
|
server_address = ('cppcheck1.osuosl.org', 8000)
|
|
|
|
bandwidth_limit = None
|
|
|
|
max_packages = None
|
|
|
|
do_upload = True
|
2022-07-13 21:09:29 +02:00
|
|
|
library_includes = LibraryIncludes()
|