4 import java.util.zip.*;
5 import org.apache.bcel.*;
6 import org.apache.bcel.generic.*;
7 import org.apache.bcel.classfile.*;
8 import org.apache.bcel.util.*;
10 public class NanoGoat {
12 public static final boolean deleteMethods = false;
13 public static SyntheticRepository repo = null;
14 public static HashSet dest = new HashSet();
15 public static HashSet constructed = new HashSet();
16 public static Hashtable subclasses = new Hashtable();
17 public static Hashtable uponconstruction = new Hashtable();
18 public static int level = 0;
22 public static void main(String[] args) throws Exception {
24 repo = SyntheticRepository.getInstance(new ClassPath(args[0]));
25 NanoGoat nanogoat = new NanoGoat();
26 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
27 for(String s = br.readLine(); s != null; s = br.readLine()) {
29 if (s.length() == 0) continue;
31 if (s.endsWith("$")) s = s.substring(0, s.length() - 1);
32 if (s.endsWith(".class")) {
33 nanogoat.visitJavaClass(repo.loadClass(s.substring(0, s.length() - 6)));
35 JavaClass cl = repo.loadClass(s.substring(0, s.lastIndexOf('.')));;
36 nanogoat.visitJavaClass(cl);
37 System.out.println("TRY " + s);
38 if (s.indexOf('(') != -1) {
39 String name = s.substring(0, s.indexOf('('));
40 String sig = s.substring(s.indexOf('(') + 1, s.indexOf(')'));
41 sig = "," + sig + ",";
42 StringTokenizer st = new StringTokenizer(sig, ",");
44 while(st.hasMoreTokens()) {
45 String tok = st.nextToken().trim();
46 if (tok.length() == 0) continue;
47 while(tok.endsWith("[]")) {
48 tok = tok.substring(0, tok.length() - 2);
51 if (tok.equals("int")) tok = "I";
52 else if (tok.equals("boolean")) tok = "Z";
53 else if (tok.equals("byte")) tok = "B";
54 else if (tok.equals("char")) tok = "C";
55 else if (tok.equals("short")) tok = "S";
56 else if (tok.equals("long")) tok = "J";
57 else if (tok.equals("float")) tok = "F";
58 else if (tok.equals("double")) tok = "D";
61 name += tok.replace('.', '/');
67 nanogoat.loadMethod(s);
69 Field[] fields = cl.getFields();
70 for(int j=0; j<fields.length; j++) {
71 if (fields[j].getName().equals(s.substring(s.lastIndexOf('.') + 1)))
72 nanogoat.visitJavaField(fields[j], cl);
75 } catch (Exception e) {
76 System.out.println("WARNING: couldn't load class for " + s);
81 System.out.println("\n\n======================================================================\n");
83 // we call start(), but the VM calls run()...
84 nanogoat.loadMethod("java.lang.Thread.run"); // we call start(), but the VM calls run()...
85 nanogoat.loadMethod("java.lang.ref.Reference.enqueue"); // the GC calls this directly
86 nanogoat.loadMethod("gnu.gcj.convert.BytesToUnicode.done"); // called by natString
87 nanogoat.loadAllMethods("gnu.gcj.runtime.StringBuffer"); // the compiler emits calls directly to this class
88 nanogoat.loadAllMethods("gnu.gcj.convert.Input_UTF8"); // retrieved via reflection
89 nanogoat.loadAllMethods("gnu.gcj.convert.Output_UTF8"); // retrieved via reflection
90 nanogoat.loadAllMethods("java.lang.reflect.Modifier"); // used all over natClass...
92 System.out.println("Dumping...");
93 ZipFile zf = new ZipFile(args[0]);
94 ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(args[0] + ".tmp"));
95 Enumeration e = zf.entries();
96 while(e.hasMoreElements()) {
97 ZipEntry ze = ((ZipEntry)e.nextElement());
98 String ss = ze.getName();
99 if (!ss.endsWith(".class")) continue;
100 ss = ss.substring(0, ss.length() - 6);
101 ss = ss.replace('/', '.');
102 dump(repo.loadClass(ss), zos);
106 new File(args[0] + ".tmp").renameTo(new File(args[0] + ".pruned"));
109 public void loadAllMethods(String classname) throws Exception {
110 visitJavaClass(repo.loadClass(classname));
111 Method[] meths = getMethods(repo.loadClass(classname));
112 for(int i=0; i<meths.length; i++)
113 visitJavaMethod(repo.loadClass(classname), meths[i]);
116 public void loadMethod(String classAndMethodName) throws Exception {
117 if (classAndMethodName.indexOf('(') == -1) classAndMethodName += '(';
118 String withoutSig = classAndMethodName.substring(0, classAndMethodName.indexOf('('));
119 String methodname = withoutSig.substring(withoutSig.lastIndexOf('.')) +
120 classAndMethodName.substring(classAndMethodName.indexOf('('));
121 methodname = methodname.trim();
122 String classname = classAndMethodName.substring(0, classAndMethodName.lastIndexOf('.'));
123 if (methodname.startsWith(".")) methodname = methodname.substring(1);
124 if (classname.endsWith("." + methodname.substring(0, methodname.indexOf('('))))
125 methodname = "<init>" + methodname.substring(methodname.indexOf('('));
126 if (methodname.equals("<clinit>(")) methodname = "<clinit>";
127 if (methodname.endsWith("()")) methodname = methodname.substring(0, methodname.length() - 2) + "(V)";
128 if (methodname.equals("<init>(V)")) methodname = "<init>()";
129 methodname = methodname.trim();
130 visitJavaClass(repo.loadClass(classname));
131 Method[] meths = getMethods(repo.loadClass(classname));
132 boolean good = false;
133 for(int i=0; i<meths.length; i++) {
134 //System.out.println("... " + (meths[i].getName() + meths[i].getSignature()));
135 if ((meths[i].getName() + meths[i].getSignature()).startsWith(methodname)
137 (methodname.endsWith("(V)") &&
138 (meths[i].getName() +
139 meths[i].getSignature()).startsWith(methodname.substring(0, methodname.length() - 3) + "()")))
141 visitJavaMethod(repo.loadClass(classname), meths[i]);
145 if (!good && !methodname.equals("<clinit>") && !methodname.equals("equals(") && !methodname.equals("hashCode(") && !methodname.equals("toString(") && !methodname.equals("finalize(") && !methodname.equals("clone(")) {
146 System.out.println("NOTFOUND:");
147 System.out.println(" " + classAndMethodName);
148 System.out.println(" " + classname);
149 System.out.println(" >" + methodname + "<");
150 System.out.println(" >" + methodname.substring(0, methodname.length() - 3) + "()" + "<");
155 public JavaClass load(Type t) throws Exception { return load(t, false); }
156 public JavaClass load(Type t, boolean clinit) throws Exception {
157 if (t == null) return null;
158 if (t instanceof ArrayType) load(((ArrayType)t).getElementType());
159 if (!(t instanceof ObjectType)) return null;
160 JavaClass jc = repo.loadClass(((ObjectType)t).getClassName());
162 if (jc != null && clinit) loadMethod(jc.getClassName() + ".<clinit>");
166 public String getMethodSignature(Method m, ConstantPoolGen cpg) throws Exception { return m.getName() + m.getSignature(); }
167 public String getMethodSignature(InvokeInstruction ii, ConstantPoolGen cpg) throws Exception {
169 Type[] argtypes = ii.getArgumentTypes(cpg);
170 for(int j=0; j<argtypes.length; j++) sig += argtypes[j].getSignature();
171 return ii.getMethodName(cpg) + "(" + sig + ")" + ii.getReturnType(cpg).getSignature();
174 public void visitJavaMethod(JavaClass jc, Method method) throws Exception {
178 if (jc.getClassName().indexOf("SharedLib") != -1) return;
179 if (jc.getClassName().indexOf("Datagram") != -1) return;
180 if (jc.getClassName().startsWith("java.io.Object")) return;
181 if (jc.getClassName().startsWith("java.util.jar.")) return;
182 if (jc.getClassName().startsWith("java.net.Inet6")) return;
184 // gcj bug; gcj can't compile this method from a .class file input; I have no idea why
185 if (jc.getClassName().equals("java.lang.System") && method.getName().equals("runFinalizersOnExit")) return;
187 // we know these can't be constructed
188 if (method.getName().equals("<init>") && jc.getClassName().startsWith("java.lang.reflect.")) return;
190 if (!dest.contains(method)) dest.add(method); else return;
192 if (method.getName().equals("<clinit>") && jc.getSuperClass() != null)
193 loadMethod(jc.getSuperClass().getClassName() + ".<clinit>");
195 if (method.isStatic() || method.getName().equals("<init>")) loadMethod(jc.getClassName() + ".<clinit>");
196 if (method.getName().equals("<init>")) {
197 // FIXME: generalize to all perinstancemethods
199 HashSet hs = (HashSet)uponconstruction.get(jc);
201 for(Iterator it = hs.iterator(); it.hasNext();)
202 visitJavaMethod(jc, (Method)it.next());
203 loadMethod(jc.getClassName() + ".equals");
204 loadMethod(jc.getClassName() + ".hashCode");
205 loadMethod(jc.getClassName() + ".toString");
206 loadMethod(jc.getClassName() + ".finalize");
207 loadMethod(jc.getClassName() + ".clone");
210 ConstantPoolGen cpg = new ConstantPoolGen(method.getConstantPool());
211 if (!method.isStatic() && !constructed.contains(jc)) {
212 HashSet hs = (HashSet)uponconstruction.get(jc);
213 if (hs == null) uponconstruction.put(jc, hs = new HashSet());
215 markMethodInSubclasses(jc, method, cpg);
221 for(int i=0; i<level; i++) System.out.print(" ");
222 System.out.print(jc.getClassName() + "." + getMethodSignature(method, cpg));
223 markMethodInSubclasses(jc, method, cpg);
224 if (method.getCode() == null) { System.out.println(); level -= 2; return; }
225 InstructionHandle[] instructions = new InstructionList(method.getCode().getCode()).getInstructionHandles();
226 System.out.println(" [" + instructions.length + " instructions]");
227 for(int i=0; i<instructions.length; i++) {
228 Instruction instr = instructions[i].getInstruction();
229 if (instr instanceof Select) {
230 InstructionHandle[] ih2 = ((Select)instr).getTargets();
231 InstructionHandle[] ih3 = new InstructionHandle[instructions.length + ih2.length];
232 System.arraycopy(instructions, 0, ih3, 0, instructions.length);
233 System.arraycopy(ih2, 0, ih3, instructions.length, ih2.length);
236 if (instr instanceof LoadClass) load(((LoadClass)instr).getLoadClassType(cpg), true);
237 //if (instr instanceof CPInstruction) load(((CPInstruction)instr).getType(cpg));
238 if (instr instanceof TypedInstruction) load(((TypedInstruction)instr).getType(cpg));
239 if (instr instanceof NEW) loadMethod(((NEW)instr).getLoadClassType(cpg).getClassName() + ".<init>");
240 if (instr instanceof org.apache.bcel.generic.FieldOrMethod)
241 load(((org.apache.bcel.generic.FieldOrMethod)instr).getClassType(cpg));
242 if (instr instanceof org.apache.bcel.generic.FieldInstruction) {
243 load(((org.apache.bcel.generic.FieldInstruction)instr).getFieldType(cpg));
244 load(((org.apache.bcel.generic.FieldInstruction)instr).getType(cpg));
245 String fieldName = ((org.apache.bcel.generic.FieldInstruction)instr).getFieldName(cpg);
246 JavaClass jc2 = repo.loadClass(((ObjectType)((org.apache.bcel.generic.FieldInstruction)instr).
247 getLoadClassType(cpg)).getClassName());
248 Field[] fields = jc2.getFields();
249 for(int j=0; j<fields.length; j++) if (fields[j].getName().equals(fieldName)) visitJavaField(fields[j], jc2);
251 if (instr instanceof InvokeInstruction) {
252 InvokeInstruction ii = (InvokeInstruction)instr;
253 String ii_sig = getMethodSignature(ii, cpg);
254 JavaClass c = repo.loadClass(((ObjectType)ii.getLoadClassType(cpg)).getClassName());
255 load(ii.getType(cpg));
256 Method[] meths = getMethods(c);
257 boolean good = false;
258 for(int i2=0; i2<meths.length; i2++) {
259 if (getMethodSignature(meths[i2], cpg).equals(ii_sig)) {
260 visitJavaMethod(c, meths[i2]);
265 if (!good) throw new Exception("couldn't find method " + getMethodSignature(ii, cpg) + " in " + c.getClassName());
268 Type[] argtypes = method.getArgumentTypes();
269 for(int i=0; i<argtypes.length; i++) load(argtypes[i]);
270 if (method.getExceptionTable() != null) {
271 String[] exntypes = method.getExceptionTable().getExceptionNames();
272 for(int i=0; i<exntypes.length; i++) visitJavaClass(repo.loadClass(exntypes[i]));
277 public void visitJavaField(Field field, JavaClass clazz) throws Exception {
278 if (dest.contains(field)) return;
280 if (field.isStatic()) loadMethod(clazz.getClassName() + ".<clinit>");
283 public void visitJavaClass(JavaClass clazz) throws Exception {
285 if (dest.contains(clazz)) return;
287 ConstantPoolGen cpg = new ConstantPoolGen(clazz.getConstantPool());
289 for(int i=0; i<level; i++) System.out.print(" ");
290 System.out.println(clazz.getClassName() + ".class");
292 JavaClass superclass = clazz.getSuperClass();
293 JavaClass[] interfaces = clazz.getAllInterfaces();
294 Field[] fields = clazz.getFields();
295 for(JavaClass sup = superclass; sup != null; sup = sup.getSuperClass()) {
296 if (subclasses.get(sup) == null) subclasses.put(sup, new HashSet());
297 ((HashSet)subclasses.get(sup)).add(clazz);
299 for(int i=0; i<interfaces.length; i++) {
300 if (subclasses.get(interfaces[i]) == null) subclasses.put(interfaces[i], new HashSet());
301 ((HashSet)subclasses.get(interfaces[i])).add(clazz);
303 for(JavaClass sup = superclass; sup != null; sup = sup.getSuperClass()) {
305 remarkMethods(sup, clazz, cpg);
307 for(int i=0; i<interfaces.length; i++) {
308 visitJavaClass(interfaces[i]);
309 remarkMethods(interfaces[i], clazz, cpg);
311 for(int i=0; i<fields.length; i++) {
312 if (!fields[i].isStatic()) { visitJavaField(fields[i], clazz); continue; }
313 load(fields[i].getType());
314 if (fields[i].getName().equals("NANOGOAT_KEEP_ALL_METHODS")) loadAllMethods(clazz.getClassName());
319 public void markMethodInSubclass(JavaClass c, Method m, JavaClass subclass, ConstantPoolGen cpg) throws Exception {
320 if (m.isStatic()) return;
321 if (m.getName().equals("<init>")) return;
322 if (m.getName().equals("equals")) return;
323 if (m.getName().equals("hashCode")) return;
324 if (m.getName().equals("clone")) return;
325 if (m.getName().equals("finalize")) return;
326 if (m.getName().equals("toString")) return;
327 String sig = getMethodSignature(m, cpg);
328 Method[] submethods = getMethods(subclass);
329 for(int j=0; j<submethods.length; j++)
330 if (getMethodSignature(submethods[j], cpg).equals(sig))
331 visitJavaMethod(subclass, submethods[j]);
333 public void markMethodInSubclasses(JavaClass c, Method m, ConstantPoolGen cpg) throws Exception {
334 if (m.isStatic()) return;
335 if (m.getName().equals("<init>")) return;
336 HashSet s = (HashSet)subclasses.get(c);
337 if (s == null) return;
338 Object[] subclasses = s.toArray();
339 for(int i=0; i<subclasses.length; i++) {
340 JavaClass subclass = (JavaClass)subclasses[i];
341 if (subclass == c) continue;
342 markMethodInSubclass(c, m, subclass, cpg);
346 public void remarkMethods(JavaClass c, JavaClass target, ConstantPoolGen cpg) throws Exception {
347 Method[] meths = getMethods(c);
348 for(int j=0; j<meths.length; j++)
349 if (dest.contains(meths[j]) ||
350 (uponconstruction.get(c) != null && ((HashSet)uponconstruction.get(c)).contains(meths[j])))
351 markMethodInSubclass(c, meths[j], target, cpg);
354 public static Hashtable methodsHashtable = new Hashtable();
355 public static Method[] getMethods(JavaClass c) {
356 Method[] ret = (Method[])methodsHashtable.get(c);
357 if (ret == null) methodsHashtable.put(c, ret = c.getMethods());
361 public static void dump(JavaClass clazz, ZipOutputStream zos) throws Exception {
362 if (!dest.contains(clazz)) return;
364 ConstantPoolGen newcpg = new ConstantPoolGen(clazz.getConstantPool());
365 ClassGen cg = new ClassGen(clazz);
366 InstructionFactory factory = new InstructionFactory(cg, newcpg);
369 cg.setConstantPool(newcpg);
371 boolean isconstructed = false;
372 Method[] methods = getMethods(clazz);
373 for(int i=0; i<methods.length; i++)
374 if (dest.contains(methods[i]) && methods[i].getName().equals("<init>"))
375 isconstructed = true;
377 // we can only prune static fields (to avoid altering object layout, which is hardcoded into
378 // CNI code), but that's okay since instance fields don't contribute to binary size
379 Field[] fields = clazz.getFields();
380 for(int i=0; i<fields.length; i++) {
381 if ((!dest.contains(fields[i]) && fields[i].isStatic()) ||
382 ((!(constructed.contains(clazz))) && !fields[i].isStatic())) {
383 System.out.println(" pruning field " + clazz.getClassName() + "." + fields[i].getName());
384 // FIXME this confuses gcj in jar-at-a-time mode
385 //cg.removeField(fields[i]);
390 boolean good = false;
391 for(int i=0; i<methods.length; i++) {
392 if (dest.contains(methods[i]) && (isconstructed || methods[i].isStatic())) {
396 if (methods[i].getCode() == null) {
397 System.out.println(" empty codeblock: " + clazz.getClassName() + "." + methods[i].getName());
400 System.out.println(" pruning " +(isconstructed?"":"unconstructed")+ " method " +
401 clazz.getClassName() + "." + methods[i].getName());
402 if (deleteMethods) { cg.removeMethod(methods[i]); continue; }
403 MethodGen mg = new MethodGen(methods[i], clazz.getClassName(), newcpg);
404 mg.removeExceptions();
405 InstructionList il = new InstructionList();
406 mg.setInstructionList(il);
407 InstructionHandle ih_0 = il.append(factory.createNew("java.lang.UnsatisfiedLinkError"));
408 il.append(InstructionConstants.DUP);
409 il.append(factory.createInvoke("java.lang.UnsatisfiedLinkError",
410 "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
411 il.append(InstructionConstants.ATHROW);
414 mg.removeExceptions();
415 mg.removeLocalVariables();
416 mg.removeExceptionHandlers();
417 mg.removeLineNumbers();
418 cg.replaceMethod(methods[i], mg.getMethod());
422 // FIXME: chain up to superclass' <clinit>... that might remove the need for this hack
423 // FIXME: gcj compiling in jar-at-a-time mode can't be convinced to let classes outside the jar override
424 // the ones inside the jar
426 if (!good && !clazz.isAbstract() && !clazz.isInterface()) {
427 System.out.println("DROPPING " + clazz.getClassName());
428 JavaClass[] ifaces = clazz.getInterfaces();
429 String[] ifacestrings = new String[ifaces.length];
430 for(int i=0; i<ifaces.length; i++) ifacestrings[i] = ifaces[i].getClassName();
431 cg = new ClassGen(clazz.getClassName(), clazz.getSuperClass().getClassName(), clazz.getFileName(),
432 clazz.getAccessFlags(), ifacestrings, newcpg);
434 System.out.println("dumping " + clazz.getClassName());
437 FilterOutputStream noclose = new FilterOutputStream(zos) { public void close() throws IOException { flush(); } };
438 zos.putNextEntry(new ZipEntry(clazz.getClassName().replace('.', '/')+".class"));
439 cg.getJavaClass().dump(noclose);