#!/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()