initial checkin
[org.ibex.nanogoat.git] / src / org / ibex / util / NanoGoat.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 public class NanoGoat {
11
12     public static final boolean deleteMethods = false;
13     public static SyntheticRepository repo = null;
14     public static HashSet dest = new HashSet();
15     public static HashSet constructed = new HashSet();
16     public static Hashtable subclasses = new Hashtable();
17     public static Hashtable uponconstruction = new Hashtable();
18     public static int level = 0;
19
20     public NanoGoat() { }
21
22     public static void main(String[] args) throws Exception {
23         int start = 1;
24         repo = SyntheticRepository.getInstance(new ClassPath(args[0]));
25         NanoGoat nanogoat = new NanoGoat();
26         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
27         for(String s = br.readLine(); s != null; s = br.readLine()) {
28             s = s.trim();
29             if (s.length() == 0) continue;
30             try {
31                 if (s.endsWith("$")) s = s.substring(0, s.length() - 1);
32                 if (s.endsWith(".class")) {
33                     nanogoat.visitJavaClass(repo.loadClass(s.substring(0, s.length() - 6)));
34                 } else {
35                     JavaClass cl = repo.loadClass(s.substring(0, s.lastIndexOf('.')));;
36                     nanogoat.visitJavaClass(cl);
37                     System.out.println("TRY " + s);
38                     if (s.indexOf('(') != -1) {
39                         String name = s.substring(0, s.indexOf('('));
40                         String sig = s.substring(s.indexOf('(') + 1, s.indexOf(')'));
41                         sig = "," + sig + ",";
42                         StringTokenizer st = new StringTokenizer(sig, ",");
43                         name += "(";
44                         while(st.hasMoreTokens()) {
45                             String tok = st.nextToken().trim();
46                             if (tok.length() == 0) continue; 
47                             while(tok.endsWith("[]")) {
48                                 tok = tok.substring(0, tok.length() - 2);
49                                 name += "[";
50                             }
51                             if (tok.equals("int")) tok = "I";
52                             else if (tok.equals("boolean")) tok = "Z";
53                             else if (tok.equals("byte")) tok = "B";
54                             else if (tok.equals("char")) tok = "C";
55                             else if (tok.equals("short")) tok = "S";
56                             else if (tok.equals("long")) tok = "J";
57                             else if (tok.equals("float")) tok = "F";
58                             else if (tok.equals("double")) tok = "D";
59                             else {
60                                 name += "L";
61                                 name += tok.replace('.', '/');
62                                 name += ";";
63                             }
64                         }
65                         name += ")";
66                         s = name;
67                         nanogoat.loadMethod(s);
68                     }
69                     Field[] fields = cl.getFields();
70                     for(int j=0; j<fields.length; j++) {
71                         if (fields[j].getName().equals(s.substring(s.lastIndexOf('.') + 1)))
72                             nanogoat.visitJavaField(fields[j], cl);
73                     }
74                 }
75             } catch (Exception e) {
76                 System.out.println("WARNING: couldn't load class for " + s);
77                 e.printStackTrace();
78             }
79         }
80
81         System.out.println("\n\n======================================================================\n");
82
83         // we call start(), but the VM calls run()...
84         nanogoat.loadMethod("java.lang.Thread.run");                  // we call start(), but the VM calls run()...
85         nanogoat.loadMethod("java.lang.ref.Reference.enqueue");       // the GC calls this directly
86         nanogoat.loadMethod("gnu.gcj.convert.BytesToUnicode.done");   // called by natString
87         nanogoat.loadAllMethods("gnu.gcj.runtime.StringBuffer");      // the compiler emits calls directly to this class
88         nanogoat.loadAllMethods("gnu.gcj.convert.Input_UTF8");        // retrieved via reflection
89         nanogoat.loadAllMethods("gnu.gcj.convert.Output_UTF8");       // retrieved via reflection
90         nanogoat.loadAllMethods("java.lang.reflect.Modifier");        // used all over natClass...
91
92         System.out.println("Dumping...");
93         ZipFile zf = new ZipFile(args[0]);
94         ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(args[0] + ".tmp"));
95         Enumeration e = zf.entries();
96         while(e.hasMoreElements()) {
97             ZipEntry ze = ((ZipEntry)e.nextElement());
98             String ss = ze.getName();
99             if (!ss.endsWith(".class")) continue;
100             ss = ss.substring(0, ss.length() - 6);
101             ss = ss.replace('/', '.');
102             dump(repo.loadClass(ss), zos);
103         }
104         zos.close();
105         zf.close();
106         new File(args[0] + ".tmp").renameTo(new File(args[0] + ".pruned"));
107     }
108
109     public void loadAllMethods(String classname) throws Exception {
110         visitJavaClass(repo.loadClass(classname));
111         Method[] meths = getMethods(repo.loadClass(classname));
112         for(int i=0; i<meths.length; i++)
113             visitJavaMethod(repo.loadClass(classname), meths[i]);
114     }
115
116     public void loadMethod(String classAndMethodName) throws Exception {
117         if (classAndMethodName.indexOf('(') == -1) classAndMethodName += '(';
118         String withoutSig = classAndMethodName.substring(0, classAndMethodName.indexOf('('));
119         String methodname = withoutSig.substring(withoutSig.lastIndexOf('.')) +
120             classAndMethodName.substring(classAndMethodName.indexOf('('));
121         methodname = methodname.trim();
122         String classname = classAndMethodName.substring(0, classAndMethodName.lastIndexOf('.'));
123         if (methodname.startsWith(".")) methodname = methodname.substring(1);
124         if (classname.endsWith("." + methodname.substring(0, methodname.indexOf('('))))
125             methodname = "<init>" + methodname.substring(methodname.indexOf('('));
126         if (methodname.equals("<clinit>(")) methodname = "<clinit>";
127         if (methodname.endsWith("()")) methodname = methodname.substring(0, methodname.length() - 2) + "(V)";
128         if (methodname.equals("<init>(V)")) methodname = "<init>()";
129         methodname = methodname.trim();
130         visitJavaClass(repo.loadClass(classname));
131         Method[] meths = getMethods(repo.loadClass(classname));
132         boolean good = false;
133         for(int i=0; i<meths.length; i++) {
134             //System.out.println("... " + (meths[i].getName() + meths[i].getSignature()));
135             if ((meths[i].getName() + meths[i].getSignature()).startsWith(methodname)
136                 ||
137                 (methodname.endsWith("(V)") &&
138                  (meths[i].getName() +
139                   meths[i].getSignature()).startsWith(methodname.substring(0, methodname.length() - 3) + "()")))
140             {
141                 visitJavaMethod(repo.loadClass(classname), meths[i]);
142                 good = true;
143             }
144         }
145         if (!good && !methodname.equals("<clinit>") && !methodname.equals("equals(") && !methodname.equals("hashCode(") && !methodname.equals("toString(") && !methodname.equals("finalize(") && !methodname.equals("clone(")) {
146             System.out.println("NOTFOUND:");
147             System.out.println("  " + classAndMethodName);
148             System.out.println("  " + classname);
149             System.out.println("  >" + methodname + "<");
150             System.out.println("  >" + methodname.substring(0, methodname.length() - 3) + "()" + "<");
151             throw new Error();
152         }
153     }
154
155     public JavaClass load(Type t) throws Exception { return load(t, false); }
156     public JavaClass load(Type t, boolean clinit) throws Exception {
157         if (t == null) return null;
158         if (t instanceof ArrayType) load(((ArrayType)t).getElementType());
159         if (!(t instanceof ObjectType)) return null;
160         JavaClass jc = repo.loadClass(((ObjectType)t).getClassName());
161         visitJavaClass(jc);
162         if (jc != null && clinit) loadMethod(jc.getClassName() + ".<clinit>");
163         return jc;
164     }
165
166     public String getMethodSignature(Method m, ConstantPoolGen cpg) throws Exception { return m.getName() + m.getSignature(); }
167     public String getMethodSignature(InvokeInstruction ii, ConstantPoolGen cpg) throws Exception {
168         String sig = "";
169         Type[] argtypes = ii.getArgumentTypes(cpg);
170         for(int j=0; j<argtypes.length; j++) sig += argtypes[j].getSignature();
171         return ii.getMethodName(cpg) + "(" + sig + ")" + ii.getReturnType(cpg).getSignature();
172     }
173
174     public void visitJavaMethod(JavaClass jc, Method method) throws Exception {
175         visitJavaClass(jc);
176
177         // kludgey....
178         if (jc.getClassName().indexOf("SharedLib") != -1) return;
179         if (jc.getClassName().indexOf("Datagram") != -1) return;
180         if (jc.getClassName().startsWith("java.io.Object")) return;
181         if (jc.getClassName().startsWith("java.util.jar.")) return;
182         if (jc.getClassName().startsWith("java.net.Inet6")) return;
183
184         // gcj bug; gcj can't compile this method from a .class file input; I have no idea why
185         if (jc.getClassName().equals("java.lang.System") && method.getName().equals("runFinalizersOnExit")) return;
186
187         // we know these can't be constructed
188         if (method.getName().equals("<init>") && jc.getClassName().startsWith("java.lang.reflect.")) return;
189
190         if (!dest.contains(method)) dest.add(method); else return;
191
192         if (method.getName().equals("<clinit>") && jc.getSuperClass() != null)
193             loadMethod(jc.getSuperClass().getClassName() + ".<clinit>");
194
195         if (method.isStatic() || method.getName().equals("<init>")) loadMethod(jc.getClassName() + ".<clinit>");
196         if (method.getName().equals("<init>")) {
197             // FIXME: generalize to all perinstancemethods
198             constructed.add(jc);
199             HashSet hs = (HashSet)uponconstruction.get(jc);
200             if (hs != null)
201                 for(Iterator it = hs.iterator(); it.hasNext();)
202                     visitJavaMethod(jc, (Method)it.next());
203             loadMethod(jc.getClassName() + ".equals");
204             loadMethod(jc.getClassName() + ".hashCode");
205             loadMethod(jc.getClassName() + ".toString");
206             loadMethod(jc.getClassName() + ".finalize");
207             loadMethod(jc.getClassName() + ".clone");
208         }
209
210         ConstantPoolGen cpg = new ConstantPoolGen(method.getConstantPool());
211         if (!method.isStatic() && !constructed.contains(jc)) {
212             HashSet hs = (HashSet)uponconstruction.get(jc);
213             if (hs == null) uponconstruction.put(jc, hs = new HashSet());
214             hs.add(method);
215             markMethodInSubclasses(jc, method, cpg);
216             dest.remove(method);
217             return;
218         }
219
220         level += 2;
221         for(int i=0; i<level; i++) System.out.print(" ");
222         System.out.print(jc.getClassName() + "." + getMethodSignature(method, cpg));
223         markMethodInSubclasses(jc, method, cpg);
224         if (method.getCode() == null) { System.out.println(); level -= 2; return; }
225         InstructionHandle[] instructions = new InstructionList(method.getCode().getCode()).getInstructionHandles();
226         System.out.println(" [" + instructions.length + " instructions]");
227         for(int i=0; i<instructions.length; i++) { 
228             Instruction instr = instructions[i].getInstruction();
229             if (instr instanceof Select) {
230                 InstructionHandle[] ih2 = ((Select)instr).getTargets();
231                 InstructionHandle[] ih3 = new InstructionHandle[instructions.length + ih2.length];
232                 System.arraycopy(instructions, 0, ih3, 0, instructions.length);
233                 System.arraycopy(ih2, 0, ih3, instructions.length, ih2.length);
234                 instructions = ih3;
235             }
236             if (instr instanceof LoadClass) load(((LoadClass)instr).getLoadClassType(cpg), true);
237             //if (instr instanceof CPInstruction) load(((CPInstruction)instr).getType(cpg));
238             if (instr instanceof TypedInstruction) load(((TypedInstruction)instr).getType(cpg));
239             if (instr instanceof NEW) loadMethod(((NEW)instr).getLoadClassType(cpg).getClassName() + ".<init>");
240             if (instr instanceof org.apache.bcel.generic.FieldOrMethod)
241                 load(((org.apache.bcel.generic.FieldOrMethod)instr).getClassType(cpg));
242             if (instr instanceof org.apache.bcel.generic.FieldInstruction) {
243                 load(((org.apache.bcel.generic.FieldInstruction)instr).getFieldType(cpg));
244                 load(((org.apache.bcel.generic.FieldInstruction)instr).getType(cpg));
245                 String fieldName = ((org.apache.bcel.generic.FieldInstruction)instr).getFieldName(cpg);
246                 JavaClass jc2 = repo.loadClass(((ObjectType)((org.apache.bcel.generic.FieldInstruction)instr).
247                                                 getLoadClassType(cpg)).getClassName());
248                 Field[] fields = jc2.getFields();
249                 for(int j=0; j<fields.length; j++) if (fields[j].getName().equals(fieldName)) visitJavaField(fields[j], jc2);
250             }
251             if (instr instanceof InvokeInstruction) {
252                 InvokeInstruction ii = (InvokeInstruction)instr;
253                 String ii_sig = getMethodSignature(ii, cpg);
254                 JavaClass c = repo.loadClass(((ObjectType)ii.getLoadClassType(cpg)).getClassName());
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         Type[] argtypes = method.getArgumentTypes();
269         for(int i=0; i<argtypes.length; i++) load(argtypes[i]);
270         if (method.getExceptionTable() != null) {
271             String[] exntypes = method.getExceptionTable().getExceptionNames();
272             for(int i=0; i<exntypes.length; i++) visitJavaClass(repo.loadClass(exntypes[i]));
273         }
274         level -= 2;
275     }
276
277     public void visitJavaField(Field field, JavaClass clazz) throws Exception {
278         if (dest.contains(field)) return;
279         dest.add(field);
280         if (field.isStatic()) loadMethod(clazz.getClassName() + ".<clinit>");
281     }
282
283     public void visitJavaClass(JavaClass clazz) throws Exception {
284
285         if (dest.contains(clazz)) return;
286         dest.add(clazz);
287         ConstantPoolGen cpg = new ConstantPoolGen(clazz.getConstantPool());
288         level += 2;
289         for(int i=0; i<level; i++) System.out.print(" ");
290         System.out.println(clazz.getClassName() + ".class");
291
292         JavaClass superclass = clazz.getSuperClass();
293         JavaClass[] interfaces = clazz.getAllInterfaces();
294         Field[] fields = clazz.getFields();
295         for(JavaClass sup = superclass; sup != null; sup = sup.getSuperClass()) {
296             if (subclasses.get(sup) == null) subclasses.put(sup, new HashSet());
297             ((HashSet)subclasses.get(sup)).add(clazz);
298         }
299         for(int i=0; i<interfaces.length; i++) {
300             if (subclasses.get(interfaces[i]) == null) subclasses.put(interfaces[i], new HashSet());
301             ((HashSet)subclasses.get(interfaces[i])).add(clazz);
302         }
303         for(JavaClass sup = superclass; sup != null; sup = sup.getSuperClass()) {
304             visitJavaClass(sup);
305             remarkMethods(sup, clazz, cpg);
306         }
307         for(int i=0; i<interfaces.length; i++) {
308             visitJavaClass(interfaces[i]);
309             remarkMethods(interfaces[i], clazz, cpg);
310         }
311         for(int i=0; i<fields.length; i++) {
312             if (!fields[i].isStatic()) { visitJavaField(fields[i], clazz); continue; }
313             load(fields[i].getType());
314             if (fields[i].getName().equals("NANOGOAT_KEEP_ALL_METHODS")) loadAllMethods(clazz.getClassName());
315         }
316         level -= 2;
317     }
318
319     public void markMethodInSubclass(JavaClass c, Method m, JavaClass subclass, ConstantPoolGen cpg) throws Exception {
320         if (m.isStatic()) return;
321         if (m.getName().equals("<init>")) return;
322         if (m.getName().equals("equals")) return;
323         if (m.getName().equals("hashCode")) return;
324         if (m.getName().equals("clone")) return;
325         if (m.getName().equals("finalize")) return;
326         if (m.getName().equals("toString")) return;
327         String sig = getMethodSignature(m, cpg);
328         Method[] submethods = getMethods(subclass);
329         for(int j=0; j<submethods.length; j++)
330             if (getMethodSignature(submethods[j], cpg).equals(sig))
331                 visitJavaMethod(subclass, submethods[j]);
332     }
333     public void markMethodInSubclasses(JavaClass c, Method m, ConstantPoolGen cpg) throws Exception {
334         if (m.isStatic()) return;
335         if (m.getName().equals("<init>")) return;
336         HashSet s = (HashSet)subclasses.get(c);
337         if (s == null) return;
338         Object[] subclasses = s.toArray();
339         for(int i=0; i<subclasses.length; i++) {
340             JavaClass subclass = (JavaClass)subclasses[i];
341             if (subclass == c) continue;
342             markMethodInSubclass(c, m, subclass, cpg);
343         }
344     }
345         
346     public void remarkMethods(JavaClass c, JavaClass target, ConstantPoolGen cpg) throws Exception {
347         Method[] meths = getMethods(c);
348         for(int j=0; j<meths.length; j++)
349             if (dest.contains(meths[j]) ||
350                 (uponconstruction.get(c) != null && ((HashSet)uponconstruction.get(c)).contains(meths[j])))
351                 markMethodInSubclass(c, meths[j], target, cpg);
352     }
353
354     public static Hashtable methodsHashtable = new Hashtable();
355     public static Method[] getMethods(JavaClass c) {
356         Method[] ret = (Method[])methodsHashtable.get(c);
357         if (ret == null) methodsHashtable.put(c, ret = c.getMethods());
358         return ret;
359     }
360
361     public static void dump(JavaClass clazz, ZipOutputStream zos) throws Exception {
362         if (!dest.contains(clazz)) return;
363
364         ConstantPoolGen newcpg = new ConstantPoolGen(clazz.getConstantPool());
365         ClassGen cg = new ClassGen(clazz);
366         InstructionFactory factory = new InstructionFactory(cg, newcpg);
367         cg.setMajor(46);
368         cg.setMinor(0);
369         cg.setConstantPool(newcpg);
370
371         boolean isconstructed = false;
372         Method[] methods = getMethods(clazz);
373         for(int i=0; i<methods.length; i++)
374             if (dest.contains(methods[i]) && methods[i].getName().equals("<init>"))
375                 isconstructed = true;
376
377         // we can only prune static fields (to avoid altering object layout, which is hardcoded into
378         // CNI code), but that's okay since instance fields don't contribute to binary size
379         Field[] fields = clazz.getFields();
380         for(int i=0; i<fields.length; i++) {
381             if ((!dest.contains(fields[i]) && fields[i].isStatic()) ||
382                 ((!(constructed.contains(clazz))) && !fields[i].isStatic())) { 
383                 System.out.println("  pruning field " + clazz.getClassName() + "." + fields[i].getName());
384                 // FIXME this confuses gcj in jar-at-a-time mode
385                 //cg.removeField(fields[i]);
386             }
387         }
388
389         int numMethods = 0;
390         boolean good = false;
391         for(int i=0; i<methods.length; i++) {
392             if (dest.contains(methods[i]) && (isconstructed || methods[i].isStatic())) {
393                 good = true;
394                 continue;
395             }
396             if (methods[i].getCode() == null) {
397                 System.out.println("  empty codeblock: " + clazz.getClassName() + "." + methods[i].getName());
398                 continue;
399             }
400             System.out.println("  pruning " +(isconstructed?"":"unconstructed")+ " method " +
401                                clazz.getClassName() + "." + methods[i].getName());
402             if (deleteMethods) { cg.removeMethod(methods[i]); continue; }
403             MethodGen mg = new MethodGen(methods[i], clazz.getClassName(), newcpg);
404             mg.removeExceptions();
405             InstructionList il = new InstructionList();
406             mg.setInstructionList(il);
407             InstructionHandle ih_0 = il.append(factory.createNew("java.lang.UnsatisfiedLinkError"));
408             il.append(InstructionConstants.DUP);
409             il.append(factory.createInvoke("java.lang.UnsatisfiedLinkError",
410                                            "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
411             il.append(InstructionConstants.ATHROW);
412             mg.setMaxStack();
413             mg.setMaxLocals();
414             mg.removeExceptions();
415             mg.removeLocalVariables();
416             mg.removeExceptionHandlers();
417             mg.removeLineNumbers();
418             cg.replaceMethod(methods[i], mg.getMethod());
419             il.dispose();
420         }
421
422         // FIXME: chain up to superclass' <clinit>... that might remove the need for this hack
423         // FIXME: gcj compiling in jar-at-a-time mode can't be convinced to let classes outside the jar override
424         //        the ones inside the jar
425         good = true;
426         if (!good && !clazz.isAbstract() && !clazz.isInterface()) {
427             System.out.println("DROPPING " + clazz.getClassName());
428             JavaClass[] ifaces = clazz.getInterfaces();
429             String[] ifacestrings = new String[ifaces.length];
430             for(int i=0; i<ifaces.length; i++) ifacestrings[i] = ifaces[i].getClassName();
431             cg = new ClassGen(clazz.getClassName(), clazz.getSuperClass().getClassName(), clazz.getFileName(),
432                               clazz.getAccessFlags(), ifacestrings, newcpg);
433         } else {
434             System.out.println("dumping " + clazz.getClassName());
435         }
436
437         FilterOutputStream noclose = new FilterOutputStream(zos) { public void close() throws IOException { flush(); } };
438         zos.putNextEntry(new ZipEntry(clazz.getClassName().replace('.', '/')+".class"));
439         cg.getJavaClass().dump(noclose);
440         noclose.flush();
441     }
442 }