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 com.brian_web.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 // FEATURE: Rebuild each method with a new constant pool to eliminate extra constant pool entries
24 public class GCClass {
25 private static final String[] PRE_REF = {
26 "java.lang.Thread.run",
27 "java.security.PrivilegedAction.run"
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;",
40 private static final String[] NO_OUTPUT = { "java", "javax", "sun", "com.sun", "apple", "com.apple" };
43 public static void main(String[] args) throws Exception {
45 System.err.println("Usage GCClass classpath outdir entrypoint1 ... [ entrypoint n]");
48 GCClass gc = new GCClass(args[0]);
49 for(int i=2;i<args.length;i++) gc.referenceMethod(args[i]);
51 gc.dump(new File(args[1]));
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();
60 public GCClass(String classpath) throws ClassNotFoundException {
61 repo = SyntheticRepository.getInstance(new ClassPath(ClassPath.SYSTEM_CLASS_PATH + File.pathSeparator + classpath));
62 for(int i=0;i<PRE_REF.length;i++) referenceMethod(PRE_REF[i]);
65 private Hashtable classRefHash(JavaClass c) { return classRefHash(new ObjectType(c.getClassName())); }
66 private Hashtable classRefHash(ObjectType c) {
67 Hashtable h = (Hashtable) references.get(c);
68 if(h == null) references.put(c,h=new Hashtable());
72 public final ObjectType referenceClass(JavaClass c) { return referenceClass(c.getClassName()); }
73 public final ObjectType referenceClass(String s) { return referenceClass(new ObjectType(s)); }
74 public final ObjectType referenceClass(Type t) {
75 if(t instanceof ObjectType) return referenceClass((ObjectType)t);
79 public final ObjectType referenceClass(ObjectType t) {
84 public void referenceMethod(String s) throws ClassNotFoundException {
85 int p = s.lastIndexOf('.');
86 if(p == -1) throw new IllegalArgumentException("invalid class/method string");
87 String cs = s.substring(0,p);
88 String ms = s.substring(p+1);
90 if(ms.equals("<init>")) instansiated.put(new ObjectType(cs),Boolean.TRUE);
92 JavaClass c = repoGet(cs);
93 Method[] methods = c.getMethods();
94 for(int i=0;i<methods.length;i++)
95 if(methods[i].getName().equals(ms))
96 referenceMethod(new MethodRef(c,methods[i]));
99 private final void referenceMethod(MethodRef m) {
100 if(completed.get(m) != null) return;
102 if(m.c.getClassName().startsWith("[")) {
103 completed.put(m,Boolean.TRUE);
107 Hashtable h = classRefHash(m.c);
108 h.put(m,Boolean.TRUE);
111 referenceClass(m.ret);
112 for(int i=0;i<m.args.length;i++) referenceClass(m.args[i]);
115 private final void referenceField(FieldRef f) {
116 Hashtable h = classRefHash(f.c);
117 h.put(f,Boolean.TRUE);
118 referenceClass(f.ftype);
121 private Hashtable repoCache = new Hashtable();
122 private JavaClass repoGet(ObjectType t) throws ClassNotFoundException { return repoGet(t.getClassName()); }
123 private JavaClass repoGet(String s) throws ClassNotFoundException {
124 Object o = repoCache.get(s);
125 if(o == null) repoCache.put(s,o = repo.loadClass(s));
126 return (JavaClass) o;
129 public void go() throws Exn, ClassNotFoundException {
130 while(work.size() != 0) {
131 while(work.size() != 0) process((MethodRef) work.remove(work.size()-1));
136 private void fixup() throws ClassNotFoundException {
137 for(Enumeration e = references.keys(); e.hasMoreElements(); ) {
138 ObjectType t = (ObjectType) e.nextElement();
139 JavaClass c = repoGet(t);
140 if(c == null) continue;
142 Hashtable refs = (Hashtable) references.get(t);
143 // add a ref to clinit is any fields/methods are referenced
144 if(refs.size() != 0) {
145 MethodRef clinit = new MethodRef(t,"<clinit>",Type.VOID,Type.NO_ARGS);
146 if(findMethod(c,clinit) != null) referenceMethod(clinit);
149 Method[] methods = c.getMethods();
150 JavaClass[] supers = c.getSuperClasses();
151 JavaClass[] interfaces = c.getInterfaces();
153 // If a subclass can be instansiated all its superclasses also can
154 if(instansiated.get(t) != null) {
155 for(int i=0;i<supers.length;i++) {
156 ObjectType st = new ObjectType(supers[i].getClassName());
157 if(instansiated.get(st) != null) break;
158 instansiated.put(st, Boolean.TRUE);
162 // If a subclass is referenced all is superclasses also are
163 for(int i=0;i<supers.length;i++) referenceClass(supers[i]);
165 // Go though each method and look for method references a
166 // superclass or interfaces version of the method, references them
167 // result in references to us
168 for(int i=0;methods != null && i<methods.length;i++) {
169 MethodRef mr = new MethodRef(c,methods[i]);
170 if(refs.get(mr) != null) continue;
171 for(int j=0;j<supers.length;j++) {
172 MethodRef smr = new MethodRef(supers[j],methods[i]);
173 Hashtable srefs = classRefHash(supers[j]);
174 if(srefs.get(smr) != null) referenceMethod(mr);
176 JavaClass[] interfaces2 = supers[j].getInterfaces();
177 for(int k=0;interfaces2 != null && k<interfaces2.length;k++) {
178 MethodRef imr = new MethodRef(interfaces2[k],methods[i]);
179 Hashtable irefs = classRefHash(interfaces2[k]);
180 if(irefs.get(imr) != null) referenceMethod(mr);
183 for(int j=0;interfaces != null && j<interfaces.length;j++) {
184 MethodRef imr = new MethodRef(interfaces[j],methods[i]);
185 Hashtable irefs = classRefHash(interfaces[j]);
186 if(irefs.get(imr) != null) referenceMethod(mr);
192 private Hashtable cpgCache = new Hashtable();
193 private void process(MethodRef mr) throws Exn, ClassNotFoundException {
194 if(completed.get(mr) != null) return;
195 completed.put(mr,Boolean.TRUE);
197 //System.err.println("Processing " + mr + "...");
199 JavaClass c = repoGet(mr.c.toString());
201 // interfaces can only have a clinit method - every other method has no definition
202 if(!c.isClass() && !mr.name.equals("<clinit>")) return;
204 Method m = findMethod(c,mr);
206 JavaClass supers[] = c.getSuperClasses();
207 for(int i=0;i<supers.length;i++) {
208 m = findMethod(supers[i],mr);
209 if(m != null) { referenceMethod(new MethodRef(supers[i],m)); return; }
211 String sig = mr.toString();
212 for(int i=0;i<IGNORED_METHODS.length;i++) {
213 String pat = IGNORED_METHODS[i];
214 if(pat.endsWith("*") ? sig.startsWith(pat.substring(0,pat.length()-1)) : sig.equals(pat)) return;
216 throw new Exn("Couldn't find " + sig);
219 Code code = m.getCode();
220 if(code == null) return;
222 ConstantPoolGen cpg = (ConstantPoolGen) cpgCache.get(c);
223 if(cpg == null) cpgCache.put(c,cpg=new ConstantPoolGen(c.getConstantPool()));
225 InstructionList insnList = new InstructionList(code.getCode());
226 Instruction[] insns = insnList.getInstructions();
228 for(int n=0;n<insns.length;n++) {
229 Instruction i = insns[n];
231 instansiated.put(((CPInstruction)i).getType(cpg),Boolean.TRUE);
232 if(i instanceof ANEWARRAY || i instanceof CHECKCAST || i instanceof INSTANCEOF || i instanceof MULTIANEWARRAY || i instanceof NEW)
233 referenceClass(((CPInstruction)i).getType(cpg));
234 else if(i instanceof FieldInstruction) // GETFIED, GETSTATIC, PUTFIELD, PUTSTATIC
235 referenceField(new FieldRef((FieldInstruction)i,cpg));
236 else if(i instanceof InvokeInstruction) // INVOKESTATIC, INVOKEVIRTUAL, INVOKESPECIAL
237 referenceMethod(new MethodRef((InvokeInstruction)i,cpg));
241 private static Method findMethod(JavaClass c, MethodRef mr) {
242 Method[] ms = c.getMethods();
243 for(int i=0;i<ms.length;i++) {
245 if(m.getName().equals(mr.name) && m.getReturnType().equals(mr.ret) && Arrays.equals(m.getArgumentTypes(),mr.args))
251 public void dump(File outdir) throws IOException, ClassNotFoundException {
252 if(!outdir.isDirectory()) throw new IOException("" + outdir + " is not a directory");
253 OUTER: for(Enumeration e = references.keys(); e.hasMoreElements(); ) {
254 ObjectType t = (ObjectType) e.nextElement();
255 String name = t.getClassName();
256 for(int i=0;i<NO_OUTPUT.length;i++) if(name.startsWith(NO_OUTPUT[i])) continue OUTER;
257 Hashtable refs = (Hashtable) references.get(t);
258 JavaClass c = repoGet(t.getClassName());
259 if(c == null) continue;
260 boolean staticOnly = c.isClass() && instansiated.get(t) == null;
261 File cf = new File(outdir,t.getClassName().replace('.',File.separatorChar) + ".class");
262 cf.getParentFile().mkdirs();
263 dumpClass(c,refs,staticOnly,cf);
267 private void dumpClass(JavaClass c, Hashtable refs, boolean staticOnly, File file) throws IOException {
268 ClassGen cg = new ClassGen(c);
269 Method[] methods= c.getMethods();
270 for(int i=0;i<methods.length;i++) {
271 Method m = methods[i];
272 MethodRef mr = new MethodRef(c,m);
273 if((staticOnly && !m.isStatic()) || refs.get(mr) == null) {
274 System.err.println("Removing method " + mr);
278 InstructionFactory fac = new InstructionFactory(cg,cg.getConstantPool());
279 InstructionList il = new InstructionList();
280 MethodGen mg = new MethodGen(m.getAccessFlags(),m.getReturnType(),m.getArgumentTypes(),null,m.getName(),c.getClassName(),il,cg.getConstantPool());
281 il.append(fac.createNew("java.lang.UnsatisfiedLinkError"));
282 il.append(InstructionConstants.DUP);
283 il.append(new PUSH(cg.getConstantPool(),"" + mr + " has been pruned"));
284 il.append(fac.createInvoke("java.lang.UnsatisfiedLinkError","<init>",Type.VOID, new Type[]{Type.STRING},Constants.INVOKESPECIAL));
285 il.append(InstructionConstants.ATHROW);
288 cg.replaceMethod(m,mg.getMethod());
291 //System.err.println("Keeping method " + mr);
295 Field[] fields = c.getFields();
296 for(int i=0;i<fields.length;i++) {
298 FieldRef fr = new FieldRef(c,f);
299 if(refs.get(fr) == null) {
300 System.err.println("Removing field " + fr);
303 //System.err.println("Keeping field " + fr);
307 JavaClass n = cg.getJavaClass();
311 public static class Exn extends Exception { public Exn(String s) { super(s); } }
313 private static class MethodRef {
319 public MethodRef(JavaClass c, Method m) {
320 this(new ObjectType(c.getClassName()),m.getName(),m.getReturnType(),m.getArgumentTypes());
323 public MethodRef(InvokeInstruction i, ConstantPoolGen cp) {
324 this(i.getClassType(cp),i.getMethodName(cp),i.getReturnType(cp),i.getArgumentTypes(cp));
327 public MethodRef(ObjectType c, String name, Type ret, Type[] args) { this.c = c; this.name = name; this.ret = ret; this.args = args; }
329 public boolean equals(Object o_) {
330 if(!(o_ instanceof MethodRef)) return false;
331 MethodRef o = (MethodRef)o_;
332 boolean r = name.equals(o.name) && c.equals(o.c) && ret.equals(o.ret) && Arrays.equals(args,o.args);
335 // FIXME: ArrayType.java in BCEL doesn't properly implement hashCode()
336 public int hashCode() {
337 int hc = name.hashCode() ^ c.hashCode(); //^ ret.hashCode();
338 //for(int i=0;i<args.length;i++) hc ^= args[i].hashCode();
341 public String toString() { return c.toString() + "." + name + Type.getMethodSignature(ret,args); }
344 private static class FieldRef {
349 public FieldRef(JavaClass c, Field f) {
350 this(new ObjectType(c.getClassName()),f.getName(),f.getType());
353 public FieldRef(FieldInstruction i, ConstantPoolGen cp) {
354 this(i.getClassType(cp),i.getFieldName(cp),i.getFieldType(cp));
356 public FieldRef(ObjectType c, String name, Type ftype) { this.c = c; this.name = name; this.ftype = ftype; }
358 public boolean equals(Object o_) {
359 if(!(o_ instanceof FieldRef)) return false;
360 FieldRef o = (FieldRef)o_;
361 return name.equals(o.name) && c.equals(o.c);
363 public int hashCode() { return name.hashCode() ^ c.hashCode(); }
364 public String toString() { return c.toString() + "." + name; }