From 0448002b1755eadd531fe1013cf46ea3ef4acfa5 Mon Sep 17 00:00:00 2001 From: Bernhard Reutner-Fischer Date: Fri, 5 Oct 2018 00:28:18 +0200 Subject: [PATCH] contrib: unused_functions.py: Handle archives one can now use verbatim the arguments used by the driver invocation to link e.g. cc1. 2018-10-04 Bernhard Reutner-Fischer * unused_functions.py: Handle archive files. From-SVN: r264856 --- contrib/ChangeLog | 4 ++ contrib/unused_functions.py | 127 +++++++++++++++++++++++++++--------- 2 files changed, 101 insertions(+), 30 deletions(-) diff --git a/contrib/ChangeLog b/contrib/ChangeLog index c1a68e6d8f7..498abb50196 100644 --- a/contrib/ChangeLog +++ b/contrib/ChangeLog @@ -1,3 +1,7 @@ +2018-10-04 Bernhard Reutner-Fischer + + * unused_functions.py: Handle archive files. + 2018-10-04 Bernhard Reutner-Fischer * unused_functions.py: New file. diff --git a/contrib/unused_functions.py b/contrib/unused_functions.py index 85b65c7982f..bf7cf36854a 100755 --- a/contrib/unused_functions.py +++ b/contrib/unused_functions.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # # Copyright (c) 2018 Free Software Foundation # Contributed by Bernhard Reutner-Fischer @@ -17,71 +18,125 @@ # 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] [-- ]\n" + sys.stderr.write("usage: %s [-v] [dirs | files] [-- ]\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 @@ -92,8 +147,8 @@ def resolve(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 @@ -101,10 +156,22 @@ def resolve(prog): 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))