pruner mostly working
authoradam <adam@megacz.com>
Wed, 18 Feb 2004 11:28:34 +0000 (11:28 +0000)
committeradam <adam@megacz.com>
Wed, 18 Feb 2004 11:28:34 +0000 (11:28 +0000)
darcs-hash:20040218112834-5007d-b475b3d44a504e35f671259c997aecbc047919c7.gz

Makefile
Makefile.upstream
src/org/ibex/plat/Linux.cc
src/org/ibex/util/BytecodePruner.java [new file with mode: 0644]

index cb554f0..671af44 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,8 @@
 # The Ibex Makefile
 #
 
 # The Ibex Makefile
 #
 
+#  auto directory creation with %/%: %?
+
 target_Darwin := powerpc-apple-darwin
 target_Win32  := i686-pc-mingw32
 target_Solaris := sparc-sun-solaris2.7
 target_Darwin := powerpc-apple-darwin
 target_Win32  := i686-pc-mingw32
 target_Solaris := sparc-sun-solaris2.7
@@ -178,7 +180,7 @@ build/$(platform)/org/ibex/plat/AWT.java.o:   ; touch .empty.c; mkdir -p $(@D);
 build/$(platform)/%.java.o: build/class/%.class
        @echo -e "\n\033[1mcompiling          .java -> .o:     $<\033[0m"
        mkdir -p `dirname $@`
 build/$(platform)/%.java.o: build/class/%.class
        @echo -e "\n\033[1mcompiling          .java -> .o:     $<\033[0m"
        mkdir -p `dirname $@`
-       $(gcj) -fCLASSPATH=build/class -c $< -o $@
+       $(gcj) -fCLASSPATH=build/pruned -c $< -o $@
 
 # FIXME detect subclasses of X11
 build/$(platform)/org/ibex/plat/Linux.cc.o: .install_WindowMaker-0.80.2_$(target) 
 
 # FIXME detect subclasses of X11
 build/$(platform)/org/ibex/plat/Linux.cc.o: .install_WindowMaker-0.80.2_$(target) 
@@ -203,6 +205,10 @@ upstream/jpeg-6b/build-$(target)/libjpeg.a: .install_jpeg-6b_$(target)
 # note: binaries appear in a different order in the dependency line vs the link line
 build/$(platform)/$(target_bin): upstream/jpeg-6b/build-$(target)/libjpeg.a build/$(platform)/builtin.o build/$(platform)/org/ibex/plat/$(platform).cc.o
        @echo -e "\n\033[1mlinking               .o -> $(target_bin)\033[0m"
 # note: binaries appear in a different order in the dependency line vs the link line
 build/$(platform)/$(target_bin): upstream/jpeg-6b/build-$(target)/libjpeg.a build/$(platform)/builtin.o build/$(platform)/org/ibex/plat/$(platform).cc.o
        @echo -e "\n\033[1mlinking               .o -> $(target_bin)\033[0m"
+       find upstream/gcc-3.3/build-i686-pc-linux-gnu/i686-pc-linux-gnu/libjava/ \
+               -name nat\*.o -or \( -name \*.o -not -name '*[A-Z]*' \) -exec nm {} \; |\
+                grep _ZN | c++filt --format java | grep " U " | sed 's_\.class\$$_.class_' | sed 's_ * U __' | sed 's_(.*__' \
+               > .natcalls
        make $(java_sources)
        rm -rf build/pruned; mkdir -p build/pruned
        javac -classpath lib/bcel-5.1.jar:build/class -d build/class src/org/ibex/util/BytecodePruner.java
        make $(java_sources)
        rm -rf build/pruned; mkdir -p build/pruned
        javac -classpath lib/bcel-5.1.jar:build/class -d build/class src/org/ibex/util/BytecodePruner.java
@@ -215,43 +221,79 @@ build/$(platform)/$(target_bin): upstream/jpeg-6b/build-$(target)/libjpeg.a buil
                        org/ibex/plat/GCJ*.class \
                        org/ibex/plat/POSIX*.class
        java -cp lib/bcel-5.1.jar:build/class org.ibex.util.BytecodePruner \
                        org/ibex/plat/GCJ*.class \
                        org/ibex/plat/POSIX*.class
        java -cp lib/bcel-5.1.jar:build/class org.ibex.util.BytecodePruner \
-               build/$(platform)/ibex.jar:upstream/install/share/java/libgcj-3.3.jar -o build/pruned
-#      rm -rf out/java/awt
-#      rm -f out/java/lang/*.*
-#      rm -f out/java/lang/reflect/*
-#      mkdir -p out/java/lang/ref
-#      mkdir -p out/java/lang/reflect
-#      cp upstream/gcc-3.3/src/libjava/java/lang/*.java out/java/lang/
-#      cp upstream/gcc-3.3/src/libjava/java/lang/ref/*.java out/java/lang/ref/
-#      cp upstream/gcc-3.3/src/libjava/java/lang/reflect/*.java out/java/lang/reflect/
+               build/$(platform)/ibex.jar:upstream/install/share/java/libgcj-3.3.jar \
+               -o build/pruned \
+               `cat .natcalls`
+
        rm -rf build/pruned/org/ibex/js/*; cp build/java/org/ibex/js/* build/pruned/org/ibex/js/
        rm -rf build/pruned/org/ibex/js/*; cp build/java/org/ibex/js/* build/pruned/org/ibex/js/
+
        rm build/pruned/java/lang/System.class
        cp upstream/gcc-3.3/src/libjava/java/lang/System.java build/pruned/java/lang/
        rm build/pruned/java/lang/System.class
        cp upstream/gcc-3.3/src/libjava/java/lang/System.java build/pruned/java/lang/
-       cd build/pruned;                                               \
-               for A in `find . -name \*.class -or -name \*.java`; do \
-                       echo compiling $$A....;                        \
-                       ../../upstream/install/bin/$(target)-gcj -w -c -fCLASSPATH=../../build/class $$A &&                     \
+
+       rm build/pruned/gnu/gcj/runtime/FirstThread*.class
+       cp upstream/gcc-3.3/src/libjava/gnu/gcj/runtime/FirstThread.java build/pruned/gnu/gcj/runtime/
+
+       rm build/pruned/org/ibex/plat/GCJ*.class
+       cp build/java/org/ibex/plat/GCJ.java build/pruned/org/ibex/plat/
+
+       cp upstream/gcc-3.3/build-$(target)/$(target)/libjava/java/lang/Object.class build/pruned/java/lang/
+
+       find build/pruned/gnu/java/locale/ -name 'LocaleInformation_*' -not -name 'LocaleInformation_en.class' -not -name 'LocaleInformation_en_US.class' -exec rm {} \;
+
+#      cd build/pruned; fastjar cvf ../../tmp.jar .
+#      java -jar lib/jarg.jar -verbose -verboseufm -normlv -normsf -normsy -normin \
+#                              -normex -nornc -nornf -nornm -nobco tmp.jar
+#      cd build/pruned; fastjar xvf ../../tmp.jar
+
+# try oneshot compile/link
+# --redefine-sym to merge errorthrowers?   or do this in C++?
+# --rename-section to try to merge Utf8 sections?
+
+       cd build/pruned;                                                  \
+               for A in `find . -name \*.class -or -name \*.java`; do    \
+                       echo compiling $$A....;                           \
+                       ../../upstream/install/bin/$(target)-gcj          \
+                               -fdata-sections -ffunction-sections -w -c -Os \
+                               -fCLASSPATH=../../build/$(platform)/ibex.jar $$A &&                     \
                                (mkdir -p ../../build/$(platform)/`dirname $$A`; mv *.o ../../build/$(platform)/`dirname $$A`); \
                done
        rm -f build/$(platform)/ibex.a
        cd upstream/gcc-3.3/build-$(target)/$(target)/libjava;           \
                $(shell pwd)/upstream/install/$(target)/bin/ar cq        \
                        $(shell pwd)/build/$(platform)/ibex.a            \
                                (mkdir -p ../../build/$(platform)/`dirname $$A`; mv *.o ../../build/$(platform)/`dirname $$A`); \
                done
        rm -f build/$(platform)/ibex.a
        cd upstream/gcc-3.3/build-$(target)/$(target)/libjava;           \
                $(shell pwd)/upstream/install/$(target)/bin/ar cq        \
                        $(shell pwd)/build/$(platform)/ibex.a            \
-                       `find . -name nat\*.o`                           \
+                       boehm.o  defineclass.o  exception.o              \
+                       posix-threads.o  posix.o  prims.o  resolve.o     \
+                       `find . -name nat\*.o | grep -v JIS`             \
                        `find . -name \*.o -not -name '*[A-Z]*'`         \
                        `find . -name \*.o -not -name '*[A-Z]*'`         \
-                       $(shell pwd)/build/$(platform)/org/ibex/plat/$(platform).cc.o \
                        `find $(shell pwd)/build/$(platform) -name \*.o`
        PATH=upstream/install/bin:$$PATH upstream/install/bin/$(target)-gcj \
                --main=org.ibex.plat.$(platform)                            \
                -Lupstream/install/$(target)/lib                            \
                -Lupstream/install/lib                                      \
                        `find $(shell pwd)/build/$(platform) -name \*.o`
        PATH=upstream/install/bin:$$PATH upstream/install/bin/$(target)-gcj \
                --main=org.ibex.plat.$(platform)                            \
                -Lupstream/install/$(target)/lib                            \
                -Lupstream/install/lib                                      \
+               -ffunction-sections -fdata-sections -Os -w -fCLASSPATH=build/$(platform)/ibex.jar \
                build/$(platform)/ibex.a                                    \
                upstream/jpeg-6b/build-$(target)/libjpeg.a                  \
                $(link_flags)                                               \
                upstream/gcc-3.3/build-$(target)/$(target)/boehm-gc/.libs/libgcjgc.a \
                -lz -ldl -lffi                                                   \
                build/$(platform)/ibex.a                                    \
                upstream/jpeg-6b/build-$(target)/libjpeg.a                  \
                $(link_flags)                                               \
                upstream/gcc-3.3/build-$(target)/$(target)/boehm-gc/.libs/libgcjgc.a \
                -lz -ldl -lffi                                                   \
+               -Wl,--noinhibit-exec,--gc-sections \
                -o $@
 
                -o $@
 
+# linker
+# --no-whole-archive,--relax,-O,2
+# 
+#                              -fomit-frame-pointer \
+#                              -fno-force-mem \
+#                              -fno-force-addr \
+#                              -fno-inline-functions \
+#                              -fnew-ra \
+#                              -fbranch-probabilities \
+#                              -finline-limit=1 \
+#                              -fno-schedule-insns \
+#                              -fno-optimize-sibling-calls \
+#                              -fno-if-conversion \
+#                              -fno-thread-jumps \
+#                              -fno-hosted \
 
 
 ### Builtin Resources ##############################################################################
 
 
 ### Builtin Resources ##############################################################################
index 45d1592..c93b9a2 100644 (file)
@@ -72,7 +72,8 @@ configure_gcc-3.3                       += --disable-shared --enable-static
 configure_binutils-2.13.2.1             += --disable-shared --enable-static
 configure_gcc-3.3_powerpc-apple-darwin  += --enable-threads=posix --disable-hash-synchronization --disable-multilib
 configure_gcc-3.3_i686-pc-mingw32       += --enable-threads=win32 --enable-hash-synchronization
 configure_binutils-2.13.2.1             += --disable-shared --enable-static
 configure_gcc-3.3_powerpc-apple-darwin  += --enable-threads=posix --disable-hash-synchronization --disable-multilib
 configure_gcc-3.3_i686-pc-mingw32       += --enable-threads=win32 --enable-hash-synchronization
-configure_gcc-3.3_i686-pc-linux-gnu     += --enable-threads=posix --enable-hash-synchronization
+configure_gcc-3.3_i686-pc-linux-gnu     += --enable-threads=posix --disable-hash-synchronization
+#configure_gcc-3.3_i686-pc-linux-gnu     += --enable-threads=posix --enable-hash-synchronization
 configure_gcc-3.3_sparc-sun-solaris2.7  += --enable-threads=posix --disable-hash-synchronization --disable-multilib
 
 configure_WindowMaker-0.80.2_$(target)  += --prefix=$(shell pwd)/upstream/install/$(target)
 configure_gcc-3.3_sparc-sun-solaris2.7  += --enable-threads=posix --disable-hash-synchronization --disable-multilib
 
 configure_WindowMaker-0.80.2_$(target)  += --prefix=$(shell pwd)/upstream/install/$(target)
index 9a9e324..e62a9ba 100644 (file)
@@ -8,6 +8,8 @@ extern const char **_Jv_argv;
 extern int _Jv_argc;
 
 void org::ibex::plat::Linux::fixEnvironment() {
 extern int _Jv_argc;
 
 void org::ibex::plat::Linux::fixEnvironment() {
+  // this wreaks havoc on gdb
+  /*
     // see http://lists.debian.org/debian-glibc/2003/debian-glibc-200311/msg00647.html
     const char* ld_assume_kernel = getenv("LD_ASSUME_KERNEL");
     if (ld_assume_kernel == NULL || strcmp("2.4.1", ld_assume_kernel)) {
     // see http://lists.debian.org/debian-glibc/2003/debian-glibc-200311/msg00647.html
     const char* ld_assume_kernel = getenv("LD_ASSUME_KERNEL");
     if (ld_assume_kernel == NULL || strcmp("2.4.1", ld_assume_kernel)) {
@@ -18,4 +20,5 @@ void org::ibex::plat::Linux::fixEnvironment() {
         printf("execvp() failed with error code %d\n", result);
        exit(-1);
     }
         printf("execvp() failed with error code %d\n", result);
        exit(-1);
     }
+  */
 }
 }
diff --git a/src/org/ibex/util/BytecodePruner.java b/src/org/ibex/util/BytecodePruner.java
new file mode 100644 (file)
index 0000000..f5dea9d
--- /dev/null
@@ -0,0 +1,384 @@
+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);
+    }
+
+}