575 lines
22 KiB
Python
575 lines
22 KiB
Python
|
# This script is used to parse the results of the Visual C++ /analyze feature.
|
||
|
# See the 'usage' section for details.
|
||
|
|
||
|
# Regular expression experimentation was done at http://www.pythonregex.com/
|
||
|
|
||
|
# The buildbot warning parser that looks at this script uses the default compile warning
|
||
|
# parser which is documented at http://buildbot.net/buildbot/docs/0.8.4/Compile.html
|
||
|
# The regex used is '.*warning[: ].*'. This means that any instance of 'warning:' or
|
||
|
# 'warning ' will be flagged as a warning. The check is case sensitive so Warning will
|
||
|
# not be flagged as a warning. This script remaps warning to 'wrning' in some places so
|
||
|
# that lists of fixed warnings or old warnings will not trigger warning detection.
|
||
|
# Similarly it remaps error to 'eror'.
|
||
|
|
||
|
# Typical warning messages might look like this:
|
||
|
# 2>d:\dota\src\tier1\bitbuf.cpp(1336): warning C6001: Using uninitialized memory 'retval': Lines: 1327, 1328, 1331, 1332, 1333, 1334, 1336
|
||
|
|
||
|
import re
|
||
|
import sys
|
||
|
import os
|
||
|
|
||
|
# Grab per-project configuration information from the analyzeconfig package
|
||
|
import analyzeconfig
|
||
|
ignorePaths = analyzeconfig.ignorePaths
|
||
|
alwaysFatalWarnings = analyzeconfig.alwaysFatalWarnings.keys()
|
||
|
fatalWhenNewWarnings = analyzeconfig.fatalWhenNewWarnings.keys()
|
||
|
remaps = analyzeconfig.remaps
|
||
|
informationalWarnings = analyzeconfig.informationalWarnings
|
||
|
|
||
|
lkgFilename = "analyzelkg.txt"
|
||
|
|
||
|
# This matches 0-3 digits and an optional '>' character. Some builds prefix the output
|
||
|
# with '10>' or something equivalent, but some builds do not.
|
||
|
prefixRePattern = r"\d?\d?\d?>?"
|
||
|
|
||
|
warningWithLinesRe = re.compile(prefixRePattern + r"(.*)\((\d+)\): warning C(\d{4,5})(.*)(: Lines:.*)")
|
||
|
warningRe = re.compile(prefixRePattern + r"(.*)\((\d+)\): warning C(\d{4,5})(.*)")
|
||
|
errorRe = re.compile(prefixRePattern + r"(.*)\((\d+)\): error C(\d{4,5})(.*)")
|
||
|
|
||
|
# For reparsing the keys that we use to store the parsed log data:
|
||
|
# The format for keys is like this:
|
||
|
# key = "%s %s in %s" % (type, warningNumber, filename)
|
||
|
parseKeyRe = re.compile(r"(.*) (\d{4,5}) in (.*)")
|
||
|
|
||
|
warningsToText = {
|
||
|
2719 : "Formal parameter with __declspec(align('n')) won't be aligned",
|
||
|
4005 : "Macro redefinition",
|
||
|
4100 : "Unreferenced formal parameter",
|
||
|
4189 : "Local variable is initialized but not referenced",
|
||
|
4245 : "Signed/unsigned mismatch",
|
||
|
4505 : "Unreferenced local function has been removed",
|
||
|
4611 : "interaction between '_setjmp' and C++ object destruction is non-portable",
|
||
|
4703 : "Potentially uninitialized local pointer variable used",
|
||
|
4789 : "Destination of memory copy is too small",
|
||
|
6001 : "Using uninitialized memory",
|
||
|
6029 : "Possible buffer overrun: use of unchecked value",
|
||
|
6053 : "Call to <function> may not zero-terminate string",
|
||
|
6054 : "String may not be zero-terminated",
|
||
|
6057 : "Buffer overrun due to number of characters/number of bytes mismatch",
|
||
|
6059 : "Incorrect length parameter",
|
||
|
6063 : "Missing string argument",
|
||
|
6064 : "Missing integer argument",
|
||
|
6066 : "Non-pointer passed as parameter when pointer is required",
|
||
|
6067 : "Parameter in call must be the address of the string",
|
||
|
6200 : "Index is out of valid index range for non-stack buffer",
|
||
|
6201 : "Out of range index",
|
||
|
6202 : "Buffer overrun for stack allocated variable in call to function",
|
||
|
6203 : "Buffer overrun for non-stack buffer",
|
||
|
6204 : "Possible buffer overrun: use of unchecked parameter",
|
||
|
6209 : "Using sizeof when a character count might be needed. Annotate with OUT_Z_CAP or its relatives",
|
||
|
6216 : "Compiler-inserted cast between semantically different integral types: a Boolean type to HRESULT",
|
||
|
6221 : "Implicit cast between semantically different integer types",
|
||
|
6219 : "Implicit cast between semantically different integer types",
|
||
|
6236 : "(<expression> || <non-zero constant>) is always a non-zero constant",
|
||
|
6244 : "Local declaration shadows declaration of same name in global scope",
|
||
|
6246 : "Local declaration shadows declaration of same name in outer scope",
|
||
|
6248 : "Setting a SECURITY_DESCRIPTOR's DACL to NULL will result in an unprotected object",
|
||
|
6258 : "Using TerminateThread does not allow proper thread clean up",
|
||
|
6262 : "Excessive stack usage in function",
|
||
|
6263 : "Using _alloca in a loop: this can quickly overflow stack",
|
||
|
6269 : "Possible incorrect order of operations: dereference ignored",
|
||
|
6270 : "Missing float argument to varargs function",
|
||
|
6271 : "Extra argument passed: parameter is not used by the format string",
|
||
|
6272 : "Non-float passed as argument <number> when float is required",
|
||
|
6273 : "Non-integer passed as a parameter when integer is required",
|
||
|
6277 : "NULL application name with an unquoted path results in a security vulnerability if the path contains spaces",
|
||
|
6278 : "Buffer is allocated with array new [], but deleted with scalar delete. Destructors will not be called",
|
||
|
6281 : "Incorrect order of operations: relational operators have higher precedence than bitwise operators",
|
||
|
6282 : "Incorrect operator: assignment of constant in Boolean context",
|
||
|
6283 : "Buffer is allocated with array new [], but deleted with scalar delete",
|
||
|
6284 : "Object passed as a parameter when string is required",
|
||
|
6286 : "(<non-zero constant> || <expression>) is always a non-zero constant.",
|
||
|
6287 : "Redundant code: the left and right sub-expressions are identical",
|
||
|
6290 : "Bitwise operation on logical result: ! has higher precedence than &. Use && or (!(x & y)) instead",
|
||
|
6293 : "Ill-defined for-loop: counts down from minimum",
|
||
|
6294 : "Ill-defined for-loop: initial condition does not satisfy test. Loop body not executed",
|
||
|
6295 : "Ill-defined for-loop: Loop executed indefinitely",
|
||
|
6297 : "Arithmetic overflow: 32-bit value is shifted, then cast to 64-bit value",
|
||
|
6298 : "Using a read-only string <pointer> as a writable string argument",
|
||
|
6302 : "Format string mismatch: character string passed as parameter when wide character string is required",
|
||
|
6306 : "Incorrect call to 'fprintf*': consider using 'vfprintf*' which accepts a va_list as an argument",
|
||
|
6313 : "Incorrect operator: zero-valued flag cannot be tested with bitwise-and. Use an equality test to check for zero-valued flags",
|
||
|
6316 : "Incorrect operator: tested expression is constant and non-zero. Use bitwise-and to determine whether bits are set",
|
||
|
6318 : "Ill-defined __try/__except: use of the constant EXCEPTION_CONTINUE_SEARCH ",
|
||
|
6328 : "Wrong parameter type passed",
|
||
|
6330 : "'const char' passed as a parameter when 'unsigned char' is required",
|
||
|
6333 : "Invalid parameter: passing MEM_RELEASE and a non-zero dwSize parameter to 'VirtualFree' is not allowed",
|
||
|
6334 : "Sizeof operator applied to an expression with an operator might yield unexpected results",
|
||
|
6336 : "Arithmetic operator has precedence over question operator, use parentheses to clarify intent",
|
||
|
6385 : "Out of range read",
|
||
|
6386 : "Out of range write",
|
||
|
6522 : "Invalid size specification: expression must be of integral type",
|
||
|
6523 : "Invalid size specification: parameter 'size' not found",
|
||
|
28199 : "Using possibly uninitialized: The variable has had its address taken but no assignment to it has been discovered.",
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
def Cleanup(textline):
|
||
|
for sourcePath in remaps.keys():
|
||
|
if textline.startswith(sourcePath):
|
||
|
return textline.replace(sourcePath, remaps[sourcePath])
|
||
|
return textline
|
||
|
|
||
|
|
||
|
|
||
|
def ParseLog(logName):
|
||
|
# Create a dictionary in which to store the results
|
||
|
# The keys for the dictionary are "warning 6328 in c:\buildbot\..."
|
||
|
# This means that the count of keys is not particularly meaningful. The
|
||
|
# length of each data item tells you the total number of raw warnings, but
|
||
|
# some of those are duplicates (from the same file being compiled multiple
|
||
|
# times). The UniqueWarningCount function can be used to find the number of
|
||
|
# unique warnings in each record.
|
||
|
#
|
||
|
# This probably could have been designed better, perhaps by having the key
|
||
|
# include the line number. Probably not worth changing now.
|
||
|
result = {}
|
||
|
lines = open(logName).readlines()
|
||
|
|
||
|
# First look for compiler crashes. Joy.
|
||
|
if analyzeconfig.abortOnCompilerCrash:
|
||
|
compilerCrashes = 0
|
||
|
for line in lines:
|
||
|
# Look for signs that the compiler crashed and if it did then abort.
|
||
|
if line.count("Please choose the Technical Support command on the Visual C++") > 0:
|
||
|
compilerCrashes += 1
|
||
|
# Print a message in the warning format so that we can see how many times the
|
||
|
# compiler crashed on the buildbot waterfall page.
|
||
|
print "cl.exe(1): warning : internal compiler error, the compiler has crashed. Aborting code analysis."
|
||
|
# If the compiler crashes one or more times then give up.
|
||
|
if compilerCrashes > 0:
|
||
|
sys.exit(0)
|
||
|
|
||
|
warningCount = 0
|
||
|
ignoredCount = 0
|
||
|
namePrinted = False
|
||
|
for line in lines:
|
||
|
# Some of the paths in the output lines have slashes instead of backslashes.
|
||
|
line = line.replace("/", "\\")
|
||
|
ignored = False
|
||
|
for path in ignorePaths:
|
||
|
if line.count(path) > 0:
|
||
|
ignored = True
|
||
|
ignoredCount += 1
|
||
|
if ignored:
|
||
|
continue
|
||
|
filename = ""
|
||
|
type = "warning"
|
||
|
# Look for warnings with filename and line number. The groups returned
|
||
|
# are:
|
||
|
# file name
|
||
|
# line number
|
||
|
# warning number
|
||
|
# warning text
|
||
|
# optionally (warningWithLinesRe only) the lines implicated in the warning
|
||
|
warningMatch = warningWithLinesRe.match(line)
|
||
|
if not warningMatch:
|
||
|
warningMatch = warningRe.match(line)
|
||
|
if not warningMatch:
|
||
|
warningMatch = errorRe.match(line)
|
||
|
if warningMatch:
|
||
|
type = "error"
|
||
|
|
||
|
# We want to record how many errors of a particular type occur in a particular source
|
||
|
# file so we create a dictionary with [file name, warning number, isError] as the key.
|
||
|
if warningMatch:
|
||
|
filename = warningMatch.groups()[0]
|
||
|
lineNumber = warningMatch.groups()[1]
|
||
|
warningNumber = warningMatch.groups()[2]
|
||
|
warningText = warningMatch.groups()[3]
|
||
|
key = "%s %s in %s" % (type, warningNumber, filename)
|
||
|
data = "%s(%s): %s C%s%s" % (filename, lineNumber, type, warningNumber, warningText)
|
||
|
warningCount += 1
|
||
|
if key in result:
|
||
|
result[key] += [data]
|
||
|
else:
|
||
|
result[key] = [data]
|
||
|
elif line.find(": warning") >= 0:
|
||
|
pass # Ignore these warnings for now
|
||
|
elif line.find(": error ") >= 0:
|
||
|
if not namePrinted:
|
||
|
namePrinted = True
|
||
|
print " Unhandled errors found in '%s'" % logName
|
||
|
print " %s" % line.strip()
|
||
|
|
||
|
uniqueWarningCount = 0
|
||
|
uniqueInformationalCount = 0
|
||
|
for key in result.keys():
|
||
|
count = UniqueWarningCount(result[key])
|
||
|
match = parseKeyRe.match(key)
|
||
|
warningNumber = match.groups()[1]
|
||
|
if warningNumber in informationalWarnings:
|
||
|
uniqueInformationalCount += count
|
||
|
else:
|
||
|
uniqueWarningCount += count
|
||
|
|
||
|
print "%d lines of output in %s, %d issues found, %d ignored, plus %d informational." % (len(lines), logName, uniqueWarningCount, ignoredCount, uniqueInformationalCount)
|
||
|
print ""
|
||
|
return result
|
||
|
|
||
|
|
||
|
|
||
|
# The output of this script is filtered by buildbot as described at
|
||
|
# http://buildbot.net/buildbot/docs/0.8.4/Compile.html which means that the
|
||
|
# warning text is generated by running it through re.match(".*warning[: ].*")
|
||
|
# The e-mails are generated by running them through BuildAnalyze.createSummary
|
||
|
# in //steam/main/tools/buildbot/shared_helpers.py. The two sets of regexes
|
||
|
# should be kept compatible.
|
||
|
# The matching is case sensitive so Warning is not matched.
|
||
|
|
||
|
def PrintEntries(newEntries, prefix, sanitize):
|
||
|
printedAlready = {}
|
||
|
for newEntry in newEntries:
|
||
|
if not newEntry in printedAlready:
|
||
|
printedAlready[newEntry] = True
|
||
|
# When printing out the list of warnings that have been fixed
|
||
|
# replace ": warning" with a string that will not be
|
||
|
# recognized by the buildbot parser as a warning so that the
|
||
|
# break e-mails will only include new warnings.
|
||
|
# Yes, this is a hack. In the future a custom parser/filter
|
||
|
# for the e-mails would be better.
|
||
|
if sanitize:
|
||
|
newEntry = newEntry.replace(": warning", ": wrning")
|
||
|
newEntry = newEntry.replace(": error", ": eror")
|
||
|
print "%s%s" % (prefix, Cleanup(newEntry))
|
||
|
|
||
|
|
||
|
|
||
|
def UniqueWarningCount(warningRecord):
|
||
|
# Warnings may be encountered multiple times (header files included
|
||
|
# from many places, or source files compiled multiple times) and these
|
||
|
# are all added to the warning record. However, for determining
|
||
|
# unique warnings we want to filter out these duplicates.
|
||
|
alreadySeen = {}
|
||
|
count = 0
|
||
|
for warning in warningRecord:
|
||
|
if not warning in alreadySeen:
|
||
|
alreadySeen[warning] = True
|
||
|
count += 1
|
||
|
return count
|
||
|
|
||
|
|
||
|
|
||
|
def DumpNewWarnings(old, new, oldname, newname):
|
||
|
newWarningsFound = False
|
||
|
warningsFixed = False
|
||
|
fatalWarningsFound = False
|
||
|
|
||
|
warningCounts = {}
|
||
|
oldWarningCounts = {}
|
||
|
sampleWarnings = {}
|
||
|
|
||
|
for key in new.keys():
|
||
|
match = parseKeyRe.match(key)
|
||
|
warningNumber = int(match.groups()[1])
|
||
|
if warningNumber in alwaysFatalWarnings:
|
||
|
fatalWarningsFound = True
|
||
|
if warningNumber in warningCounts:
|
||
|
warningCounts[warningNumber] += UniqueWarningCount(new[key])
|
||
|
else:
|
||
|
warningCounts[warningNumber] = UniqueWarningCount(new[key])
|
||
|
sampleWarnings[warningNumber] = new[key][0]
|
||
|
if not key in old:
|
||
|
newWarningsFound = True
|
||
|
if warningNumber in fatalWhenNewWarnings:
|
||
|
fatalWarningsFound = True
|
||
|
for key in old.keys():
|
||
|
match = parseKeyRe.match(key)
|
||
|
warningNumber = int(match.groups()[1])
|
||
|
if warningNumber in oldWarningCounts:
|
||
|
oldWarningCounts[warningNumber] += UniqueWarningCount(old[key])
|
||
|
else:
|
||
|
oldWarningCounts[warningNumber] = UniqueWarningCount(old[key])
|
||
|
if not warningNumber in sampleWarnings:
|
||
|
sampleWarnings[warningNumber] = old[key][0]
|
||
|
if not key in new:
|
||
|
warningsFixed = True
|
||
|
|
||
|
if fatalWarningsFound:
|
||
|
errorCode = 10
|
||
|
elif newWarningsFound:
|
||
|
errorCode = 10
|
||
|
else:
|
||
|
errorCode = 0
|
||
|
|
||
|
# Make three passes through the warnings so that we group fatal, fatal-when-new, and
|
||
|
# new warnings together, with the fatal warnings first.
|
||
|
# The colons at the beginning of blank lines are so that buildbot's BuildAnalyze.createSummary
|
||
|
# will retain those lines.
|
||
|
for type in ["Fatal", "Fatal-when-new", "New"]:
|
||
|
fixing = "required"
|
||
|
if type == "New":
|
||
|
fixing = "optional"
|
||
|
message = "%s warning or warnings found. Fixing these is %s:\n:" % (type, fixing)
|
||
|
for key in new.keys():
|
||
|
newEntries = new[key]
|
||
|
match = parseKeyRe.match(key)
|
||
|
warningNumber = int(match.groups()[1])
|
||
|
if warningNumber in alwaysFatalWarnings:
|
||
|
if type == "Fatal":
|
||
|
print message
|
||
|
message = ":"
|
||
|
PrintEntries(newEntries, " ", False)
|
||
|
elif not key in old:
|
||
|
if warningNumber in fatalWhenNewWarnings:
|
||
|
if type == "Fatal-when-new":
|
||
|
print message
|
||
|
message = ":"
|
||
|
PrintEntries(newEntries, " ", False)
|
||
|
else:
|
||
|
if type == "New":
|
||
|
print message
|
||
|
message = ":"
|
||
|
PrintEntries(newEntries, " ", False)
|
||
|
|
||
|
# If message is short then that means it was printed and then assigned to a short
|
||
|
# string, which means some warnings of this type were printed, which means we should
|
||
|
# print a separator.
|
||
|
if len(message) < 2:
|
||
|
print ":\n:\n:\n:\n:"
|
||
|
|
||
|
|
||
|
|
||
|
if warningsFixed:
|
||
|
print "\n\n\n\n\nOld issues that have been fixed:"
|
||
|
for key in old.keys():
|
||
|
oldEntries = old[key]
|
||
|
if not key in new:
|
||
|
print "Warning fixed in %s:" % newname
|
||
|
print "%d times:" % len(oldEntries)
|
||
|
PrintEntries(oldEntries, " ", True)
|
||
|
print ""
|
||
|
else:
|
||
|
newEntries = new[key]
|
||
|
# Disable printing decreased warning counts -- too much noise.
|
||
|
if False and len(newEntries) < len(oldEntries):
|
||
|
print "Decreased wrning count:"
|
||
|
print " Old (%s):" % oldname
|
||
|
print " %d times:" % len(oldEntries)
|
||
|
PrintEntries(oldEntries, " ", True)
|
||
|
print " New (%s):" % newname
|
||
|
print " %d times:" % len(newEntries)
|
||
|
PrintEntries(newEntries, " ", True)
|
||
|
print ""
|
||
|
|
||
|
print "\n\n\n"
|
||
|
warningStats = []
|
||
|
for warningNumber in warningCounts.keys():
|
||
|
warningCount = warningCounts[warningNumber]
|
||
|
if warningNumber in oldWarningCounts:
|
||
|
warningDiff = warningCount - oldWarningCounts[warningNumber]
|
||
|
else:
|
||
|
warningDiff = warningCount
|
||
|
warningStats.append((warningCount, warningNumber, warningDiff))
|
||
|
for warningNumber in oldWarningCounts.keys():
|
||
|
if not warningNumber in warningCounts:
|
||
|
warningStats.append((0, warningNumber, -oldWarningCounts[warningNumber]))
|
||
|
warningStats.sort()
|
||
|
warningStats.reverse()
|
||
|
for warningStat in warningStats:
|
||
|
warningNumber = warningStat[1]
|
||
|
description = ""
|
||
|
if warningNumber in warningsToText:
|
||
|
description = ", %s" % warningsToText[warningNumber]
|
||
|
else:
|
||
|
# Replace warning/error with wrning/eror so that these warning summaries don't trigger the
|
||
|
# warning detection logic.
|
||
|
description = ", example: %s" % sampleWarnings[warningNumber].replace("warning", "wrning").replace("error", "eror")
|
||
|
print "%3d occurrences of C%d, changed %d%s" % (warningStat[0], warningStat[1], warningStat[2], description)
|
||
|
|
||
|
# Print a summary of all stack related warnings in the new data, regardless of whether they were in the old.
|
||
|
bigStackCulprits = {}
|
||
|
allocaCulprits = {}
|
||
|
# c:\src\simplify.cpp(1840): warning C6262: : Function uses '28708' bytes of stack: exceeds /analyze:stacksize'16384'. Consider moving some data to heap
|
||
|
stackUsedRe = re.compile("(.*): warning C6262: Function uses '(\d*)' .*")
|
||
|
print "\n\n\n"
|
||
|
print "Stack related summary:"
|
||
|
print "C6263: Using _alloca in a loop: this can quickly overflow stack"
|
||
|
bigStackCulprits = []
|
||
|
for key in new.keys():
|
||
|
# warning C6262: Function uses '400352' bytes of stack
|
||
|
# warning C6263: Using _alloca in a loop
|
||
|
stackMatch = parseKeyRe.match(key)
|
||
|
if stackMatch:
|
||
|
warningNumber = stackMatch.groups()[1]
|
||
|
if warningNumber == "6262":
|
||
|
#print "Found warning %s in %s" % (warningNumber, stackMatch.groups()[2])
|
||
|
entries = new[key]
|
||
|
printed = {}
|
||
|
for entry in entries:
|
||
|
if not entry in printed:
|
||
|
match = stackUsedRe.match(entry)
|
||
|
if match:
|
||
|
location = match.groups()[0]
|
||
|
stackBytes = int(match.groups()[1])
|
||
|
printed[entry] = True
|
||
|
bigStackCulprits.append((stackBytes, location))
|
||
|
elif warningNumber == "6263":
|
||
|
#print "Found warning %s in %s" % (warningNumber, stackMatch.groups()[2])
|
||
|
entries = new[key]
|
||
|
printed = {}
|
||
|
for entry in entries:
|
||
|
if not entry in printed:
|
||
|
print Cleanup(entry[:entry.find(": ")])
|
||
|
printed[entry] = True
|
||
|
|
||
|
print "\n\n"
|
||
|
print "C6262: Functions that use many bytes of stack"
|
||
|
bigStackCulprits.sort()
|
||
|
bigStackCulprits.reverse()
|
||
|
print "filename(linenumber): bytes"
|
||
|
# Print a sorted summary of functions using excessive stack. It would be tidier
|
||
|
# to print the size first (better alignment) but then the output can't be used
|
||
|
# in the Visual Studio output window to jump to the code in question.
|
||
|
|
||
|
# Get the lengths of all of the file names
|
||
|
lengths = []
|
||
|
for val in bigStackCulprits:
|
||
|
lengths.append(len(Cleanup(val[1])))
|
||
|
lengths.sort()
|
||
|
if len(lengths) > 0:
|
||
|
# Set the length at the 9xth percentile so that most of the sizes
|
||
|
# are lined up.
|
||
|
formatLength = lengths[int(len(lengths)*.97)]
|
||
|
formatString = "%%-%ds: %%7d" % formatLength
|
||
|
for val in bigStackCulprits:
|
||
|
print formatString % (Cleanup(val[1]), val[0])
|
||
|
|
||
|
# Print a list of all of the outstanding warnings
|
||
|
print "\n\n\n"
|
||
|
print "Outstanding warnings are:"
|
||
|
DumpWarnings(new, True)
|
||
|
return (errorCode, fatalWarningsFound)
|
||
|
|
||
|
|
||
|
|
||
|
def DumpWarnings(new, ignoreInformational):
|
||
|
filePrinted = {}
|
||
|
# If we just scan the dictionary then warnings will be grouped
|
||
|
# by warning-number-in-file, but different warning numbers from the
|
||
|
# same file will be scattered, and different files from the same
|
||
|
# directory will also be scattered.
|
||
|
# We really want warnings sorted by path name. To do that we scan
|
||
|
# through the dictionary and add all of the entries to a dictionary
|
||
|
# whose primary key is filename (path). Then we sort those keys.
|
||
|
warningsByFile = {}
|
||
|
for key in new.keys():
|
||
|
match = parseKeyRe.match(key)
|
||
|
type, warningNumber, filename = match.groups()
|
||
|
if filename in warningsByFile:
|
||
|
warningsByFile[filename].append(key)
|
||
|
else:
|
||
|
warningsByFile[filename] = [key]
|
||
|
|
||
|
filenames = warningsByFile.keys()
|
||
|
filenames.sort();
|
||
|
|
||
|
for filename in filenames:
|
||
|
for key in warningsByFile[filename]:
|
||
|
match = parseKeyRe.match(key)
|
||
|
warningNumber = match.groups()[1]
|
||
|
if ignoreInformational and warningNumber in informationalWarnings:
|
||
|
pass
|
||
|
else:
|
||
|
newEntries = new[key]
|
||
|
print "%d times:" % len(newEntries)
|
||
|
PrintEntries(newEntries, " ", True)
|
||
|
print ""
|
||
|
|
||
|
if ignoreInformational:
|
||
|
# Print the 6244 and 6246 warnings together in a group. We print
|
||
|
# them here so that they are sorted by file name.
|
||
|
print "\n\n\nVariable shadowing warnings"
|
||
|
for filename in filenames:
|
||
|
for key in warningsByFile[filename]:
|
||
|
match = parseKeyRe.match(key)
|
||
|
warningNumber = match.groups()[1]
|
||
|
if warningNumber == "6244" or warningNumber == "6246":
|
||
|
newEntries = new[key]
|
||
|
PrintEntries(newEntries, " ", True)
|
||
|
print ""
|
||
|
|
||
|
|
||
|
|
||
|
def GetLogFileName(arg):
|
||
|
# Special indicator for last-known-good. This means that the script
|
||
|
# should look for analysislkg.txt and extract a file name from it.
|
||
|
# Temporarily have "2" be equivalent to "lkg" to allow for a transition
|
||
|
# to the lkg model.
|
||
|
if arg == "lkg" or arg == "2":
|
||
|
try:
|
||
|
lines = open(lkgFilename).readlines()
|
||
|
if len(lines) > 0:
|
||
|
result = lines[0].strip()
|
||
|
print "LKG analysis results are in '%s'" % result
|
||
|
return result
|
||
|
else:
|
||
|
print "No data found in %s" % lkgFilename
|
||
|
except IOError:
|
||
|
print "Failed to open %s" % lkgFilename
|
||
|
arg = 2
|
||
|
|
||
|
try:
|
||
|
x = int(arg)
|
||
|
except:
|
||
|
return arg
|
||
|
|
||
|
if x <= 0:
|
||
|
print "Numerical arguments must be from 1 to numlogs (%s)" % arg
|
||
|
sys.exit(10)
|
||
|
basedir = r"."
|
||
|
dirEntries = os.listdir(basedir)
|
||
|
logRe = re.compile(r"analyze(.*)_cl_(\d+).txt");
|
||
|
logs = []
|
||
|
for entry in dirEntries:
|
||
|
if logRe.match(entry):
|
||
|
logs.append(entry)
|
||
|
# This will throw an exception if there aren't enough log files
|
||
|
# available.
|
||
|
newname = os.path.join(basedir, logs[-x])
|
||
|
return newname
|
||
|
|
||
|
|
||
|
|
||
|
if len(sys.argv) < 2:
|
||
|
print "Usage:"
|
||
|
print "To get a comparison between two error log files:"
|
||
|
print " Syntax: parseerrors newlogfile oldlogfile"
|
||
|
print "To get a summary of a single log file:"
|
||
|
print " Syntax: parseerrors logfile"
|
||
|
print "To get a summary of the two most recent log files:"
|
||
|
print " Syntax: parseerrors 1 2"
|
||
|
print "Log files can also be indicated by number where '1' is the"
|
||
|
print "most recent, '2' is second oldest, etc."
|
||
|
sys.exit(0)
|
||
|
|
||
|
newname = GetLogFileName(sys.argv[1])
|
||
|
resultnew = ParseLog(newname)
|
||
|
if len(sys.argv) >= 3:
|
||
|
oldname = GetLogFileName(sys.argv[2])
|
||
|
resultold = ParseLog(oldname)
|
||
|
result = DumpNewWarnings(resultold, resultnew, oldname, newname)
|
||
|
errorCode = result[0]
|
||
|
fatalWarningsFound = result[1]
|
||
|
if fatalWarningsFound == 0:
|
||
|
if analyzeconfig.updateLastKnownGood:
|
||
|
print "Updating last-known-good."
|
||
|
lkgOutput = open(lkgFilename, "wt")
|
||
|
lkgOutput.write(newname)
|
||
|
else:
|
||
|
print "Updating last-known-good is disabled."
|
||
|
sys.exit(errorCode)
|
||
|
else:
|
||
|
DumpWarnings(resultnew, False)
|