Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

574 lines
22 KiB

  1. # This script is used to parse the results of the Visual C++ /analyze feature.
  2. # See the 'usage' section for details.
  3. # Regular expression experimentation was done at http://www.pythonregex.com/
  4. # The buildbot warning parser that looks at this script uses the default compile warning
  5. # parser which is documented at http://buildbot.net/buildbot/docs/0.8.4/Compile.html
  6. # The regex used is '.*warning[: ].*'. This means that any instance of 'warning:' or
  7. # 'warning ' will be flagged as a warning. The check is case sensitive so Warning will
  8. # not be flagged as a warning. This script remaps warning to 'wrning' in some places so
  9. # that lists of fixed warnings or old warnings will not trigger warning detection.
  10. # Similarly it remaps error to 'eror'.
  11. # Typical warning messages might look like this:
  12. # 2>d:\dota\src\tier1\bitbuf.cpp(1336): warning C6001: Using uninitialized memory 'retval': Lines: 1327, 1328, 1331, 1332, 1333, 1334, 1336
  13. import re
  14. import sys
  15. import os
  16. # Grab per-project configuration information from the analyzeconfig package
  17. import analyzeconfig
  18. ignorePaths = analyzeconfig.ignorePaths
  19. alwaysFatalWarnings = analyzeconfig.alwaysFatalWarnings.keys()
  20. fatalWhenNewWarnings = analyzeconfig.fatalWhenNewWarnings.keys()
  21. remaps = analyzeconfig.remaps
  22. informationalWarnings = analyzeconfig.informationalWarnings
  23. lkgFilename = "analyzelkg.txt"
  24. # This matches 0-3 digits and an optional '>' character. Some builds prefix the output
  25. # with '10>' or something equivalent, but some builds do not.
  26. prefixRePattern = r"\d?\d?\d?>?"
  27. warningWithLinesRe = re.compile(prefixRePattern + r"(.*)\((\d+)\): warning C(\d{4,5})(.*)(: Lines:.*)")
  28. warningRe = re.compile(prefixRePattern + r"(.*)\((\d+)\): warning C(\d{4,5})(.*)")
  29. errorRe = re.compile(prefixRePattern + r"(.*)\((\d+)\): error C(\d{4,5})(.*)")
  30. # For reparsing the keys that we use to store the parsed log data:
  31. # The format for keys is like this:
  32. # key = "%s %s in %s" % (type, warningNumber, filename)
  33. parseKeyRe = re.compile(r"(.*) (\d{4,5}) in (.*)")
  34. warningsToText = {
  35. 2719 : "Formal parameter with __declspec(align('n')) won't be aligned",
  36. 4005 : "Macro redefinition",
  37. 4100 : "Unreferenced formal parameter",
  38. 4189 : "Local variable is initialized but not referenced",
  39. 4245 : "Signed/unsigned mismatch",
  40. 4505 : "Unreferenced local function has been removed",
  41. 4611 : "interaction between '_setjmp' and C++ object destruction is non-portable",
  42. 4703 : "Potentially uninitialized local pointer variable used",
  43. 4789 : "Destination of memory copy is too small",
  44. 6001 : "Using uninitialized memory",
  45. 6029 : "Possible buffer overrun: use of unchecked value",
  46. 6053 : "Call to <function> may not zero-terminate string",
  47. 6054 : "String may not be zero-terminated",
  48. 6057 : "Buffer overrun due to number of characters/number of bytes mismatch",
  49. 6059 : "Incorrect length parameter",
  50. 6063 : "Missing string argument",
  51. 6064 : "Missing integer argument",
  52. 6066 : "Non-pointer passed as parameter when pointer is required",
  53. 6067 : "Parameter in call must be the address of the string",
  54. 6200 : "Index is out of valid index range for non-stack buffer",
  55. 6201 : "Out of range index",
  56. 6202 : "Buffer overrun for stack allocated variable in call to function",
  57. 6203 : "Buffer overrun for non-stack buffer",
  58. 6204 : "Possible buffer overrun: use of unchecked parameter",
  59. 6209 : "Using sizeof when a character count might be needed. Annotate with OUT_Z_CAP or its relatives",
  60. 6216 : "Compiler-inserted cast between semantically different integral types: a Boolean type to HRESULT",
  61. 6221 : "Implicit cast between semantically different integer types",
  62. 6219 : "Implicit cast between semantically different integer types",
  63. 6236 : "(<expression> || <non-zero constant>) is always a non-zero constant",
  64. 6244 : "Local declaration shadows declaration of same name in global scope",
  65. 6246 : "Local declaration shadows declaration of same name in outer scope",
  66. 6248 : "Setting a SECURITY_DESCRIPTOR's DACL to NULL will result in an unprotected object",
  67. 6258 : "Using TerminateThread does not allow proper thread clean up",
  68. 6262 : "Excessive stack usage in function",
  69. 6263 : "Using _alloca in a loop: this can quickly overflow stack",
  70. 6269 : "Possible incorrect order of operations: dereference ignored",
  71. 6270 : "Missing float argument to varargs function",
  72. 6271 : "Extra argument passed: parameter is not used by the format string",
  73. 6272 : "Non-float passed as argument <number> when float is required",
  74. 6273 : "Non-integer passed as a parameter when integer is required",
  75. 6277 : "NULL application name with an unquoted path results in a security vulnerability if the path contains spaces",
  76. 6278 : "Buffer is allocated with array new [], but deleted with scalar delete. Destructors will not be called",
  77. 6281 : "Incorrect order of operations: relational operators have higher precedence than bitwise operators",
  78. 6282 : "Incorrect operator: assignment of constant in Boolean context",
  79. 6283 : "Buffer is allocated with array new [], but deleted with scalar delete",
  80. 6284 : "Object passed as a parameter when string is required",
  81. 6286 : "(<non-zero constant> || <expression>) is always a non-zero constant.",
  82. 6287 : "Redundant code: the left and right sub-expressions are identical",
  83. 6290 : "Bitwise operation on logical result: ! has higher precedence than &. Use && or (!(x & y)) instead",
  84. 6293 : "Ill-defined for-loop: counts down from minimum",
  85. 6294 : "Ill-defined for-loop: initial condition does not satisfy test. Loop body not executed",
  86. 6295 : "Ill-defined for-loop: Loop executed indefinitely",
  87. 6297 : "Arithmetic overflow: 32-bit value is shifted, then cast to 64-bit value",
  88. 6298 : "Using a read-only string <pointer> as a writable string argument",
  89. 6302 : "Format string mismatch: character string passed as parameter when wide character string is required",
  90. 6306 : "Incorrect call to 'fprintf*': consider using 'vfprintf*' which accepts a va_list as an argument",
  91. 6313 : "Incorrect operator: zero-valued flag cannot be tested with bitwise-and. Use an equality test to check for zero-valued flags",
  92. 6316 : "Incorrect operator: tested expression is constant and non-zero. Use bitwise-and to determine whether bits are set",
  93. 6318 : "Ill-defined __try/__except: use of the constant EXCEPTION_CONTINUE_SEARCH ",
  94. 6328 : "Wrong parameter type passed",
  95. 6330 : "'const char' passed as a parameter when 'unsigned char' is required",
  96. 6333 : "Invalid parameter: passing MEM_RELEASE and a non-zero dwSize parameter to 'VirtualFree' is not allowed",
  97. 6334 : "Sizeof operator applied to an expression with an operator might yield unexpected results",
  98. 6336 : "Arithmetic operator has precedence over question operator, use parentheses to clarify intent",
  99. 6385 : "Out of range read",
  100. 6386 : "Out of range write",
  101. 6522 : "Invalid size specification: expression must be of integral type",
  102. 6523 : "Invalid size specification: parameter 'size' not found",
  103. 28199 : "Using possibly uninitialized: The variable has had its address taken but no assignment to it has been discovered.",
  104. }
  105. def Cleanup(textline):
  106. for sourcePath in remaps.keys():
  107. if textline.startswith(sourcePath):
  108. return textline.replace(sourcePath, remaps[sourcePath])
  109. return textline
  110. def ParseLog(logName):
  111. # Create a dictionary in which to store the results
  112. # The keys for the dictionary are "warning 6328 in c:\buildbot\..."
  113. # This means that the count of keys is not particularly meaningful. The
  114. # length of each data item tells you the total number of raw warnings, but
  115. # some of those are duplicates (from the same file being compiled multiple
  116. # times). The UniqueWarningCount function can be used to find the number of
  117. # unique warnings in each record.
  118. #
  119. # This probably could have been designed better, perhaps by having the key
  120. # include the line number. Probably not worth changing now.
  121. result = {}
  122. lines = open(logName).readlines()
  123. # First look for compiler crashes. Joy.
  124. if analyzeconfig.abortOnCompilerCrash:
  125. compilerCrashes = 0
  126. for line in lines:
  127. # Look for signs that the compiler crashed and if it did then abort.
  128. if line.count("Please choose the Technical Support command on the Visual C++") > 0:
  129. compilerCrashes += 1
  130. # Print a message in the warning format so that we can see how many times the
  131. # compiler crashed on the buildbot waterfall page.
  132. print "cl.exe(1): warning : internal compiler error, the compiler has crashed. Aborting code analysis."
  133. # If the compiler crashes one or more times then give up.
  134. if compilerCrashes > 0:
  135. sys.exit(0)
  136. warningCount = 0
  137. ignoredCount = 0
  138. namePrinted = False
  139. for line in lines:
  140. # Some of the paths in the output lines have slashes instead of backslashes.
  141. line = line.replace("/", "\\")
  142. ignored = False
  143. for path in ignorePaths:
  144. if line.count(path) > 0:
  145. ignored = True
  146. ignoredCount += 1
  147. if ignored:
  148. continue
  149. filename = ""
  150. type = "warning"
  151. # Look for warnings with filename and line number. The groups returned
  152. # are:
  153. # file name
  154. # line number
  155. # warning number
  156. # warning text
  157. # optionally (warningWithLinesRe only) the lines implicated in the warning
  158. warningMatch = warningWithLinesRe.match(line)
  159. if not warningMatch:
  160. warningMatch = warningRe.match(line)
  161. if not warningMatch:
  162. warningMatch = errorRe.match(line)
  163. if warningMatch:
  164. type = "error"
  165. # We want to record how many errors of a particular type occur in a particular source
  166. # file so we create a dictionary with [file name, warning number, isError] as the key.
  167. if warningMatch:
  168. filename = warningMatch.groups()[0]
  169. lineNumber = warningMatch.groups()[1]
  170. warningNumber = warningMatch.groups()[2]
  171. warningText = warningMatch.groups()[3]
  172. key = "%s %s in %s" % (type, warningNumber, filename)
  173. data = "%s(%s): %s C%s%s" % (filename, lineNumber, type, warningNumber, warningText)
  174. warningCount += 1
  175. if key in result:
  176. result[key] += [data]
  177. else:
  178. result[key] = [data]
  179. elif line.find(": warning") >= 0:
  180. pass # Ignore these warnings for now
  181. elif line.find(": error ") >= 0:
  182. if not namePrinted:
  183. namePrinted = True
  184. print " Unhandled errors found in '%s'" % logName
  185. print " %s" % line.strip()
  186. uniqueWarningCount = 0
  187. uniqueInformationalCount = 0
  188. for key in result.keys():
  189. count = UniqueWarningCount(result[key])
  190. match = parseKeyRe.match(key)
  191. warningNumber = match.groups()[1]
  192. if warningNumber in informationalWarnings:
  193. uniqueInformationalCount += count
  194. else:
  195. uniqueWarningCount += count
  196. print "%d lines of output in %s, %d issues found, %d ignored, plus %d informational." % (len(lines), logName, uniqueWarningCount, ignoredCount, uniqueInformationalCount)
  197. print ""
  198. return result
  199. # The output of this script is filtered by buildbot as described at
  200. # http://buildbot.net/buildbot/docs/0.8.4/Compile.html which means that the
  201. # warning text is generated by running it through re.match(".*warning[: ].*")
  202. # The e-mails are generated by running them through BuildAnalyze.createSummary
  203. # in //steam/main/tools/buildbot/shared_helpers.py. The two sets of regexes
  204. # should be kept compatible.
  205. # The matching is case sensitive so Warning is not matched.
  206. def PrintEntries(newEntries, prefix, sanitize):
  207. printedAlready = {}
  208. for newEntry in newEntries:
  209. if not newEntry in printedAlready:
  210. printedAlready[newEntry] = True
  211. # When printing out the list of warnings that have been fixed
  212. # replace ": warning" with a string that will not be
  213. # recognized by the buildbot parser as a warning so that the
  214. # break e-mails will only include new warnings.
  215. # Yes, this is a hack. In the future a custom parser/filter
  216. # for the e-mails would be better.
  217. if sanitize:
  218. newEntry = newEntry.replace(": warning", ": wrning")
  219. newEntry = newEntry.replace(": error", ": eror")
  220. print "%s%s" % (prefix, Cleanup(newEntry))
  221. def UniqueWarningCount(warningRecord):
  222. # Warnings may be encountered multiple times (header files included
  223. # from many places, or source files compiled multiple times) and these
  224. # are all added to the warning record. However, for determining
  225. # unique warnings we want to filter out these duplicates.
  226. alreadySeen = {}
  227. count = 0
  228. for warning in warningRecord:
  229. if not warning in alreadySeen:
  230. alreadySeen[warning] = True
  231. count += 1
  232. return count
  233. def DumpNewWarnings(old, new, oldname, newname):
  234. newWarningsFound = False
  235. warningsFixed = False
  236. fatalWarningsFound = False
  237. warningCounts = {}
  238. oldWarningCounts = {}
  239. sampleWarnings = {}
  240. for key in new.keys():
  241. match = parseKeyRe.match(key)
  242. warningNumber = int(match.groups()[1])
  243. if warningNumber in alwaysFatalWarnings:
  244. fatalWarningsFound = True
  245. if warningNumber in warningCounts:
  246. warningCounts[warningNumber] += UniqueWarningCount(new[key])
  247. else:
  248. warningCounts[warningNumber] = UniqueWarningCount(new[key])
  249. sampleWarnings[warningNumber] = new[key][0]
  250. if not key in old:
  251. newWarningsFound = True
  252. if warningNumber in fatalWhenNewWarnings:
  253. fatalWarningsFound = True
  254. for key in old.keys():
  255. match = parseKeyRe.match(key)
  256. warningNumber = int(match.groups()[1])
  257. if warningNumber in oldWarningCounts:
  258. oldWarningCounts[warningNumber] += UniqueWarningCount(old[key])
  259. else:
  260. oldWarningCounts[warningNumber] = UniqueWarningCount(old[key])
  261. if not warningNumber in sampleWarnings:
  262. sampleWarnings[warningNumber] = old[key][0]
  263. if not key in new:
  264. warningsFixed = True
  265. if fatalWarningsFound:
  266. errorCode = 10
  267. elif newWarningsFound:
  268. errorCode = 10
  269. else:
  270. errorCode = 0
  271. # Make three passes through the warnings so that we group fatal, fatal-when-new, and
  272. # new warnings together, with the fatal warnings first.
  273. # The colons at the beginning of blank lines are so that buildbot's BuildAnalyze.createSummary
  274. # will retain those lines.
  275. for type in ["Fatal", "Fatal-when-new", "New"]:
  276. fixing = "required"
  277. if type == "New":
  278. fixing = "optional"
  279. message = "%s warning or warnings found. Fixing these is %s:\n:" % (type, fixing)
  280. for key in new.keys():
  281. newEntries = new[key]
  282. match = parseKeyRe.match(key)
  283. warningNumber = int(match.groups()[1])
  284. if warningNumber in alwaysFatalWarnings:
  285. if type == "Fatal":
  286. print message
  287. message = ":"
  288. PrintEntries(newEntries, " ", False)
  289. elif not key in old:
  290. if warningNumber in fatalWhenNewWarnings:
  291. if type == "Fatal-when-new":
  292. print message
  293. message = ":"
  294. PrintEntries(newEntries, " ", False)
  295. else:
  296. if type == "New":
  297. print message
  298. message = ":"
  299. PrintEntries(newEntries, " ", False)
  300. # If message is short then that means it was printed and then assigned to a short
  301. # string, which means some warnings of this type were printed, which means we should
  302. # print a separator.
  303. if len(message) < 2:
  304. print ":\n:\n:\n:\n:"
  305. if warningsFixed:
  306. print "\n\n\n\n\nOld issues that have been fixed:"
  307. for key in old.keys():
  308. oldEntries = old[key]
  309. if not key in new:
  310. print "Warning fixed in %s:" % newname
  311. print "%d times:" % len(oldEntries)
  312. PrintEntries(oldEntries, " ", True)
  313. print ""
  314. else:
  315. newEntries = new[key]
  316. # Disable printing decreased warning counts -- too much noise.
  317. if False and len(newEntries) < len(oldEntries):
  318. print "Decreased wrning count:"
  319. print " Old (%s):" % oldname
  320. print " %d times:" % len(oldEntries)
  321. PrintEntries(oldEntries, " ", True)
  322. print " New (%s):" % newname
  323. print " %d times:" % len(newEntries)
  324. PrintEntries(newEntries, " ", True)
  325. print ""
  326. print "\n\n\n"
  327. warningStats = []
  328. for warningNumber in warningCounts.keys():
  329. warningCount = warningCounts[warningNumber]
  330. if warningNumber in oldWarningCounts:
  331. warningDiff = warningCount - oldWarningCounts[warningNumber]
  332. else:
  333. warningDiff = warningCount
  334. warningStats.append((warningCount, warningNumber, warningDiff))
  335. for warningNumber in oldWarningCounts.keys():
  336. if not warningNumber in warningCounts:
  337. warningStats.append((0, warningNumber, -oldWarningCounts[warningNumber]))
  338. warningStats.sort()
  339. warningStats.reverse()
  340. for warningStat in warningStats:
  341. warningNumber = warningStat[1]
  342. description = ""
  343. if warningNumber in warningsToText:
  344. description = ", %s" % warningsToText[warningNumber]
  345. else:
  346. # Replace warning/error with wrning/eror so that these warning summaries don't trigger the
  347. # warning detection logic.
  348. description = ", example: %s" % sampleWarnings[warningNumber].replace("warning", "wrning").replace("error", "eror")
  349. print "%3d occurrences of C%d, changed %d%s" % (warningStat[0], warningStat[1], warningStat[2], description)
  350. # Print a summary of all stack related warnings in the new data, regardless of whether they were in the old.
  351. bigStackCulprits = {}
  352. allocaCulprits = {}
  353. # c:\src\simplify.cpp(1840): warning C6262: : Function uses '28708' bytes of stack: exceeds /analyze:stacksize'16384'. Consider moving some data to heap
  354. stackUsedRe = re.compile("(.*): warning C6262: Function uses '(\d*)' .*")
  355. print "\n\n\n"
  356. print "Stack related summary:"
  357. print "C6263: Using _alloca in a loop: this can quickly overflow stack"
  358. bigStackCulprits = []
  359. for key in new.keys():
  360. # warning C6262: Function uses '400352' bytes of stack
  361. # warning C6263: Using _alloca in a loop
  362. stackMatch = parseKeyRe.match(key)
  363. if stackMatch:
  364. warningNumber = stackMatch.groups()[1]
  365. if warningNumber == "6262":
  366. #print "Found warning %s in %s" % (warningNumber, stackMatch.groups()[2])
  367. entries = new[key]
  368. printed = {}
  369. for entry in entries:
  370. if not entry in printed:
  371. match = stackUsedRe.match(entry)
  372. if match:
  373. location = match.groups()[0]
  374. stackBytes = int(match.groups()[1])
  375. printed[entry] = True
  376. bigStackCulprits.append((stackBytes, location))
  377. elif warningNumber == "6263":
  378. #print "Found warning %s in %s" % (warningNumber, stackMatch.groups()[2])
  379. entries = new[key]
  380. printed = {}
  381. for entry in entries:
  382. if not entry in printed:
  383. print Cleanup(entry[:entry.find(": ")])
  384. printed[entry] = True
  385. print "\n\n"
  386. print "C6262: Functions that use many bytes of stack"
  387. bigStackCulprits.sort()
  388. bigStackCulprits.reverse()
  389. print "filename(linenumber): bytes"
  390. # Print a sorted summary of functions using excessive stack. It would be tidier
  391. # to print the size first (better alignment) but then the output can't be used
  392. # in the Visual Studio output window to jump to the code in question.
  393. # Get the lengths of all of the file names
  394. lengths = []
  395. for val in bigStackCulprits:
  396. lengths.append(len(Cleanup(val[1])))
  397. lengths.sort()
  398. if len(lengths) > 0:
  399. # Set the length at the 9xth percentile so that most of the sizes
  400. # are lined up.
  401. formatLength = lengths[int(len(lengths)*.97)]
  402. formatString = "%%-%ds: %%7d" % formatLength
  403. for val in bigStackCulprits:
  404. print formatString % (Cleanup(val[1]), val[0])
  405. # Print a list of all of the outstanding warnings
  406. print "\n\n\n"
  407. print "Outstanding warnings are:"
  408. DumpWarnings(new, True)
  409. return (errorCode, fatalWarningsFound)
  410. def DumpWarnings(new, ignoreInformational):
  411. filePrinted = {}
  412. # If we just scan the dictionary then warnings will be grouped
  413. # by warning-number-in-file, but different warning numbers from the
  414. # same file will be scattered, and different files from the same
  415. # directory will also be scattered.
  416. # We really want warnings sorted by path name. To do that we scan
  417. # through the dictionary and add all of the entries to a dictionary
  418. # whose primary key is filename (path). Then we sort those keys.
  419. warningsByFile = {}
  420. for key in new.keys():
  421. match = parseKeyRe.match(key)
  422. type, warningNumber, filename = match.groups()
  423. if filename in warningsByFile:
  424. warningsByFile[filename].append(key)
  425. else:
  426. warningsByFile[filename] = [key]
  427. filenames = warningsByFile.keys()
  428. filenames.sort();
  429. for filename in filenames:
  430. for key in warningsByFile[filename]:
  431. match = parseKeyRe.match(key)
  432. warningNumber = match.groups()[1]
  433. if ignoreInformational and warningNumber in informationalWarnings:
  434. pass
  435. else:
  436. newEntries = new[key]
  437. print "%d times:" % len(newEntries)
  438. PrintEntries(newEntries, " ", True)
  439. print ""
  440. if ignoreInformational:
  441. # Print the 6244 and 6246 warnings together in a group. We print
  442. # them here so that they are sorted by file name.
  443. print "\n\n\nVariable shadowing warnings"
  444. for filename in filenames:
  445. for key in warningsByFile[filename]:
  446. match = parseKeyRe.match(key)
  447. warningNumber = match.groups()[1]
  448. if warningNumber == "6244" or warningNumber == "6246":
  449. newEntries = new[key]
  450. PrintEntries(newEntries, " ", True)
  451. print ""
  452. def GetLogFileName(arg):
  453. # Special indicator for last-known-good. This means that the script
  454. # should look for analysislkg.txt and extract a file name from it.
  455. # Temporarily have "2" be equivalent to "lkg" to allow for a transition
  456. # to the lkg model.
  457. if arg == "lkg" or arg == "2":
  458. try:
  459. lines = open(lkgFilename).readlines()
  460. if len(lines) > 0:
  461. result = lines[0].strip()
  462. print "LKG analysis results are in '%s'" % result
  463. return result
  464. else:
  465. print "No data found in %s" % lkgFilename
  466. except IOError:
  467. print "Failed to open %s" % lkgFilename
  468. arg = 2
  469. try:
  470. x = int(arg)
  471. except:
  472. return arg
  473. if x <= 0:
  474. print "Numerical arguments must be from 1 to numlogs (%s)" % arg
  475. sys.exit(10)
  476. basedir = r"."
  477. dirEntries = os.listdir(basedir)
  478. logRe = re.compile(r"analyze(.*)_cl_(\d+).txt");
  479. logs = []
  480. for entry in dirEntries:
  481. if logRe.match(entry):
  482. logs.append(entry)
  483. # This will throw an exception if there aren't enough log files
  484. # available.
  485. newname = os.path.join(basedir, logs[-x])
  486. return newname
  487. if len(sys.argv) < 2:
  488. print "Usage:"
  489. print "To get a comparison between two error log files:"
  490. print " Syntax: parseerrors newlogfile oldlogfile"
  491. print "To get a summary of a single log file:"
  492. print " Syntax: parseerrors logfile"
  493. print "To get a summary of the two most recent log files:"
  494. print " Syntax: parseerrors 1 2"
  495. print "Log files can also be indicated by number where '1' is the"
  496. print "most recent, '2' is second oldest, etc."
  497. sys.exit(0)
  498. newname = GetLogFileName(sys.argv[1])
  499. resultnew = ParseLog(newname)
  500. if len(sys.argv) >= 3:
  501. oldname = GetLogFileName(sys.argv[2])
  502. resultold = ParseLog(oldname)
  503. result = DumpNewWarnings(resultold, resultnew, oldname, newname)
  504. errorCode = result[0]
  505. fatalWarningsFound = result[1]
  506. if fatalWarningsFound == 0:
  507. if analyzeconfig.updateLastKnownGood:
  508. print "Updating last-known-good."
  509. lkgOutput = open(lkgFilename, "wt")
  510. lkgOutput.write(newname)
  511. else:
  512. print "Updating last-known-good is disabled."
  513. sys.exit(errorCode)
  514. else:
  515. DumpWarnings(resultnew, False)