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