#!/usr/bin/env python
+# -*- coding: utf-8 -*-
#
# Copyright (c) 2018 Free Software Foundation
# Contributed by Bernhard Reutner-Fischer <aldot@gcc.gnu.org>
# unused_functions.py gcc/cp gcc/c-family/ gcc/*-c.o | grep -v "'gt_"
import sys, os
+from tempfile import mkdtemp
+from subprocess import Popen, PIPE
def usage():
- sys.stderr.write("usage: %s [dirs | files] [-- <readelf options>]\n"
+ sys.stderr.write("usage: %s [-v] [dirs | files] [-- <readelf options>]\n"
% sys.argv[0])
+ sys.stderr.write("\t-v\tVerbose output\n");
sys.exit(1)
-(odir, sym_args) = (set(), "")
+(odir, sym_args, tmpd, verbose) = (set(), "", None, False)
for i in range(1, len(sys.argv)):
f = sys.argv[i]
- if f == "--": # sym_args
- sym_args = " ".join(sys.argv[i + 1:])
+ if f == '--': # sym_args
+ sym_args = ' '.join(sys.argv[i + 1:])
break
+ if f == '-v':
+ verbose = True
+ continue
if not os.path.exists(f):
sys.stderr.write("Error: No such file or directory '%s'\n" % f)
usage()
else:
+ if f.endswith('.a') and tmpd is None:
+ tmpd = mkdtemp(prefix='unused_fun')
odir.add(f)
+def dbg(args):
+ if not verbose: return
+ print(args)
+
def get_symbols(file):
syms = {}
- for l in os.popen("readelf -W -s %s %s | c++filt" % (sym_args, file)).readlines():
+ rargs = "readelf -W -s %s %s" % (sym_args, file)
+ p0 = Popen((a for a in rargs.split(' ') if a.strip() != ''), stdout=PIPE)
+ p1 = Popen(["c++filt"], stdin=p0.stdout, stdout=PIPE,
+ universal_newlines=True)
+ lines = p1.communicate()[0]
+ for l in lines.split('\n'):
l = l.strip()
- if not (len(l) and l[0].isdigit() and len(l.split()) == 8):
- continue
- num, value, size, typ, bind, vis, ndx, name = l.split()
+ if not len(l) or not l[0].isdigit(): continue
+ larr = l.split()
+ if len(larr) != 8: continue
+ num, value, size, typ, bind, vis, ndx, name = larr
if typ == 'SECTION' or typ == 'FILE': continue
# I don't think we have many aliases in gcc, re-instate the addr
# lut otherwise.
- if vis != "DEFAULT": continue
+ if vis != 'DEFAULT': continue
#value = int(value, 16)
#size = int(size, 16) if size.startswith('0x') else int(size)
- defined = ndx != "UND"
- globl = bind == "GLOBAL"
+ defined = ndx != 'UND'
+ globl = bind == 'GLOBAL'
# c++ RID_FUNCTION_NAME dance. FORNOW: Handled as local use
# Is that correct?
- if name.endswith("::__FUNCTION__") and typ == "OBJECT":
- name = name[0:(len(name) - len("::__FUNCTION__"))]
+ if name.endswith('::__FUNCTION__') and typ == 'OBJECT':
+ name = name[0:(len(name) - len('::__FUNCTION__'))]
if defined: defined = False
if defined and not globl: continue
syms.setdefault(name, {})
- syms[name][["use","def"][defined]] = True
- syms[name][["local","global"][globl]] = True
+ syms[name][['use','def'][defined]] = True
+ syms[name][['local','global'][globl]] = True
# Note: we could filter out e.g. debug_* symbols by looking for
# value in the debug_macro sections.
+ if p1.returncode != 0:
+ print("Warning: Reading file '%s' exited with %r|%r"
+ % (file, p0.returncode, p1.returncode))
+ p0.kill()
return syms
(oprog, nprog) = ({}, {})
def walker(paths):
+ def ar_x(archive):
+ dbg("Archive %s" % path)
+ f = os.path.abspath(archive)
+ f = os.path.splitdrive(f)[1]
+ d = tmpd + os.path.sep + f
+ d = os.path.normpath(d)
+ owd = os.getcwd()
+ try:
+ os.makedirs(d)
+ os.chdir(d)
+ p0 = Popen(["ar", "x", "%s" % os.path.join(owd, archive)],
+ stderr=PIPE, universal_newlines=True)
+ p0.communicate()
+ if p0.returncode > 0: d = None # assume thin archive
+ except:
+ dbg("ar x: Error: %s: %s" % (archive, sys.exc_info()[0]))
+ os.chdir(owd)
+ raise
+ os.chdir(owd)
+ if d: dbg("Extracted to %s" % (d))
+ return (archive, d)
+
+ def ar_t(archive):
+ dbg("Thin archive, using existing files:")
+ try:
+ p0 = Popen(["ar", "t", "%s" % archive], stdout=PIPE,
+ universal_newlines=True)
+ ret = p0.communicate()[0]
+ return ret.split('\n')
+ except:
+ dbg("ar t: Error: %s: %s" % (archive, sys.exc_info()[0]))
+ raise
+
prog = {}
for path in paths:
if os.path.isdir(path):
for r, dirs, files in os.walk(path):
- for f in files:
- # TODO: maybe extract .a to a tmpdir and walk that, too
- # maybe /there/foolib.a(file.o) as name?
- if not f.endswith(".o"): continue
- p = os.path.join(r, f)
- prog[os.path.normpath(p)] = get_symbols(p)
- for d in dirs:
- tem = prog.copy()
- tem.update(walker([os.path.join(r, d)]))
- prog = tem
+ if files: dbg("Files %s" % ", ".join(files))
+ if dirs: dbg("Dirs %s" % ", ".join(dirs))
+ prog.update(walker([os.path.join(r, f) for f in files]))
+ prog.update(walker([os.path.join(r, d) for d in dirs]))
else:
+ if path.endswith('.a'):
+ if ar_x(path)[1] is not None: continue # extract worked
+ prog.update(walker(ar_t(path)))
+ if not path.endswith('.o'): continue
+ dbg("Reading symbols from %s" % (path))
prog[os.path.normpath(path)] = get_symbols(path)
return prog
for (f, g) in ((f,g) for f in x for g in x if f != g):
refs = set()
# for each defined symbol
- for s in (s for s in prog[f] if prog[f][s].get("def") and s in prog[g]):
- if prog[g][s].get("use"):
+ for s in (s for s in prog[f] if prog[f][s].get('def') and s in prog[g]):
+ if prog[g][s].get('use'):
refs.add(s)
for s in refs:
# Prune externally referenced symbols as speed optimization only
use |= refs
return use
-oprog = walker(odir)
-oused = resolve(oprog)
+try:
+ oprog = walker(odir)
+ if tmpd is not None:
+ oprog.update(walker([tmpd]))
+ oused = resolve(oprog)
+finally:
+ try:
+ p0 = Popen(["rm", "-r", "-f", "%s" % (tmpd)], stderr=PIPE, stdout=PIPE)
+ p0.communicate()
+ if p0.returncode != 0: raise "rm '%s' didn't work out" % (tmpd)
+ except:
+ from shutil import rmtree
+ rmtree(tmpd, ignore_errors=True)
+
for (i,s) in ((i,s) for i in oprog.keys() for s in oprog[i] if oprog[i][s]):
- if oprog[i][s].get("def") and not oprog[i][s].get("use"):
+ if oprog[i][s].get('def') and not oprog[i][s].get('use'):
print("%s: Symbol '%s' declared extern but never referenced externally"
% (i,s))