summaryrefslogtreecommitdiff
path: root/devtools/parse_analyze_errors.py
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /devtools/parse_analyze_errors.py
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'devtools/parse_analyze_errors.py')
-rw-r--r--devtools/parse_analyze_errors.py574
1 files changed, 574 insertions, 0 deletions
diff --git a/devtools/parse_analyze_errors.py b/devtools/parse_analyze_errors.py
new file mode 100644
index 0000000..e70ea80
--- /dev/null
+++ b/devtools/parse_analyze_errors.py
@@ -0,0 +1,574 @@
+# 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)