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.


git-svn-id: https://svn.code.sf.net/p/jode/code/trunk@1340 379699f6-c40d-0410-875b-85095c16579e
master
hoenicke 23 years ago
parent dab92b2d4d
commit 6acf9d8d7a
  1. 2
      jode/AUTHORS
  2. 20
      jode/ChangeLog
  3. 125
      jode/build.xml
  4. 6
      jode/config.props
  5. 3
      jode/scripts/jcpp.pl
  6. 57
      jode/src/net/sf/jode/bytecode/BasicBlocks.java
  7. 15
      jode/src/net/sf/jode/bytecode/BinaryInfo.java
  8. 9
      jode/src/net/sf/jode/bytecode/Block.java
  9. 135
      jode/src/net/sf/jode/bytecode/ClassInfo.java
  10. 473
      jode/src/net/sf/jode/bytecode/ClassPath.java
  11. 22
      jode/src/net/sf/jode/bytecode/ConstantPool.java
  12. 91
      jode/src/net/sf/jode/bytecode/FieldInfo.java
  13. 108
      jode/src/net/sf/jode/bytecode/GrowableConstantPool.java
  14. 127
      jode/src/net/sf/jode/bytecode/Instruction.java
  15. 40
      jode/src/net/sf/jode/bytecode/LocalVariableInfo.java
  16. 14
      jode/src/net/sf/jode/bytecode/MethodInfo.java
  17. 8
      jode/src/net/sf/jode/expr/InvokeOperator.java
  18. 4
      jode/src/net/sf/jode/jvm/Interpreter.java
  19. 2
      jode/src/net/sf/jode/jvm/NewObject.java
  20. 6
      jode/src/net/sf/jode/jvm/RuntimeEnvironment.java
  21. 5
      jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java
  22. 940
      jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java
  23. 2
      jode/src/net/sf/jode/util/SimpleMap.java

@ -1 +1 @@
Jochen Hoenicke <Jochen.Hoenicke@Informatik.Uni-Oldenburg.DE>
Jochen Hoenicke <Jochen.Hoenicke@Informatik.Uni-Oldenburg.DE>

@ -1,3 +1,23 @@
2001-08-08 Jochen Hoenicke <jochen@gnu.org>
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 <jochen@gnu.org>
Documentation updates (INSTALL, javadoc).

@ -25,17 +25,19 @@
<property name="build" value="${basedir}/build"/>
<property name="props" value="${basedir}/props"/>
<property name="dist" value="${basedir}/dist"/>
<property name="doc" value="${basedir}/doc"/>
<property name="lib" value="${basedir}/lib"/>
<property name="src" value="${basedir}/src"/>
<property name="release" value="${basedir}/release"/>
<property name="distdir" value="${release}/jode-${version}"/>
<property name="scripts" value="${basedir}/scripts"/>
<property name="api.doc" value="doc/api"/>
<property name="test.src" value="test/src"/>
<property name="test.build" value="test/build"/>
<property name="test.log" value="test/log"/>
<property name="test" value="${basedir}/test"/>
<property name="test.src" value="${test}/src"/>
<property name="test.build" value="${test}/build"/>
<property name="test.log" value="${test}/log"/>
<property name="jcpp" value="${scripts}/jcpp.pl"/>
<property name="php2html" value="${scripts}/php2html.pl"/>
@ -55,8 +57,8 @@
<target name="all" depends="build,doc"/>
<!-- clean all -->
<target name="clean" depends="clean-jcpp,clean-build,clean-doc,clean-test,clean-dist"/>
<target name="cvsclean" depends="clean,clean-html"/>
<target name="clean" depends="clean-jcpp,clean-build,clean-doc,clean-test"/>
<target name="cvsclean" depends="clean,clean-html,clean-release"/>
<!-- ********* jcpp targets ******* -->
@ -135,8 +137,9 @@
</target>
<target name="build-1.1">
<property name="jdk1.1.forced" value="on"/>
<antcall target="build"/>
<antcall target="build">
<param name="jdk1.1.forced" value="on"/>
</antcall>
</target>
<target name="build" depends="check-jdk,preconfig">
@ -145,16 +148,13 @@
destdir="${build}"
classpathref="project.classpath"
deprecation="on">
<!--
<exclude name="net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java"/>
<exclude name="net/sf/jode/obfuscator/modules/LocalOptimizer.java"/>
<exclude name="net/sf/jode/obfuscator/modules/LocalizeFieldTransformer.java"/>
<!--
<exclude name="net/sf/jode/bytecode/*Subroutine*" />
-->
</javac>
<copy todir="${build}">
<fileset dir="${props}"/>
</copy>
</target>
<!-- clean the class files -->
@ -162,50 +162,78 @@
<delete dir="${build}"/>
</target>
<!-- ********* Create Jar files ******* -->
<!-- ********* Create Release files ******* -->
<target name="dist-name1" unless="jdk1.2+">
<property name="dist.file" value="jode-${version}-JDK1.1"/>
</target>
<target name="dist-name2" if="jdk1.2+">
<property name="dist.file" value="jode-${version}"/>
</target>
<target name="dist-name" depends="preconfig,dist-name1,dist-name2"/>
<target name="dist" depends="dist-name,clean-dist,dist-class,dist-src,doc">
<jar jarfile="${dist}/${dist.name}.jar">
<fileset dir="${dist}"/>
<fileset dir=".">
<patternset>
<include name="${doc}/*.html"/>
<include name="${doc}/api/**"/>
</patternset>
</fileset>
<target name="release" depends="release-bin,release-bin11,release-src"/>
<target name="release-bindist" depends="build">
<jar jarfile="${distdir}/jode.jar">
<fileset dir="${build}" includes="**/*.class"/>
<fileset dir="${props}" includes="**/*.properties"/>
</jar>
<copy todir="${distdir}">
<fileset dir="${lib}">
<patternset>
<include name="*getopt*.jar"/>
<include name="*collection*.jar" unless="jdk1.2+" />
</patternset>
<include name="*getopt*.jar" />
<include name="*collection*.jar" unless="jdk1.2+" />
</fileset>
</jar>
<fileset dir="${basedir}"
includes="AUTHORS,COPYING,NEWS,README,THANKS,TODO">
<include name="doc/*.html" />
<include name="doc/*.gif" />
<include name="doc/*.jos" />
<include name="doc/*.perl" />
</fileset>
</copy>
</target>
<target name="dist-class" depends="build">
<mkdir dir="${dist}"/>
<jar jarfile="${dist}/jode.jar"
basedir="${build}"/>
<target name="release-bin" depends="doc-html">
<antcall target="clean"/>
<mkdir dir="${release}"/>
<mkdir dir="${distdir}"/>
<antcall target="release-bindist"/>
<jar jarfile="${release}/jode-${version}.jar"
basedir="${release}" includes="jode-${version}/**"/>
<delete dir="${distdir}"/>
<antcall target="clean"/>
</target>
<target name="dist-src">
<mkdir dir="${dist}"/>
<jar jarfile="${dist}/src.jar"
basedir="${src}"/>
<target name="release-bin11" depends="doc-html">
<antcall target="clean"/>
<mkdir dir="${release}"/>
<mkdir dir="${distdir}"/>
<antcall target="release-bindist">
<param name="jdk1.1.forced" value="on"/>
</antcall>
<jar jarfile="${release}/jode-${version}-JDK1.1.jar"
basedir="${release}" includes="jode-${version}/**"/>
<delete dir="${distdir}"/>
<antcall target="clean"/>
</target>
<target name="clean-dist">
<delete dir="${dist}"/>
<target name="release-src" depends="doc-html">
<antcall target="clean"/>
<mkdir dir="${release}"/>
<mkdir dir="${distdir}"/>
<copy todir="${distdir}">
<fileset dir="${basedir}"
includes="AUTHORS,COPYING,INSTALL,NEWS,README,THANKS,TODO,ChangeLog">
<include name="build.xml,config.props,project*.dtd"/>
<include name="doc/**"/>
<include name="scripts/**"/>
<include name="src/**"/>
<include name="test/**"/>
<include name="props/**"/>
<include name="lib/**"/>
</fileset>
</copy>
<jar jarfile="${release}/jode-${version}-src.jar"
basedir="${release}" includes="jode-${version}/**"/>
<delete dir="${distdir}"/>
</target>
<target name="clean-release">
<delete dir="${release}"/>
</target>
<!-- ********* Javadoc targets ********** -->
@ -231,7 +259,9 @@
sourcepath="${src}"
destdir="${api.doc}"
use="yes">
<link offline="${javadoc.offline}" href="http://java.sun.com/products/jdk/1.2/docs/api/" packagelistLoc="${javadoc.packagelistLoc}"/>
<link offline="${javadoc.offline}"
href="${javadoc.href}"
packagelistLoc="${javadoc.packagelistLoc}"/>
</javadoc>
</target>
@ -263,8 +293,7 @@
<classpath>
<pathelement path="${test.build}"/>
<pathelement path="${build}"/>
<fileset dir="lib" includes="*.jar"/>
<fileset dir="/opt/ant/lib" includes="*.jar"/>
<path refid="project.classpath"/>
</classpath>
<formatter type="plain" />
<batchtest fork="no" todir="${test.log}">

@ -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?
#

@ -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 {

@ -49,25 +49,43 @@ import java.lang.UnsupportedOperationException;
* Instead this information is stored inside the blocks. See
* <code>Block</code> for details.</p>
*
* <p>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 <code>opc_ret</code> 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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
* <!-- <p>Future work: 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 <code>opc_ret</code> 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.</p> -->
*
* <p>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.</p>
* necessary, you don't need to care about that.</p>
*
* <h3>Creating new BasicBlocks</h3>
*
* <p>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: </p>
*
* <pre>
* 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 });
* </pre>
*
* @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);
}

@ -42,7 +42,7 @@ import java.util.Iterator;
*
* <p>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.</p>
*
* <p>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();
}

@ -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;

@ -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
* <code>load</code> 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.
* <code>HIERARCHY</code>.
* @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
* <code>NONE</code> or anything that <code>load</code> 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. <br>
*
* 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

@ -60,22 +60,27 @@ import net.sf.jode.util.UnifyHash;
* <li> A URL (unified resource location), pointing to a directory </li>
* <li> A URL pointing to a jar or zip file. </li>
* <li> A Jar URL (see {@link java.net.JarURLConnection}), useful if
* the jar file is not packed correctly</li>
* <li> The reflection URL <code>reflection:/</code> This location can
* only load declarations of classes. If a security manager is
* present, it can only load public declarations.</li>
* the jar file is not packed correctly.</li>
* <li> The reflection URL <code>reflection:/</code>. 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. </li>
* </ul>
*
* 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 <code>.class</code>
* extension. For example if the class path points to
* <code>/home/java</code>, the class <code>java.lang.Object</code> is
* loaded from <code>/home/java/java/lang/Object.class</code>.
* loaded from <code>/home/java/java/lang/Object.class</code>. 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. <br>
*
* We currently allow both pathSeparatorChar and
* altPathSeparatorChar and decide if it is a protocol separator
* by context. This doesn't always work, so use
* <code>altPathSeparator</code>, or the ClassPath(String[])
* constructor.
* <code>altPathSeparator</code>, 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. <br>
*
* 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}. <br>
*
* 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. <br>
* 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. <br>
* 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. <br>
* 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. <br>
*
* 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. <br>
*
* Note that this method is also used by
* {@link ClassPath#listClassesAndPackages}. <br>
*
* 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. <br>
* 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.
* <br>
*
* 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. <br>
*
* 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.<br>
*
* @param classname the dot-separated full qualified name of the class.
* For inner classes you must use the bytecode name with $,
* e.g. <code>java.util.Map$Entry.</code>
* e.g. <code>java.util.Map$Entry</code>.
* @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. <br>
*
* 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 <code>/</code>.
* 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 <code>/</code>.
* 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 <code>/</code>.
* 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();
}
}

@ -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];
}
/**

@ -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:
*
* <dl>
*
* <dt>name</dt><dd>The field's name</dd>
*
* <dt>type</dt><dd>The field's {@link TypeSignature type signature}
* in bytecode format.</dd>
*
* <dt>modifiers</dt><dd>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}. </dt>
*
* <dt>synthetic</dt><dd>true if this field is synthetic.</dd>
*
* <dt>deprecated</dt><dd>true if this field is deprecated.</dd>
*
* <dt>constant</dt> <dd>Final static fields may have a constant
* value. This is either of type String, Integer, Long, Float or
* Double. </dt>
*
* </dl>
*
* @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;
}

@ -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];

@ -20,11 +20,13 @@
package net.sf.jode.bytecode;
/**
* This class represents an instruction in the byte code.
* <p> This class represents an instruction in the byte code.
* Instructions can be created with the static {@link #forOpcode}
* methods. </p>
*
* 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.
* <p> 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. </p>
*
* The opcodes we map are:
* <pre>
@ -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 <code>astore</code>, <code>istore</code>, <code>lstore</code>,
* <code>fstore</code> or <code>dstore</code>.
*/
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()];
}

@ -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. <br>
*
* You can't modify a LocalVariableInfo, for this reason they can and
* will be shared.<br>
*
* 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)

@ -27,8 +27,8 @@ import java.lang.Comparable;
///#enddef
/**
* <p>Represents a java bytecode method. A method consists of the following
* parts:</p>
* Represents a java bytecode method. A method consists of the following
* parts:
*
* <dl>
*
@ -37,9 +37,13 @@ import java.lang.Comparable;
* <dt>type</dt><dd>The method's {@link TypeSignature type signature}
* in bytecode format.</dd>
*
* <dt>modifiers</dt><dd>The modifiers of the method like private, public etc.
* These are created by or-ing the constants defined in
* {@link java.lang.reflect.Modifier}. </dt>
* <dt>modifiers</dt><dd>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}. </dt>
*
* <dt>basicblocks</dt><dd>the bytecode of the method in form of
* {@link BasicBlocks basic blocks}, null if it is native or

@ -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();

@ -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
*/

@ -24,7 +24,7 @@ package net.sf.jode.jvm;
*
* @author Jochen Hoenicke
*/
public class NewObject {
class NewObject {
Object instance;
String type;

@ -34,8 +34,10 @@ import java.lang.reflect.InvocationTargetException;
* <li> array of primitive type is mapped to itself (not array of Integer)</li>
* <li> array of other types are mapped to array of mapped other type </li>
* </ul>
*
* @author Jochen Hoenicke */
*
* @author Jochen Hoenicke
* @see SimpleRuntimeEnvironment
*/
public interface RuntimeEnvironment {
/**
* Get the value of a field member.

@ -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) {

@ -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<<j;
if (poppedEntries[bottom + count + depth + j])
popped3 |= 1<<j;
}
for (int j=0; j< depth; j++) {
if (poppedEntries[bottom + count + j])
newDepth++;
poppedEntries[bottom + j]
= poppedEntries[bottom + count + j];
}
for (int j=0; j< count; j++) {
poppedEntries[bottom + depth + j]
= (popped1 & popped3 & (1 << j)) != 0;
}
// adjust the depth
depth = newDepth;
int all = (count == 2) ? 3 : 1;
if (popped1 == all)
// dup was not necessary
break;
if (depth == 0 && popped3 == all)
// dup was not necessary
break;
// adjust the count
if ((popped1 & popped3 & 1) != 0) {
count--;
popped1 >>= 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());
}

@ -96,5 +96,3 @@ public class SimpleMap extends AbstractMap {
return null;
}
}

Loading…
Cancel
Save