initial 2
[org.ibex.gcclass.git] / src / com / brian_web / gcclass / GCClass.java
1 // Copyright (C) 2004 Brian Alliet
2
3 // Based on NanoGoat by Adam Megac
4
5 // Copyright (C) 2004 Adam Megacz <adam@ibex.org> all rights reserved.
6 //
7 // You may modify, copy, and redistribute this code under the terms of
8 // the GNU Library Public License version 2.1, with the exception of
9 // the portion of clause 6a after the semicolon (aka the "obnoxious
10 // relink clause")
11
12 package com.brian_web.gcclass;
13
14 import java.util.*;
15 import java.io.*;
16
17 import org.apache.bcel.Constants;
18 import org.apache.bcel.util.*;
19 import org.apache.bcel.generic.*;
20 import org.apache.bcel.classfile.*;
21
22 // FEATURE: Rebuild each method with a new constant pool to eliminate extra constant pool entries
23 // FEATURE: Optimize away INSTANCEOF if the class can never be instansiated
24
25 public class GCClass {
26     // This is called outside pure java so we don't see the call
27     private static final String[] PRE_REF = { "java.lang.Thread.run" };
28     // The JDK uses some voodoo under the hood to implement these methods
29     private static final String[] IGNORED_METHODS = {
30         "java.net.SocketImpl.setOption(ILjava/lang/Object;)V",
31         "java.net.SocketImpl.getOption(I)Ljava/lang/Object;",
32         "java.awt.geom.*",
33         "apple.awt.*"
34     };
35     
36     private static final String[] NO_OUTPUT = { "java", "javax", "sun", "com.sun", "apple", "com.apple" };
37     
38     
39     public static void main(String[] args) throws Exception {
40         if(args.length < 3) {
41             System.err.println("Usage GCClass  classpath outdir entrypoint1 ... [ entrypoint n]");
42             System.exit(1);
43         }
44         GCClass gc = new GCClass(args[0]);
45         for(int i=2;i<args.length;i++) gc.referenceMethod(args[i]);
46         gc.go();
47         gc.dump(new File(args[1]));
48     }
49     
50     private final Repository repo;
51     private final Vector work = new Vector();
52     private final Hashtable completed = new Hashtable();
53     private final Hashtable references = new Hashtable();
54     
55     public GCClass(String classpath) throws ClassNotFoundException {
56         System.err.println(ClassPath.SYSTEM_CLASS_PATH + File.pathSeparator + classpath);
57         repo = SyntheticRepository.getInstance(new ClassPath(ClassPath.SYSTEM_CLASS_PATH + File.pathSeparator + classpath));
58         for(int i=0;i<PRE_REF.length;i++) referenceMethod(PRE_REF[i]);
59     }
60     
61     private Hashtable classRefHash(JavaClass c) { return classRefHash(new ObjectType(c.getClassName())); }
62     private Hashtable classRefHash(ObjectType c) {
63         Hashtable h = (Hashtable) references.get(c);
64         if(h == null) references.put(c,h=new Hashtable());
65         return h;
66     }
67     
68     public final ObjectType referenceClass(JavaClass c) { return referenceClass(c.getClassName()); }
69     public final ObjectType referenceClass(String s) { return referenceClass(new ObjectType(s)); }
70     public final ObjectType referenceClass(Type t) {
71         if(t instanceof ObjectType) return referenceClass((ObjectType)t);
72         return null;
73     }
74     
75     public final ObjectType referenceClass(ObjectType t) {
76         classRefHash(t);
77         return t;
78     }
79     
80     public final void referenceMethod(String s) throws ClassNotFoundException {
81         int p = s.lastIndexOf('.');
82         if(p == -1) throw new IllegalArgumentException("invalid class/method string");
83         String cs = s.substring(0,p);
84         String ms = s.substring(p+1);
85         
86         JavaClass c = repoGet(cs);
87         Method[] methods = c.getMethods();
88         for(int i=0;i<methods.length;i++)
89             if(methods[i].getName().equals(ms))
90                 referenceMethod(new MethodRef(c,methods[i]));
91     }
92         
93     public final void referenceMethod(MethodRef m) {
94         if(completed.get(m) != null) return;
95         
96         if(m.c.getClassName().startsWith("[")) {
97             completed.put(m,Boolean.TRUE);
98             return;
99         }
100         
101         Hashtable h = classRefHash(m.c);
102         h.put(m,Boolean.TRUE);
103         work.add(m);
104         
105         referenceClass(m.ret);
106         for(int i=0;i<m.args.length;i++) referenceClass(m.args[i]);
107     }
108     
109     public final void referenceField(FieldRef f) {
110         Hashtable h = classRefHash(f.c);
111         h.put(f,Boolean.TRUE);
112         referenceClass(f.ftype);
113     }
114     
115     private Hashtable repoCache = new Hashtable();
116     public JavaClass repoGet(ObjectType t) throws ClassNotFoundException { return repoGet(t.getClassName()); }
117     public JavaClass repoGet(String s) throws ClassNotFoundException {
118         Object o = repoCache.get(s);
119         if(o == null) repoCache.put(s,o = repo.loadClass(s));
120         return (JavaClass) o;
121     }
122     
123     public void go() throws Exn, ClassNotFoundException {
124         while(work.size() != 0) {
125             while(work.size() != 0) process((MethodRef) work.remove(work.size()-1));
126             fixup();
127         }
128     }
129     
130     private void fixup() throws ClassNotFoundException {
131         for(Enumeration e = references.keys(); e.hasMoreElements(); ) {
132             ObjectType t = (ObjectType) e.nextElement();
133             JavaClass c = repoGet(t);
134             if(c == null) continue;
135             Hashtable refs = (Hashtable) references.get(t);
136             if(refs.size() != 0) {
137                 MethodRef clinit = new MethodRef(t,"<clinit>",Type.VOID,Type.NO_ARGS);
138                 if(findMethod(c,clinit) != null) referenceMethod(clinit);
139             }
140             Method[] methods = c.getMethods();
141             JavaClass[] supers = c.getSuperClasses();
142             JavaClass[] interfaces = c.getInterfaces();
143             //System.err.println("Fixing up " + t);
144             for(int i=0;i<supers.length;i++) referenceClass(supers[i]);
145             for(int i=0;methods != null && i<methods.length;i++) {
146                 MethodRef mr = new MethodRef(c,methods[i]);
147                 if(refs.get(mr) != null) continue;
148                 for(int j=0;j<supers.length;j++) {
149                     MethodRef smr = new MethodRef(supers[j],methods[i]);
150                     Hashtable srefs = classRefHash(supers[j]);
151                     if(srefs.get(smr) != null) referenceMethod(mr);
152                     
153                     JavaClass[] interfaces2 = supers[j].getInterfaces();
154                     for(int k=0;interfaces2 != null && k<interfaces2.length;k++) {
155                         MethodRef imr = new MethodRef(interfaces2[k],methods[i]);
156                         Hashtable irefs = classRefHash(interfaces2[k]);
157                         if(irefs.get(imr) != null) referenceMethod(mr);
158                     }
159                 }
160                 for(int j=0;interfaces != null && j<interfaces.length;j++) {
161                     MethodRef imr = new MethodRef(interfaces[j],methods[i]);
162                     Hashtable irefs = classRefHash(interfaces[j]);
163                     if(irefs.get(imr) != null) referenceMethod(mr);
164                 }
165             }                                             
166         }
167     }
168     
169     private Hashtable cpgCache = new Hashtable();
170     private void process(MethodRef mr) throws Exn, ClassNotFoundException {
171         if(completed.get(mr) != null) return;
172         completed.put(mr,Boolean.TRUE);
173         
174         //System.err.println("Processing " + mr + "...");
175
176         JavaClass c = repoGet(mr.c.toString());
177         
178         if(!c.isClass() && !mr.name.equals("<clinit>")) return;
179         
180         Method m = findMethod(c,mr);
181         if(m == null) {
182             JavaClass supers[] = c.getSuperClasses();
183             for(int i=0;i<supers.length;i++) {
184                 m = findMethod(supers[i],mr);
185                 if(m != null) { referenceMethod(new MethodRef(supers[i],m)); return; }
186             }
187             String sig = mr.toString();
188             for(int i=0;i<IGNORED_METHODS.length;i++) {
189                 String pat = IGNORED_METHODS[i];
190                 if(pat.endsWith("*") ? sig.startsWith(pat.substring(0,pat.length()-1)) : sig.equals(pat)) return;
191             }
192             throw new Exn("Couldn't find " + sig);
193         }
194         
195         Code code = m.getCode();
196         if(code == null) return;
197         
198         ConstantPoolGen cpg = (ConstantPoolGen) cpgCache.get(c);
199         if(cpg == null) cpgCache.put(c,cpg=new ConstantPoolGen(c.getConstantPool()));
200         
201         InstructionList insnList = new InstructionList(code.getCode());
202         Instruction[] insns = insnList.getInstructions();
203         
204         for(int n=0;n<insns.length;n++) {
205             Instruction i = insns[n];
206             if(i instanceof ANEWARRAY || i instanceof CHECKCAST || i instanceof INSTANCEOF || i instanceof MULTIANEWARRAY || i instanceof NEW)
207                 referenceClass(((CPInstruction)i).getType(cpg));
208             else if(i instanceof FieldInstruction) // GETFIED, GETSTATIC, PUTFIELD, PUTSTATIC
209                 referenceField(new FieldRef((FieldInstruction)i,cpg));
210             else if(i instanceof InvokeInstruction) // INVOKESTATIC, INVOKEVIRTUAL, INVOKESPECIAL
211                 referenceMethod(new MethodRef((InvokeInstruction)i,cpg));
212         }
213     }
214     
215     private static Method findMethod(JavaClass c, MethodRef mr) {
216         Method[] ms = c.getMethods();
217         for(int i=0;i<ms.length;i++) {
218             Method m = ms[i];
219             if(m.getName().equals(mr.name) && m.getReturnType().equals(mr.ret) && Arrays.equals(m.getArgumentTypes(),mr.args))
220                return m;
221         }
222         return null;
223     }
224     
225     public void dump(File outdir) throws IOException, ClassNotFoundException {
226         if(!outdir.isDirectory()) throw new IOException("" + outdir + " is not a directory");
227         OUTER: for(Enumeration e = references.keys(); e.hasMoreElements(); ) {
228             ObjectType t = (ObjectType) e.nextElement();
229             String name =  t.getClassName();
230             for(int i=0;i<NO_OUTPUT.length;i++) if(name.startsWith(NO_OUTPUT[i])) continue OUTER;
231             Hashtable refs = (Hashtable) references.get(t);
232             JavaClass c = repoGet(t.getClassName());
233             if(c == null) continue;
234             File cf = new File(outdir,t.getClassName().replace('.',File.separatorChar) + ".class");
235             cf.getParentFile().mkdirs();
236             dumpClass(c,refs,cf);
237         }
238     }
239     
240     private void dumpClass(JavaClass c, Hashtable refs, File file) throws IOException {
241         ClassGen cg = new ClassGen(c);
242         Method[] methods= c.getMethods();
243         for(int i=0;i<methods.length;i++) {
244             Method m = methods[i];
245             MethodRef mr = new MethodRef(c,m);
246             if(refs.get(mr) == null) {
247                 System.err.println("Removing method " + mr);
248                 if(false) {
249                     cg.removeMethod(m);
250                 } else {
251                     InstructionFactory fac = new InstructionFactory(cg,cg.getConstantPool());
252                     InstructionList il = new InstructionList();
253                     MethodGen mg = new MethodGen(m.getAccessFlags(),m.getReturnType(),m.getArgumentTypes(),null,m.getName(),c.getClassName(),il,cg.getConstantPool());
254                     il.append(fac.createNew("java.lang.UnsatisfiedLinkError"));
255                     il.append(InstructionConstants.DUP);
256                     il.append(new PUSH(cg.getConstantPool(),"" + mr + " has been pruned"));
257                     il.append(fac.createInvoke("java.lang.UnsatisfiedLinkError","<init>",Type.VOID, new Type[]{Type.STRING},Constants.INVOKESPECIAL));
258                     il.append(InstructionConstants.ATHROW);
259                     mg.setMaxStack();
260                     mg.setMaxLocals();
261                     cg.replaceMethod(m,mg.getMethod());
262                 }
263             } else {
264                 //System.err.println("Keeping method " + mr);
265             }
266         }
267         
268         Field[] fields = c.getFields();
269         for(int i=0;i<fields.length;i++) {
270             Field f = fields[i];
271             FieldRef fr = new FieldRef(c,f);
272             if(refs.get(fr) == null) {
273                 System.err.println("Removing field " + fr);
274                 cg.removeField(f);
275             } else {
276                 //System.err.println("Keeping field " + fr);
277             }
278         }
279         
280         JavaClass n = cg.getJavaClass();
281         n.dump(file);
282     }
283     
284     public static class Exn extends Exception { public Exn(String s) { super(s); } }
285         
286         
287     private static class MethodRef {
288         ObjectType c;
289         String name;
290         Type ret;
291         Type[] args;
292         
293         public MethodRef(JavaClass c, Method m) {
294             this(new ObjectType(c.getClassName()),m.getName(),m.getReturnType(),m.getArgumentTypes());
295         }
296         
297         public MethodRef(InvokeInstruction i, ConstantPoolGen cp) {
298             this(i.getClassType(cp),i.getMethodName(cp),i.getReturnType(cp),i.getArgumentTypes(cp));
299         }
300         
301         public MethodRef(ObjectType c, String name, Type ret, Type[] args) {
302             this.c = c;
303             this.name = name;
304             this.ret = ret;
305             this.args = args;
306         }
307         
308         public boolean equals(Object o_) {
309             if(!(o_ instanceof MethodRef)) return false;
310             MethodRef o = (MethodRef)o_;
311             boolean r = name.equals(o.name) && c.equals(o.c) && ret.equals(o.ret) && Arrays.equals(args,o.args);
312             return r;
313         }
314         
315         // FIXME: ArrayType.java in BCEL doesn't properly implement hashCode()
316         public int hashCode() {
317             int hc = name.hashCode()  ^ c.hashCode(); //^ ret.hashCode();
318             //for(int i=0;i<args.length;i++) hc ^= args[i].hashCode();
319             return hc;
320         }
321         
322         public String toString() { return c.toString() + "." + name + Type.getMethodSignature(ret,args); }
323     }
324     
325     public static class FieldRef {
326         ObjectType c;
327         String name;
328         Type ftype;
329         
330         public FieldRef(JavaClass c, Field f) {
331             this(new ObjectType(c.getClassName()),f.getName(),f.getType());
332         }
333         
334         public FieldRef(FieldInstruction i, ConstantPoolGen cp) {
335             this(i.getClassType(cp),i.getFieldName(cp),i.getFieldType(cp));
336         }
337         public FieldRef(ObjectType c, String name, Type ftype) { this.c = c; this.name = name; this.ftype = ftype; }
338         
339         public boolean equals(Object o_) {
340             if(!(o_ instanceof FieldRef)) return false;
341             FieldRef o = (FieldRef)o_;
342             return name.equals(o.name) && c.equals(o.c);
343         }
344         
345         public int hashCode() {
346             return name.hashCode() ^ c.hashCode();
347         }
348         
349         public String toString() { return c.toString() + "." + name; }
350     }
351 }