pruner mostly working
[org.ibex.core.git] / src / org / ibex / util / BytecodePruner.java
1 package org.ibex.util;
2 import java.util.*;
3 import java.io.*;
4 import java.util.zip.*;
5 import org.apache.bcel.*;
6 import org.apache.bcel.generic.*;
7 import org.apache.bcel.classfile.*;
8 import org.apache.bcel.util.*;
9
10 // Reachability rules:
11
12 // - a constructor is reachable iff it is called
13 // - a static method is reachable iff it is called
14 // - a nonstatic method is reachable 
15 // - a static field is reachable iff it is referenced
16 // - a nonstatic field is reachable iff it is referenced
17 // - <clinit> is reachable iff any methods, static methods, fields, or constructors are reachable
18
19 // - if a method is reachable, all the methods it overrides are reachable
20
21 // FIXME: nonstatic method invocation or field access implies that object will be constructed (ie hint)
22
23 public class BytecodePruner {
24
25     // FIXME
26     public static SyntheticRepository repo = null;
27
28     public static HashSet dest = new HashSet();
29
30     public static String outdir = ".";
31
32     public void loadAllMethods(String classname) throws Exception {
33         visitJavaClass(repo.loadClass(classname));
34         Method[] meths = repo.loadClass(classname).getMethods();
35         for(int i=0; i<meths.length; i++) visitJavaMethod(repo.loadClass(classname), meths[i]);
36     }
37     public void loadMethod(String classAndMethodName) throws Exception {
38         String classname = classAndMethodName.substring(0, classAndMethodName.lastIndexOf('.'));
39         String methodname = classAndMethodName.substring(classAndMethodName.lastIndexOf('.') + 1);
40         visitJavaClass(repo.loadClass(classname));
41         Method[] meths = repo.loadClass(classname).getMethods();
42         for(int i=0; i<meths.length; i++)
43             if (meths[i].getName().equals(methodname))
44                 visitJavaMethod(repo.loadClass(classname), meths[i]);
45     }
46     public static void main(String[] s) throws Exception {
47         int start = 1;
48         if (s.length >= 3 && s[1].equals("-o")) { outdir = s[2]; start += 2; }
49         repo = SyntheticRepository.getInstance(new ClassPath(s[0]));
50         BytecodePruner bcp = new BytecodePruner();
51
52         for(int i=start; i<s.length; i++) {
53             try {
54                 if (s[i].endsWith(".class")) {
55                     bcp.visitJavaClass(repo.loadClass(s[i].substring(0, s[i].length() - 6)));
56                 } else {
57                     JavaClass cl = repo.loadClass(s[i].substring(0, s[i].lastIndexOf('.')));;
58                     bcp.visitJavaClass(cl);
59                     Method[] meths = cl.getMethods();
60                     for(int j=0; j<meths.length; j++) {
61                         if (meths[j].getName().equals(s[i].substring(s[i].lastIndexOf('.') + 1)))
62                             bcp.visitJavaMethod(cl, meths[j]);
63                     }
64                 }
65             } catch (Exception e) {
66                 System.out.println("WARNING: couldn't load class for " + s[i]);
67             }
68         }
69         System.out.println("\n\n======================================================================\n");
70
71         // we call start(), but the VM calls run()...
72         bcp.loadMethod("java.lang.Thread.run");
73
74         bcp.loadAllMethods("java.lang.Throwable");
75         bcp.loadAllMethods("java.io.PrintStream");
76         bcp.loadAllMethods("gnu.gcj.runtime.StringBuffer");
77         bcp.loadAllMethods("java.security.cert.Certificate");
78
79         bcp.loadMethod("java.util.SimpleTimeZone.useDaylightTime");
80         bcp.loadMethod("java.util.TimeZone.getAvailableIDs");
81         bcp.loadMethod("java.util.TimeZone.getDefaultTimeZoneId");
82         bcp.loadMethod("java.util.Collections$SynchronizedIterator.hasNext");
83         bcp.loadMethod("java.util.Hashtable$HashIterator.hasNext");
84
85         bcp.visitJavaClass(repo.loadClass("java.util.Stack"));
86         bcp.visitJavaClass(repo.loadClass("gnu.classpath.Configuration"));
87         bcp.visitJavaClass(repo.loadClass("gnu.gcj.runtime.JNIWeakRef"));
88
89         bcp.visitJavaClass(repo.loadClass("gnu.gcj.protocol.http.Handler"));
90         bcp.visitJavaClass(repo.loadClass("gnu.gcj.protocol.file.Handler"));
91         bcp.visitJavaClass(repo.loadClass("gnu.gcj.protocol.jar.Handler"));
92         bcp.visitJavaClass(repo.loadClass("gnu.gcj.protocol.core.Handler"));
93         bcp.visitJavaClass(repo.loadClass("gnu.gcj.runtime.FinalizerThread"));
94         bcp.visitJavaClass(repo.loadClass("gnu.gcj.runtime.FirstThread"));
95
96         // SecurityManager hacks to avoid java.security?
97         // URL and all descendents?  Probably impossible.
98         // ObjectInput/ObjectOutput?  Serialization?
99
100         // often called from native subclasses....
101         bcp.loadAllMethods("org.ibex.Surface");
102         bcp.loadAllMethods("org.ibex.Picture");
103         bcp.loadAllMethods("org.ibex.PixelBuffer");
104         bcp.loadAllMethods("org.ibex.Platform");
105         bcp.loadAllMethods("org.ibex.Scheduler");
106         bcp.loadAllMethods("org.ibex.plat.X11");
107         bcp.loadAllMethods("org.ibex.plat.X11$X11Picture");
108         bcp.loadAllMethods("org.ibex.plat.X11$X11PixelBuffer");
109         bcp.loadAllMethods("org.ibex.plat.X11$X11Surface");
110         bcp.loadAllMethods("org.ibex.XMLRPC");
111
112         bcp.loadAllMethods("java.util.Date");
113         bcp.loadAllMethods("java.text.DateFormat");
114         bcp.loadAllMethods("java.text.NumberFormat");
115
116
117         Method[] meths = repo.loadClass("org.ibex.plat.Linux").getMethods();
118         for(int i=0; i<meths.length; i++) {
119             if (meths[i].getName().equals("main"))
120                 bcp.visitJavaMethod(repo.loadClass("org.ibex.plat.Linux"), meths[i]);
121         }
122         System.out.println();
123
124         System.out.println("Dumping...");
125
126         StringTokenizer st = new StringTokenizer(s[0], ":");
127         while(st.hasMoreTokens()) {
128             ZipFile zf = new ZipFile(st.nextToken());
129             Enumeration e = zf.entries();
130             while(e.hasMoreElements()) {
131                 String ss = ((ZipEntry)e.nextElement()).getName();
132                 if (!ss.endsWith(".class")) continue;
133                 ss = ss.substring(0, ss.length() - 6);
134                 ss = ss.replace('/', '.');
135                 dump(repo.loadClass(ss));
136             }
137         }
138     }
139
140     public BytecodePruner() { }
141
142     public static void dump(JavaClass clazz) throws Exception {
143         if (clazz.getClassName().startsWith("java.sql.")) return;
144
145         ConstantPoolGen newcpg = new ConstantPoolGen(clazz.getConstantPool());
146         ClassGen cg = new ClassGen(clazz);
147         InstructionFactory factory = new InstructionFactory(cg, newcpg);
148         cg.setMajor(46);
149         cg.setMinor(0);
150         cg.setConstantPool(newcpg);
151         Field[] fields = clazz.getFields();
152         int numFields = 0;
153         for(int i=0; i<fields.length; i++)
154             if (!dest.contains(fields[i]) && false) { 
155                 System.out.println("  pruning " + clazz.getClassName() + "." + fields[i].getName());
156                 fields[i] = null; 
157             } else numFields++;
158
159         // superprune: URLClassLoader, convert.In/Output other than needed, unneeded locales
160         // reflective metadata is killing us...
161         
162         Method[] methods = clazz.getMethods();
163         int numMethods = 0;
164         boolean good = false;
165         for(int i=0; i<methods.length; i++)
166             if (clazz.getClassName().startsWith("gnu.")
167                 || clazz.getClassName().startsWith("java.lang.")
168                 || clazz.getClassName().startsWith("java.io.")
169                 || clazz.getClassName().startsWith("org.ibex.")) {
170                 good = true;
171             } else if (dest.contains(methods[i])) {
172                 if (!methods[i].getName().equals("<clinit>")) good = true;
173             } else {
174                 if (methods[i].getCode() != null) {
175                     System.out.println("  pruning " + clazz.getClassName() + "." + methods[i].getName());
176                     MethodGen mg = new MethodGen(methods[i], clazz.getClassName(), newcpg);
177                     mg.removeExceptions();
178                     InstructionList il = new InstructionList();
179                     mg.setInstructionList(il);
180
181                     InstructionHandle ih_0 = il.append(factory.createNew("java.lang.UnsatisfiedLinkError"));
182                     il.append(InstructionConstants.DUP);
183                     il.append(factory.createInvoke("java.lang.UnsatisfiedLinkError",
184                                                     "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
185                     il.append(InstructionConstants.ATHROW);
186
187                     mg.setMaxStack();
188                     mg.setMaxLocals();
189                     mg.removeExceptions();
190                     mg.removeLocalVariables();
191                     mg.removeExceptionHandlers();
192                     mg.removeLineNumbers();
193
194                     cg.replaceMethod(methods[i], mg.getMethod());
195                     il.dispose();
196                 }
197             }
198         if ((clazz.getClassName().startsWith("gnu.java.locale.LocaleInformation") &&
199              !clazz.getClassName().endsWith("LocaleInformation_en") &&
200              !clazz.getClassName().endsWith("LocaleInformation") &&
201              !clazz.getClassName().endsWith("LocaleInformation_en_US"))
202             ||
203             (!good &&
204              !clazz.isInterface() &&
205              !clazz.isAbstract() &&
206              !clazz.getClassName().endsWith("Error") &&
207              !clazz.getClassName().endsWith("Exception") &&
208              !clazz.getClassName().endsWith("Permission"))) {
209
210             System.out.println("DROPPING " + clazz.getClassName());
211             return;
212         }
213         new File(outdir + "/" + new File(clazz.getClassName().replace('.', '/')).getParent()).mkdirs();
214         System.out.println("dumping " + clazz.getClassName());
215         cg.getJavaClass().dump(outdir + "/" + clazz.getClassName().replace('.', '/') + ".class");
216     }
217
218     public JavaClass sig2class(String sig) throws Exception {
219         if (sig == null) return null;
220         while (sig.length() > 0 && (sig.charAt(0) == 'L' || sig.charAt(0) == '[')) {
221             if (sig.charAt(0) == 'L') sig = sig.substring(1, sig.length() - 1);
222             else if (sig.charAt(0) == '[') sig = sig.substring(1, sig.length());
223         }
224         if (sig.length() <= 1) return null;
225         if (sig.equals("<null object>")) return null;
226         if (sig.startsWith("<return address")) return null;
227         return repo.loadClass(sig);
228     }
229     public void load(String sig) throws Exception {
230         if (sig == null) return;
231         while (sig.length() > 0 && (sig.charAt(0) == 'L' || sig.charAt(0) == '[')) {
232             if (sig.charAt(0) == 'L') sig = sig.substring(1, sig.length() - 1);
233             else if (sig.charAt(0) == '[') sig = sig.substring(1, sig.length());
234         }
235         if (sig.length() <= 1) return;
236         if (sig.equals("<null object>")) return;
237         if (sig.startsWith("<return address")) return;
238         visitJavaClass(repo.loadClass(sig));
239     }
240     public void load(Type t) throws Exception {
241         if (t == null) return;
242         //String sig = t.getSignature();
243         if (t instanceof ArrayType) load(((ArrayType)t).getElementType());
244         if (!(t instanceof ObjectType)) return;
245         load(((ObjectType)t).getClassName());
246     }
247
248     // hashtable of hashsets
249     public static Hashtable subclasses = new Hashtable();
250
251     public String getMethodSignature(Method m) throws Exception {
252         ConstantPoolGen cpg = new ConstantPoolGen(m.getConstantPool());
253         return m.getName() + m.getSignature();
254     }
255
256     public String getMethodSignature(InvokeInstruction ii, ConstantPoolGen cpg) throws Exception {
257         String sig = "";
258         Type[] argtypes = ii.getArgumentTypes(cpg);
259         for(int j=0; j<argtypes.length; j++) sig += argtypes[j].getSignature();
260         return ii.getMethodName(cpg) + "(" + sig + ")" + ii.getReturnType(cpg).getSignature();
261     }
262     public static int level = 0;
263     public void visitJavaMethod(JavaClass jc, Method method) throws Exception {
264         visitJavaClass(jc);
265         if (dest.contains(method)) return;
266         dest.add(method);
267         level += 2;
268         for(int i=0; i<level; i++) System.out.print(" ");
269         System.out.println(jc.getClassName() + "." + getMethodSignature(method));
270         markMethodInSubclasses(jc, method);
271         ConstantPoolGen cpg = new ConstantPoolGen(method.getConstantPool());
272         if (method.getCode() == null) { level -= 2; return; }
273         byte[] code = method.getCode().getCode();
274         InstructionList il = new InstructionList(code);
275         Instruction[] instructions = il.getInstructions();
276         for(int i=0; i<instructions.length; i++){ 
277             Instruction instr = instructions[i];
278             if (instr instanceof LoadClass) load(((LoadClass)instr).getLoadClassType(cpg));
279             if (instr instanceof CPInstruction) load(((CPInstruction)instr).getType(cpg));
280             if (instr instanceof InvokeInstruction) {
281                 InvokeInstruction ii = (InvokeInstruction)instr;
282                 String ii_sig = getMethodSignature(ii, cpg);
283                 JavaClass c = sig2class(ii.getLoadClassType(cpg).getSignature());
284                 load(ii.getReturnType(cpg));
285                 load(ii.getType(cpg));
286                 Method[] meths = c.getMethods();
287                 boolean good = false;
288                 for(int i2=0; i2<meths.length; i2++) {
289                     if (getMethodSignature(meths[i2]).equals(ii_sig)) {
290                         visitJavaMethod(c, meths[i2]);
291                         good = true;
292                         break;
293                     }
294                 }
295                 if (!good)
296                     throw new Exception("couldn't find method " + getMethodSignature(ii, cpg) + " in " + c.getClassName());
297             }
298         }
299         level -= 2;
300         load(method.getReturnType());
301         Type[] argtypes = method.getArgumentTypes();
302         for(int i=0; i<argtypes.length; i++) load(argtypes[i]);
303         if (method.getExceptionTable() != null) {
304             String[] exntypes = method.getExceptionTable().getExceptionNames();
305             for(int i=0; i<exntypes.length; i++) load(exntypes[i]);
306         }
307     }
308
309     public void visitJavaField(Field field) throws Exception {
310         if (dest.contains(field)) return;
311         dest.add(field);
312         load(field.getType());
313     }
314
315     public void visitJavaClass(JavaClass clazz) throws Exception {
316
317         if (dest.contains(clazz)) return;
318         dest.add(clazz);
319
320         String name = clazz.getClassName();
321
322         JavaClass superclass = clazz.getSuperClass();
323
324         JavaClass[] interfaces = clazz.getAllInterfaces();
325         Field[] fields = clazz.getFields();
326         Method[] methods = clazz.getMethods();
327         System.out.println(clazz.getClassName() + ".class");
328         for(int i=0; i<methods.length; i++)
329             if (methods[i].getName().equals("<clinit>")) visitJavaMethod(clazz, methods[i]);
330         for(int i=0; i<methods.length; i++) {
331             if (methods[i].getName().equals("equals")) visitJavaMethod(clazz, methods[i]);
332             if (methods[i].getName().equals("hashCode")) visitJavaMethod(clazz, methods[i]);
333             if (methods[i].getName().equals("finalize")) visitJavaMethod(clazz, methods[i]);
334             if (methods[i].getName().equals("clone")) visitJavaMethod(clazz, methods[i]);
335             if (methods[i].getName().equals("toString")) visitJavaMethod(clazz, methods[i]);
336         }
337         for(int i=0; i<fields.length; i++) visitJavaField(fields[i]);
338         if (superclass != null) {
339             for(JavaClass sup = superclass; sup != null; sup = sup.getSuperClass()) {
340                 if (subclasses.get(sup) == null) subclasses.put(sup, new HashSet());
341                 ((HashSet)subclasses.get(sup)).add(clazz);
342                 visitJavaClass(sup);
343                 remarkMethods(sup, clazz);
344             }
345         }
346         for(int i=0; i<interfaces.length; i++) {
347             if (subclasses.get(interfaces[i]) == null) subclasses.put(interfaces[i], new HashSet());
348             ((HashSet)subclasses.get(interfaces[i])).add(clazz);
349             visitJavaClass(interfaces[i]);
350             remarkMethods(interfaces[i], clazz);
351         }
352     }
353
354     public void markMethodInSubclasses(JavaClass c, Method m, JavaClass subclass) throws Exception {
355         if (m.isStatic()) return;
356         String sig = getMethodSignature(m);
357         Method[] submethods = subclass.getMethods();
358         for(int j=0; j<submethods.length; j++)
359             if (getMethodSignature(submethods[j]).equals(sig))
360                 visitJavaMethod(subclass, submethods[j]);
361     }
362     public void markMethodInSubclasses(JavaClass c, Method m) throws Exception {
363         if (m.isStatic()) return;
364         HashSet s = (HashSet)subclasses.get(c);
365         if (s == null) return;
366         Object[] subclasses = s.toArray();
367         for(int i=0; i<subclasses.length; i++) {
368             JavaClass subclass = (JavaClass)subclasses[i];
369             if (subclass == c) return;
370             markMethodInSubclasses(c, m, subclass);
371         }
372     }
373         
374     public void remarkMethods(JavaClass c) throws Exception {
375         Method[] meths = c.getMethods();
376         for(int j=0; j<meths.length; j++) if (dest.contains(meths[j])) markMethodInSubclasses(c, meths[j]);
377     }
378
379     public void remarkMethods(JavaClass c, JavaClass target) throws Exception {
380         Method[] meths = c.getMethods();
381         for(int j=0; j<meths.length; j++) if (dest.contains(meths[j])) markMethodInSubclasses(c, meths[j], target);
382     }
383
384 }