|  | #!/usr/bin/env python | 
|  | # Copyright (c) 2011 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. | 
|  |  | 
|  | """Prints a report of symbols stripped by the linker due to being unused. | 
|  |  | 
|  | To use, build with these linker flags: | 
|  | -Wl,--gc-sections | 
|  | -Wl,--print-gc-sections | 
|  | the first one is the default in Release; search build/common.gypi for it | 
|  | and to see where to add the other. | 
|  |  | 
|  | Then build, saving the output into a file: | 
|  | make chrome 2>&1 | tee buildlog | 
|  | and run this script on it: | 
|  | ./tools/unused-symbols-report.py buildlog > report.html | 
|  | """ | 
|  |  | 
|  | import cgi | 
|  | import optparse | 
|  | import os | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  |  | 
|  | cppfilt_proc = None | 
|  | def Demangle(sym): | 
|  | """Demangle a C++ symbol by passing it through c++filt.""" | 
|  | global cppfilt_proc | 
|  | if cppfilt_proc is None: | 
|  | cppfilt_proc = subprocess.Popen(['c++filt'], stdin=subprocess.PIPE, | 
|  | stdout=subprocess.PIPE) | 
|  | print >>cppfilt_proc.stdin, sym | 
|  | return cppfilt_proc.stdout.readline().strip() | 
|  |  | 
|  |  | 
|  | def Unyuck(sym): | 
|  | """Attempt to prettify a C++ symbol by some basic heuristics.""" | 
|  | sym = sym.replace('std::basic_string<char, std::char_traits<char>, ' | 
|  | 'std::allocator<char> >', 'std::string') | 
|  | sym = sym.replace('std::basic_string<wchar_t, std::char_traits<wchar_t>, ' | 
|  | 'std::allocator<wchar_t> >', 'std::wstring') | 
|  | sym = sym.replace('std::basic_string<unsigned short, ' | 
|  | 'base::string16_char_traits, ' | 
|  | 'std::allocator<unsigned short> >', 'string16') | 
|  | sym = re.sub(r', std::allocator<\S+\s+>', '', sym) | 
|  | return sym | 
|  |  | 
|  |  | 
|  | def Parse(input, skip_paths=None, only_paths=None): | 
|  | """Parse the --print-gc-sections build output. | 
|  |  | 
|  | Args: | 
|  | input: iterable over the lines of the build output | 
|  |  | 
|  | Yields: | 
|  | (target name, path to .o file, demangled symbol) | 
|  | """ | 
|  | symbol_re = re.compile(r"'\.text\.(\S+)' in file '(\S+)'$") | 
|  | path_re = re.compile(r"^out/[^/]+/[^/]+/([^/]+)/(.*)$") | 
|  | for line in input: | 
|  | match = symbol_re.search(line) | 
|  | if not match: | 
|  | continue | 
|  | symbol, path = match.groups() | 
|  | symbol = Unyuck(Demangle(symbol)) | 
|  | path = os.path.normpath(path) | 
|  | if skip_paths and skip_paths in path: | 
|  | continue | 
|  | if only_paths and only_paths not in path: | 
|  | continue | 
|  | match = path_re.match(path) | 
|  | if not match: | 
|  | print >>sys.stderr, "Skipping weird path", path | 
|  | continue | 
|  | target, path = match.groups() | 
|  | yield target, path, symbol | 
|  |  | 
|  |  | 
|  | # HTML header for our output page. | 
|  | TEMPLATE_HEADER = """<!DOCTYPE html> | 
|  | <head> | 
|  | <style> | 
|  | body { | 
|  | font-family: sans-serif; | 
|  | font-size: 0.8em; | 
|  | } | 
|  | h1, h2 { | 
|  | font-weight: normal; | 
|  | margin: 0.5em 0; | 
|  | } | 
|  | h2 { | 
|  | margin-top: 1em; | 
|  | } | 
|  | tr:hover { | 
|  | background: #eee; | 
|  | } | 
|  | .permalink { | 
|  | padding-left: 1ex; | 
|  | font-size: 80%; | 
|  | text-decoration: none; | 
|  | color: #ccc; | 
|  | } | 
|  | .symbol { | 
|  | font-family: WebKitWorkAround, monospace; | 
|  | margin-left: 4ex; | 
|  | text-indent: -4ex; | 
|  | padding: 0.5ex 1ex; | 
|  | } | 
|  | .file { | 
|  | padding: 0.5ex 1ex; | 
|  | padding-left: 2ex; | 
|  | font-family: WebKitWorkAround, monospace; | 
|  | font-size: 90%; | 
|  | color: #777; | 
|  | } | 
|  | </style> | 
|  | </head> | 
|  | <body> | 
|  | <h1>chrome symbols deleted at link time</h1> | 
|  | """ | 
|  |  | 
|  |  | 
|  | def Output(iter): | 
|  | """Print HTML given an iterable of (target, path, symbol) tuples.""" | 
|  | targets = {} | 
|  | for target, path, symbol in iter: | 
|  | entries = targets.setdefault(target, []) | 
|  | entries.append((symbol, path)) | 
|  |  | 
|  | print TEMPLATE_HEADER | 
|  | print "<p>jump to target:" | 
|  | print "<select onchange='document.location.hash = this.value'>" | 
|  | for target in sorted(targets.keys()): | 
|  | print "<option>%s</option>" % target | 
|  | print "</select></p>" | 
|  |  | 
|  | for target in sorted(targets.keys()): | 
|  | print "<h2>%s" % target | 
|  | print "<a class=permalink href='#%s' name='%s'>#</a>" % (target, target) | 
|  | print "</h2>" | 
|  | print "<table width=100% cellspacing=0>" | 
|  | for symbol, path in sorted(targets[target]): | 
|  | htmlsymbol = cgi.escape(symbol).replace('::', '::<wbr>') | 
|  | print "<tr><td><div class=symbol>%s</div></td>" % htmlsymbol | 
|  | print "<td valign=top><div class=file>%s</div></td></tr>" % path | 
|  | print "</table>" | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = optparse.OptionParser(usage='%prog [options] buildoutput\n\n' + | 
|  | __doc__) | 
|  | parser.add_option("--skip-paths", metavar="STR", default="third_party", | 
|  | help="skip paths matching STR [default=%default]") | 
|  | parser.add_option("--only-paths", metavar="STR", | 
|  | help="only include paths matching STR [default=%default]") | 
|  | opts, args = parser.parse_args() | 
|  |  | 
|  | if len(args) < 1: | 
|  | parser.print_help() | 
|  | sys.exit(1) | 
|  |  | 
|  | iter = Parse(open(args[0]), | 
|  | skip_paths=opts.skip_paths, | 
|  | only_paths=opts.only_paths) | 
|  | Output(iter) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |