1 // Copyright (C) 2004 Brian Alliet
3 // Based on NanoGoat by Adam Megacz
5 // Copyright (C) 2004 Adam Megacz <adam@ibex.org> all rights reserved.
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
12 package org.ibex.gcclass;
17 import org.apache.bcel.Constants;
18 import org.apache.bcel.util.*;
19 import org.apache.bcel.generic.*;
20 import org.apache.bcel.classfile.*;
22 public class GCClass {
23 private static final String[] PRE_REF = {
24 "java.lang.Thread.run",
25 "java.security.PrivilegedAction.run"
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;",
38 private static final String[] IGNORED_FIELDS = {
39 "java.io.ObjectInputStream.SUBCLASS_IMPLEMENTATION_PERMISSION"
42 private static final String[] NO_OUTPUT = { "java", "javax", "sun", "com.sun", "apple", "com.apple" };
45 public static void main(String[] args) throws Exception {
47 System.err.println("Usage GCClass classpath outdir entrypoint1 ... [ entrypoint n]");
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));
55 gc.referenceMethod(args[i]);
58 gc.dump(new File(args[1]));
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();
68 public GCClass(String classpath) throws ClassNotFoundException {
69 if(classpath.startsWith("="))
70 classpath = classpath.substring(1);
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++) {
76 referenceMethod(PRE_REF[i]);
77 } catch(ClassNotFoundException e) {
78 System.err.println("WARNING: Couldn't preref: " + PRE_REF[i]);
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());
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);
97 public final ObjectType referenceClass(ObjectType t) {
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;
109 if(cs.startsWith("-")) { cs = cs.substring(1); skip = true; }
111 if(!skip && (ms.equals("*") || ms.equals("<init>")))
112 instansiated.put(new ObjectType(cs),Boolean.TRUE);
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);
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);
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());
147 private final void referenceMethod(MethodRef m) {
148 if(completed.get(m) != null) return;
150 if(m.c.getClassName().startsWith("[")) {
151 completed.put(m,Boolean.TRUE);
155 Hashtable h = classRefHash(m.c);
156 h.put(m,Boolean.TRUE);
159 referenceClass(m.ret);
160 for(int i=0;i<m.args.length;i++) referenceClass(m.args[i]);
163 private final void referenceField(FieldRef f) throws ClassNotFoundException {
164 if(completed.get(f) != null) return;
166 Hashtable h = classRefHash(f.c);
167 h.put(f,Boolean.TRUE);
169 // process(FieldRef) doesn't create much work so we don't bother queuing it
172 referenceClass(f.ftype);
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;
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);
189 } catch(ClassNotFoundException e) {
191 String refBy = mr.refBy == null ? "unknown" : mr.refBy.toString();
192 throw new Exn("ERROR: " + refBy + " references " + mr + " which cannot be found");
197 } catch(ClassNotFoundException e) {
199 throw new Exn("ClassNotFoundException in fixup");
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);
210 if(c == null) continue;
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);
218 Method[] methods = c.getMethods();
219 JavaClass[] supers = c.getSuperClasses();
220 JavaClass[] interfaces = c.getInterfaces();
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);
231 // If a subclass is referenced all is superclasses also are
232 for(int i=0;i<supers.length;i++) referenceClass(supers[i]);
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);
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);
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);
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);
266 //System.err.println("Processing " + mr + "...");
268 JavaClass c = repoGet(mr.c.toString());
270 // interfaces can only have a clinit method - every other method has no definition
271 if(!c.isClass() && !mr.name.equals("<clinit>")) return;
273 Method m = findMethod(c,mr);
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; }
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;
285 throw new ClassNotFoundException("" + mr + " not found (but the class was)");
288 Code code = m.getCode();
289 if(code == null) return;
291 ConstantPoolGen cpg = (ConstantPoolGen) cpgCache.get(c);
292 if(cpg == null) cpgCache.put(c,cpg=new ConstantPoolGen(c.getConstantPool()));
294 InstructionList insnList = new InstructionList(code.getCode());
295 Instruction[] insns = insnList.getInstructions();
297 for(int n=0;n<insns.length;n++) {
298 Instruction i = insns[n];
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));
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));
315 private void process(FieldRef fr) throws ClassNotFoundException {
316 if(completed.get(fr) != null) return;
317 completed.put(fr,Boolean.TRUE);
319 JavaClass c = repoGet(fr.c.toString());
320 Field f = findField(c,fr);
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; }
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;
332 throw new ClassNotFoundException("" + fr + " not found (but the class was)");
337 private static Method findMethod(JavaClass c, MethodRef mr) {
338 Method[] ms = c.getMethods();
339 for(int i=0;i<ms.length;i++) {
341 if(m.getName().equals(mr.name) && m.getReturnType().equals(mr.ret) && Arrays.equals(m.getArgumentTypes(),mr.args))
347 private static Field findField(JavaClass c, FieldRef fr) {
348 Field[] fs = c.getFields();
349 for(int i=0;i<fs.length;i++) {
351 if(f.getName().equals(fr.name) && f.getType().equals(fr.ftype))
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);
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();
377 ConstantPoolGen cp = new ConstantPoolGen();
378 ClassGen cg = new ClassGen(c.getClassName(),c.getSuperclassName(),c.getSourceFileName(),c.getAccessFlags(),c.getInterfaceNames(),cp);
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);
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);
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));
396 il.append(fac.createInvoke("java.lang.UnsatisfiedLinkError","<init>",Type.VOID,Type.NO_ARGS,Constants.INVOKESPECIAL));
398 il.append(InstructionConstants.ATHROW);
401 cg.addMethod(mg.getMethod());
404 MethodGen mg = new MethodGen(m,cg.getClassName(),oldCP);
405 mg.setConstantPool(cp);
406 if(mg.getInstructionList() != null) mg.getInstructionList().replaceConstantPool(oldCP, cp);
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));
418 mg.removeLineNumbers();
419 mg.removeLocalVariables();
420 cg.addMethod(mg.getMethod());
424 Field[] fields = c.getFields();
425 for(int i=0;i<fields.length;i++) {
427 FieldRef fr = new FieldRef(c,f);
428 if(refs.get(fr) == null) {
429 System.err.println("Removing field " + fr);
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");
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]);
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());
458 JavaClass n = cg.getJavaClass();
462 public static class Exn extends Exception { public Exn(String s) { super(s); } }
464 private static class MethodRef {
471 public MethodRef(JavaClass c, Method m) {
472 this(new ObjectType(c.getClassName()),m.getName(),m.getReturnType(),m.getArgumentTypes());
475 public MethodRef(InvokeInstruction i, ConstantPoolGen cp, MethodRef refBy) {
476 this(i.getClassType(cp),i.getMethodName(cp),i.getReturnType(cp),i.getArgumentTypes(cp));
480 public MethodRef(ObjectType c, String name, Type ret, Type[] args) { this.c = c; this.name = name; this.ret = ret; this.args = args; }
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);
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();
494 public String toString() { return c.toString() + "." + name + Type.getMethodSignature(ret,args); }
497 private static class FieldRef {
503 public FieldRef(JavaClass c, Field f) {
504 this(new ObjectType(c.getClassName()),f.getName(),f.getType());
507 public FieldRef(FieldInstruction i, ConstantPoolGen cp) {
508 this(i.getClassType(cp),i.getFieldName(cp),i.getFieldType(cp));
510 public FieldRef(ObjectType c, String name, Type ftype) { this.c = c; this.name = name; this.ftype = ftype; }
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);
517 public int hashCode() { return name.hashCode() ^ c.hashCode(); }
518 public String toString() { return c.toString() + "." + name; }