9c4f7af5e30af5c549ede38196a4eec598bcda9b
[org.ibex.gcclass.git] / src / org / ibex / 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 org.ibex.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 public class GCClass {
23     private static final String[] PRE_REF = {
24         "java.lang.Thread.run",
25         "java.security.PrivilegedAction.run"
26     };
27     
28     // NOTE: This doesn't mean these classes are ignored alltogether
29     //       failures to resolve them are just ignored
30     private static final String[] IGNORED_METHODS = {
31         "java.net.SocketImpl.setOption(ILjava/lang/Object;)V",
32         "java.net.SocketImpl.getOption(I)Ljava/lang/Object;",
33         "java.awt.geom.*",
34         "apple.awt.*",
35         "java.security.*"
36     };
37     
38     private static final String[] IGNORED_FIELDS = {
39         "java.io.ObjectInputStream.SUBCLASS_IMPLEMENTATION_PERMISSION"
40     };
41     
42     private static final String[] NO_OUTPUT = { "java", "javax", "sun", "com.sun", "apple", "com.apple" };
43     
44     
45     public static void main(String[] args) throws Exception {
46         if(args.length < 3) {
47             System.err.println("Usage GCClass classpath outdir entrypoint1 ... [ entrypoint n]");
48             System.exit(1);
49         }
50         GCClass gc = new GCClass(args[0]);
51         for(int i=2;i<args.length;i++) {
52             if(args[i].startsWith("hint:"))
53                 gc.parseHint(args[i].substring(5));
54             else
55                 gc.referenceMethod(args[i]);
56         }
57         gc.go();
58         gc.dump(new File(args[1]));
59     }
60     
61     private final Repository repo;
62     private final Vector work = new Vector();
63     private final Hashtable completed = new Hashtable();
64     private final Hashtable references = new Hashtable();
65     private final Hashtable instansiated = new Hashtable();
66     private final Hashtable hints = new Hashtable();
67     
68     public GCClass(String classpath) throws ClassNotFoundException {
69         if(classpath.startsWith("="))
70             classpath = classpath.substring(1);
71         else
72             classpath = ClassPath.SYSTEM_CLASS_PATH + File.pathSeparator + classpath;
73         repo = SyntheticRepository.getInstance(new ClassPath(classpath));
74         for(int i=0;i<PRE_REF.length;i++) {
75             try {
76                 referenceMethod(PRE_REF[i]);
77             } catch(ClassNotFoundException e) {
78                 System.err.println("WARNING: Couldn't preref: " + PRE_REF[i]);
79             }
80         }
81     }
82     
83     private Hashtable classRefHash(JavaClass c) { return classRefHash(new ObjectType(c.getClassName())); }
84     private Hashtable classRefHash(ObjectType c) {
85         Hashtable h = (Hashtable) references.get(c);
86         if(h == null) references.put(c,h=new Hashtable());
87         return h;
88     }
89     
90     public final ObjectType referenceClass(JavaClass c) { return referenceClass(c.getClassName()); }
91     public final ObjectType referenceClass(String s) { return referenceClass(new ObjectType(s)); }
92     public final ObjectType referenceClass(Type t) {
93         if(t instanceof ObjectType) return referenceClass((ObjectType)t);
94         return null;
95     }
96     
97     public final ObjectType referenceClass(ObjectType t) {
98         classRefHash(t);
99         return t;
100     }
101     
102     public void referenceMethod(String s) throws ClassNotFoundException {
103         int p = s.lastIndexOf('.');
104         if(p == -1) throw new IllegalArgumentException("invalid class/method string");
105         String cs = s.substring(0,p);
106         String ms = s.substring(p+1);
107         boolean skip = false;
108         
109         if(cs.startsWith("-")) { cs = cs.substring(1); skip = true; }
110         
111         if(!skip && (ms.equals("*") || ms.equals("<init>")))
112             instansiated.put(new ObjectType(cs),Boolean.TRUE);
113         
114         JavaClass c = repoGet(cs);
115         Method[] methods = c.getMethods();
116         for(int i=0;i<methods.length;i++) {
117             if(ms.equals("*") || methods[i].getName().equals(ms)) {
118                 MethodRef mr = new MethodRef(c,methods[i]);
119                 if(skip) completed.put(mr,Boolean.TRUE);
120                 else referenceMethod(mr);
121             }
122         }
123     }
124     
125     public void parseHint(String s) throws ClassNotFoundException {
126         int p = s.indexOf(':');
127         if(p == -1) throw new IllegalArgumentException("invalid  hint");
128         String cms = s.substring(0,p);
129         String hint = s.substring(p+1);
130         p = cms.lastIndexOf('.');
131         if(p == -1)  throw new IllegalArgumentException("invalid hint");
132         String cs = cms.substring(0,p);
133         String ms = cms.substring(p+1);
134         
135         JavaClass c = repoGet(cs);
136         Method[] methods = c.getMethods();
137         for(int i=0;i<methods.length;i++) {
138             if(ms.equals("*") || methods[i].getName().equals(ms)) {
139                 MethodRef mr = new MethodRef(c,methods[i]);
140                 Vector v = (Vector) hints.get(mr);
141                 if(v == null) hints.put(mr,v=new Vector());
142                 v.add(hint);
143             }
144         }
145     }
146         
147     private final void referenceMethod(MethodRef m) {
148         if(completed.get(m) != null) return;
149         
150         if(m.c.getClassName().startsWith("[")) {
151             completed.put(m,Boolean.TRUE);
152             return;
153         }
154         
155         Hashtable h = classRefHash(m.c);
156         h.put(m,Boolean.TRUE);
157         work.add(m);
158         
159         referenceClass(m.ret);
160         for(int i=0;i<m.args.length;i++) referenceClass(m.args[i]);
161     }
162     
163     private final void referenceField(FieldRef f) throws ClassNotFoundException  {
164         if(completed.get(f) != null) return;
165         
166         Hashtable h = classRefHash(f.c);
167         h.put(f,Boolean.TRUE);
168         
169         // process(FieldRef) doesn't create much work so we don't bother queuing it 
170         process(f);
171         
172         referenceClass(f.ftype);
173     }
174     
175     private Hashtable repoCache = new Hashtable();
176     private JavaClass repoGet(ObjectType t) throws ClassNotFoundException { return repoGet(t.getClassName()); }
177     private JavaClass repoGet(String s) throws ClassNotFoundException {
178         Object o = repoCache.get(s);
179         if(o == null) repoCache.put(s,o = repo.loadClass(s));
180         return (JavaClass) o;
181     }
182     
183     public void go() throws Exn {
184         while(work.size() != 0) {
185             while(work.size() != 0) {
186                 MethodRef mr = (MethodRef) work.remove(work.size()-1);
187                 try {
188                     process(mr);
189                 } catch(ClassNotFoundException e) {
190                     e.printStackTrace();
191                     String refBy = mr.refBy == null ? "unknown" : mr.refBy.toString();
192                     throw new Exn("ERROR: " + refBy + " references " + mr + " which cannot be found");
193                 }
194             }
195             try {
196                 fixup();
197             } catch(ClassNotFoundException e) {
198                 e.printStackTrace();
199                 throw new Exn("ClassNotFoundException in fixup");
200             }
201         }
202     }
203         
204     private void fixup() throws ClassNotFoundException {
205         for(Enumeration e = references.keys(); e.hasMoreElements(); ) {
206             ObjectType t = (ObjectType) e.nextElement();
207             JavaClass c = repoGet(t);
208             Hashtable refs = (Hashtable) references.get(t);
209              
210             if(c == null) continue;
211            
212             // add a ref to clinit if any fields/methods are referenced
213             if(refs.size() != 0) {
214                 MethodRef clinit = new MethodRef(t,"<clinit>",Type.VOID,Type.NO_ARGS);
215                 if(findMethod(c,clinit) != null) referenceMethod(clinit);
216             }
217             
218             Method[] methods = c.getMethods();
219             JavaClass[] supers = c.getSuperClasses();
220             JavaClass[] interfaces = c.getInterfaces();
221             
222             // If a subclass can be instansiated all its superclasses also can
223             if(instansiated.get(t) != null) {
224                 for(int i=0;i<supers.length;i++) {
225                     ObjectType st = new ObjectType(supers[i].getClassName());
226                     if(instansiated.get(st) != null) break;
227                     instansiated.put(st, Boolean.TRUE);
228                 }
229             }
230             
231             // If a subclass is referenced all is superclasses also are
232             for(int i=0;i<supers.length;i++) referenceClass(supers[i]);
233             
234             // Go though each method and look for method references a
235             // superclass or interfaces version of the method, references them
236             // result in references to us
237             for(int i=0;methods != null && i<methods.length;i++) {
238                 MethodRef mr = new MethodRef(c,methods[i]);
239                 if(refs.get(mr) != null) continue;
240                 for(int j=0;j<supers.length;j++) {
241                     MethodRef smr = new MethodRef(supers[j],methods[i]);
242                     Hashtable srefs = classRefHash(supers[j]);
243                     if(srefs.get(smr) != null) referenceMethod(mr);
244                     
245                     JavaClass[] interfaces2 = supers[j].getInterfaces();
246                     for(int k=0;interfaces2 != null && k<interfaces2.length;k++) {
247                         MethodRef imr = new MethodRef(interfaces2[k],methods[i]);
248                         Hashtable irefs = classRefHash(interfaces2[k]);
249                         if(irefs.get(imr) != null) referenceMethod(mr);
250                     }
251                 }
252                 for(int j=0;interfaces != null && j<interfaces.length;j++) {
253                     MethodRef imr = new MethodRef(interfaces[j],methods[i]);
254                     Hashtable irefs = classRefHash(interfaces[j]);
255                     if(irefs.get(imr) != null) referenceMethod(mr);
256                 }
257             }                                             
258         }
259     }
260     
261     private Hashtable cpgCache = new Hashtable();
262     private void process(MethodRef mr) throws Exn, ClassNotFoundException {
263         if(completed.get(mr) != null) return;
264         completed.put(mr,Boolean.TRUE);
265         
266         //System.err.println("Processing " + mr + "...");
267
268         JavaClass c = repoGet(mr.c.toString());
269         
270         // interfaces can only have a clinit method - every other method has no definition
271         if(!c.isClass() && !mr.name.equals("<clinit>")) return;
272         
273         Method m = findMethod(c,mr);
274         if(m == null) {
275             JavaClass supers[] = c.getSuperClasses();
276             for(int i=0;i<supers.length;i++) {
277                 m = findMethod(supers[i],mr);
278                 if(m != null) { referenceMethod(new MethodRef(supers[i],m)); return; }
279             }
280             String sig = mr.toString();
281             for(int i=0;i<IGNORED_METHODS.length;i++) {
282                 String pat = IGNORED_METHODS[i];
283                 if(pat.endsWith("*") ? sig.startsWith(pat.substring(0,pat.length()-1)) : sig.equals(pat)) return;
284             }
285             throw new ClassNotFoundException("" + mr + " not found (but the class was)");
286         }
287         
288         Code code = m.getCode();
289         if(code == null) return;
290         
291         ConstantPoolGen cpg = (ConstantPoolGen) cpgCache.get(c);
292         if(cpg == null) cpgCache.put(c,cpg=new ConstantPoolGen(c.getConstantPool()));
293         
294         InstructionList insnList = new InstructionList(code.getCode());
295         Instruction[] insns = insnList.getInstructions();
296         
297         for(int n=0;n<insns.length;n++) {
298             Instruction i = insns[n];
299             if(i instanceof NEW)
300                 instansiated.put(((CPInstruction)i).getType(cpg),Boolean.TRUE);
301             if(i instanceof ANEWARRAY || i instanceof CHECKCAST || i instanceof INSTANCEOF || i instanceof MULTIANEWARRAY || i instanceof NEW)
302                 referenceClass(((CPInstruction)i).getType(cpg));
303             else if(i instanceof FieldInstruction) // GETFIED, GETSTATIC, PUTFIELD, PUTSTATIC
304                 referenceField(new FieldRef((FieldInstruction)i,cpg));
305             else if(i instanceof InvokeInstruction) // INVOKESTATIC, INVOKEVIRTUAL, INVOKESPECIAL
306                 referenceMethod(new MethodRef((InvokeInstruction)i,cpg,mr));
307         }
308         
309         if(hints.get(mr) != null) {
310             Vector v = (Vector) hints.get(mr);
311             for(int i=0;i<v.size();i++) referenceMethod((String) v.elementAt(i));
312         }
313     }
314     
315     private void process(FieldRef fr) throws ClassNotFoundException {
316         if(completed.get(fr) != null) return;
317         completed.put(fr,Boolean.TRUE);
318
319         JavaClass c = repoGet(fr.c.toString());
320         Field f = findField(c,fr);
321         if(f == null) {
322             JavaClass supers[] = c.getSuperClasses();
323             for(int i=0;i<supers.length;i++) {
324                 f = findField(supers[i],fr);
325                 if(f != null) { referenceField(new FieldRef(supers[i],f)); return; }
326             }
327             String sig = fr.toString();
328             for(int i=0;i<IGNORED_FIELDS.length;i++) {
329                 String pat = IGNORED_FIELDS[i];
330                 if(pat.endsWith("*") ? sig.startsWith(pat.substring(0,pat.length()-1)) : sig.equals(pat)) return;
331             }
332             throw new ClassNotFoundException("" + fr + " not found (but the class was)");            
333         }
334         /* nothing to do */
335     }
336     
337     private static Method findMethod(JavaClass c, MethodRef mr) {
338         Method[] ms = c.getMethods();
339         for(int i=0;i<ms.length;i++) {
340             Method m = ms[i];
341             if(m.getName().equals(mr.name) && m.getReturnType().equals(mr.ret) && Arrays.equals(m.getArgumentTypes(),mr.args))
342                return m;
343         }
344         return null;
345     }
346     
347     private static Field findField(JavaClass c, FieldRef fr) {
348         Field[] fs = c.getFields();
349         for(int i=0;i<fs.length;i++) {
350             Field f = fs[i];
351             if(f.getName().equals(fr.name) && f.getType().equals(fr.ftype))
352                 return f;
353         }
354         return null;
355     }
356     
357     public void dump(File outdir) throws IOException, ClassNotFoundException {
358         if(!outdir.isDirectory()) throw new IOException("" + outdir + " is not a directory");
359         OUTER: for(Enumeration e = references.keys(); e.hasMoreElements(); ) {
360             ObjectType t = (ObjectType) e.nextElement();
361             String name =  t.getClassName();
362             for(int i=0;i<NO_OUTPUT.length;i++) if(name.startsWith(NO_OUTPUT[i])) continue OUTER;
363             Hashtable refs = (Hashtable) references.get(t);
364             JavaClass c = repoGet(t.getClassName());
365             if(c == null) continue;
366             boolean staticOnly = c.isClass() && instansiated.get(t) == null;
367             File cf = new File(outdir,t.getClassName().replace('.',File.separatorChar) + ".class");
368             cf.getParentFile().mkdirs();
369             dumpClass(c,refs,staticOnly,cf);
370         }
371     }
372     
373     private void dumpClass(JavaClass c, Hashtable refs, boolean staticOnly, File file) throws IOException {
374         ClassGen oldCG = new ClassGen(c);
375         ConstantPoolGen oldCP = oldCG.getConstantPool();
376         
377         ConstantPoolGen cp = new ConstantPoolGen();
378         ClassGen cg = new ClassGen(c.getClassName(),c.getSuperclassName(),c.getSourceFileName(),c.getAccessFlags(),c.getInterfaceNames(),cp);
379         
380         Method[] methods= oldCG.getMethods();
381         for(int i=0;i<methods.length;i++) {
382             Method m = methods[i];
383             MethodRef mr = new MethodRef(c,m);
384             if((staticOnly && !m.isStatic()) || refs.get(mr) == null) {
385                 System.err.println("Removing method " + mr);
386                 if(true) {
387                     InstructionFactory fac = new InstructionFactory(cg,cg.getConstantPool());
388                     InstructionList il = new InstructionList();
389                     MethodGen mg = new MethodGen(m.getAccessFlags(),m.getReturnType(),m.getArgumentTypes(),null,m.getName(),c.getClassName(),il,cp);
390                     il.append(fac.createNew("java.lang.UnsatisfiedLinkError"));
391                     il.append(InstructionConstants.DUP);
392                     if(false) {
393                         il.append(new PUSH(cg.getConstantPool(),"" + mr + " has been pruned"));
394                         il.append(fac.createInvoke("java.lang.UnsatisfiedLinkError","<init>",Type.VOID, new Type[]{Type.STRING},Constants.INVOKESPECIAL));
395                     } else {
396                         il.append(fac.createInvoke("java.lang.UnsatisfiedLinkError","<init>",Type.VOID,Type.NO_ARGS,Constants.INVOKESPECIAL));
397                     }
398                     il.append(InstructionConstants.ATHROW);
399                     mg.setMaxStack();
400                     mg.setMaxLocals();
401                     cg.addMethod(mg.getMethod());
402                 }
403             } else {                
404                 MethodGen mg = new MethodGen(m,cg.getClassName(),oldCP);
405                 mg.setConstantPool(cp);
406                 if(mg.getInstructionList() != null) mg.getInstructionList().replaceConstantPool(oldCP, cp);
407                 
408                 Attribute[] attrs = m.getAttributes();
409                 for(int j=0;j<attrs.length;j++) {
410                     Attribute a = attrs[j];
411                     if(a instanceof Code || a instanceof ExceptionTable) continue;
412                     mg.removeAttribute(a);
413                     Constant con = oldCP.getConstant(a.getNameIndex());
414                     a.setNameIndex(cp.addConstant(con,oldCP));
415                     mg.addAttribute(a);                    
416                 }
417                 
418                 mg.removeLineNumbers();
419                 mg.removeLocalVariables();
420                 cg.addMethod(mg.getMethod());
421             }
422         }
423         
424         Field[] fields = c.getFields();
425         for(int i=0;i<fields.length;i++) {
426             Field f = fields[i];
427             FieldRef fr = new FieldRef(c,f);
428             if(refs.get(fr) == null) {
429                 System.err.println("Removing field " + fr);
430             } else {
431                 //System.err.println("Keeping field " + fr);
432                 FieldGen fg = new FieldGen(f.getAccessFlags(),f.getType(),f.getName(),cp);
433                 Attribute[] attrs = f.getAttributes();
434                 for(int j=0;j<attrs.length;j++) {
435                     if(attrs[j] instanceof ConstantValue) {
436                         ConstantObject co = (ConstantObject) oldCP.getConstant(((ConstantValue)attrs[i]).getConstantValueIndex());
437                         Object o = co.getConstantValue(oldCP.getConstantPool());
438                         if(co instanceof ConstantLong) fg.setInitValue(((Number)o).longValue());
439                         else if(co instanceof ConstantInteger) fg.setInitValue(((Number)o).intValue());
440                         else if(co instanceof ConstantFloat) fg.setInitValue(((Number)o).floatValue());
441                         else if(co instanceof ConstantDouble) fg.setInitValue(((Number)o).floatValue());
442                         else if(co instanceof ConstantString) fg.setInitValue((String)o);
443                         else throw new Error("should never happen");
444                     } else {
445                         Attribute a = attrs[j];
446                         Constant con = oldCP.getConstant(a.getNameIndex());
447                         a.setNameIndex(cp.addConstant(con,oldCP));
448                         //System.err.println("Adding attribute: " + attrs[j]);
449                         fg.addAttribute(a);
450                     }
451                 }
452                 /*if(f.getConstantValue() != null) throw new Error("this might be broken");
453                 FieldGen fg = new FieldGen(f.getAccessFlags(),f.getType(),f.getName(),cp);*/
454                 cg.addField(fg.getField());
455             }
456         }
457         
458         JavaClass n = cg.getJavaClass();
459         n.dump(file);
460     }
461     
462     public static class Exn extends Exception { public Exn(String s) { super(s); } }
463     
464     private static class MethodRef {
465         ObjectType c;
466         String name;
467         Type ret;
468         Type[] args;
469         MethodRef refBy;
470                 
471         public MethodRef(JavaClass c, Method m) {
472             this(new ObjectType(c.getClassName()),m.getName(),m.getReturnType(),m.getArgumentTypes());
473         }
474         
475         public MethodRef(InvokeInstruction i, ConstantPoolGen cp, MethodRef refBy) {
476             this(i.getClassType(cp),i.getMethodName(cp),i.getReturnType(cp),i.getArgumentTypes(cp));
477             this.refBy = refBy;
478         }
479         
480         public MethodRef(ObjectType c, String name, Type ret, Type[] args) { this.c = c; this.name = name; this.ret = ret; this.args = args; }
481         
482         public boolean equals(Object o_) {
483             if(!(o_ instanceof MethodRef)) return false;
484             MethodRef o = (MethodRef)o_;
485             boolean r = name.equals(o.name) && c.equals(o.c) && ret.equals(o.ret) && Arrays.equals(args,o.args);
486             return r;
487         }
488         // FIXME: ArrayType.java in BCEL doesn't properly implement hashCode()
489         public int hashCode() {
490             int hc = name.hashCode()  ^ c.hashCode(); //^ ret.hashCode();
491             //for(int i=0;i<args.length;i++) hc ^= args[i].hashCode();
492             return hc;
493         }
494         public String toString() { return c.toString() + "." + name + Type.getMethodSignature(ret,args); }
495     }
496     
497     private static class FieldRef {
498         ObjectType c;
499         String name;
500         Type ftype;
501         MethodRef refBy;
502         
503         public FieldRef(JavaClass c, Field f) {
504             this(new ObjectType(c.getClassName()),f.getName(),f.getType());
505         }
506         
507         public FieldRef(FieldInstruction i, ConstantPoolGen cp) {
508             this(i.getClassType(cp),i.getFieldName(cp),i.getFieldType(cp));
509         }
510         public FieldRef(ObjectType c, String name, Type ftype) { this.c = c; this.name = name; this.ftype = ftype; }
511         
512         public boolean equals(Object o_) {
513             if(!(o_ instanceof FieldRef)) return false;
514             FieldRef o = (FieldRef)o_;
515             return name.equals(o.name) && c.equals(o.c);
516         }
517         public int hashCode() { return name.hashCode() ^ c.hashCode(); }
518         public String toString() { return c.toString() + "." + name; }
519     }
520 }