* reduce.py: Allow reducing error messages, print output in case of error Allow reducing code that triggers (false positive) error messages. Print Cppcheck output in case Cppcheck returns unsuccessfully and no segfault is expected. This helps fixing messed up command lines (for example issues with the path). * Use "else" as suggested
291 lines
7.3 KiB
Python
Executable File
291 lines
7.3 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import subprocess
|
|
import sys
|
|
|
|
def show_syntax():
|
|
print('Syntax:')
|
|
print(' reduce.py --cmd=<full command> --expected=<expected text output> --file=<source file> [--segfault]')
|
|
print('')
|
|
print("Example. source file = foo/bar.c")
|
|
print(" reduce.py --cmd='./cppcheck --enable=style foo/bar.c' --expected=\"Variable 'x' is reassigned\" --file=foo/bar.c")
|
|
sys.exit(1)
|
|
|
|
if len(sys.argv) == 1:
|
|
show_syntax()
|
|
|
|
CMD = None
|
|
EXPECTED = None
|
|
SEGFAULT = False
|
|
FILE = None
|
|
BACKUPFILE = None
|
|
for arg in sys.argv[1:]:
|
|
if arg.startswith('--cmd='):
|
|
CMD = arg[arg.find('=') + 1:]
|
|
elif arg.startswith('--expected='):
|
|
EXPECTED = arg[arg.find('=') + 1:]
|
|
elif arg.startswith('--file='):
|
|
FILE = arg[arg.find('=') + 1:]
|
|
BACKUPFILE = FILE + '.bak'
|
|
elif arg == '--segfault':
|
|
SEGFAULT = True
|
|
|
|
if CMD is None:
|
|
print('Abort: No --cmd')
|
|
show_syntax()
|
|
|
|
if not SEGFAULT and EXPECTED is None:
|
|
print('Abort: No --expected')
|
|
show_syntax()
|
|
|
|
if FILE is None:
|
|
print('Abort: No --file')
|
|
show_syntax()
|
|
|
|
print('CMD=' + CMD)
|
|
if SEGFAULT:
|
|
print('EXPECTED=SEGFAULT')
|
|
else:
|
|
print('EXPECTED=' + EXPECTED)
|
|
print('FILE=' + FILE)
|
|
|
|
|
|
def runtool():
|
|
p = subprocess.Popen(CMD.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
comm = p.communicate()
|
|
if SEGFAULT:
|
|
if p.returncode != 0:
|
|
return True
|
|
elif p.returncode == 0:
|
|
out = comm[0] + '\n' + comm[1]
|
|
if EXPECTED in out:
|
|
return True
|
|
else:
|
|
# Something could be wrong, for example the command line for Cppcheck (CMD).
|
|
# Print the output to give a hint how to fix it.
|
|
print('Error: {}\n{}'.format(comm[0], comm[1]))
|
|
return False
|
|
|
|
|
|
def writefile(filename, filedata):
|
|
f = open(filename, 'wt')
|
|
for line in filedata:
|
|
f.write(line)
|
|
f.close()
|
|
|
|
|
|
def replaceandrun(what, filedata, i, line):
|
|
print(what + ' ' + str(i + 1) + '/' + str(len(filedata)) + '..')
|
|
bak = filedata[i]
|
|
filedata[i] = line
|
|
writefile(FILE, filedata)
|
|
if runtool():
|
|
print('pass')
|
|
writefile(BACKUPFILE, filedata)
|
|
return True
|
|
print('fail')
|
|
filedata[i] = bak
|
|
return False
|
|
|
|
|
|
def replaceandrun2(what, filedata, i, line1, line2):
|
|
print(what + ' ' + str(i + 1) + '/' + str(len(filedata)) + '..')
|
|
bak1 = filedata[i]
|
|
bak2 = filedata[i + 1]
|
|
filedata[i] = line1
|
|
filedata[i + 1] = line2
|
|
writefile(FILE, filedata)
|
|
if runtool():
|
|
print('pass')
|
|
writefile(BACKUPFILE, filedata)
|
|
else:
|
|
print('fail')
|
|
filedata[i] = bak1
|
|
filedata[i + 1] = bak2
|
|
|
|
|
|
def clearandrun(what, filedata, i1, i2):
|
|
print(what + ' ' + str(i1 + 1) + '/' + str(len(filedata)) + '..')
|
|
filedata2 = list(filedata)
|
|
i = i1
|
|
while i <= i2 and i < len(filedata2):
|
|
filedata2[i] = ''
|
|
i = i + 1
|
|
writefile(FILE, filedata2)
|
|
if runtool():
|
|
print('pass')
|
|
writefile(BACKUPFILE, filedata2)
|
|
return filedata2
|
|
print('fail')
|
|
return filedata
|
|
|
|
|
|
def removecomments(filedata):
|
|
for i in range(len(filedata)):
|
|
line = filedata[i]
|
|
if '//' in line:
|
|
replaceandrun('remove comment', filedata, i, line[:line.find('//')].rstrip())
|
|
|
|
|
|
def checkpar(line):
|
|
par = 0
|
|
for c in line:
|
|
if c == '(' or c == '[':
|
|
par = par + 1
|
|
elif c == ')' or c == ']':
|
|
par = par - 1
|
|
if par < 0:
|
|
return False
|
|
return par == 0
|
|
|
|
|
|
def combinelines(filedata):
|
|
if len(filedata) < 3:
|
|
return
|
|
|
|
lines = []
|
|
|
|
for i in range(len(filedata) - 1):
|
|
fd1 = filedata[i].rstrip()
|
|
if fd1.endswith(','):
|
|
fd2 = filedata[i + 1].lstrip()
|
|
if fd2 != '':
|
|
lines.append(i)
|
|
|
|
chunksize = len(lines)
|
|
while chunksize > 10:
|
|
i = 0
|
|
while i < len(lines):
|
|
i1 = i
|
|
i2 = i + chunksize
|
|
i = i2
|
|
if i2 > len(lines):
|
|
i2 = len(lines)
|
|
|
|
filedata2 = list(filedata)
|
|
for line in lines[i1:i2]:
|
|
filedata2[line] = filedata2[line].rstrip() + filedata2[line + 1].lstrip()
|
|
filedata2[line + 1] = ''
|
|
|
|
if replaceandrun('combine lines', filedata2, lines[i1] + 1, ''):
|
|
filedata = filedata2
|
|
lines[i1:i2] = []
|
|
i = i1
|
|
|
|
chunksize = chunksize / 2
|
|
|
|
for line in lines:
|
|
fd1 = filedata[line].rstrip()
|
|
fd2 = filedata[line + 1].lstrip()
|
|
replaceandrun2('combine lines', filedata, line, fd1 + fd2, '')
|
|
|
|
|
|
def removedirectives(filedata):
|
|
for i in range(len(filedata)):
|
|
if filedata[i].lstrip().startswith('#'):
|
|
replaceandrun('remove preprocessor directive', filedata, i, '')
|
|
|
|
|
|
def removeblocks(filedata):
|
|
if len(filedata) < 3:
|
|
return filedata
|
|
|
|
for i in range(len(filedata)):
|
|
strippedline = filedata[i].strip()
|
|
if len(strippedline) == 0:
|
|
continue
|
|
if strippedline[-1] not in ';{}':
|
|
continue
|
|
|
|
i1 = i + 1
|
|
while i1 < len(filedata) and filedata[i1].startswith('#'):
|
|
i1 = i1 + 1
|
|
|
|
i2 = i1
|
|
indent = 0
|
|
while i2 < len(filedata):
|
|
for c in filedata[i2]:
|
|
if c == '}':
|
|
indent = indent - 1
|
|
if indent == 0:
|
|
indent = -100
|
|
elif c == '{':
|
|
indent = indent + 1
|
|
if indent < 0:
|
|
break
|
|
i2 = i2 + 1
|
|
if indent == -100:
|
|
indent = 0
|
|
if i2 == i1 or i2 >= len(filedata):
|
|
continue
|
|
if filedata[i2].strip() != '}' and filedata[i2].strip() != '};':
|
|
continue
|
|
if indent < 0:
|
|
i2 = i2 - 1
|
|
filedata = clearandrun('remove codeblock', filedata, i1, i2)
|
|
|
|
return filedata
|
|
|
|
|
|
def removeline(filedata):
|
|
stmt = True
|
|
for i in range(len(filedata)):
|
|
line = filedata[i]
|
|
strippedline = line.strip()
|
|
|
|
if len(strippedline) == 0:
|
|
continue
|
|
|
|
if stmt and strippedline[-1] == ';' and checkpar(line) and '{' not in line and '}' not in line:
|
|
replaceandrun('remove line', filedata, i, '')
|
|
|
|
elif stmt and '{' in strippedline and strippedline.find('}') == len(strippedline) - 1:
|
|
replaceandrun('remove line', filedata, i, '')
|
|
|
|
if strippedline[-1] in ';{}':
|
|
stmt = True
|
|
else:
|
|
stmt = False
|
|
|
|
|
|
# reduce..
|
|
print('Make sure error can be reproduced...')
|
|
if not runtool():
|
|
print("Cannot reproduce")
|
|
sys.exit(1)
|
|
|
|
f = open(FILE, 'rt')
|
|
filedata = f.readlines()
|
|
f.close()
|
|
|
|
writefile(BACKUPFILE, filedata)
|
|
|
|
while True:
|
|
filedata1 = list(filedata)
|
|
|
|
print('remove comments...')
|
|
removecomments(filedata)
|
|
|
|
print('remove preprocessor directives...')
|
|
removedirectives(filedata)
|
|
|
|
print('remove blocks...')
|
|
filedata = removeblocks(filedata)
|
|
|
|
print('combine lines..')
|
|
combinelines(filedata)
|
|
|
|
print('remove line...')
|
|
removeline(filedata)
|
|
|
|
# if filedata and filedata2 are identical then stop
|
|
if len(filedata1) == len(filedata):
|
|
i = 0
|
|
while i < len(filedata1):
|
|
if filedata[i] != filedata1[i]:
|
|
break
|
|
i = i + 1
|
|
if i == len(filedata1):
|
|
break
|
|
|
|
writefile(FILE, filedata)
|