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)
|