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