blob: b7974b0d79be7aafa5ea9ac6de13e193143a3eae [file] [log] [blame]
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
Run this script to summarize VC++ warnings and errors. This is normally used
to summarize the results of Chrome's /analyze runs. Just pass the name of
a file containing build output -- typically with a _full.txt ending -- and
a *_summary.txt file will be created. The warnings are grouped
by warning number, sorted by source file/line, and uniquified.
In addition a summary is created at the end that records how many unique
warnings of each type there are, and which warning numbers were the noisiest.
If you pass -codesnippets as the final argument then a few lines of code will
be extracted for each warning. This is useful for creating summaries of fixed
import re
import sys
import os
from collections import defaultdict
grabCodeSnippets = False
if len(sys.argv) < 2:
print "Missing input filename."
inputName = sys.argv[1]
outputName = inputName.replace("_full", "_summary")
if inputName == outputName:
outputName += "_summary.txt"
if len(sys.argv) > 2:
if sys.argv[2] == "-codesnippets":
grabCodeSnippets = True
snippetExtent = 3 # How many lines of context to grap, +/-
print "Unsupported command line option."
# Code snippets, typically used for filing bugs or demonstrating issues,
# don't need detailed line information, so strip it out.
stripLines = grabCodeSnippets
# Typical warning and error patterns:
# wspiapi.h(933) : warning C6102: Using 'SystemDir'
# : warning C6387: 'child_thread_handle' could be '0':
# unistr.cpp(1823) : warning C28193: 'temporary value' holds a value that must
# be examined.: Lines: 1823, 1824
# LINK : warning LNK4014: cannot find member object nativec\\malloc.obj
# hash_set(17): error C2338: <hash_set> is deprecated and will be REMOVED
# Regex to extract warning/error number for processing warnings.
# Note that in VS 2015 there is no space before the colon before 'warning'
warningRe = re.compile(r".*: (warning|error) (C\d{4,5}|LNK\d{4,5}):")
# Regex to extract file/line/"warning" and line number. This is used when
# grabbing a snippet of nearby code.
codeRe = re.compile(r"(.*)\((\d*)\) : .*")
failedBuild = False
# For each warning ID we will have a dictionary of uniquified warning lines
warnByID = defaultdict(dict)
# We will also count how many times warnings show up in the raw output, to make
# it easier to address the really noisy ones.
warnCountByID = defaultdict(int)
output = open(outputName, "wt")
# Scan the input building up a database of warnings, discarding duplicates.
for line in open(inputName).readlines():
# Detect and warn on failed builds since their results will be incomplete.
if line.count("subcommand failed") > 0:
failedBuild = True
# Ignore lines without warnings
if line.count(": warning ") == 0 and line.count(": error ") == 0:
# Ignore "Command line warning D9025 : overriding '/WX' with '/WX-'" and
# warnings from depot_tools header files
if line.count("D9025") > 0 or line.count("depot_tools") > 0:
# Ignore warnings from unit tests -- some are intentional
if line.count("") > 0:
match = warningRe.match(line)
if match:
warningID = match.groups()[1]
warningID = " " # A few warnings lack a warning ID
warnCountByID[warningID] += 1
# Insert warnings (warning ID and text) into a dictionary to automatically
# purge duplicates.
if stripLines:
linesIndex = line.find(": Lines:")
if linesIndex >= 0:
line = line[:linesIndex]
warnByID[warningID][line.strip()] = True
if failedBuild:
print >>output, "Build did not entirely succeed!"
warnIDsByCount = warnByID.keys()
# Sort by (post uniquification) warning frequency, least frequent first
# Sort first by ID, and then by ID frequency. Sort is stable so this gives us
# consistent results within warning IDs with the same frequency
warnIDsByCount.sort(lambda x, y: cmp(x, y))
warnIDsByCount.sort(lambda x, y: cmp(len(warnByID[x]), len(warnByID[y])))
# Print all the warnings, grouped by warning ID and sorted by frequency,
# then filename
totalWarnings = 0
print >>output, "All warnings by type, sorted by count:"
for warnID in warnIDsByCount:
warningLines = warnByID[warnID].keys()
totalWarnings += len(warningLines)
warningLines.sort() # Sort by file name
for warningText in warningLines:
print >>output, warningText
if grabCodeSnippets:
codeMatch = codeRe.match(warningText)
if codeMatch:
file, line = codeMatch.groups()
line = int(line)
lines = open(file).readlines()
lines = lines[line-snippetExtent-1:line+snippetExtent]
for line in lines:
print >>output, line,
print >>output # Blank separator line between warning types
print >>output, "Warning counts by type, sorted by count:"
for warnID in warnIDsByCount:
warningLines = warnByID[warnID].keys()
# Get a sample of this type of warning
warningExemplar = warningLines[0]
# Clean up the warning exemplar
linesIndex = warningExemplar.find(": Lines:")
if linesIndex >= 0:
warningExemplar = warningExemplar[:linesIndex]
warnIDIndex = warningExemplar.find(warnID)
if warnIDIndex >= 0:
warningExemplar = warningExemplar[warnIDIndex + len(warnID) + 2:]
# Print the warning count and warning number -- omitting the leading 'C' or
# 'LNK' so that searching on C6001 won't find the summary.
count = len(warnByID[warnID])
while warnID[0].isalpha():
warnID = warnID[1:]
print >>output, "%4d: %5s, eg. %s" % (count, warnID, warningExemplar)
print >>output
print >>output, "%d warnings of %d types" % (totalWarnings, len(warnIDsByCount))
print >>output
print >>output, "Noisy warning counts in raw output:"
totalRawCount = 0
for warnID in warnCountByID.keys():
if warnCountByID[warnID] > 500:
print >>output, "%5s: %6d" % (warnID[1:], warnCountByID[warnID])
totalRawCount += warnCountByID[warnID]
print >>output, "Total: %6d" % totalRawCount