1#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Prints a report of symbols stripped by the linker due to being unused.
7
8To use, build with these linker flags:
9  -Wl,--gc-sections
10  -Wl,--print-gc-sections
11the first one is the default in Release; search build/common.gypi for it
12and to see where to add the other.
13
14Then build, saving the output into a file:
15  make chrome 2>&1 | tee buildlog
16and run this script on it:
17  ./tools/unused-symbols-report.py buildlog > report.html
18"""
19
20import cgi
21import optparse
22import os
23import re
24import subprocess
25import sys
26
27cppfilt_proc = None
28def Demangle(sym):
29  """Demangle a C++ symbol by passing it through c++filt."""
30  global cppfilt_proc
31  if cppfilt_proc is None:
32    cppfilt_proc = subprocess.Popen(['c++filt'], stdin=subprocess.PIPE,
33                                    stdout=subprocess.PIPE)
34  print >>cppfilt_proc.stdin, sym
35  return cppfilt_proc.stdout.readline().strip()
36
37
38def Unyuck(sym):
39  """Attempt to prettify a C++ symbol by some basic heuristics."""
40  sym = sym.replace('std::basic_string<char, std::char_traits<char>, '
41                    'std::allocator<char> >', 'std::string')
42  sym = sym.replace('std::basic_string<wchar_t, std::char_traits<wchar_t>, '
43                    'std::allocator<wchar_t> >', 'std::wstring')
44  sym = sym.replace('std::basic_string<unsigned short, '
45                    'base::string16_char_traits, '
46                    'std::allocator<unsigned short> >', 'string16')
47  sym = re.sub(r', std::allocator<\S+\s+>', '', sym)
48  return sym
49
50
51def Parse(input, skip_paths=None, only_paths=None):
52  """Parse the --print-gc-sections build output.
53
54  Args:
55    input: iterable over the lines of the build output
56
57  Yields:
58    (target name, path to .o file, demangled symbol)
59  """
60  symbol_re = re.compile(r"'\.text\.(\S+)' in file '(\S+)'$")
61  path_re = re.compile(r"^out/[^/]+/[^/]+/([^/]+)/(.*)$")
62  for line in input:
63    match = symbol_re.search(line)
64    if not match:
65      continue
66    symbol, path = match.groups()
67    symbol = Unyuck(Demangle(symbol))
68    path = os.path.normpath(path)
69    if skip_paths and skip_paths in path:
70      continue
71    if only_paths and only_paths not in path:
72      continue
73    match = path_re.match(path)
74    if not match:
75      print >>sys.stderr, "Skipping weird path", path
76      continue
77    target, path = match.groups()
78    yield target, path, symbol
79
80
81# HTML header for our output page.
82TEMPLATE_HEADER = """<!DOCTYPE html>
83<head>
84<style>
85body {
86  font-family: sans-serif;
87  font-size: 0.8em;
88}
89h1, h2 {
90  font-weight: normal;
91  margin: 0.5em 0;
92}
93h2 {
94  margin-top: 1em;
95}
96tr:hover {
97  background: #eee;
98}
99.permalink {
100  padding-left: 1ex;
101  font-size: 80%;
102  text-decoration: none;
103  color: #ccc;
104}
105.symbol {
106  font-family: WebKitWorkAround, monospace;
107  margin-left: 4ex;
108  text-indent: -4ex;
109  padding: 0.5ex 1ex;
110}
111.file {
112  padding: 0.5ex 1ex;
113  padding-left: 2ex;
114  font-family: WebKitWorkAround, monospace;
115  font-size: 90%;
116  color: #777;
117}
118</style>
119</head>
120<body>
121<h1>chrome symbols deleted at link time</h1>
122"""
123
124
125def Output(iter):
126  """Print HTML given an iterable of (target, path, symbol) tuples."""
127  targets = {}
128  for target, path, symbol in iter:
129    entries = targets.setdefault(target, [])
130    entries.append((symbol, path))
131
132  print TEMPLATE_HEADER
133  print "<p>jump to target:"
134  print "<select onchange='document.location.hash = this.value'>"
135  for target in sorted(targets.keys()):
136    print "<option>%s</option>" % target
137  print "</select></p>"
138
139  for target in sorted(targets.keys()):
140    print "<h2>%s" % target
141    print "<a class=permalink href='#%s' name='%s'>#</a>" % (target, target)
142    print "</h2>"
143    print "<table width=100% cellspacing=0>"
144    for symbol, path in sorted(targets[target]):
145      htmlsymbol = cgi.escape(symbol).replace('::', '::<wbr>')
146      print "<tr><td><div class=symbol>%s</div></td>" % htmlsymbol
147      print "<td valign=top><div class=file>%s</div></td></tr>" % path
148    print "</table>"
149
150
151def main():
152  parser = optparse.OptionParser(usage='%prog [options] buildoutput\n\n' +
153                                 __doc__)
154  parser.add_option("--skip-paths", metavar="STR", default="third_party",
155                    help="skip paths matching STR [default=%default]")
156  parser.add_option("--only-paths", metavar="STR",
157                    help="only include paths matching STR [default=%default]")
158  opts, args = parser.parse_args()
159
160  if len(args) < 1:
161    parser.print_help()
162    sys.exit(1)
163
164  iter = Parse(open(args[0]),
165               skip_paths=opts.skip_paths,
166               only_paths=opts.only_paths)
167  Output(iter)
168
169
170if __name__ == '__main__':
171  main()
172