+package org.ibex.util;
+import java.util.*;
+import java.io.*;
+import java.util.zip.*;
+import org.apache.bcel.*;
+import org.apache.bcel.generic.*;
+import org.apache.bcel.classfile.*;
+import org.apache.bcel.util.*;
+
+// Reachability rules:
+
+// - a constructor is reachable iff it is called
+// - a static method is reachable iff it is called
+// - a nonstatic method is reachable
+// - a static field is reachable iff it is referenced
+// - a nonstatic field is reachable iff it is referenced
+// - <clinit> is reachable iff any methods, static methods, fields, or constructors are reachable
+
+// - if a method is reachable, all the methods it overrides are reachable
+
+// FIXME: nonstatic method invocation or field access implies that object will be constructed (ie hint)
+
+public class BytecodePruner {
+
+ // FIXME
+ public static SyntheticRepository repo = null;
+
+ public static HashSet dest = new HashSet();
+
+ public static String outdir = ".";
+
+ public void loadAllMethods(String classname) throws Exception {
+ visitJavaClass(repo.loadClass(classname));
+ Method[] meths = repo.loadClass(classname).getMethods();
+ for(int i=0; i<meths.length; i++) visitJavaMethod(repo.loadClass(classname), meths[i]);
+ }
+ public void loadMethod(String classAndMethodName) throws Exception {
+ String classname = classAndMethodName.substring(0, classAndMethodName.lastIndexOf('.'));
+ String methodname = classAndMethodName.substring(classAndMethodName.lastIndexOf('.') + 1);
+ visitJavaClass(repo.loadClass(classname));
+ Method[] meths = repo.loadClass(classname).getMethods();
+ for(int i=0; i<meths.length; i++)
+ if (meths[i].getName().equals(methodname))
+ visitJavaMethod(repo.loadClass(classname), meths[i]);
+ }
+ public static void main(String[] s) throws Exception {
+ int start = 1;
+ if (s.length >= 3 && s[1].equals("-o")) { outdir = s[2]; start += 2; }
+ repo = SyntheticRepository.getInstance(new ClassPath(s[0]));
+ BytecodePruner bcp = new BytecodePruner();
+
+ for(int i=start; i<s.length; i++) {
+ try {
+ if (s[i].endsWith(".class")) {
+ bcp.visitJavaClass(repo.loadClass(s[i].substring(0, s[i].length() - 6)));
+ } else {
+ JavaClass cl = repo.loadClass(s[i].substring(0, s[i].lastIndexOf('.')));;
+ bcp.visitJavaClass(cl);
+ Method[] meths = cl.getMethods();
+ for(int j=0; j<meths.length; j++) {
+ if (meths[j].getName().equals(s[i].substring(s[i].lastIndexOf('.') + 1)))
+ bcp.visitJavaMethod(cl, meths[j]);
+ }
+ }
+ } catch (Exception e) {
+ System.out.println("WARNING: couldn't load class for " + s[i]);
+ }
+ }
+ System.out.println("\n\n======================================================================\n");
+
+ // we call start(), but the VM calls run()...
+ bcp.loadMethod("java.lang.Thread.run");
+
+ bcp.loadAllMethods("java.lang.Throwable");
+ bcp.loadAllMethods("java.io.PrintStream");
+ bcp.loadAllMethods("gnu.gcj.runtime.StringBuffer");
+ bcp.loadAllMethods("java.security.cert.Certificate");
+
+ bcp.loadMethod("java.util.SimpleTimeZone.useDaylightTime");
+ bcp.loadMethod("java.util.TimeZone.getAvailableIDs");
+ bcp.loadMethod("java.util.TimeZone.getDefaultTimeZoneId");
+ bcp.loadMethod("java.util.Collections$SynchronizedIterator.hasNext");
+ bcp.loadMethod("java.util.Hashtable$HashIterator.hasNext");
+
+ bcp.visitJavaClass(repo.loadClass("java.util.Stack"));
+ bcp.visitJavaClass(repo.loadClass("gnu.classpath.Configuration"));
+ bcp.visitJavaClass(repo.loadClass("gnu.gcj.runtime.JNIWeakRef"));
+
+ bcp.visitJavaClass(repo.loadClass("gnu.gcj.protocol.http.Handler"));
+ bcp.visitJavaClass(repo.loadClass("gnu.gcj.protocol.file.Handler"));
+ bcp.visitJavaClass(repo.loadClass("gnu.gcj.protocol.jar.Handler"));
+ bcp.visitJavaClass(repo.loadClass("gnu.gcj.protocol.core.Handler"));
+ bcp.visitJavaClass(repo.loadClass("gnu.gcj.runtime.FinalizerThread"));
+ bcp.visitJavaClass(repo.loadClass("gnu.gcj.runtime.FirstThread"));
+
+ // SecurityManager hacks to avoid java.security?
+ // URL and all descendents? Probably impossible.
+ // ObjectInput/ObjectOutput? Serialization?
+
+ // often called from native subclasses....
+ bcp.loadAllMethods("org.ibex.Surface");
+ bcp.loadAllMethods("org.ibex.Picture");
+ bcp.loadAllMethods("org.ibex.PixelBuffer");
+ bcp.loadAllMethods("org.ibex.Platform");
+ bcp.loadAllMethods("org.ibex.Scheduler");
+ bcp.loadAllMethods("org.ibex.plat.X11");
+ bcp.loadAllMethods("org.ibex.plat.X11$X11Picture");
+ bcp.loadAllMethods("org.ibex.plat.X11$X11PixelBuffer");
+ bcp.loadAllMethods("org.ibex.plat.X11$X11Surface");
+ bcp.loadAllMethods("org.ibex.XMLRPC");
+
+ bcp.loadAllMethods("java.util.Date");
+ bcp.loadAllMethods("java.text.DateFormat");
+ bcp.loadAllMethods("java.text.NumberFormat");
+
+
+ Method[] meths = repo.loadClass("org.ibex.plat.Linux").getMethods();
+ for(int i=0; i<meths.length; i++) {
+ if (meths[i].getName().equals("main"))
+ bcp.visitJavaMethod(repo.loadClass("org.ibex.plat.Linux"), meths[i]);
+ }
+ System.out.println();
+
+ System.out.println("Dumping...");
+
+ StringTokenizer st = new StringTokenizer(s[0], ":");
+ while(st.hasMoreTokens()) {
+ ZipFile zf = new ZipFile(st.nextToken());
+ Enumeration e = zf.entries();
+ while(e.hasMoreElements()) {
+ String ss = ((ZipEntry)e.nextElement()).getName();
+ if (!ss.endsWith(".class")) continue;
+ ss = ss.substring(0, ss.length() - 6);
+ ss = ss.replace('/', '.');
+ dump(repo.loadClass(ss));
+ }
+ }
+ }
+
+ public BytecodePruner() { }
+
+ public static void dump(JavaClass clazz) throws Exception {
+ if (clazz.getClassName().startsWith("java.sql.")) return;
+
+ ConstantPoolGen newcpg = new ConstantPoolGen(clazz.getConstantPool());
+ ClassGen cg = new ClassGen(clazz);
+ InstructionFactory factory = new InstructionFactory(cg, newcpg);
+ cg.setMajor(46);
+ cg.setMinor(0);
+ cg.setConstantPool(newcpg);
+ Field[] fields = clazz.getFields();
+ int numFields = 0;
+ for(int i=0; i<fields.length; i++)
+ if (!dest.contains(fields[i]) && false) {
+ System.out.println(" pruning " + clazz.getClassName() + "." + fields[i].getName());
+ fields[i] = null;
+ } else numFields++;
+
+ // superprune: URLClassLoader, convert.In/Output other than needed, unneeded locales
+ // reflective metadata is killing us...
+
+ Method[] methods = clazz.getMethods();
+ int numMethods = 0;
+ boolean good = false;
+ for(int i=0; i<methods.length; i++)
+ if (clazz.getClassName().startsWith("gnu.")
+ || clazz.getClassName().startsWith("java.lang.")
+ || clazz.getClassName().startsWith("java.io.")
+ || clazz.getClassName().startsWith("org.ibex.")) {
+ good = true;
+ } else if (dest.contains(methods[i])) {
+ if (!methods[i].getName().equals("<clinit>")) good = true;
+ } else {
+ if (methods[i].getCode() != null) {
+ System.out.println(" pruning " + clazz.getClassName() + "." + methods[i].getName());
+ MethodGen mg = new MethodGen(methods[i], clazz.getClassName(), newcpg);
+ mg.removeExceptions();
+ InstructionList il = new InstructionList();
+ mg.setInstructionList(il);
+
+ InstructionHandle ih_0 = il.append(factory.createNew("java.lang.UnsatisfiedLinkError"));
+ il.append(InstructionConstants.DUP);
+ il.append(factory.createInvoke("java.lang.UnsatisfiedLinkError",
+ "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
+ il.append(InstructionConstants.ATHROW);
+
+ mg.setMaxStack();
+ mg.setMaxLocals();
+ mg.removeExceptions();
+ mg.removeLocalVariables();
+ mg.removeExceptionHandlers();
+ mg.removeLineNumbers();
+
+ cg.replaceMethod(methods[i], mg.getMethod());
+ il.dispose();
+ }
+ }
+ if ((clazz.getClassName().startsWith("gnu.java.locale.LocaleInformation") &&
+ !clazz.getClassName().endsWith("LocaleInformation_en") &&
+ !clazz.getClassName().endsWith("LocaleInformation") &&
+ !clazz.getClassName().endsWith("LocaleInformation_en_US"))
+ ||
+ (!good &&
+ !clazz.isInterface() &&
+ !clazz.isAbstract() &&
+ !clazz.getClassName().endsWith("Error") &&
+ !clazz.getClassName().endsWith("Exception") &&
+ !clazz.getClassName().endsWith("Permission"))) {
+
+ System.out.println("DROPPING " + clazz.getClassName());
+ return;
+ }
+ new File(outdir + "/" + new File(clazz.getClassName().replace('.', '/')).getParent()).mkdirs();
+ System.out.println("dumping " + clazz.getClassName());
+ cg.getJavaClass().dump(outdir + "/" + clazz.getClassName().replace('.', '/') + ".class");
+ }
+
+ public JavaClass sig2class(String sig) throws Exception {
+ if (sig == null) return null;
+ while (sig.length() > 0 && (sig.charAt(0) == 'L' || sig.charAt(0) == '[')) {
+ if (sig.charAt(0) == 'L') sig = sig.substring(1, sig.length() - 1);
+ else if (sig.charAt(0) == '[') sig = sig.substring(1, sig.length());
+ }
+ if (sig.length() <= 1) return null;
+ if (sig.equals("<null object>")) return null;
+ if (sig.startsWith("<return address")) return null;
+ return repo.loadClass(sig);
+ }
+ public void load(String sig) throws Exception {
+ if (sig == null) return;
+ while (sig.length() > 0 && (sig.charAt(0) == 'L' || sig.charAt(0) == '[')) {
+ if (sig.charAt(0) == 'L') sig = sig.substring(1, sig.length() - 1);
+ else if (sig.charAt(0) == '[') sig = sig.substring(1, sig.length());
+ }
+ if (sig.length() <= 1) return;
+ if (sig.equals("<null object>")) return;
+ if (sig.startsWith("<return address")) return;
+ visitJavaClass(repo.loadClass(sig));
+ }
+ public void load(Type t) throws Exception {
+ if (t == null) return;
+ //String sig = t.getSignature();
+ if (t instanceof ArrayType) load(((ArrayType)t).getElementType());
+ if (!(t instanceof ObjectType)) return;
+ load(((ObjectType)t).getClassName());
+ }
+
+ // hashtable of hashsets
+ public static Hashtable subclasses = new Hashtable();
+
+ public String getMethodSignature(Method m) throws Exception {
+ ConstantPoolGen cpg = new ConstantPoolGen(m.getConstantPool());
+ return m.getName() + m.getSignature();
+ }
+
+ public String getMethodSignature(InvokeInstruction ii, ConstantPoolGen cpg) throws Exception {
+ String sig = "";
+ Type[] argtypes = ii.getArgumentTypes(cpg);
+ for(int j=0; j<argtypes.length; j++) sig += argtypes[j].getSignature();
+ return ii.getMethodName(cpg) + "(" + sig + ")" + ii.getReturnType(cpg).getSignature();
+ }
+ public static int level = 0;
+ public void visitJavaMethod(JavaClass jc, Method method) throws Exception {
+ visitJavaClass(jc);
+ if (dest.contains(method)) return;
+ dest.add(method);
+ level += 2;
+ for(int i=0; i<level; i++) System.out.print(" ");
+ System.out.println(jc.getClassName() + "." + getMethodSignature(method));
+ markMethodInSubclasses(jc, method);
+ ConstantPoolGen cpg = new ConstantPoolGen(method.getConstantPool());
+ if (method.getCode() == null) { level -= 2; return; }
+ byte[] code = method.getCode().getCode();
+ InstructionList il = new InstructionList(code);
+ Instruction[] instructions = il.getInstructions();
+ for(int i=0; i<instructions.length; i++){
+ Instruction instr = instructions[i];
+ if (instr instanceof LoadClass) load(((LoadClass)instr).getLoadClassType(cpg));
+ if (instr instanceof CPInstruction) load(((CPInstruction)instr).getType(cpg));
+ if (instr instanceof InvokeInstruction) {
+ InvokeInstruction ii = (InvokeInstruction)instr;
+ String ii_sig = getMethodSignature(ii, cpg);
+ JavaClass c = sig2class(ii.getLoadClassType(cpg).getSignature());
+ load(ii.getReturnType(cpg));
+ load(ii.getType(cpg));
+ Method[] meths = c.getMethods();
+ boolean good = false;
+ for(int i2=0; i2<meths.length; i2++) {
+ if (getMethodSignature(meths[i2]).equals(ii_sig)) {
+ visitJavaMethod(c, meths[i2]);
+ good = true;
+ break;
+ }
+ }
+ if (!good)
+ throw new Exception("couldn't find method " + getMethodSignature(ii, cpg) + " in " + c.getClassName());
+ }
+ }
+ level -= 2;
+ load(method.getReturnType());
+ Type[] argtypes = method.getArgumentTypes();
+ for(int i=0; i<argtypes.length; i++) load(argtypes[i]);
+ if (method.getExceptionTable() != null) {
+ String[] exntypes = method.getExceptionTable().getExceptionNames();
+ for(int i=0; i<exntypes.length; i++) load(exntypes[i]);
+ }
+ }
+
+ public void visitJavaField(Field field) throws Exception {
+ if (dest.contains(field)) return;
+ dest.add(field);
+ load(field.getType());
+ }
+
+ public void visitJavaClass(JavaClass clazz) throws Exception {
+
+ if (dest.contains(clazz)) return;
+ dest.add(clazz);
+
+ String name = clazz.getClassName();
+
+ JavaClass superclass = clazz.getSuperClass();
+
+ JavaClass[] interfaces = clazz.getAllInterfaces();
+ Field[] fields = clazz.getFields();
+ Method[] methods = clazz.getMethods();
+ System.out.println(clazz.getClassName() + ".class");
+ for(int i=0; i<methods.length; i++)
+ if (methods[i].getName().equals("<clinit>")) visitJavaMethod(clazz, methods[i]);
+ for(int i=0; i<methods.length; i++) {
+ if (methods[i].getName().equals("equals")) visitJavaMethod(clazz, methods[i]);
+ if (methods[i].getName().equals("hashCode")) visitJavaMethod(clazz, methods[i]);
+ if (methods[i].getName().equals("finalize")) visitJavaMethod(clazz, methods[i]);
+ if (methods[i].getName().equals("clone")) visitJavaMethod(clazz, methods[i]);
+ if (methods[i].getName().equals("toString")) visitJavaMethod(clazz, methods[i]);
+ }
+ for(int i=0; i<fields.length; i++) visitJavaField(fields[i]);
+ if (superclass != null) {
+ for(JavaClass sup = superclass; sup != null; sup = sup.getSuperClass()) {
+ if (subclasses.get(sup) == null) subclasses.put(sup, new HashSet());
+ ((HashSet)subclasses.get(sup)).add(clazz);
+ visitJavaClass(sup);
+ remarkMethods(sup, clazz);
+ }
+ }
+ for(int i=0; i<interfaces.length; i++) {
+ if (subclasses.get(interfaces[i]) == null) subclasses.put(interfaces[i], new HashSet());
+ ((HashSet)subclasses.get(interfaces[i])).add(clazz);
+ visitJavaClass(interfaces[i]);
+ remarkMethods(interfaces[i], clazz);
+ }
+ }
+
+ public void markMethodInSubclasses(JavaClass c, Method m, JavaClass subclass) throws Exception {
+ if (m.isStatic()) return;
+ String sig = getMethodSignature(m);
+ Method[] submethods = subclass.getMethods();
+ for(int j=0; j<submethods.length; j++)
+ if (getMethodSignature(submethods[j]).equals(sig))
+ visitJavaMethod(subclass, submethods[j]);
+ }
+ public void markMethodInSubclasses(JavaClass c, Method m) throws Exception {
+ if (m.isStatic()) return;
+ HashSet s = (HashSet)subclasses.get(c);
+ if (s == null) return;
+ Object[] subclasses = s.toArray();
+ for(int i=0; i<subclasses.length; i++) {
+ JavaClass subclass = (JavaClass)subclasses[i];
+ if (subclass == c) return;
+ markMethodInSubclasses(c, m, subclass);
+ }
+ }
+
+ public void remarkMethods(JavaClass c) throws Exception {
+ Method[] meths = c.getMethods();
+ for(int j=0; j<meths.length; j++) if (dest.contains(meths[j])) markMethodInSubclasses(c, meths[j]);
+ }
+
+ public void remarkMethods(JavaClass c, JavaClass target) throws Exception {
+ Method[] meths = c.getMethods();
+ for(int j=0; j<meths.length; j++) if (dest.contains(meths[j])) markMethodInSubclasses(c, meths[j], target);
+ }
+
+}