diff --git a/jode/AUTHORS b/jode/AUTHORS index fc38b9f..0f30348 100644 --- a/jode/AUTHORS +++ b/jode/AUTHORS @@ -1 +1 @@ -Jochen Hoenicke \ No newline at end of file +Jochen Hoenicke diff --git a/jode/ChangeLog b/jode/ChangeLog index 3b32dbd..1e1ed4a 100644 --- a/jode/ChangeLog +++ b/jode/ChangeLog @@ -1,3 +1,23 @@ +2001-08-08 Jochen Hoenicke + More Documentation updates. + * build.xml: Release rules. + * scripts/jcpp.pl: Don't make backups of original. + * net/sf/jode/bytecode/BasicBlocks.java (setBlocks): Check that + successors are inside method. + * net/sf/jode/bytecode/Block.java (getStackHeight): New Method. + * net/sf/jode/bytecode/ClassPath.java (Location): public class to + model a component of the class path. Previously it was Path. + (ClassPath): New constructors added that take Location objects. + * net/sf/jode/bytecode/ConstantPool.java (getClassName): Cache + constants. + * net/sf/jode/bytecode/GrowableConstantPool.java: Made public. + (grow): Check that not too many constants are added. + (reserveLongConstants): Removed (not used). + (copyConstant): Removed (not used). + * net/sf/jode/jvm/NewObject.java: Made package protected. + * net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java: + Big updates (almost rewrote from scratch). Still doesn't compile. + 2001-08-05 Jochen Hoenicke Documentation updates (INSTALL, javadoc). diff --git a/jode/build.xml b/jode/build.xml index f15fa07..153a52e 100644 --- a/jode/build.xml +++ b/jode/build.xml @@ -25,17 +25,19 @@ - + + - - - + + + + @@ -55,8 +57,8 @@ - - + + @@ -135,8 +137,9 @@ - - + + + @@ -145,16 +148,13 @@ destdir="${build}" classpathref="project.classpath" deprecation="on"> - - - - @@ -162,50 +162,78 @@ - + - - - - - - - - - - - - - - - - - + + + + + + + + - - - - + + - + + + + + + + - - - + + + + + + + + - - - + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + @@ -231,7 +259,9 @@ sourcepath="${src}" destdir="${api.doc}" use="yes"> - + @@ -263,8 +293,7 @@ - - + diff --git a/jode/config.props b/jode/config.props index 150fd2a..a78fa1d 100644 --- a/jode/config.props +++ b/jode/config.props @@ -1,9 +1,11 @@ # Do you have online access for generating javadoc? # If not, where are your local files. javadoc.offline=false +#javadoc.href=http://java.sun.com/products/jdk/1.2/docs/api/ javadoc.packagelistLoc= -# javadoc.offline=true -# javadoc.packagelistLoc=/usr/doc/inet/java/jdk1.2/docs/api +#javadoc.offline=true +javadoc.href=file:/usr/doc/inet/java/jdk1.2/docs/api +#javadoc.packagelistLoc=/usr/doc/inet/java/jdk1.2/docs/api # Is Perl installed on your system? # diff --git a/jode/scripts/jcpp.pl b/jode/scripts/jcpp.pl index 7f886f8..91e7b7b 100755 --- a/jode/scripts/jcpp.pl +++ b/jode/scripts/jcpp.pl @@ -225,8 +225,7 @@ for (@files) { if ($changes == 0) { unlink "$file.tmp"; } else { - (rename "$file", "$file.orig" - and rename "$file.tmp", "$file") + (unlink "$file" and rename "$file.tmp", "$file") or print STDERR "$file: Couldn't rename files.\n"; } } else { diff --git a/jode/src/net/sf/jode/bytecode/BasicBlocks.java b/jode/src/net/sf/jode/bytecode/BasicBlocks.java index 8975096..ba12142 100644 --- a/jode/src/net/sf/jode/bytecode/BasicBlocks.java +++ b/jode/src/net/sf/jode/bytecode/BasicBlocks.java @@ -49,25 +49,43 @@ import java.lang.UnsupportedOperationException; * Instead this information is stored inside the blocks. See * Block for details.

* - *

A subroutine block, i.e. a block where some jsr instructions may - * jump to, must store its return address in a local variable - * immediately. There must be exactly one block with the - * corresponding opc_ret instruction and all blocks that - * belong to this subroutine must point to the ret block. Bytecode - * that doesn't have this condition is automatically transformed on - * reading.

- * *

Exception Handlers are represented by the Handler class. Their * start/end range must span over some consecutive BasicBlocks and * there handler must be another basic block.

* - *

If you want to create or modify the byte code, you must first set - * the basic blocks and then set exception handlers. If you set new - * blocks the previous exception handlers will be removed.

+ * * *

When the code is written to a class file, the blocks are written * in the given order. Goto and return instructions are inserted as - * necessary, you don't have to care about that.

+ * necessary, you don't need to care about that.

+ * + *

Creating new BasicBlocks

+ * + *

If you want to create a new BasicBlocks object, first create the + * Block objects, then initialize them (you need to have all successor + * blocks created for this). Afterwards create a new BasicBlock and + * fill its sub blocks:

+ * + *
+ *   MethodInfo myMethod = new MethodInfo("foo", "()V", PUBLIC);
+ *   Block blocks = new Block[10];
+ *   for (int i = 0; i < 10; i++) blocks[i] = new Block();
+ *   blocks[0].setCode(new Instruction[] {...}, 
+ *                     new Block[] {blocks[3], blocks[1]});
+ *   ...
+ *   Handler[] excHandlers = new Handler[1];
+ *   excHandlers[0] = new Handler(blocks[2], blocks[5], blocks[6],
+ *                                "java.lang.NullPointerException");
+ *   BasicBlocks bb = new BasicBlocks(myMethod);
+ *   bb.setCode(blocks, blocks[0], excHandlers);
+ *   classInfo.setMethods(new MethodInfo[] { myMethod });
+ * 
* * @see net.sf.jode.bytecode.Block * @see net.sf.jode.bytecode.Instruction @@ -238,13 +256,13 @@ public class BasicBlocks extends BinaryInfo implements Opcodes { public void setBlocks(Block[] blocks, Block startBlock, Handler[] handlers) { - for (int i = 0; i < blocks.length; i++) - blocks[i].blockNr = i; this.blocks = blocks; this.startBlock = startBlock; + exceptionHandlers = handlers.length == 0 ? Handler.EMPTY : handlers; ArrayList activeHandlers = new ArrayList(); for (int i = 0; i < blocks.length; i++) { + blocks[i].blockNr = i; for (int j = 0; j < handlers.length; j++) { if (handlers[j].getStart() == blocks[i]) activeHandlers.add(handlers[j]); @@ -259,6 +277,17 @@ public class BasicBlocks extends BinaryInfo implements Opcodes { activeHandlers.remove(handlers[j]); } } + /* Check if all successor blocks are in this basic block */ + for (int i = 0; i < blocks.length; i++) { + Block[] succs = blocks[i].getSuccs(); + for (int j = 0; j < succs.length; j++) { + if (succs[j] != null + && succs[j] != blocks[succs[j].blockNr]) + throw new IllegalArgumentException + ("Succ " + j + " of block " + i + + " not in basicblocks"); + } + } updateMaxStackLocals(); // TransformSubroutine.createSubroutineInfo(this); } diff --git a/jode/src/net/sf/jode/bytecode/BinaryInfo.java b/jode/src/net/sf/jode/bytecode/BinaryInfo.java index 9ffa1fd..662b98d 100644 --- a/jode/src/net/sf/jode/bytecode/BinaryInfo.java +++ b/jode/src/net/sf/jode/bytecode/BinaryInfo.java @@ -42,7 +42,7 @@ import java.util.Iterator; * *

There are some predefined attributes, even the Code of a Method * is an attribute. These predefined attributes are all handled by - * this package as appropriate. This methods are only useful for non + * this package as appropriate. These methods are only useful for non * standard attributes.

* *

You can provide new attributes by overriding the protected @@ -253,7 +253,7 @@ public class BinaryInfo { * BasicBlocks. * * @return the total length of all attributes, including their - * headers and the number of attributes field. + * headers and the "number of attributes" field. */ protected int getAttributeSize() { int size = 2; /* attribute count */ @@ -267,8 +267,9 @@ public class BinaryInfo { /** * Finds a non standard attribute with the given name. You don't - * have access to the constant pool. Instead extend this class - * and override readAttribute method if you need the pool. + * have access to the constant pool. If you need the pool don't + * use this method but extend this class and override + * readAttribute method. * @param name the name of the attribute. * @return the contents of the attribute, null if not found. * @see #readAttribute @@ -282,12 +283,14 @@ public class BinaryInfo { /** * Gets all non standard attributes. * @return an iterator for all attributes. The values returned by - * the next() method of the iterator are of byte[] type. + * the next() method of the iterator are of Map.Entry type. The + * key of the entry is the name of the attribute, while the values + * are the byte[] contents. * @see #findAttribute */ public Iterator getAttributes() { if (unknownAttributes != null) - return unknownAttributes.values().iterator(); + return unknownAttributes.entrySet().iterator(); return Collections.EMPTY_SET.iterator(); } diff --git a/jode/src/net/sf/jode/bytecode/Block.java b/jode/src/net/sf/jode/bytecode/Block.java index 88de266..8ade2ac 100644 --- a/jode/src/net/sf/jode/bytecode/Block.java +++ b/jode/src/net/sf/jode/bytecode/Block.java @@ -232,6 +232,15 @@ public final class Block { throw new IllegalArgumentException("no single successor block"); } + /** + * Returns the stack height at the beginning of the block. This + * is automatically calculated, when the block is inserted in a + * basic block. + */ + public int getStackHeight () { + return stackHeight; + } + public void getStackPopPush (int[] poppush) { poppush[0] = maxpop; poppush[1] = delta + maxpop; diff --git a/jode/src/net/sf/jode/bytecode/ClassInfo.java b/jode/src/net/sf/jode/bytecode/ClassInfo.java index f3dd540..7cd368e 100644 --- a/jode/src/net/sf/jode/bytecode/ClassInfo.java +++ b/jode/src/net/sf/jode/bytecode/ClassInfo.java @@ -356,7 +356,11 @@ public final class ClassInfo extends BinaryInfo implements Comparable { } ClassInfo(String name, ClassPath classpath) { - this.name = name.intern(); + /* Name may be null when reading class with unknown name from + * stream. + */ + if (name != null) + this.name = name.intern(); this.classpath = classpath; } @@ -658,10 +662,10 @@ public final class ClassInfo extends BinaryInfo implements Comparable { } /** - * Reads a class file from a data input stream. You should really + * Reads a class file from a data input stream. Normally you should * load a class from its classpath instead. This may * be useful for special kinds of input streams, that ClassPath - * doesn't handle though. + * doesn't handle. * * @param input The input stream, containing the class in standard * bytecode format. @@ -700,56 +704,43 @@ public final class ClassInfo extends BinaryInfo implements Comparable { ConstantPool cpool = new ConstantPool(); cpool.read(input); - /* always read modifiers, name, super, ifaces */ - { - modifiers = input.readUnsignedShort(); - String className = cpool.getClassName(input.readUnsignedShort()); - if (!name.equals(className)) - throw new ClassFormatException("wrong name " + className); - int superID = input.readUnsignedShort(); - superclass = superID == 0 ? null - : classpath.getClassInfo(cpool.getClassName(superID)); - int count = input.readUnsignedShort(); - interfaces = new ClassInfo[count]; - for (int i = 0; i < count; i++) { - interfaces[i] = classpath.getClassInfo - (cpool.getClassName(input.readUnsignedShort())); - } - } + /* modifiers */ + modifiers = input.readUnsignedShort(); + /* name */ + String className = cpool.getClassName(input.readUnsignedShort()); + if (name == null) + name = className; + else if (!name.equals(className)) + throw new ClassFormatException("wrong name " + className); + + /* superclass */ + int superID = input.readUnsignedShort(); + superclass = superID == 0 ? null + : classpath.getClassInfo(cpool.getClassName(superID)); + + /* interfaces */ + int count = input.readUnsignedShort(); + interfaces = new ClassInfo[count]; + for (int i = 0; i < count; i++) { + interfaces[i] = classpath.getClassInfo + (cpool.getClassName(input.readUnsignedShort())); + } /* fields */ - if (howMuch >= PUBLICDECLARATIONS) { - int count = input.readUnsignedShort(); - fields = new FieldInfo[count]; - for (int i = 0; i < count; i++) { - fields[i] = new FieldInfo(); - fields[i].read(cpool, input, howMuch); - } - } else { - byte[] skipBuf = new byte[6]; - int count = input.readUnsignedShort(); - for (int i = 0; i < count; i++) { - input.readFully(skipBuf); // modifier, name, type - skipAttributes(input); - } - } + count = input.readUnsignedShort(); + fields = new FieldInfo[count]; + for (int i = 0; i < count; i++) { + fields[i] = new FieldInfo(); + fields[i].read(cpool, input, howMuch); + } /* methods */ - if (howMuch >= PUBLICDECLARATIONS) { - int count = input.readUnsignedShort(); - methods = new MethodInfo[count]; - for (int i = 0; i < count; i++) { - methods[i] = new MethodInfo(); - methods[i].read(cpool, input, howMuch); - } - } else { - byte[] skipBuf = new byte[6]; - int count = input.readUnsignedShort(); - for (int i = 0; i < count; i++) { - input.readFully(skipBuf); // modifier, name, type - skipAttributes(input); - } - } + count = input.readUnsignedShort(); + methods = new MethodInfo[count]; + for (int i = 0; i < count; i++) { + methods[i] = new MethodInfo(); + methods[i].read(cpool, input, howMuch); + } /* initialize inner classes to empty array, in case there * is no InnerClasses attribute. @@ -769,9 +760,19 @@ public final class ClassInfo extends BinaryInfo implements Comparable { if (ci.status < OUTERCLASS) ci.mergeOuterInfo(null, null, -1, false); } + + /* Set status */ status = howMuch; } + /****** WRITING CLASS FILES ***************************************/ + + /** + * Reserves constant pool entries for String, Integer and Float + * constants needed by the bytecode. These constants should have + * small constant pool indices so that a ldc instead of a ldc_w + * bytecode can be used. + */ private void reserveSmallConstants(GrowableConstantPool gcp) { for (int i = 0; i < fields.length; i++) fields[i].reserveSmallConstants(gcp); @@ -780,8 +781,11 @@ public final class ClassInfo extends BinaryInfo implements Comparable { methods[i].reserveSmallConstants(gcp); } - /****** WRITING CLASS FILES ***************************************/ - + /** + * Reserves all constant pool entries needed by this class. This + * is necessary, because the constant pool is the first thing + * written to the class file. + */ private void prepareWriting(GrowableConstantPool gcp) { gcp.putClassName(name); gcp.putClassName(superclass.name); @@ -828,6 +832,9 @@ public final class ClassInfo extends BinaryInfo implements Comparable { prepareAttributes(gcp); } + /** + * Count the attributes needed by the class. + */ protected int getAttributeCount() { int count = super.getAttributeCount(); if (sourceFile != null) @@ -837,6 +844,10 @@ public final class ClassInfo extends BinaryInfo implements Comparable { return count; } + /** + * Write the attributes needed by the class, namely SourceFile + * and InnerClasses attributes. + */ protected void writeAttributes(GrowableConstantPool gcp, DataOutputStream output) throws IOException { @@ -958,7 +969,12 @@ public final class ClassInfo extends BinaryInfo implements Comparable { } /** - * Guess the contents of a class. It + * Guess the contents of a class. This is a last resort if the + * file can't be read by the class path. It generates outer class + * information based on the class name, assumes that the class + * extends java.lang.Object, implements no interfaces and has no + * fields, methods or inner classes. + * * @param howMuch The amount of information that should be read, e.g. * HIERARCHY. * @see #OUTERCLASS @@ -1018,7 +1034,7 @@ public final class ClassInfo extends BinaryInfo implements Comparable { } /** - * This is the counter part to load. It will drop all + * This is the counter part to load and guess. It will drop all * informations bigger than "keep" and clean up the memory. * @param keep tells how much info we should keep, can be * NONE or anything that load accepts. @@ -1217,8 +1233,8 @@ public final class ClassInfo extends BinaryInfo implements Comparable { /** * Sets the name of this class info. Note that by changing the * name you may overwrite an already loaded class. This can have - * ugly effects, as that overwritten class may still exist, but - * can't be loaded via the classPath. + * ugly effects, as references to that overwritten class may still + * exist. */ public void setName(String newName) { /* The class name is used as index in the hash table. We have @@ -1396,6 +1412,10 @@ public final class ClassInfo extends BinaryInfo implements Comparable { * Checks if this class is a super class of child. This loads the * complete hierarchy of child on demand and can throw an IOException * if some classes are not found or broken. + * + * It doesn't check for cycles in class hierarchy, so it may get + * into an eternal loop. + * * @param child the class that should be a child class of us. * @return true if this is as super class of child, false otherwise * @exception IOException if hierarchy of child could not be loaded. @@ -1414,7 +1434,10 @@ public final class ClassInfo extends BinaryInfo implements Comparable { * complete hierarchy of clazz on demand and can throw an IOException * if some classes are not found or broken. If this class is not an * interface it returns false, but you should check it yourself for - * better performance. + * better performance.
+ * + * It doesn't check for cycles in class hierarchy, so it may get + * into an eternal loop. * @param clazz the class to be checked. * @return true if this is a interface and is implemented by clazz, * false otherwise diff --git a/jode/src/net/sf/jode/bytecode/ClassPath.java b/jode/src/net/sf/jode/bytecode/ClassPath.java index b2f2af8..ded50ab 100644 --- a/jode/src/net/sf/jode/bytecode/ClassPath.java +++ b/jode/src/net/sf/jode/bytecode/ClassPath.java @@ -60,22 +60,27 @@ import net.sf.jode.util.UnifyHash; *

  • A URL (unified resource location), pointing to a directory
  • *
  • A URL pointing to a jar or zip file.
  • *
  • A Jar URL (see {@link java.net.JarURLConnection}), useful if - * the jar file is not packed correctly
  • - *
  • The reflection URL reflection:/ This location can - * only load declarations of classes. If a security manager is - * present, it can only load public declarations.
  • + * the jar file is not packed correctly. + *
  • The reflection URL reflection:/. This is a + * special location, which fills the ClassInfo with the information + * from the java reflection API. Obviously it can't load any files + * nor the full bytecode. It only loads declarations of classes. If a + * security manager is present, it can only load public + * declarations.
  • * * - * We use standard java means to find a class file: package correspong + * We use standard java means to find a class file: package correspond * to directories and the class file must have the .class * extension. For example if the class path points to * /home/java, the class java.lang.Object is - * loaded from /home/java/java/lang/Object.class. + * loaded from /home/java/java/lang/Object.class. Of course + * you can write your own {@link Location}s that break this rule. * * A class path can have another ClassPath as fallback. If * ClassInfo.loadInfo is called and the class isn't found the fallback * ClassPath is searched instead. This repeats until there is no - * further fallback. + * further fallback. The fallback is not used when listing classes or + * files. * * The main method for creating classes is {@link #getClassInfo}. The * other available methods are useful to find other files in the @@ -86,51 +91,133 @@ import net.sf.jode.util.UnifyHash; * them. * * @author Jochen Hoenicke - * @version 1.1 */ + * @version 1.1 + */ public class ClassPath { /** - * We need a different pathSeparatorChar, since ':' (used for most - * UNIX System) is used a protocol separator in URLs. + * We need a different pathSeparatorChar, since ':' (used for UNIX + * systems) is also used as protocol separator in URLs.
    * * We currently allow both pathSeparatorChar and * altPathSeparatorChar and decide if it is a protocol separator * by context. This doesn't always work, so use - * altPathSeparator, or the ClassPath(String[]) - * constructor. + * altPathSeparator, or better yet the + * ClassPath(String[]) or ClassPath(Location[]) constructors. */ public static final char altPathSeparatorChar = ','; - private class Path { - public boolean exists(String file) { + /** + * A location is a single component of the ClassPath. It provides + * methods to find files, list all files and reading them.
    + * + * Files and directories are always separated by "/" in this class, + * even under Windows where the default is a "\". This behaviour + * is consistent with that of {@link ZipFile}.
    + * + * You can extend this class to provide your own custom locations. + */ + public static class Location { + /** + * Tells whether there exists a file or directory with + * the given name at this location.
    + * The default implementation returns false. + * @param file the name of the file, directories are always + * separated by "/". + * @return true if a file exists at this location. + */ + protected boolean exists(String file) { return false; } - public boolean isDirectory(String file) { + + /** + * Tells whether there exists a directory (or package) with + * the given name at this location.
    + * The default implementation returns false. + * @param file the name of the directory, subdirectories are always + * separated by "/". + * @return true if a file exists at this location. + */ + protected boolean isDirectory(String file) { return false; } - public InputStream getFile(String file) throws IOException { + + /** + * Returns an input stream that reads the given file. It is only + * called for files for which exists returns true.
    + * The default implementation returns null. + * @param file the name of the file, subdirectories are always + * separated by "/". + * @return an input stream for the given file, or null if file + * was not found. + * @exception IOException if an io exception occured while opening + * the file. + */ + protected InputStream getFile(String file) throws IOException { return null; } - public Enumeration listFiles(String directory) { + + /** + * Lists the files and subdirectory in a directory. This is + * only called for directories for which isDirectory returns + * true.
    + * + * The objects returned by the nextElement() + * method of the Enumeration should be of type String and + * contain the file resp directory name without any parent + * directory names.
    + * + * Note that this method is also used by + * {@link ClassPath#listClassesAndPackages}.
    + * + * The default implementation returns null, which is equivalent + * to an empty enumeration. + * + * @param directory the name of the directory, subdirectories + * are always separated by "/". + * @return an enumeration, listing the file names. It may + * return null instead of an empty enumeration. + */ + protected Enumeration listFiles(String directory) { return null; } - public boolean loadClass(ClassInfo clazz, int howMuch) + /** + * Loads a class from this location and fills it with the given + * information.
    + * The default implementation will get the corresponding ".class" + * file via getFile() and fill the information from the stream. + * So normally there is no need to override this method. + *
    + * + * If you want to build classes on the fly, for example if you + * wrote a parser for java files and want to build class files + * from them, you can override this method. + * + * @param clazz the dot separated full qualified class name. + * @param howMuch the amount of information to load + * @return true, if loading the class was successful, false + * if it was not found. + * @exception ClassFormatException if class format is illegal + * @exception IOException if an io exception occured while reading + * the class. + * @see ClassInfo#read + */ + protected boolean loadClass(ClassInfo clazz, int howMuch) throws IOException, ClassFormatException { String file = clazz.getName().replace('.', '/') + ".class"; if (!exists(file)) return false; DataInputStream input = new DataInputStream - (new BufferedInputStream - (getFile(file))); + (new BufferedInputStream(getFile(file))); clazz.read(input, howMuch); return true; } } - private class ReflectionPath extends Path { - public boolean loadClass(ClassInfo classinfo, int howMuch) + private static class ReflectionLocation extends Location { + protected boolean loadClass(ClassInfo classinfo, int howMuch) throws IOException, ClassFormatException { if (howMuch > ClassInfo.DECLARATIONS) @@ -157,14 +244,14 @@ public class ClassPath { } } - private class LocalPath extends Path { + private static class LocalLocation extends Location { private File dir; - public LocalPath(File path) { + protected LocalLocation(File path) { dir = path; } - public boolean exists(String filename) { + protected boolean exists(String filename) { if (java.io.File.separatorChar != '/') filename = filename .replace('/', java.io.File.separatorChar); @@ -175,14 +262,14 @@ public class ClassPath { } } - public boolean isDirectory(String filename) { + protected boolean isDirectory(String filename) { if (java.io.File.separatorChar != '/') filename = filename .replace('/', java.io.File.separatorChar); return new File(dir, filename).isDirectory(); } - public InputStream getFile(String filename) throws IOException { + protected InputStream getFile(String filename) throws IOException { if (java.io.File.separatorChar != '/') filename = filename .replace('/', java.io.File.separatorChar); @@ -190,7 +277,7 @@ public class ClassPath { return new FileInputStream(f); } - public Enumeration listFiles(String directory) { + protected Enumeration listFiles(String directory) { if (File.separatorChar != '/') directory = directory .replace('/', File.separatorChar); @@ -222,7 +309,7 @@ public class ClassPath { } } - private class ZipPath extends Path { + private static class ZipLocation extends Location { private Hashtable entries = new Hashtable(); private ZipFile file; private byte[] contents; @@ -261,7 +348,7 @@ public class ClassPath { } while (name.length() > 0); } - public ZipPath(ZipFile zipfile, String prefix) { + ZipLocation(ZipFile zipfile, String prefix) { this.file = zipfile; this.prefix = prefix; @@ -272,7 +359,7 @@ public class ClassPath { } } - public ZipPath(byte[] zipcontents, String prefix) + ZipLocation(byte[] zipcontents, String prefix) throws IOException { this.contents = zipcontents; @@ -290,7 +377,7 @@ public class ClassPath { zis.close(); } - public boolean exists(String filename) { + protected boolean exists(String filename) { if (entries.containsKey(filename)) return true; @@ -307,11 +394,11 @@ public class ClassPath { return false; } - public boolean isDirectory(String filename) { + protected boolean isDirectory(String filename) { return entries.containsKey(filename); } - public InputStream getFile(String filename) throws IOException { + protected InputStream getFile(String filename) throws IOException { String fullname = prefix != null ? prefix + filename : filename; if (contents != null) { ZipInputStream zis = new ZipInputStream @@ -353,7 +440,7 @@ public class ClassPath { return null; } - public Enumeration listFiles(String directory) { + protected Enumeration listFiles(String directory) { Vector direntries = (Vector) entries.get(directory); if (direntries != null) return direntries.elements(); @@ -365,14 +452,14 @@ public class ClassPath { } } - private class URLPath extends Path { + private static class URLLocation extends Location { private URL base; - public URLPath(URL base) { + public URLLocation(URL base) { this.base = base; } - public boolean exists(String filename) { + protected boolean exists(String filename) { try { URL url = new URL(base, filename); URLConnection conn = url.openConnection(); @@ -384,7 +471,7 @@ public class ClassPath { } } - public InputStream getFile(String filename) throws IOException { + protected InputStream getFile(String filename) throws IOException { try { URL url = new URL(base, filename); URLConnection conn = url.openConnection(); @@ -395,9 +482,12 @@ public class ClassPath { } } - public boolean loadClass(ClassInfo clazz, int howMuch) + protected boolean loadClass(ClassInfo clazz, int howMuch) throws IOException, ClassFormatException { + /* We override this method to avoid the costs of the + * exists call, and to optimize howMuch. + */ String file = clazz.getName().replace('.', '/') + ".class"; InputStream is = getFile(file); if (is == null) @@ -405,7 +495,11 @@ public class ClassPath { DataInputStream input = new DataInputStream (new BufferedInputStream(is)); - clazz.read(input, howMuch); + + /* Reading an URL may be expensive. Therefore we ignore + * howMuch and read everything to avoid reading it again. + */ + clazz.read(input, ClassInfo.ALL); return true; } @@ -414,7 +508,7 @@ public class ClassPath { } } - private Path[] paths; + private Location[] paths; private UnifyHash classes = new UnifyHash(); ClassPath fallback = null; @@ -422,7 +516,8 @@ public class ClassPath { /** * Creates a new class path for the given path. See the class * description for more information, which kind of paths are - * supported. + * supported. When a class or a file is not found in the class + * path the fallback is used. * @param path An array of paths. * @param fallback The fallback classpath. */ @@ -441,6 +536,25 @@ public class ClassPath { initPath(paths); } + /** + * Creates a new class path for the given path. When a class + * or a file is not found in the class path the fallback is used. + * @param locs An array of locations. + * @param fallback The fallback classpath. + */ + public ClassPath(Location[] locs, ClassPath fallback) { + this.fallback = fallback; + paths = locs; + } + + /** + * Creates a new class path for the given path. + * @param locs An array of locations. + */ + public ClassPath(Location[] locs) { + paths = locs; + } + /** * Creates a new class path for the given path. See the class * description for more information, which kind of paths are @@ -468,7 +582,66 @@ public class ClassPath { initPath(tokenizeClassPath(path)); } - private String[] tokenizeClassPath(String path) { + /** + * Creates a location for a given path component. See the + * class comment which path components are supported. + * @param path the path component. + * @return a location corresponding to the class. + * @exception NullPointerException if path is null. + * @exception IOException if an io exception occured while accessing the + * path component. + * @exception SecurityException if a security exception occured + * while accessing the path component. + */ + public static Location createLocation(String path) + throws IOException, SecurityException + { + String zipPrefix = null; + // The special reflection URL + if (path.startsWith("reflection:")) + return new ReflectionLocation(); + + // We handle jar URL's ourself, this makes them work even with + // java 1.1 + if (path.startsWith("jar:")) { + int index = 0; + do { + index = path.indexOf('!', index); + } while (index != -1 && index != path.length()-1 + && path.charAt(index+1) != '/'); + + if (index == -1 || index == path.length() - 1) + throw new MalformedURLException(path); + zipPrefix = path.substring(index+2); + if (!zipPrefix.endsWith("/")) + zipPrefix += "/"; + path = path.substring(4, index); + } + + int index = path.indexOf(':'); + /* Grrr, we need to distinguish c:\foo from URLs. */ + if (index > 1) { + // This looks like an URL. + URL base = new URL(path); + URLConnection connection = base.openConnection(); + if (zipPrefix != null + || path.endsWith(".zip") || path.endsWith(".jar") + || connection.getContentType().endsWith("/zip")) { + // This is a zip file. Read it into memory. + byte[] contents = readURLZip(connection); + return new ZipLocation(contents, zipPrefix); + } else + return new URLLocation(base); + } else { + File dir = new File(path); + if (zipPrefix != null || !dir.isDirectory()) { + return new ZipLocation(new ZipFile(dir), zipPrefix); + } else + return new LocalLocation(dir); + } + } + + private static String[] tokenizeClassPath(String path) { // Calculate a good approximation (rounded upwards) of the tokens // in this path. int length = 1; @@ -528,133 +701,66 @@ public class ClassPath { return tokens; } - private byte[] readURLZip(URLConnection conn) { + private static byte[] readURLZip(URLConnection conn) throws IOException { int length = conn.getContentLength(); if (length <= 0) // Give a approximation if length is unknown length = 10240; else // Increase the length by one, so we hopefully don't need - // to grow the array later (we need a little overshot to + // to grow the array later (we need one byte overshot to // know when the end is reached). length++; byte[] contents = new byte[length]; - try { - InputStream is = conn.getInputStream(); - int pos = 0; - for (;;) { - // This is ugly, is.available() may return zero even - // if there are more bytes. - int avail = Math.max(is.available(), 1); - if (pos + is.available() > contents.length) { - // grow the byte array. - byte[] newarr = new byte - [Math.max(2*contents.length, pos + is.available())]; - System.arraycopy(contents, 0, newarr, 0, pos); - contents = newarr; - } - int count = is.read(contents, pos, contents.length-pos); - if (count == -1) - break; - pos += count; - } - if (pos < contents.length) { - // shrink the byte array again. - byte[] newarr = new byte[pos]; + InputStream is = conn.getInputStream(); + int pos = 0; + for (;;) { + // This is ugly, is.available() may return zero even + // if there are more bytes. + int avail = Math.max(is.available(), 1); + if (pos + avail > contents.length) { + // grow the byte array. + byte[] newarr = new byte + [Math.max(2*contents.length, pos + avail)]; System.arraycopy(contents, 0, newarr, 0, pos); contents = newarr; } - return contents; - } catch (IOException ex) { - return null; + int count = is.read(contents, pos, contents.length-pos); + if (count == -1) + break; + pos += count; + } + if (pos < contents.length) { + // shrink the byte array again. + byte[] newarr = new byte[pos]; + System.arraycopy(contents, 0, newarr, 0, pos); + contents = newarr; } + return contents; } private void initPath(String[] tokens) { int length = tokens.length; - paths = new Path[length]; + paths = new Location[length]; for (int i = 0; i < length; i++) { - String path = tokens[i]; - if (path == null) + if (tokens[i] == null) continue; - - String zipPrefix = null; - // The special reflection URL - if (path.startsWith("reflection:")) { - paths[i] = new ReflectionPath(); - continue; - } - - // We handle jar URL's ourself. - if (path.startsWith("jar:")) { - int index = 0; - do { - index = path.indexOf('!', index); - } while (index != -1 && index != path.length()-1 - && path.charAt(index+1) != '/'); - - if (index == -1 || index == path.length() - 1) { - GlobalOptions.err.println("Warning: Illegal jar url " -+ path + "."); - continue; - } - zipPrefix = path.substring(index+2); - if (!zipPrefix.endsWith("/")) - zipPrefix += "/"; - path = path.substring(4, index); - } - int index = path.indexOf(':'); - if (index != -1 && index < path.length()-2 - && path.charAt(index+1) == '/' - && path.charAt(index+2) == '/') { - // This looks like an URL. - try { - URL base = new URL(path); - try { - URLConnection connection = base.openConnection(); - if (zipPrefix != null - || path.endsWith(".zip") || path.endsWith(".jar") - || connection.getContentType().endsWith("/zip")) { - // This is a zip file. Read it into memory. - byte[] contents = readURLZip(connection); - if (contents != null) - paths[i] = new ZipPath(contents, zipPrefix); - } else - paths[i] = new URLPath(base); - } catch (IOException ex) { - GlobalOptions.err.println - ("Warning: IO exception while accessing " - +path+"."); - } catch (SecurityException ex) { - GlobalOptions.err.println - ("Warning: Security exception while accessing " - +path+"."); - } - } catch (MalformedURLException ex) { - GlobalOptions.err.println - ("Warning: Malformed URL "+ path + "."); - } - } else { - try { - File dir = new File(path); - if (zipPrefix != null || !dir.isDirectory()) { - try { - paths[i] = new ZipPath(new ZipFile(dir), - zipPrefix); - } catch (java.io.IOException ex) { - GlobalOptions.err.println - ("Warning: Can't read "+ path + "."); - } - } else - paths[i] = new LocalPath(dir); - } catch (SecurityException ex) { - GlobalOptions.err.println - ("Warning: SecurityException while accessing " - + path + "."); - } + try { + paths[i] = createLocation(tokens[i]); + } catch (MalformedURLException ex) { + GlobalOptions.err.println + ("Warning: Malformed URL "+ tokens[i] + "."); + } catch (IOException ex) { + GlobalOptions.err.println + ("Warning: IO exception while accessing " + +tokens[i]+"."); + } catch (SecurityException ex) { + GlobalOptions.err.println + ("Warning: Security exception while accessing " + +tokens[i]+"."); } } } @@ -663,10 +769,17 @@ public class ClassPath { /** * Creates a new class info for a class residing in this search * path. This doesn't load the class immediately, this is done by - * ClassInfo.loadInfo. It is no error if class doesn't exists. + * ClassInfo.loadInfo. It is no error if class doesn't exists.
    + * + * ClassInfos are guaranteed to be unique, i.e. if you have two + * ClsssInfo objects loaded from the same class path with the same + * classname they will always be identical. The only exception is + * if you use setName() or getClassInfoFromStream() and explicitly + * overwrite a previous class.
    + * * @param classname the dot-separated full qualified name of the class. * For inner classes you must use the bytecode name with $, - * e.g. java.util.Map$Entry. + * e.g. java.util.Map$Entry. * @exception IllegalArgumentException if class name isn't valid. */ public ClassInfo getClassInfo(String classname) @@ -684,6 +797,44 @@ public class ClassPath { return clazz; } + /** + * Creates a new class info from an input stream containing the + * bytecode. This method is useful if you don't know the class + * name or if you have the class in an unusual location. The + * class is fully loaded ({@link ClassInfo#ALL}) when you use this + * method.
    + * + * If a class with the same name was already created with + * getClassInfo() it will effectively be removed from the class + * path, although references to it may still exists elsewhere. + * + * @param stream the input stream containing the bytecode. + * @return the ClassInfo representing this class. + * @exception IOException if an io exception occurs. + * @exception ClassFormatException if bytecode isn't valid. + */ + public ClassInfo getClassInfoFromStream(InputStream stream) + throws IOException, ClassFormatException + { + ClassInfo classInfo = new ClassInfo(null, this); + classInfo.read(new DataInputStream(new BufferedInputStream(stream)), + ClassInfo.ALL); + String classname = classInfo.getName(); + /* Remove the classinfo with the same name from this path if + * it exists. + */ + Iterator iter = classes.iterateHashCode(classname.hashCode()); + while (iter.hasNext()) { + ClassInfo clazz = (ClassInfo) iter.next(); + if (clazz.getName().equals(classname)) { + iter.remove(); + break; + } + } + classes.put(classname.hashCode(), classInfo); + return classInfo; + } + /** * Updates the classes unify hash for a class renaming. This * should be only called by {@link ClassInfo#setName}. @@ -740,7 +891,7 @@ public class ClassPath { /** * Searches for a file in the class path. * @param filename the filename. The path components should be separated - * by /. + * by "/". * @return An InputStream for the file. */ public InputStream getFile(String filename) throws IOException { @@ -748,6 +899,8 @@ public class ClassPath { if (paths[i] != null && paths[i].exists(filename)) return paths[i].getFile(filename); } + if (fallback != null) + return fallback.getFile(filename); throw new FileNotFoundException(filename); } @@ -755,7 +908,7 @@ public class ClassPath { * Searches for a filename in the class path and tells if it is a * directory. * @param filename the filename. The path components should be separated - * by /. + * by "/". * @return true, if filename exists and is a directory, false otherwise. */ public boolean isDirectory(String filename) { @@ -768,7 +921,8 @@ public class ClassPath { /** * Searches for a filename in the class path and tells if it is a - * package. This is the same as isDirectory. + * package. This is the same as isDirectory, but takes dot separated + * names. * @param fqn the full qualified name. The components should be dot * separated. * @return true, if filename exists and is a package, false otherwise. @@ -781,7 +935,7 @@ public class ClassPath { /** * Get a list of all files in a given directory. * @param dirName the directory name. The path components must - * be separated by /. + * be separated by "/". * @return An enumeration with all files/directories in the given * directory. If dirName doesn't denote a directory it returns null. */ @@ -855,6 +1009,9 @@ public class ClassPath { }; } + /** + * Loads the contents of a class. This is only called by ClassInfo. + */ boolean loadClass(ClassInfo clazz, int howMuch) throws IOException, ClassFormatException { @@ -867,13 +1024,17 @@ public class ClassPath { return false; } + /** + * Returns a string representation of this classpath. + * @return a string useful for debugging purposes. + */ public String toString() { StringBuffer sb = new StringBuffer("ClassPath["); for (int i = 0; i < paths.length; i++) { if (paths[i] != null) sb.append(paths[i]).append(','); } - sb.append(fallback).append(']'); + sb.append("fallback=").append(fallback).append(']'); return sb.toString(); } } diff --git a/jode/src/net/sf/jode/bytecode/ConstantPool.java b/jode/src/net/sf/jode/bytecode/ConstantPool.java index f90b7ce..e73dabd 100644 --- a/jode/src/net/sf/jode/bytecode/ConstantPool.java +++ b/jode/src/net/sf/jode/bytecode/ConstantPool.java @@ -30,9 +30,10 @@ import java.lang.UnsupportedOperationException; ///#enddef /** - * This class represent the constant pool. You normally don't need to - * access this class, except if you want to add your own custom - * attributes that use the constant pool. + * This class represent the constant pool. Normally you wont need to + * touch this class, as ClassInfo already does all the hard work. You + * will only need it if you want to add your own custom attributes + * that use the constant pool. * * @author Jochen Hoenicke */ @@ -179,13 +180,16 @@ public class ConstantPool { public String getClassName(int i) throws ClassFormatException { if (tags[i] != CLASS) throw new ClassFormatException("Tag mismatch"); - String clName = getUTF8(indices1[i]); - try { - TypeSignature.checkTypeSig("L"+clName+";"); - } catch (IllegalArgumentException ex) { - throw new ClassFormatException(ex.getMessage()); + if (constants[i] == null) { + String clName = getUTF8(indices1[i]); + try { + TypeSignature.checkTypeSig("L"+clName+";"); + } catch (IllegalArgumentException ex) { + throw new ClassFormatException(ex.getMessage()); + } + constants[i] = clName.replace('/','.').intern(); } - return clName.replace('/','.').intern(); + return (String) constants[i]; } /** diff --git a/jode/src/net/sf/jode/bytecode/FieldInfo.java b/jode/src/net/sf/jode/bytecode/FieldInfo.java index a257d00..c810ae7 100644 --- a/jode/src/net/sf/jode/bytecode/FieldInfo.java +++ b/jode/src/net/sf/jode/bytecode/FieldInfo.java @@ -26,6 +26,39 @@ import java.lang.reflect.Modifier; import java.lang.Comparable; ///#enddef +/** + * Represents a java bytecode field (class variable). A field + * consists of the following parts: + * + *
    + * + *
    name
    The field's name
    + * + *
    type
    The field's {@link TypeSignature type signature} + * in bytecode format.
    + * + *
    modifiers
    The modifiers of the field like private, public etc. + * These are created by or-ing the constants {@link Modifier#PUBLIC}, + * {@link Modifier#PRIVATE}, {@link Modifier#PROTECTED}, + * {@link Modifier#STATIC}, {@link Modifier#FINAL}, + * {@link Modifier#VOLATILE}, {@link Modifier#TRANSIENT}, + * {@link Modifier#STRICT} + * of class {@link java.lang.reflect.Modifier}. + * + *
    synthetic
    true if this field is synthetic.
    + * + *
    deprecated
    true if this field is deprecated.
    + * + *
    constant
    Final static fields may have a constant + * value. This is either of type String, Integer, Long, Float or + * Double. + * + *
    + * + * @author Jochen Hoenicke + * @see net.sf.jode.bytecode.TypeSignature + * @see net.sf.jode.bytecode.BasicBlocks + */ public final class FieldInfo extends BinaryInfo implements Comparable { int modifier; String name; @@ -34,10 +67,21 @@ public final class FieldInfo extends BinaryInfo implements Comparable { Object constant; boolean syntheticFlag; boolean deprecatedFlag; - + + /** + * Creates a new empty field info. + */ public FieldInfo() { } + /** + * Creates a new field with given name, type and modifiers. + * @param name the name of the field. + * @param typeSig the typeSig the type signature. + * @param modifier the modifier + * @see TypeSignature + * @see Modifier + */ public FieldInfo(String name, String typeSig, int modifier) { this.name = name; this.typeSig = typeSig; @@ -134,7 +178,7 @@ public final class FieldInfo extends BinaryInfo implements Comparable { } void write(GrowableConstantPool constantPool, - DataOutputStream output) throws IOException { + DataOutputStream output) throws IOException { output.writeShort(modifier); output.writeShort(constantPool.putUTF8(name)); output.writeShort(constantPool.putUTF8(typeSig)); @@ -147,38 +191,81 @@ public final class FieldInfo extends BinaryInfo implements Comparable { super.drop(keep); } + /** + * Gets the name of the field. + * @return the name. + */ public String getName() { return name; } + /** + * Gets the type signature of the field. + * @return the type signature. + * @see TypeSignature + */ public String getType() { return typeSig; } + /** + * Gets the modifier of the field. + * @return the modifiers. + * @see Modifier + */ public int getModifiers() { return modifier; } + /** + * Tells whether this field is synthetic. + * @return true if the field is synthetic. + */ public boolean isSynthetic() { return syntheticFlag; } + /** + * Tells whether this field is deprecated. + * @return true if the field is deprecated. + */ public boolean isDeprecated() { return deprecatedFlag; } + /** + * Gets the constant value of the field. For static final fields + * that have a simple String, int, float, double or long constant, + * this returns the corresponding constant as String, Integer, Float + * Double or long. For other fields it returns null. + * @return The constant, or null. + */ public Object getConstant() { return constant; } + /** + * Sets the name of the field. + * @param newName the name. + */ public void setName(String newName) { name = newName; } + /** + * Sets the type signature of the field. + * @param newType the type signature. + * @see TypeSignature + */ public void setType(String newType) { typeSig = newType; } + /** + * Sets the modifier of the field. + * @param newModifier the modifiers. + * @see Modifier + */ public void setModifiers(int newModifier) { modifier = newModifier; } diff --git a/jode/src/net/sf/jode/bytecode/GrowableConstantPool.java b/jode/src/net/sf/jode/bytecode/GrowableConstantPool.java index 56432f5..88e7ab4 100644 --- a/jode/src/net/sf/jode/bytecode/GrowableConstantPool.java +++ b/jode/src/net/sf/jode/bytecode/GrowableConstantPool.java @@ -23,11 +23,14 @@ import java.io.IOException; import java.util.Hashtable; /** - * This class represent a constant pool, where new constants can be added to. + * This class represent a constant pool, where new constants can be + * added to. Normally you wont need to touch this class, as ClassInfo + * already does all the hard work. You will only need it if you want + * to add your own custom attributes that use the constant pool. * * @author Jochen Hoenicke */ -class GrowableConstantPool extends ConstantPool { +public class GrowableConstantPool extends ConstantPool { Hashtable entryToIndex = new Hashtable(); boolean written; @@ -59,6 +62,9 @@ class GrowableConstantPool extends ConstantPool { } } + /** + * Create a new growable constant pool + */ public GrowableConstantPool () { count = 1; tags = new int[128]; @@ -68,11 +74,13 @@ class GrowableConstantPool extends ConstantPool { written = false; } - public final void grow(int wantedSize) { + private final void grow(int wantedSize) { if (written) throw new IllegalStateException("adding to written ConstantPool"); + if (wantedSize > 65535) + throw new IllegalArgumentException("Too many constants added"); if (tags.length < wantedSize) { - int newSize = Math.max(tags.length*2, wantedSize); + int newSize = Math.min(65535, Math.max(tags.length*2, wantedSize)); int[] tmpints = new int[newSize]; System.arraycopy(tags, 0, tmpints, 0, count); tags = tmpints; @@ -117,7 +125,7 @@ class GrowableConstantPool extends ConstantPool { return newIndex; } - int putIndexed(int tag, Object obj1, int index1, int index2) { + private int putIndexed(int tag, Object obj1, int index1, int index2) { Key key = new Key(tag, obj1, index2); Integer indexObj = (Integer) entryToIndex.get(key); if (indexObj != null) { @@ -135,16 +143,45 @@ class GrowableConstantPool extends ConstantPool { return count++; } + /** + * Adds a new UTF8 entry to the constant pool and returns the index. + * If it already exists it will reuse the previous entry. + * @param utf the UTF8 string. + * @return the index of the pool entry. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ public final int putUTF8(String utf) { return putConstant(UTF8, utf); } + /** + * Adds a new class name entry to the constant pool and returns + * the index. If it already exists it will reuse the previous + * entry. + * @param name the dot separated full qualified class name. + * @return the index of the pool entry. + * @exception IllegalArgumentException if the class name is illegal. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ public int putClassName(String name) { name = name.replace('.','/'); TypeSignature.checkTypeSig("L"+name+";"); return putIndexed(CLASS, name, putUTF8(name), 0); } + /** + * Adds a new class entry to the constant pool and returns + * the index. If it already exists it will reuse the previous + * entry. This is the same as putClassName, except for the format + * of the parameter and that it can also handle arrays. + * @param name the type signature of the class to add. + * @return the index of the pool entry. + * @exception IllegalArgumentException if the class name is illegal. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ public int putClassType(String name) { TypeSignature.checkTypeSig(name); if (name.charAt(0) == 'L') @@ -154,6 +191,19 @@ class GrowableConstantPool extends ConstantPool { return putIndexed(CLASS, name, putUTF8(name), 0); } + /** + * Adds a new field/method or interface reference to the constant + * pool and returns the index. If it already exists it will reuse + * the previous entry. + * @param tag the tag of the reference, one of FIELDREF, METHODREF + * or INTERFACEMETHODREF. + * @param ref the reference. + * @return the index of the pool entry. + * @exception IllegalArgumentException if the reference type or + * class name is illegal. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ public int putRef(int tag, Reference ref) { String className = ref.getClazz(); String typeSig = ref.getType(); @@ -172,10 +222,14 @@ class GrowableConstantPool extends ConstantPool { } /** - * Puts a constant into this constant pool - * @param c the constant, must be of type - * Integer, Long, Float, Double or String - * @return the index into the pool of this constant. + * Puts a "one slot" constant into this constant pool. If it + * already exists it will reuse the previous entry. + * @param c the constant; must be of type + * Integer, Float or String + * @return the index of the pool entry. + * @exception IllegalArgumentException if the constant is of wrong type. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. */ public int putConstant(Object c) { if (c instanceof String) { @@ -194,10 +248,13 @@ class GrowableConstantPool extends ConstantPool { } /** - * Puts a constant into this constant pool - * @param c the constant, must be of type - * Integer, Long, Float, Double or String - * @return the index into the pool of this constant. + * Puts a "double slot" constant into this constant pool. If it + * already exists it will reuse the previous entry. + * @param c the constant; must be of type Long or Double + * @return the index of the pool entry. + * @exception IllegalArgumentException if the constant is of wrong type. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. */ public int putLongConstant(Object c) { int tag; @@ -212,9 +269,11 @@ class GrowableConstantPool extends ConstantPool { } /** - * Reserve an entry in this constant pool for a constant (for ldc). + * Reserves an entry in this constant pool for a constant (for ldc). + * The constant must still be put into the pool, before the pool may + * be written. * @param c the constant, must be of type - * Integer, Long, Float, Double or String + * Integer, Float or String * @return the reserved index into the pool of this constant. */ public int reserveConstant(Object c) { @@ -226,25 +285,14 @@ class GrowableConstantPool extends ConstantPool { } /** - * Reserve an entry in this constant pool for a constant (for ldc). - * @param c the constant, must be of type - * Integer, Long, Float, Double or String - * @return the reserved index into the pool of this constant. + * Writes the constant pool to the stream. This is normally called + * by {@link ClassInfo#write}. + * @param stream the stream to write to. + * @exception IOException if it occured while writing. */ - public int reserveLongConstant(Object c) { - return putLongConstant(c); - } - - public int copyConstant(ConstantPool cp, int index) - throws ClassFormatException { - return putConstant(cp.getConstant(index)); - } - public void write(DataOutputStream stream) throws IOException { written = true; - if (count > 65536) - throw new ClassFormatError("Too many constants"); stream.writeShort(count); for (int i=1; i< count; i++) { int tag = tags[i]; diff --git a/jode/src/net/sf/jode/bytecode/Instruction.java b/jode/src/net/sf/jode/bytecode/Instruction.java index 7063205..9e2db65 100644 --- a/jode/src/net/sf/jode/bytecode/Instruction.java +++ b/jode/src/net/sf/jode/bytecode/Instruction.java @@ -20,11 +20,13 @@ package net.sf.jode.bytecode; /** - * This class represents an instruction in the byte code. + *

    This class represents an instruction in the byte code. + * Instructions can be created with the static {@link #forOpcode} + * methods.

    * - * We only allow a subset of opcodes. Other opcodes are mapped to - * their simpler version. When writing the bytecode the shortest - * possible bytecode is produced. + *

    We only allow a subset of opcodes. Other opcodes are mapped to + * their simpler version. Don't worry about this, when writing the + * bytecode the shortest possible bytecode is produced.

    * * The opcodes we map are: *
    @@ -216,55 +218,104 @@ public class Instruction implements Opcodes{
         }
     
         /**
    -     * Returns the opcode of the instruction.  
    +     * Gets the opcode of the instruction.  
    +     * @return the opcode of the instruction.  
          */
         public final int getOpcode() {
     	return lineAndOpcode & 0xff;
         }
     
    +    /**
    +     * Tells whether there is a line number information for this
    +     * instruction.
    +     * @return true if there is a line number information for this
    +     * instruction.
    +     */
         public final boolean hasLineNr() {
     	return lineAndOpcode >= 0;
         }
     
    +    /**
    +     * Gets the line number of this instruction.
    +     * @return the line number, or -1 if there isn't one.
    +     */
         public final int getLineNr() {
     	return lineAndOpcode >> 8;
         }
     
    +    /**
    +     * Sets the line number of this instruction.
    +     * @param nr the line number; use -1 to clear it.
    +     */
         public final void setLineNr(int nr) {
     	lineAndOpcode = (nr << 8) | (lineAndOpcode & 0xff);
         }
     
    +    /**
    +     * Checks whether this instruction is a local store instruction, i.e.
    +     * one of astore, istore, lstore, 
    +     * fstore or dstore.
    +     */
         public boolean isStore() {
     	return false;
         }
     
    +    /**
    +     * Checks whether this instruction accesses a local slot.
    +     */
         public boolean hasLocal() {
     	return false;
         }
     	    
    +    /**
    +     * Gets the slot number of the local this instruction accesses.
    +     * @return the slot number.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * access a local slot.
    +     */
         public int getLocalSlot()
         {
     	throw new IllegalArgumentException();
     	// UnsupportedOperationException would be more appropriate
         }
     
    +    /**
    +     * Gets the information of the local this instruction accesses.
    +     * @return the local variable info.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * access a local.
    +     */
         public LocalVariableInfo getLocalInfo()
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Sets the information of the local this instruction accesses.
    +     * @param info the local variable info.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * access a local.
    +     */
         public void setLocalInfo(LocalVariableInfo info) 
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Sets the slot of the local this instruction accesses.
    +     * @param slot the local slot
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * access a local.
    +     */
         public void setLocalSlot(int slot) 
         {
     	throw new IllegalArgumentException();
         }
     
         /**
    -     * Get the increment for an opc_iinc instruction.
    +     * Gets the increment for an opc_iinc instruction.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
          */
         public int getIncrement()
         {
    @@ -272,7 +323,9 @@ public class Instruction implements Opcodes{
         }
     
         /**
    -     * Set the increment for an opc_iinc instruction.
    +     * Sets the increment for an opc_iinc instruction.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
          */
         public void setIncrement(int incr)
         {
    @@ -280,7 +333,9 @@ public class Instruction implements Opcodes{
         }
     
         /**
    -     * Get the dimensions for an opc_anewarray opcode.
    +     * Gets the dimensions for an opc_multianewarray opcode.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
          */
         public int getDimensions()
         {
    @@ -288,53 +343,101 @@ public class Instruction implements Opcodes{
         }
     
         /**
    -     * Set the dimensions for an opc_anewarray opcode.
    +     * Sets the dimensions for an opc_multianewarray opcode.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
          */
         public void setDimensions(int dims)
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Gets the constant for a opc_ldc or opc_ldc2_w opcode.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
    +     */
         public Object getConstant() 
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Sets the constant for a opc_ldc or opc_ldc2_w opcode.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
    +     */
         public void setConstant(Object constant) 
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Gets the reference of the field or method this instruction accesses.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
    +     */
         public Reference getReference()
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Sets the reference of the field or method this instruction accesses.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
    +     */
         public void setReference(Reference ref)
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Gets the class type this instruction uses, e.g if its a class cast.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
    +     */
         public String getClazzType() 
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Sets the class type this instruction uses, e.g if its a class cast.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
    +     */
         public void setClazzType(String type)
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Gets the values of a opc_lookupswitch opcode.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
    +     */
         public int[] getValues()
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Sets the values of a opc_lookupswitch opcode.
    +     * @throws IllegalArgumentException if this instruction doesn't
    +     * support this.
    +     */
         public void setValues(int[] values) 
         {
     	throw new IllegalArgumentException();
         }
     
    +    /**
    +     * Checks whether this instruction always changes program flow.
    +     * Returns false for opc_jsr it.
    +     * @return true if this instruction always changes flow, i.e. if
    +     * its an unconditional jump, a return, a throw, a ret or a switch.
    +     */
         public final boolean doesAlwaysJump() {
     	switch (getOpcode()) {
     	case opc_ret:
    @@ -380,6 +483,12 @@ public class Instruction implements Opcodes{
     	return toString();
         }
     
    +    /**
    +     * Gets a printable representation of the opcode with its
    +     * parameters.  This will not include the destination for jump
    +     * instructions, since this information is not stored inside the
    +     * instruction.  
    +     */
         public String toString() {
     	return opcodeString[getOpcode()];
         }
    diff --git a/jode/src/net/sf/jode/bytecode/LocalVariableInfo.java b/jode/src/net/sf/jode/bytecode/LocalVariableInfo.java
    index f2cdeaa..622c2ad 100644
    --- a/jode/src/net/sf/jode/bytecode/LocalVariableInfo.java
    +++ b/jode/src/net/sf/jode/bytecode/LocalVariableInfo.java
    @@ -24,7 +24,17 @@ import java.util.Iterator;
     ///#enddef
     
     /**
    - * A simple class containing the info of the LocalVariableTable
    + * A simple class containing the info of the LocalVariableTable.  This
    + * info is stored decentral: every load, store or iinc instruction contains
    + * the info for its local.  When writing code it will automatically be
    + * collected again. 
    + * + * You can't modify a LocalVariableInfo, for this reason they can and + * will be shared.
    + * + * This information consists of name, type signature and slot number. + * There is no public constructor; use the static getInfo() methods + * instead. */ public final class LocalVariableInfo { private String name, type; @@ -56,14 +66,25 @@ public final class LocalVariableInfo { for (int i=start; i< upper; i++) anonymous[i] = new LocalVariableInfo(i); } - + + /** + * Creates a new local variable info, with no name or type. + * @param slot the slot number. + */ public static LocalVariableInfo getInfo(int slot) { if (slot >= anonymous.length) grow(Math.max(slot + 1, anonymous.length * 2)); return anonymous[slot]; } - public static LocalVariableInfo getInfo(int slot, String name, String type) { + /** + * Creates a new local variable info, with given name and type. + * @param slot the slot number. + * @param name the name of the local. + * @param type the type signature of the local. + */ + public static LocalVariableInfo getInfo(int slot, + String name, String type) { if (name == null && type == null) return getInfo(slot); int hash = slot ^ name.hashCode() ^ type.hashCode(); @@ -80,18 +101,31 @@ public final class LocalVariableInfo { return lvi; } + /** + * Gets the slot number. + */ public int getSlot() { return slot; } + /** + * Gets the name. + */ public String getName() { return name; } + /** + * Gets the type signature. + * @see TypeSignature + */ public String getType() { return type; } + /** + * Gets a string representation for debugging purposes. + */ public String toString() { String result = ""+slot; if (name != null) diff --git a/jode/src/net/sf/jode/bytecode/MethodInfo.java b/jode/src/net/sf/jode/bytecode/MethodInfo.java index ec5ec0f..8356a11 100644 --- a/jode/src/net/sf/jode/bytecode/MethodInfo.java +++ b/jode/src/net/sf/jode/bytecode/MethodInfo.java @@ -27,8 +27,8 @@ import java.lang.Comparable; ///#enddef /** - *

    Represents a java bytecode method. A method consists of the following - * parts:

    + * Represents a java bytecode method. A method consists of the following + * parts: * *
    * @@ -37,9 +37,13 @@ import java.lang.Comparable; *
    type
    The method's {@link TypeSignature type signature} * in bytecode format.
    * - *
    modifiers
    The modifiers of the method like private, public etc. - * These are created by or-ing the constants defined in - * {@link java.lang.reflect.Modifier}. + *
    modifiers
    The modifiers of the field like private, public etc. + * These are created by or-ing the constants {@link Modifier#PUBLIC}, + * {@link Modifier#PRIVATE}, {@link Modifier#PROTECTED}, + * {@link Modifier#STATIC}, {@link Modifier#FINAL}, + * {@link Modifier#SYNCHRONIZED}, {@link Modifier#NATIVE}, + * {@link Modifier#ABSTRACT}, {@link Modifier#STRICT} + * of class {@link java.lang.reflect.Modifier}. * *
    basicblocks
    the bytecode of the method in form of * {@link BasicBlocks basic blocks}, null if it is native or diff --git a/jode/src/net/sf/jode/expr/InvokeOperator.java b/jode/src/net/sf/jode/expr/InvokeOperator.java index d49f377..9b7efa3 100644 --- a/jode/src/net/sf/jode/expr/InvokeOperator.java +++ b/jode/src/net/sf/jode/expr/InvokeOperator.java @@ -82,6 +82,10 @@ public final class InvokeOperator extends Operator * first element is the hint type of the return value, the * remaining entries are the hint types of the parameters. All * hint types may be null, if that parameter shouldn't be hinted. + * + * The reason why we don't put the class name into the top level + * key, is that we don't necessarily know the class. We may have + * a sub class, but the hint should of course still apply. */ private final static HashMap hintTypes = new HashMap(); @@ -100,7 +104,7 @@ public final class InvokeOperator extends Operator * make much sense to hint for byte, since its constant * representation is more difficult than an int * representation. If you have more hints to suggest, please - * write contact me. (see GlobalOptions.EMAIL) + * contact me. (see GlobalOptions.EMAIL) */ Type tCharHint = new IntegerType(IntegerType.IT_I, IntegerType.IT_C); Type[] hintC = new Type[] { tCharHint }; @@ -1208,7 +1212,7 @@ public final class InvokeOperator extends Operator } writer.endOp(); - /* No the easier part: Dump the arguments from arg to length. + /* Now the easier part: Dump the arguments from arg to length. * We still need to check for casts though. */ writer.breakOp(); diff --git a/jode/src/net/sf/jode/jvm/Interpreter.java b/jode/src/net/sf/jode/jvm/Interpreter.java index 0959a74..abb6f24 100644 --- a/jode/src/net/sf/jode/jvm/Interpreter.java +++ b/jode/src/net/sf/jode/jvm/Interpreter.java @@ -36,8 +36,8 @@ import java.util.Iterator; /** * This class is a java virtual machine written in java :-). Well not - * exactly. It does only handle a subset of the opcodes and is mainly - * written do deobfuscate Strings. + * exactly. It is only a bytecode interpreter, you have to supply the + * rest of the VM (the runtime environment). * * @author Jochen Hoenicke */ diff --git a/jode/src/net/sf/jode/jvm/NewObject.java b/jode/src/net/sf/jode/jvm/NewObject.java index dddedab..0f2b0b3 100644 --- a/jode/src/net/sf/jode/jvm/NewObject.java +++ b/jode/src/net/sf/jode/jvm/NewObject.java @@ -24,7 +24,7 @@ package net.sf.jode.jvm; * * @author Jochen Hoenicke */ -public class NewObject { +class NewObject { Object instance; String type; diff --git a/jode/src/net/sf/jode/jvm/RuntimeEnvironment.java b/jode/src/net/sf/jode/jvm/RuntimeEnvironment.java index 7c8414f..cce0092 100644 --- a/jode/src/net/sf/jode/jvm/RuntimeEnvironment.java +++ b/jode/src/net/sf/jode/jvm/RuntimeEnvironment.java @@ -34,8 +34,10 @@ import java.lang.reflect.InvocationTargetException; *
  • array of primitive type is mapped to itself (not array of Integer)
  • *
  • array of other types are mapped to array of mapped other type
  • * - * - * @author Jochen Hoenicke */ + * + * @author Jochen Hoenicke + * @see SimpleRuntimeEnvironment + */ public interface RuntimeEnvironment { /** * Get the value of a field member. diff --git a/jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java b/jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java index 8ca526f..1b37d94 100644 --- a/jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java +++ b/jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java @@ -27,6 +27,11 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; +/** + * This is a runtime environment using reflection. + * Monitors are not supported by this class so exit/enterMonitor + * will through an InterpreterException. + */ public class SimpleRuntimeEnvironment implements RuntimeEnvironment { public static Object fromReflectType(String typeSig, Object value) { diff --git a/jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java b/jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java index 82915e2..364ac2b 100644 --- a/jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java +++ b/jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java @@ -65,191 +65,481 @@ public class RemovePopAnalyzer implements CodeTransformer, Opcodes { public BlockInfo analyzeBlock(Block block, BlockInfo oldInfo) { } - public BlockInfo[] calcBlockInfos(BasicBlocks bb) { - Block[] blocks = bb.getBlocks(); - BlockInfo[] infos = new BlockInfo[blocks.length]; - int poppush[] = new int[2]; - int maxStack = bb.getMaxStack(); - int poppedLen = maxStack >> 5; - for (int i = 0; i < blocks.length; i++) { - BitSet popped = new BitSet(); - Instruction[] instrs = blocks[i].getInstructions(); - int[] removed = instrs.length >> 5; - - // Be conservative with stack depth at end of block - int depth = maxStack; - - for (int j = instrs.length; j-- > 0; ) { - Instruction instr = instrs[j]; - - instr.getStackPopPush(poppush); - - // Now check if the parameters of this instr are needed. - boolean params_needed = false; - switch (instr.getOpcode()) { - case opc_invokevirtual: - case opc_invokespecial: - case opc_invokestatic: - case opc_invokeinterface: - case opc_checkcast: - /* These instructions have side effects, parameters - * are always needed. Adjust depth. - */ - params_needed = true; - depth += poppush[1]; - break; - - default: - /* Check if all results are needed, adjust depth */ - for (int k = 0; k < poppush[1]; k++) { - if (!popped.get(depth++)) - params_needed = true; - } - } + /** + * This method propagates pops through a dup instruction, eventually + * generating new code and a new set of popped instructions. + * + * @param opcode the opcode of the dup instruction. + * @param newInstruction a list where the new instructions should + * be added to the front. + * @param stackDepth the stack depth after the dup is executed. + * @param poppedEntries the stack slots that should be popped at + * the end of this dup. + * @return The stack slots that should be popped at the start of + * the dup. + */ + byte movePopsThroughDup(int opcode, + List newInstructions, + int poppedEntries) { + int count = (opcode - opc_dup)/3+1; + int depth = (opcode - opc_dup)%3; + + /* Calculate which entries can be popped before this instruction, + * and update opcode. + */ + int newPopped = + (((poppedEntries + 1) << depth) - 1) & (poppedEntries >> count); + int mask = ((1 << count) - 1); + int poppedDeep = poppedEntries & mask; + int poppedTop = (poppedEntries >> (depth + count)) & mask; + boolean swapAfterDup = false; + boolean swapBeforeDup = false; + + for (int i = count+depth; i > depth; i--) { + if ((newPopped & (1 << i)) != 0) + depth--; + } - if (params_needed) { - /* mark params as needed */ - for (int k = 0; k < poppush[0]; k++) - popped.clear(--depth); - } else { - removed[j >> 5] |= 1 << (j & 31); - /* mark params as popped */ - for (int k = 0; k < poppush[0]; k++) - popped.set(--depth); - } + // adjust the depth + for (int i = depth; i > 0; i--) { + if ((newPopped & (1 << i)) != 0) + depth--; + } + + // adjust the count and poppedDeep/3 + if ((poppedDeep & poppedTop & 1) != 0) { + count--; + poppedDeep >>= 1; + poppedTop >>= 1; + mask >>= 1; + } else if ((poppedDeep & poppedTop & 2) != 0) { + count--; + poppedDeep &= 1; + poppedTop &= 1; + mask &= 1; + } + + if (poppedDeep == mask + || (depth == 0 && poppedTop == mask)) { + // dup was not necessary + return newPopped; + } + + /* Now (poppedDeep & poppedTop) == 0 */ + + if (poppedTop > 0) { + /* Insert the pop for the top elements, we add + * the dup later in front of these instructions. + */ + if (poppedTop == 3) { + newInstructions.addFirst + (Instruction.forOpcode(opc_pop2)); + } else { + newInstructions.addFirst + (Instruction.forOpcode(opc_pop)); + if (count == 2 && poppedTop == 1) + swapAfterDup = true; } - - int[] poppedArr = new int[poppedLen]; - if (blocks[i] != bb.getStartBlock()) { - /* Only can pop entries before this block if - * this isn't the start block. + } + + if (poppedDeep != 0) { + if (poppedDeep == 2) { + /* We swap before and after dupping to get to + * poppedDeep = 1 case. */ - for (int k = 0; k < block_pop; k++) { - if (popped.get(depth+k)) - poppedArr[k >> 5] |= 1 << (k & 31); - } + swapAfterDup = !swapAfterDup; + swapBeforeDup = true; } - infos[i] = new BlockInfo(poppedArr, removed); + /* The bottom most value is popped; decrease count + * and increase depth, so that it won't be created + * in the first place. + */ + depth++; + count--; } + + /* Now all pops are resolved */ + /* Do a dup with count and depth now. */ + + if (swapAfterDup) + newInstructions.addFirst + (Instruction.forOpcode(opc_swap)); - /* Now start sharing poppedEntries as necessary. */ - int[] empty = new int[poppedLen]; - next_block: - for (int i = 1; i < blocks.length; i++) { - /* Try to find an earlier block with a same predecessor. */ - for (int j = 0; j < blocks.length; j++) { - Block[] succs = blocks[j].getSuccs(); - if (succs.length < 2) - continue; - - int blockNr = -1; - boolean isAPred = false; - for (int k = 0; k < succs.length; k++) { - if (succs[k] == blocks[i]) - isAPred = true; - if (succs[k] != null && succs[k].getBlockNr() < i) - blockNr = succs[k].getBlockNr(); - if (isAPred && blockNr >= 0) { - int[] common = infos[blockNr].poppedEntries; - int[] my = infos[i].poppedEntries; - for (int k = 0; k < poppedLen; k++) - common[k] &= my[k]; - infos[i].poppedEntries = common; - continue next_block; - } - } - } + if (depth < 3) { + newInstructions.addFirst + (Instruction.forOpcode(opc_pop - 3 + + depth + 3 * count)); + } else { + // I hope that this will almost never happen. + // depth = 3, count = 1; + // Note that instructions are backwards. + newInstructions.addFirst + (Instruction.forOpcode(opc_pop2)); //DABCD< + newInstructions.addFirst + (Instruction.forOpcode(opc_dup2_x2)); //DABCDAB< + newInstructions.addFirst + (Instruction.forOpcode(opc_pop)); //DCDAB< + newInstructions.addFirst + (Instruction.forOpcode(opc_dup_x2)); //DCDABD< + newInstructions.addFirst + (Instruction.forOpcode(opc_pop)); //DCABD< + newInstructions.addFirst + (Instruction.forOpcode(opc_dup2_x2)); //DCABDC< + swappedBeforeDup = !swappedBeforeDup //ABDC< } - /* Now propagate poppedEntries through blocks */ - boolean changed = true; - while (changed) { - changed = false; - next_block: - for (int i = 0; i < blocks.length; i++) { - Block[] succs = blocks[i].getSuccs(); - int[] succPops = null; - for (int j = 0; j < succs.length; j++) { - if (succs[j] != null) { - succPops = infos[succs[j].getBlockNr()].poppedEntries; + if (swapBeforeDup) + newInstructions.addFirst + (Instruction.forOpcode(opc_swap)); + return newPopped + } + + /** + * This method analyzes a block from end to start and removes the + * pop instructions together with their pushes. It propagates pops + * to the front removing various instructions on the fly, which may + * generate new pops for their operands and so on. + * + * @param block the block of code. + * @param poppedEntries the stack slots that should be popped at + * the end of this block. + * @return the stack slots that should be popped at the start of + * the block. + */ + BitSet movePopsToFront(Block block, BitSet poppedEntries) { + /* Calculate stack height at end of block. */ + Instruction[] oldInstrs = block.getInstructions(); + LinkedList newInstructions = new LinkedList(); + + int instrNr = oldInstrs.length; + int stackDepth = block.getStackHeight() + block.getStackDelta(); + while (instrNr > 0) { + Instruction instr = oldInstrs[--instrNr]; + if (instr.getOpcode() == opc_nop) + continue; + if (instr.getOpcode() == opc_pop) { + popsAtEnd.set(stackDepth++); + continue; + } + if (instr.getOpcode() == opc_pop2) { + popsAtEnd.set(stackDepth++); + popsAtEnd.set(stackDepth++); + continue; + } + + instr.getStackPopPush(poppush); + + /* Check if this instr pushes a popped Entry. */ + boolean push_a_popped = false; + boolean push_all_popped = true; + for (int j=0; j < poppush[1]; j++) { + if (poppedEntries.get(j)) + push_a_popped = true; + else + push_all_popped = false; + } + + if (!push_a_popped) { + // Normal case: + // add the instruction and adjust stack depth. + newInstructions.addFirst(instr); + stackDepth += poppush[0] - poppush[1]; + continue; + } + + /* We push an entry, that gets popped later */ + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_dup: + case opc_dup_x1: + case opc_dup_x2: + case opc_dup2: + case opc_dup2_x1: + case opc_dup2_x2: { + int popped = 0; + for (int j = poppush[1] ; j > 0; j--) { + popped <<= 1; + if (poppedEntries.get(--stackDepth)) { + popped |= 1; + poppedEntries.clear(stackDepth); } } - if (succPops == null) - continue; - blocks[i].getStackPopPush(poppush); - int[] myPops = infos[i].poppedPush; - for (int k = poppush[1]; k < maxStack; k++) { - - if (succs.length == 0 || succs[0] == null) - continue; - int[] succsPoppedEntries - = infos[succs[0].getBlockNr()].poppedEntries; - for (int j = 0; j < succs.length; j++) { - if (succs[j] == null) - continue next_block; - int[] thisPopped - = infos[succs[j].getBlockNr()].poppedEntries; - - for (int k = 0; k < poppedLen; k++) { - succsPoppedEntries &= - - - for (int j = instrs.length; j-- > 0; ) { - Instruction instr = instrs[j]; - - instr.getStackPopPush(poppush); - - // Now check if the parameters of this instr are needed. - boolean params_needed = false; - switch (instr.getOpcode()) { - case opc_invokevirtual: - case opc_invokespecial: - case opc_invokestatic: - case opc_invokeinterface: - case opc_checkcast: - /* These instructions have side effects, parameters - * are always needed. Adjust depth. - */ - params_needed = true; - depth += poppush[1]; - break; - - case opc_pop: - case opc_pop2: - break; - - default: - /* Check if all results are needed, adjust depth */ - for (int k = 0; k < poppush[1]; k++) { - if (!popped.get(depth++)) - params_needed = true; + popped = movePopsThroughDup(opcode, newInstructions, + popped); + for (int j=0; j < poppush[1]; j++) { + if ((popped & 1) != 0) + poppedEntries.set(stackDepth); + stackDepth++; + popped >>=1; + } + break; + } + + case opc_swap: + if (!push_all_popped) { + // swap the popped status + if (poppedEntries.get(stackDepth - 1)) { + poppedEntries.clear(stackDepth - 1); + poppedEntries.set(stackDepth - 2); + } else { + poppedEntries.set(stackDepth - 1); + poppedEntries.clear(stackDepth - 2); } - /* If opcode has no result it has side effects. */ - if (poppush[1] == 0) - params_needed = true; } - if (block_pop < maxStack - depth) - block_pop = maxStack - depth; - - if (params_needed) { - /* mark params as needed */ - for (int k = 0; k < poppush[0]; k++) - popped.clear(--depth); + + case opc_ldc2_w: + case opc_lload: case opc_dload: + case opc_i2l: case opc_i2d: + case opc_f2l: case opc_f2d: + case opc_ldc: + case opc_iload: case opc_fload: case opc_aload: + case opc_new: + case opc_lneg: case opc_dneg: + case opc_l2d: case opc_d2l: + case opc_laload: case opc_daload: + case opc_ineg: case opc_fneg: + case opc_i2f: case opc_f2i: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_newarray: case opc_anewarray: + case opc_arraylength: + case opc_instanceof: + case opc_lshl: case opc_lshr: case opc_lushr: + case opc_iaload: case opc_faload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + case opc_iadd: case opc_fadd: + case opc_isub: case opc_fsub: + case opc_imul: case opc_fmul: + case opc_idiv: case opc_fdiv: + case opc_irem: case opc_frem: + case opc_iand: case opc_ior : case opc_ixor: + case opc_ishl: case opc_ishr: case opc_iushr: + case opc_fcmpl: case opc_fcmpg: + case opc_l2i: case opc_l2f: + case opc_d2i: case opc_d2f: + case opc_ladd: case opc_dadd: + case opc_lsub: case opc_dsub: + case opc_lmul: case opc_dmul: + case opc_ldiv: case opc_ddiv: + case opc_lrem: case opc_drem: + case opc_land: case opc_lor : case opc_lxor: + case opc_lcmp: + case opc_dcmpl: case opc_dcmpg: + case opc_getstatic: + case opc_getfield: + case opc_multianewarray: + + /* The simple instructions, that can be removed. */ + if (!push_all_popped) + throw new InternalError("pop half of a long"); + if (poppush[0] < poppush[1]) { + for (int j=0; j < poppush[0] - poppush[1]; j++) + poppedEntries.set(stackDepth++); + } else if (poppush[0] < poppush[1]) { + for (int j=0; j < poppush[0] - poppush[1]; j++) + poppedEntries.clear(--stackDepth); + } + + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_checkcast: + + /* These instructions can't be removed, since + * they have side effects. + */ + if (!push_all_popped) + throw new InternalError("pop half of a long"); + if (poppush[1] == 1) { + poppedEntries.clear(--stackDepth); + newInstructions + .addFirst(Instruction.forOpcode(opc_pop)); } else { - removed[j >> 5] |= 1 << (j & 31); - /* mark params as popped */ - for (int k = 0; k < poppush[0]; k++) - popped.set(--depth); + poppedEntries.clear(--stackDepth); + poppedEntries.clear(--stackDepth); + newInstructions + .addFirst(Instruction.forOpcode(opc_pop2)); } + newInstructions.addFirst(instr); + default: + throw new InternalError("Unexpected opcode!"); + } + } + blocks[i].setCode((Instruction[]) newInstructions + .toArray(new Instruction[newInstructions.size()]), + blocks[i].getSuccs()); + return poppedEntries + } + + /** + * This method analyzes a block from start to end and inserts the + * pop instructions at the right places. It is used if a pop couldn't + * be removed for some reason. + * + * @param block the block of code. + * @param poppedEntries the stack slots that should be popped at + * the end of this block. + * @return the stack slots that should be popped at the start of + * the block. + */ + void movePopsToTail(Block block, BitSet poppedEntries) { + /* Calculate stack height at end of block. */ + Instruction[] oldInstrs = block.getInstructions(); + ArrayList newInstructions = new ArrayList(); + + int instrNr = oldInstrs.length; + int stackDepth = block.getStackHeight(); + for (instrNr = 0; instrNr < oldInstrs.length; instrNr++) { + while (poppedEntries.get(stackDepth-1)) { + poppedEntries.clear(--stackDepth); + /* XXX opc_pop2?*/ + newInstructions.add(Instruction.forOpcode(opc_pop)); + } + + Instruction instr = oldInstrs[--instrNr]; + instr.getStackPopPush(poppush); + for (stackDepth + + /* Check if this instr pushes a popped Entry. */ + boolean pops_a_popped = false; + boolean pops_all_popped = true; + for (int j=0; j < poppush[1]; j++) { + if (poppedEntries.get(j)) + push_a_popped = true; + else + push_all_popped = false; + } + + if (!push_a_popped) { + // Normal case: + // add the instruction and adjust stack depth. + newInstructions.addFirst(instr); + stackDepth += poppush[0] - poppush[1]; + continue; } - if (blocks[j] == null) - ; + /* We push an entry, that gets popped later */ + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_dup: + case opc_dup_x1: + case opc_dup_x2: + case opc_dup2: + case opc_dup2_x1: + case opc_dup2_x2: { + int popped = 0; + for (int j = poppush[1] ; j > 0; j--) { + popped <<= 1; + if (poppedEntries.get(--stackDepth)) { + popped |= 1; + poppedEntries.clear(stackDepth); + } + } + popped = movePopsThroughDup(opcode, newInstructions, + popped); + for (int j=0; j < poppush[1]; j++) { + if ((popped & 1) != 0) + poppedEntries.set(stackDepth); + stackDepth++; + popped >>=1; } + break; + } + + case opc_swap: + if (!push_all_popped) { + // swap the popped status + if (poppedEntries.get(stackDepth - 1)) { + poppedEntries.clear(stackDepth - 1); + poppedEntries.set(stackDepth - 2); + } else { + poppedEntries.set(stackDepth - 1); + poppedEntries.clear(stackDepth - 2); + } + } + + case opc_ldc2_w: + case opc_lload: case opc_dload: + case opc_i2l: case opc_i2d: + case opc_f2l: case opc_f2d: + case opc_ldc: + case opc_iload: case opc_fload: case opc_aload: + case opc_new: + case opc_lneg: case opc_dneg: + case opc_l2d: case opc_d2l: + case opc_laload: case opc_daload: + case opc_ineg: case opc_fneg: + case opc_i2f: case opc_f2i: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_newarray: case opc_anewarray: + case opc_arraylength: + case opc_instanceof: + case opc_lshl: case opc_lshr: case opc_lushr: + case opc_iaload: case opc_faload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + case opc_iadd: case opc_fadd: + case opc_isub: case opc_fsub: + case opc_imul: case opc_fmul: + case opc_idiv: case opc_fdiv: + case opc_irem: case opc_frem: + case opc_iand: case opc_ior : case opc_ixor: + case opc_ishl: case opc_ishr: case opc_iushr: + case opc_fcmpl: case opc_fcmpg: + case opc_l2i: case opc_l2f: + case opc_d2i: case opc_d2f: + case opc_ladd: case opc_dadd: + case opc_lsub: case opc_dsub: + case opc_lmul: case opc_dmul: + case opc_ldiv: case opc_ddiv: + case opc_lrem: case opc_drem: + case opc_land: case opc_lor : case opc_lxor: + case opc_lcmp: + case opc_dcmpl: case opc_dcmpg: + case opc_getstatic: + case opc_getfield: + case opc_multianewarray: + + /* The simple instructions, that can be removed. */ + if (!push_all_popped) + throw new InternalError("pop half of a long"); + if (poppush[0] < poppush[1]) { + for (int j=0; j < poppush[0] - poppush[1]; j++) + poppedEntries.set(stackDepth++); + } else if (poppush[0] < poppush[1]) { + for (int j=0; j < poppush[0] - poppush[1]; j++) + poppedEntries.clear(--stackDepth); + } + + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_checkcast: + + /* These instructions can't be removed, since + * they have side effects. + */ + if (!push_all_popped) + throw new InternalError("pop half of a long"); + if (poppush[1] == 1) { + poppedEntries.clear(--stackDepth); + newInstructions + .addFirst(Instruction.forOpcode(opc_pop)); + } else { + poppedEntries.clear(--stackDepth); + poppedEntries.clear(--stackDepth); + newInstructions + .addFirst(Instruction.forOpcode(opc_pop2)); + } + newInstructions.addFirst(instr); + default: + throw new InternalError("Unexpected opcode!"); } } - return infos; + block.setCode((Instruction[]) newInstructions + .toArray(new Instruction[newInstructions.size()]), + block.getSuccs()); } public void transformCode(BasicBlocks bb) { @@ -262,326 +552,6 @@ public class RemovePopAnalyzer implements CodeTransformer, Opcodes { boolean poppedEntries[] = new boolean[bb.getMaxStack()]; Block[] blocks = bb.getBlocks(); for (int i = 0; i < blocks.length; i++) { - LinkedList newInstructions = new LinkedList(); - Instruction[] oldInstrs = blocks[i].getInstructions(); - int instrNr = oldInstrs.length; - int stackDepth = 0; - while (instrNr > 0) { - Instruction instr = oldInstrs[instrNr]; - if (instr.getOpcode() == opc_nop) - continue; - if (instr.getOpcode() == opc_pop) { - poppedEntries[stackDepth++] = true; - continue; - } - if (instr.getOpcode() == opc_pop2) { - poppedEntries[stackDepth++] = true; - poppedEntries[stackDepth++] = true; - continue; - } - - instr.getStackPopPush(poppush); - // First look if stackDepth was right - if (stackDepth < poppush[1]) { - int diff = poppush[1] - stackDepth; - System.arraycopy(poppedEntries, 0, - poppedEntries, diff, stackDepth); - for (int j=0; j< diff; i++) - poppedEntries[j] = false; - } - // Now check if this instr pushes a popped Entry. - boolean push_a_popped = false; - boolean push_all_popped = true; - for (int j=0; j < poppush[1]; j++) { - if (poppedEntries[j]) - push_a_popped = true; - else - push_all_popped = false; - } - if (push_a_popped) { - /* We push an entry, that gets popped later */ - int opcode = instr.getOpcode(); - switch (opcode) { - case opc_dup: - case opc_dup_x1: - case opc_dup_x2: - case opc_dup2: - case opc_dup2_x1: - case opc_dup2_x2: { - - int count = (opcode - opc_dup)/3+1; - int depth = (opcode - opc_dup)%3; - stackDepth -= count; - int bottom = stackDepth - count - depth; - - int popped1 = 0; - int popped3 = 0; - int newDepth = 0; - - // get the popped mask and adjust poppedEntries. - for (int j=0; j< count; j++) { - if (poppedEntries[bottom + j]) - popped1 |= 1<>= 1; - popped3 >>= 2; - } else if ((popped1 & popped3 & 2) != 0) { - count--; - popped1 &= 1; - popped3 &= 1; - } - - if (popped1 == 1) { - // count == 2, popped1 = 1, - depth++; - count--; - popped1 = 0; - popped3 >>= 1; - } - if (count == 2 && popped1 == 0 && popped3 > 0) { - // Do the normal dup2 and pop the right - // element afterwards. - if (popped3 == 3) { - newInstructions.addFirst - (Instruction.forOpcode(opc_pop2)); - } else { - newInstructions.addFirst - (Instruction.forOpcode(opc_pop)); - if (popped3 == 1) - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); - } - popped3 = 0; - } - if (popped1 == popped3) { - // popped1 == popped3 == 0 - // Do a dupcount_xdepth now. - if (depth < 3) { - newInstructions.addFirst - (Instruction.forOpcode(opc_pop - 3 + - depth + 3 * count)); - break; - } else { - // I hope that this will almost never happen. - // depth = 3, count = 1; - // Note that instructions are backwards. - newInstructions.addFirst - (Instruction.forOpcode(opc_pop2)); //DABCD - newInstructions.addFirst - (Instruction.forOpcode(opc_dup2_x2)); //DABCDAB - newInstructions.addFirst - (Instruction.forOpcode(opc_pop)); //DCDAB - newInstructions.addFirst - (Instruction.forOpcode(opc_dup_x2)); //DCDABD - newInstructions.addFirst - (Instruction.forOpcode(opc_pop)); //DCABD - newInstructions.addFirst - (Instruction.forOpcode(opc_dup2_x2)); //DCABDC - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); //ABDC - break; - } - } - - if (count == 1) { - // Possible states: - // popped1 = 0; popped3 = 1; - // depth = 1 or depth = 2 - if (depth == 1) { - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); - } else { - newInstructions.addFirst - (Instruction.forOpcode(opc_pop)); - newInstructions.addFirst(instr); - } - break; - } - - // count = 2; popped1 = 2 - // Possible states: - // dpth/pop3 0 1 - // 0 AB AAB AB - // 1 ABC BABC BAC - // 2 ABCD CABCD CABD - if (popped3 == 0) { - if (depth == 0) { - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); - newInstructions.addFirst - (Instruction.forOpcode(opc_dup_x1)); - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); - } else if (depth == 1) { - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); //BABC - newInstructions.addFirst - (Instruction.forOpcode(opc_dup_x2)); //BACB - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); //ACB - } else { - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); //CABCD - newInstructions.addFirst - (Instruction.forOpcode(opc_pop2)); //CABDC - newInstructions.addFirst - (Instruction.forOpcode(opc_dup2_x2)); //CABDCAB - newInstructions.addFirst - (Instruction.forOpcode(opc_pop)); //CDCAB - newInstructions.addFirst - (Instruction.forOpcode(opc_dup_x2)); //CDCABC - newInstructions.addFirst - (Instruction.forOpcode(opc_pop)); //CDABC - newInstructions.addFirst - (Instruction.forOpcode(opc_dup2_x2)); //CDABCD - } - } else { - if (depth == 0) { - } else if (depth == 1) { - newInstructions.addFirst - (Instruction.forOpcode(opc_pop)); //BAC - newInstructions.addFirst - (Instruction.forOpcode(opc_dup_x2)); //BACB - newInstructions.addFirst - (Instruction.forOpcode(opc_swap)); //ACB - } else { - newInstructions.addFirst - (Instruction.forOpcode(opc_pop2)); //CABD - newInstructions.addFirst - (Instruction.forOpcode(opc_dup2_x1)); //CABDAB - newInstructions.addFirst - (Instruction.forOpcode(opc_pop2)); //CDAB - newInstructions.addFirst - (Instruction.forOpcode(opc_dup2_x2)); //CDABCD - } - } - break; - } - case opc_swap: { - // swap the popped status - boolean tmp = poppedEntries[stackDepth - 1]; - poppedEntries[stackDepth - 1] - = poppedEntries[stackDepth - 2]; - poppedEntries[stackDepth - 2] = tmp; - } - - - // Now the simple instructions, that can be removed. - // delta = -2; - case opc_ldc2_w: - case opc_lload: case opc_dload: - if (!push_all_popped) - throw new InternalError("pop half of a long"); - poppedEntries[--stackDepth] = false; - poppedEntries[--stackDepth] = false; - continue; - - case opc_i2l: case opc_i2d: - case opc_f2l: case opc_f2d: - case opc_ldc: - case opc_iload: case opc_fload: case opc_aload: - case opc_new: - case opc_lneg: case opc_dneg: - case opc_l2d: case opc_d2l: - case opc_laload: case opc_daload: - case opc_ineg: case opc_fneg: - case opc_i2f: case opc_f2i: - case opc_i2b: case opc_i2c: case opc_i2s: - case opc_newarray: case opc_anewarray: - case opc_arraylength: - case opc_instanceof: - case opc_lshl: case opc_lshr: case opc_lushr: - case opc_iaload: case opc_faload: case opc_aaload: - case opc_baload: case opc_caload: case opc_saload: - case opc_iadd: case opc_fadd: - case opc_isub: case opc_fsub: - case opc_imul: case opc_fmul: - case opc_idiv: case opc_fdiv: - case opc_irem: case opc_frem: - case opc_iand: case opc_ior : case opc_ixor: - case opc_ishl: case opc_ishr: case opc_iushr: - case opc_fcmpl: case opc_fcmpg: - case opc_l2i: case opc_l2f: - case opc_d2i: case opc_d2f: - case opc_ladd: case opc_dadd: - case opc_lsub: case opc_dsub: - case opc_lmul: case opc_dmul: - case opc_ldiv: case opc_ddiv: - case opc_lrem: case opc_drem: - case opc_land: case opc_lor : case opc_lxor: - case opc_lcmp: - case opc_dcmpl: case opc_dcmpg: - case opc_getstatic: - case opc_getfield: - case opc_multianewarray: - - if (!push_all_popped) - throw new InternalError("pop half of a long"); - if (poppush[0] < poppush[1]) { - for (int j=0; j < poppush[0] - poppush[1]; j++) - poppedEntries[stackDepth++] = true; - } else if (poppush[0] < poppush[1]) { - for (int j=0; j < poppush[0] - poppush[1]; j++) - poppedEntries[--stackDepth] = false; - } - - case opc_invokevirtual: - case opc_invokespecial: - case opc_invokestatic: - case opc_invokeinterface: - case opc_checkcast: - if (!push_all_popped) - throw new InternalError("pop half of a long"); - if (poppush[1] == 1) { - poppedEntries[--stackDepth] = false; - newInstructions - .addFirst(Instruction.forOpcode(opc_pop)); - } else { - poppedEntries[--stackDepth] = false; - poppedEntries[--stackDepth] = false; - newInstructions - .addFirst(Instruction.forOpcode(opc_pop2)); - } - newInstructions.addFirst(instr); - default: - throw new InternalError("Unexpected opcode!"); - } - } else { - // Add the instruction .. - newInstructions.addFirst(instr); - // .. and adjust stackDepth. - stackDepth += poppush[0] - poppush[1]; - } - } - for (int j=0; j < stackDepth; j++) { - // XXXX - } blocks[i].setCode((Instruction[]) newInstructions .toArray(oldInstrs), blocks[i].getSuccs()); } diff --git a/jode/src/net/sf/jode/util/SimpleMap.java b/jode/src/net/sf/jode/util/SimpleMap.java index 4ca2eef..b900c32 100644 --- a/jode/src/net/sf/jode/util/SimpleMap.java +++ b/jode/src/net/sf/jode/util/SimpleMap.java @@ -96,5 +96,3 @@ public class SimpleMap extends AbstractMap { return null; } } - -