5b8397016fb8e141142a3a0552ce9886d72bdf0d
[org.ibex.gcclass.git] / src / com / brian_web / gcclass / GCClass.java
1 // Copyright (C) 2004 Brian Alliet
2
3 // Based on NanoGoat by Adam Megacz
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
24 public class GCClass {
25     private static final String[] PRE_REF = {
26         "java.lang.Thread.run",
27         "java.security.PrivilegedAction.run"
28     };
29     
30     // NOTE: This doesn't mean these classes are ignored alltogether
31     //       failures to resolve them are just ignored
32     private static final String[] IGNORED_METHODS = {
33         "java.net.SocketImpl.setOption(ILjava/lang/Object;)V",
34         "java.net.SocketImpl.getOption(I)Ljava/lang/Object;",
35         "java.awt.geom.*",
36         "apple.awt.*",
37         "java.security.*"
38     };
39     
40     private static final String[] NO_OUTPUT = { "java", "javax", "sun", "com.sun", "apple", "com.apple" };
41     
42     
43     public static void main(String[] args) throws Exception {
44         if(args.length < 3) {
45             System.err.println("Usage GCClass classpath outdir entrypoint1 ... [ entrypoint n]");
46             System.exit(1);
47         }
48         GCClass gc = new GCClass(args[0]);
49         for(int i=2;i<args.length;i++) gc.referenceMethod(args[i]);
50         gc.go();
51         gc.dump(new File(args[1]));
52     }
53     
54     private final Repository repo;
55     private final Vector work = new Vector();
56     private final Hashtable completed = new Hashtable();
57     private final Hashtable references = new Hashtable();
58     private final Hashtable instansiated = new Hashtable();
59     
60     public GCClass(String classpath) throws ClassNotFoundException {
61         if(classpath.startsWith("="))
62             classpath = classpath.substring(1);
63         else
64             classpath = ClassPath.SYSTEM_CLASS_PATH + File.pathSeparator + classpath;
65         repo = SyntheticRepository.getInstance(new ClassPath(classpath));
66         for(int i=0;i<PRE_REF.length;i++) {
67             try {
68                 referenceMethod(PRE_REF[i]);
69             } catch(ClassNotFoundException e) {
70                 System.err.println("WARNING: Couldn't preref: " + PRE_REF[i]);
71             }
72         }
73     }
74     
75     private Hashtable classRefHash(JavaClass c) { return classRefHash(new ObjectType(c.getClassName())); }
76     private Hashtable classRefHash(ObjectType c) {
77         Hashtable h = (Hashtable) references.get(c);
78         if(h == null) references.put(c,h=new Hashtable());
79         return h;
80     }
81     
82     public final ObjectType referenceClass(JavaClass c) { return referenceClass(c.getClassName()); }
83     public final ObjectType referenceClass(String s) { return referenceClass(new ObjectType(s)); }
84     public final ObjectType referenceClass(Type t) {
85         if(t instanceof ObjectType) return referenceClass((ObjectType)t);
86         return null;
87     }
88     
89     public final ObjectType referenceClass(ObjectType t) {
90         classRefHash(t);
91         return t;
92     }
93     
94     public void referenceMethod(String s) throws ClassNotFoundException {
95         int p = s.lastIndexOf('.');
96         if(p == -1) throw new IllegalArgumentException("invalid class/method string");
97         String cs = s.substring(0,p);
98         String ms = s.substring(p+1);
99         boolean skip = false;
100         
101         if(cs.startsWith("-")) { cs = cs.substring(1); skip = true; }
102         
103         if(!skip && (ms.equals("*") || ms.equals("<init>")))
104             instansiated.put(new ObjectType(cs),Boolean.TRUE);
105         
106         JavaClass c = repoGet(cs);
107         Method[] methods = c.getMethods();
108         for(int i=0;i<methods.length;i++) {
109             if(ms.equals("*") || methods[i].getName().equals(ms)) {
110                 MethodRef mr = new MethodRef(c,methods[i]);
111                 if(skip) completed.put(mr,Boolean.TRUE);
112                 else referenceMethod(mr);
113             }
114         }
115     }
116         
117     private final void referenceMethod(MethodRef m) {
118         if(completed.get(m) != null) return;
119         
120         if(m.c.getClassName().startsWith("[")) {
121             completed.put(m,Boolean.TRUE);
122             return;
123         }
124         
125         Hashtable h = classRefHash(m.c);
126         h.put(m,Boolean.TRUE);
127         work.add(m);
128         
129         referenceClass(m.ret);
130         for(int i=0;i<m.args.length;i++) referenceClass(m.args[i]);
131     }
132     
133     private final void referenceField(FieldRef f) {
134         Hashtable h = classRefHash(f.c);
135         h.put(f,Boolean.TRUE);
136         referenceClass(f.ftype);
137     }
138     
139     private Hashtable repoCache = new Hashtable();
140     private JavaClass repoGet(ObjectType t) throws ClassNotFoundException { return repoGet(t.getClassName()); }
141     private JavaClass repoGet(String s) throws ClassNotFoundException {
142         Object o = repoCache.get(s);
143         if(o == null) repoCache.put(s,o = repo.loadClass(s));
144         return (JavaClass) o;
145     }
146     
147     public void go() throws Exn {
148         while(work.size() != 0) {
149             while(work.size() != 0) {
150                 MethodRef mr = (MethodRef) work.remove(work.size()-1);
151                 try {
152                     process(mr);
153                 } catch(ClassNotFoundException e) {
154                     e.printStackTrace();
155                     String refBy = mr.refBy == null ? "unknown" : mr.refBy.toString();
156                     throw new Exn("ERROR: " + refBy + " references " + mr + " which cannot be found");
157                 }
158             }
159             try {
160                 fixup();
161             } catch(ClassNotFoundException e) {
162                 e.printStackTrace();
163                 throw new Exn("ClassNotFoundException in fixup");
164             }
165         }
166     }
167     
168     private void fixup() throws ClassNotFoundException {
169         for(Enumeration e = references.keys(); e.hasMoreElements(); ) {
170             ObjectType t = (ObjectType) e.nextElement();
171             JavaClass c = repoGet(t);
172             if(c == null) continue;
173             
174             Hashtable refs = (Hashtable) references.get(t);
175             // add a ref to clinit is any fields/methods are referenced
176             if(refs.size() != 0) {
177                 MethodRef clinit = new MethodRef(t,"<clinit>",Type.VOID,Type.NO_ARGS);
178                 if(findMethod(c,clinit) != null) referenceMethod(clinit);
179             }
180             
181             Method[] methods = c.getMethods();
182             JavaClass[] supers = c.getSuperClasses();
183             JavaClass[] interfaces = c.getInterfaces();
184             
185             // If a subclass can be instansiated all its superclasses also can
186             if(instansiated.get(t) != null) {
187                 for(int i=0;i<supers.length;i++) {
188                     ObjectType st = new ObjectType(supers[i].getClassName());
189                     if(instansiated.get(st) != null) break;
190                     instansiated.put(st, Boolean.TRUE);
191                 }
192             }
193             
194             // If a subclass is referenced all is superclasses also are
195             for(int i=0;i<supers.length;i++) referenceClass(supers[i]);
196             
197             // Go though each method and look for method references a
198             // superclass or interfaces version of the method, references them
199             // result in references to us
200             for(int i=0;methods != null && i<methods.length;i++) {
201                 MethodRef mr = new MethodRef(c,methods[i]);
202                 if(refs.get(mr) != null) continue;
203                 for(int j=0;j<supers.length;j++) {
204                     MethodRef smr = new MethodRef(supers[j],methods[i]);
205                     Hashtable srefs = classRefHash(supers[j]);
206                     if(srefs.get(smr) != null) referenceMethod(mr);
207                     
208                     JavaClass[] interfaces2 = supers[j].getInterfaces();
209                     for(int k=0;interfaces2 != null && k<interfaces2.length;k++) {
210                         MethodRef imr = new MethodRef(interfaces2[k],methods[i]);
211                         Hashtable irefs = classRefHash(interfaces2[k]);
212                         if(irefs.get(imr) != null) referenceMethod(mr);
213                     }
214                 }
215                 for(int j=0;interfaces != null && j<interfaces.length;j++) {
216                     MethodRef imr = new MethodRef(interfaces[j],methods[i]);
217                     Hashtable irefs = classRefHash(interfaces[j]);
218                     if(irefs.get(imr) != null) referenceMethod(mr);
219                 }
220             }                                             
221         }
222     }
223     
224     private Hashtable cpgCache = new Hashtable();
225     private void process(MethodRef mr) throws Exn, ClassNotFoundException {
226         if(completed.get(mr) != null) return;
227         completed.put(mr,Boolean.TRUE);
228         
229         //System.err.println("Processing " + mr + "...");
230
231         JavaClass c = repoGet(mr.c.toString());
232         
233         // interfaces can only have a clinit method - every other method has no definition
234         if(!c.isClass() && !mr.name.equals("<clinit>")) return;
235         
236         Method m = findMethod(c,mr);
237         if(m == null) {
238             JavaClass supers[] = c.getSuperClasses();
239             for(int i=0;i<supers.length;i++) {
240                 m = findMethod(supers[i],mr);
241                 if(m != null) { referenceMethod(new MethodRef(supers[i],m)); return; }
242             }
243             String sig = mr.toString();
244             for(int i=0;i<IGNORED_METHODS.length;i++) {
245                 String pat = IGNORED_METHODS[i];
246                 if(pat.endsWith("*") ? sig.startsWith(pat.substring(0,pat.length()-1)) : sig.equals(pat)) return;
247             }
248             throw new ClassNotFoundException("" + mr + " not found (but the class was)");
249         }
250         
251         Code code = m.getCode();
252         if(code == null) return;
253         
254         ConstantPoolGen cpg = (ConstantPoolGen) cpgCache.get(c);
255         if(cpg == null) cpgCache.put(c,cpg=new ConstantPoolGen(c.getConstantPool()));
256         
257         InstructionList insnList = new InstructionList(code.getCode());
258         Instruction[] insns = insnList.getInstructions();
259         
260         for(int n=0;n<insns.length;n++) {
261             Instruction i = insns[n];
262             if(i instanceof NEW)
263                 instansiated.put(((CPInstruction)i).getType(cpg),Boolean.TRUE);
264             if(i instanceof ANEWARRAY || i instanceof CHECKCAST || i instanceof INSTANCEOF || i instanceof MULTIANEWARRAY || i instanceof NEW)
265                 referenceClass(((CPInstruction)i).getType(cpg));
266             else if(i instanceof FieldInstruction) // GETFIED, GETSTATIC, PUTFIELD, PUTSTATIC
267                 referenceField(new FieldRef((FieldInstruction)i,cpg));
268             else if(i instanceof InvokeInstruction) // INVOKESTATIC, INVOKEVIRTUAL, INVOKESPECIAL
269                 referenceMethod(new MethodRef((InvokeInstruction)i,cpg,mr));
270         }
271     }
272     
273     private static Method findMethod(JavaClass c, MethodRef mr) {
274         Method[] ms = c.getMethods();
275         for(int i=0;i<ms.length;i++) {
276             Method m = ms[i];
277             if(m.getName().equals(mr.name) && m.getReturnType().equals(mr.ret) && Arrays.equals(m.getArgumentTypes(),mr.args))
278                return m;
279         }
280         return null;
281     }
282     
283     public void dump(File outdir) throws IOException, ClassNotFoundException {
284         if(!outdir.isDirectory()) throw new IOException("" + outdir + " is not a directory");
285         OUTER: for(Enumeration e = references.keys(); e.hasMoreElements(); ) {
286             ObjectType t = (ObjectType) e.nextElement();
287             String name =  t.getClassName();
288             for(int i=0;i<NO_OUTPUT.length;i++) if(name.startsWith(NO_OUTPUT[i])) continue OUTER;
289             Hashtable refs = (Hashtable) references.get(t);
290             JavaClass c = repoGet(t.getClassName());
291             if(c == null) continue;
292             boolean staticOnly = c.isClass() && instansiated.get(t) == null;
293             File cf = new File(outdir,t.getClassName().replace('.',File.separatorChar) + ".class");
294             cf.getParentFile().mkdirs();
295             dumpClass(c,refs,staticOnly,cf);
296         }
297     }
298     
299     private void dumpClass(JavaClass c, Hashtable refs, boolean staticOnly, File file) throws IOException {
300         ClassGen cg = new ClassGen(c);
301         Method[] methods= c.getMethods();
302         for(int i=0;i<methods.length;i++) {
303             Method m = methods[i];
304             MethodRef mr = new MethodRef(c,m);
305             if((staticOnly && !m.isStatic()) || refs.get(mr) == null) {
306                 System.err.println("Removing method " + mr);
307                 if(false) {
308                     cg.removeMethod(m);
309                 } else {
310                     InstructionFactory fac = new InstructionFactory(cg,cg.getConstantPool());
311                     InstructionList il = new InstructionList();
312                     MethodGen mg = new MethodGen(m.getAccessFlags(),m.getReturnType(),m.getArgumentTypes(),null,m.getName(),c.getClassName(),il,cg.getConstantPool());
313                     il.append(fac.createNew("java.lang.UnsatisfiedLinkError"));
314                     il.append(InstructionConstants.DUP);
315                     il.append(new PUSH(cg.getConstantPool(),"" + mr + " has been pruned"));
316                     il.append(fac.createInvoke("java.lang.UnsatisfiedLinkError","<init>",Type.VOID, new Type[]{Type.STRING},Constants.INVOKESPECIAL));
317                     il.append(InstructionConstants.ATHROW);
318                     mg.setMaxStack();
319                     mg.setMaxLocals();
320                     cg.replaceMethod(m,mg.getMethod());
321                 }
322             } else {
323                 //System.err.println("Keeping method " + mr);
324             }
325         }
326         
327         Field[] fields = c.getFields();
328         for(int i=0;i<fields.length;i++) {
329             Field f = fields[i];
330             FieldRef fr = new FieldRef(c,f);
331             if(refs.get(fr) == null) {
332                 System.err.println("Removing field " + fr);
333                 cg.removeField(f);
334             } else {
335                 //System.err.println("Keeping field " + fr);
336             }
337         }
338         
339         JavaClass n = cg.getJavaClass();
340         n.dump(file);
341     }
342     
343     public static class Exn extends Exception { public Exn(String s) { super(s); } }
344     
345     private static class MethodRef {
346         ObjectType c;
347         String name;
348         Type ret;
349         Type[] args;
350         MethodRef refBy;
351                 
352         public MethodRef(JavaClass c, Method m) {
353             this(new ObjectType(c.getClassName()),m.getName(),m.getReturnType(),m.getArgumentTypes());
354         }
355         
356         public MethodRef(InvokeInstruction i, ConstantPoolGen cp, MethodRef refBy) {
357             this(i.getClassType(cp),i.getMethodName(cp),i.getReturnType(cp),i.getArgumentTypes(cp));
358             this.refBy = refBy;
359         }
360         
361         public MethodRef(ObjectType c, String name, Type ret, Type[] args) { this.c = c; this.name = name; this.ret = ret; this.args = args; }
362         
363         public boolean equals(Object o_) {
364             if(!(o_ instanceof MethodRef)) return false;
365             MethodRef o = (MethodRef)o_;
366             boolean r = name.equals(o.name) && c.equals(o.c) && ret.equals(o.ret) && Arrays.equals(args,o.args);
367             return r;
368         }
369         // FIXME: ArrayType.java in BCEL doesn't properly implement hashCode()
370         public int hashCode() {
371             int hc = name.hashCode()  ^ c.hashCode(); //^ ret.hashCode();
372             //for(int i=0;i<args.length;i++) hc ^= args[i].hashCode();
373             return hc;
374         }
375         public String toString() { return c.toString() + "." + name + Type.getMethodSignature(ret,args); }
376     }
377     
378     private static class FieldRef {
379         ObjectType c;
380         String name;
381         Type ftype;
382         MethodRef refBy;
383         
384         public FieldRef(JavaClass c, Field f) {
385             this(new ObjectType(c.getClassName()),f.getName(),f.getType());
386         }
387         
388         public FieldRef(FieldInstruction i, ConstantPoolGen cp) {
389             this(i.getClassType(cp),i.getFieldName(cp),i.getFieldType(cp));
390         }
391         public FieldRef(ObjectType c, String name, Type ftype) { this.c = c; this.name = name; this.ftype = ftype; }
392         
393         public boolean equals(Object o_) {
394             if(!(o_ instanceof FieldRef)) return false;
395             FieldRef o = (FieldRef)o_;
396             return name.equals(o.name) && c.equals(o.c);
397         }
398         public int hashCode() { return name.hashCode() ^ c.hashCode(); }
399         public String toString() { return c.toString() + "." + name; }
400     }
401 }