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