annotate contrib/unused_functions.py @ 158:494b0b89df80 default tip

...
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Mon, 25 May 2020 18:13:55 +0900
parents 84e7813d76e9
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
131
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
1 #!/usr/bin/env python
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
2 # -*- coding: utf-8 -*-
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
3 #
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
4 # Copyright (c) 2018 Free Software Foundation
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
5 # Contributed by Bernhard Reutner-Fischer <aldot@gcc.gnu.org>
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
6 # Inspired by bloat-o-meter from busybox.
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
7
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
8 # This software may be used and distributed according to the terms and
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
9 # conditions of the GNU General Public License as published by the Free
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
10 # Software Foundation.
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
11
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
12 # For a set of object-files, determine symbols that are
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
13 # - public but should be static
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
14
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
15 # Examples:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
16 # unused_functions.py ./gcc/fortran
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
17 # unused_functions.py gcc/c gcc/c-family/ gcc/*-c.o | grep -v "'gt_"
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
18 # unused_functions.py gcc/cp gcc/c-family/ gcc/*-c.o | grep -v "'gt_"
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
19
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
20 import sys, os
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
21 from tempfile import mkdtemp
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
22 from subprocess import Popen, PIPE
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
23
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
24 def usage():
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
25 sys.stderr.write("usage: %s [-v] [dirs | files] [-- <readelf options>]\n"
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
26 % sys.argv[0])
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
27 sys.stderr.write("\t-v\tVerbose output\n");
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
28 sys.exit(1)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
29
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
30 (odir, sym_args, tmpd, verbose) = (set(), "", None, False)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
31
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
32 for i in range(1, len(sys.argv)):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
33 f = sys.argv[i]
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
34 if f == '--': # sym_args
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
35 sym_args = ' '.join(sys.argv[i + 1:])
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
36 break
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
37 if f == '-v':
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
38 verbose = True
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
39 continue
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
40 if not os.path.exists(f):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
41 sys.stderr.write("Error: No such file or directory '%s'\n" % f)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
42 usage()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
43 else:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
44 if f.endswith('.a') and tmpd is None:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
45 tmpd = mkdtemp(prefix='unused_fun')
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
46 odir.add(f)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
47
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
48 def dbg(args):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
49 if not verbose: return
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
50 print(args)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
51
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
52 def get_symbols(file):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
53 syms = {}
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
54 rargs = "readelf -W -s %s %s" % (sym_args, file)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
55 p0 = Popen((a for a in rargs.split(' ') if a.strip() != ''), stdout=PIPE)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
56 p1 = Popen(["c++filt"], stdin=p0.stdout, stdout=PIPE,
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
57 universal_newlines=True)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
58 lines = p1.communicate()[0]
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
59 for l in lines.split('\n'):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
60 l = l.strip()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
61 if not len(l) or not l[0].isdigit(): continue
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
62 larr = l.split()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
63 if len(larr) != 8: continue
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
64 num, value, size, typ, bind, vis, ndx, name = larr
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
65 if typ == 'SECTION' or typ == 'FILE': continue
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
66 # I don't think we have many aliases in gcc, re-instate the addr
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
67 # lut otherwise.
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
68 if vis != 'DEFAULT': continue
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
69 #value = int(value, 16)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
70 #size = int(size, 16) if size.startswith('0x') else int(size)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
71 defined = ndx != 'UND'
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
72 globl = bind == 'GLOBAL'
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
73 # c++ RID_FUNCTION_NAME dance. FORNOW: Handled as local use
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
74 # Is that correct?
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
75 if name.endswith('::__FUNCTION__') and typ == 'OBJECT':
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
76 name = name[0:(len(name) - len('::__FUNCTION__'))]
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
77 if defined: defined = False
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
78 if defined and not globl: continue
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
79 syms.setdefault(name, {})
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
80 syms[name][['use','def'][defined]] = True
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
81 syms[name][['local','global'][globl]] = True
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
82 # Note: we could filter out e.g. debug_* symbols by looking for
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
83 # value in the debug_macro sections.
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
84 if p1.returncode != 0:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
85 print("Warning: Reading file '%s' exited with %r|%r"
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
86 % (file, p0.returncode, p1.returncode))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
87 p0.kill()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
88 return syms
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
89
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
90 (oprog, nprog) = ({}, {})
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
91
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
92 def walker(paths):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
93 def ar_x(archive):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
94 dbg("Archive %s" % path)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
95 f = os.path.abspath(archive)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
96 f = os.path.splitdrive(f)[1]
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
97 d = tmpd + os.path.sep + f
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
98 d = os.path.normpath(d)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
99 owd = os.getcwd()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
100 try:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
101 os.makedirs(d)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
102 os.chdir(d)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
103 p0 = Popen(["ar", "x", "%s" % os.path.join(owd, archive)],
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
104 stderr=PIPE, universal_newlines=True)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
105 p0.communicate()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
106 if p0.returncode > 0: d = None # assume thin archive
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
107 except:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
108 dbg("ar x: Error: %s: %s" % (archive, sys.exc_info()[0]))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
109 os.chdir(owd)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
110 raise
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
111 os.chdir(owd)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
112 if d: dbg("Extracted to %s" % (d))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
113 return (archive, d)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
114
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
115 def ar_t(archive):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
116 dbg("Thin archive, using existing files:")
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
117 try:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
118 p0 = Popen(["ar", "t", "%s" % archive], stdout=PIPE,
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
119 universal_newlines=True)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
120 ret = p0.communicate()[0]
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
121 return ret.split('\n')
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
122 except:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
123 dbg("ar t: Error: %s: %s" % (archive, sys.exc_info()[0]))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
124 raise
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
125
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
126 prog = {}
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
127 for path in paths:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
128 if os.path.isdir(path):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
129 for r, dirs, files in os.walk(path):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
130 if files: dbg("Files %s" % ", ".join(files))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
131 if dirs: dbg("Dirs %s" % ", ".join(dirs))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
132 prog.update(walker([os.path.join(r, f) for f in files]))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
133 prog.update(walker([os.path.join(r, d) for d in dirs]))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
134 else:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
135 if path.endswith('.a'):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
136 if ar_x(path)[1] is not None: continue # extract worked
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
137 prog.update(walker(ar_t(path)))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
138 if not path.endswith('.o'): continue
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
139 dbg("Reading symbols from %s" % (path))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
140 prog[os.path.normpath(path)] = get_symbols(path)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
141 return prog
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
142
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
143 def resolve(prog):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
144 x = prog.keys()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
145 use = set()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
146 # for each unique pair of different files
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
147 for (f, g) in ((f,g) for f in x for g in x if f != g):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
148 refs = set()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
149 # for each defined symbol
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
150 for s in (s for s in prog[f] if prog[f][s].get('def') and s in prog[g]):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
151 if prog[g][s].get('use'):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
152 refs.add(s)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
153 for s in refs:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
154 # Prune externally referenced symbols as speed optimization only
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
155 for i in (i for i in x if s in prog[i]): del prog[i][s]
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
156 use |= refs
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
157 return use
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
158
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
159 try:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
160 oprog = walker(odir)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
161 if tmpd is not None:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
162 oprog.update(walker([tmpd]))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
163 oused = resolve(oprog)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
164 finally:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
165 try:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
166 p0 = Popen(["rm", "-r", "-f", "%s" % (tmpd)], stderr=PIPE, stdout=PIPE)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
167 p0.communicate()
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
168 if p0.returncode != 0: raise "rm '%s' didn't work out" % (tmpd)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
169 except:
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
170 from shutil import rmtree
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
171 rmtree(tmpd, ignore_errors=True)
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
172
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
173 for (i,s) in ((i,s) for i in oprog.keys() for s in oprog[i] if oprog[i][s]):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
174 if oprog[i][s].get('def') and not oprog[i][s].get('use'):
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
175 print("%s: Symbol '%s' declared extern but never referenced externally"
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
176 % (i,s))
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
177
84e7813d76e9 gcc-8.2
mir3636
parents:
diff changeset
178