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 24 years ago
parent dab92b2d4d
commit 6acf9d8d7a
  1. 20
      jode/ChangeLog
  2. 123
      jode/build.xml
  3. 6
      jode/config.props
  4. 3
      jode/scripts/jcpp.pl
  5. 57
      jode/src/net/sf/jode/bytecode/BasicBlocks.java
  6. 15
      jode/src/net/sf/jode/bytecode/BinaryInfo.java
  7. 9
      jode/src/net/sf/jode/bytecode/Block.java
  8. 89
      jode/src/net/sf/jode/bytecode/ClassInfo.java
  9. 421
      jode/src/net/sf/jode/bytecode/ClassPath.java
  10. 12
      jode/src/net/sf/jode/bytecode/ConstantPool.java
  11. 87
      jode/src/net/sf/jode/bytecode/FieldInfo.java
  12. 108
      jode/src/net/sf/jode/bytecode/GrowableConstantPool.java
  13. 127
      jode/src/net/sf/jode/bytecode/Instruction.java
  14. 38
      jode/src/net/sf/jode/bytecode/LocalVariableInfo.java
  15. 14
      jode/src/net/sf/jode/bytecode/MethodInfo.java
  16. 8
      jode/src/net/sf/jode/expr/InvokeOperator.java
  17. 4
      jode/src/net/sf/jode/jvm/Interpreter.java
  18. 2
      jode/src/net/sf/jode/jvm/NewObject.java
  19. 4
      jode/src/net/sf/jode/jvm/RuntimeEnvironment.java
  20. 5
      jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java
  21. 712
      jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java
  22. 2
      jode/src/net/sf/jode/util/SimpleMap.java

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

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

@ -1,9 +1,11 @@
# Do you have online access for generating javadoc? # Do you have online access for generating javadoc?
# If not, where are your local files. # If not, where are your local files.
javadoc.offline=false javadoc.offline=false
#javadoc.href=http://java.sun.com/products/jdk/1.2/docs/api/
javadoc.packagelistLoc= javadoc.packagelistLoc=
# javadoc.offline=true #javadoc.offline=true
# javadoc.packagelistLoc=/usr/doc/inet/java/jdk1.2/docs/api 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? # Is Perl installed on your system?
# #

@ -225,8 +225,7 @@ for (@files) {
if ($changes == 0) { if ($changes == 0) {
unlink "$file.tmp"; unlink "$file.tmp";
} else { } else {
(rename "$file", "$file.orig" (unlink "$file" and rename "$file.tmp", "$file")
and rename "$file.tmp", "$file")
or print STDERR "$file: Couldn't rename files.\n"; or print STDERR "$file: Couldn't rename files.\n";
} }
} else { } else {

@ -49,25 +49,43 @@ import java.lang.UnsupportedOperationException;
* Instead this information is stored inside the blocks. See * Instead this information is stored inside the blocks. See
* <code>Block</code> for details.</p> * <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 * <p>Exception Handlers are represented by the Handler class. Their
* start/end range must span over some consecutive BasicBlocks and * start/end range must span over some consecutive BasicBlocks and
* there handler must be another basic block.</p> * there handler must be another basic block.</p>
* *
* <p>If you want to create or modify the byte code, you must first set * <!-- <p>Future work: A subroutine block, i.e. a block where some jsr
* the basic blocks and then set exception handlers. If you set new * instructions may jump to, must store its return address in a local
* blocks the previous exception handlers will be removed.</p> * 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 * <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 * 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.Block
* @see net.sf.jode.bytecode.Instruction * @see net.sf.jode.bytecode.Instruction
@ -238,13 +256,13 @@ public class BasicBlocks extends BinaryInfo implements Opcodes {
public void setBlocks(Block[] blocks, Block startBlock, public void setBlocks(Block[] blocks, Block startBlock,
Handler[] handlers) { Handler[] handlers) {
for (int i = 0; i < blocks.length; i++)
blocks[i].blockNr = i;
this.blocks = blocks; this.blocks = blocks;
this.startBlock = startBlock; this.startBlock = startBlock;
exceptionHandlers = handlers.length == 0 ? Handler.EMPTY : handlers; exceptionHandlers = handlers.length == 0 ? Handler.EMPTY : handlers;
ArrayList activeHandlers = new ArrayList(); ArrayList activeHandlers = new ArrayList();
for (int i = 0; i < blocks.length; i++) { for (int i = 0; i < blocks.length; i++) {
blocks[i].blockNr = i;
for (int j = 0; j < handlers.length; j++) { for (int j = 0; j < handlers.length; j++) {
if (handlers[j].getStart() == blocks[i]) if (handlers[j].getStart() == blocks[i])
activeHandlers.add(handlers[j]); activeHandlers.add(handlers[j]);
@ -259,6 +277,17 @@ public class BasicBlocks extends BinaryInfo implements Opcodes {
activeHandlers.remove(handlers[j]); 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(); updateMaxStackLocals();
// TransformSubroutine.createSubroutineInfo(this); // TransformSubroutine.createSubroutineInfo(this);
} }

@ -42,7 +42,7 @@ import java.util.Iterator;
* *
* <p>There are some predefined attributes, even the Code of a Method * <p>There are some predefined attributes, even the Code of a Method
* is an attribute. These predefined attributes are all handled by * 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> * standard attributes.</p>
* *
* <p>You can provide new attributes by overriding the protected * <p>You can provide new attributes by overriding the protected
@ -253,7 +253,7 @@ public class BinaryInfo {
* BasicBlocks. * BasicBlocks.
* *
* @return the total length of all attributes, including their * @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() { protected int getAttributeSize() {
int size = 2; /* attribute count */ int size = 2; /* attribute count */
@ -267,8 +267,9 @@ public class BinaryInfo {
/** /**
* Finds a non standard attribute with the given name. You don't * Finds a non standard attribute with the given name. You don't
* have access to the constant pool. Instead extend this class * have access to the constant pool. If you need the pool don't
* and override readAttribute method if you need the pool. * use this method but extend this class and override
* readAttribute method.
* @param name the name of the attribute. * @param name the name of the attribute.
* @return the contents of the attribute, null if not found. * @return the contents of the attribute, null if not found.
* @see #readAttribute * @see #readAttribute
@ -282,12 +283,14 @@ public class BinaryInfo {
/** /**
* Gets all non standard attributes. * Gets all non standard attributes.
* @return an iterator for all attributes. The values returned by * @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 * @see #findAttribute
*/ */
public Iterator getAttributes() { public Iterator getAttributes() {
if (unknownAttributes != null) if (unknownAttributes != null)
return unknownAttributes.values().iterator(); return unknownAttributes.entrySet().iterator();
return Collections.EMPTY_SET.iterator(); return Collections.EMPTY_SET.iterator();
} }

@ -232,6 +232,15 @@ public final class Block {
throw new IllegalArgumentException("no single successor 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) { public void getStackPopPush (int[] poppush) {
poppush[0] = maxpop; poppush[0] = maxpop;
poppush[1] = delta + maxpop; poppush[1] = delta + maxpop;

@ -356,6 +356,10 @@ public final class ClassInfo extends BinaryInfo implements Comparable {
} }
ClassInfo(String name, ClassPath classpath) { ClassInfo(String name, ClassPath classpath) {
/* Name may be null when reading class with unknown name from
* stream.
*/
if (name != null)
this.name = name.intern(); this.name = name.intern();
this.classpath = classpath; 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 * <code>load</code> a class from its classpath instead. This may
* be useful for special kinds of input streams, that ClassPath * 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 * @param input The input stream, containing the class in standard
* bytecode format. * bytecode format.
@ -700,56 +704,43 @@ public final class ClassInfo extends BinaryInfo implements Comparable {
ConstantPool cpool = new ConstantPool(); ConstantPool cpool = new ConstantPool();
cpool.read(input); cpool.read(input);
/* always read modifiers, name, super, ifaces */ /* modifiers */
{
modifiers = input.readUnsignedShort(); modifiers = input.readUnsignedShort();
/* name */
String className = cpool.getClassName(input.readUnsignedShort()); String className = cpool.getClassName(input.readUnsignedShort());
if (!name.equals(className)) if (name == null)
name = className;
else if (!name.equals(className))
throw new ClassFormatException("wrong name " + className); throw new ClassFormatException("wrong name " + className);
/* superclass */
int superID = input.readUnsignedShort(); int superID = input.readUnsignedShort();
superclass = superID == 0 ? null superclass = superID == 0 ? null
: classpath.getClassInfo(cpool.getClassName(superID)); : classpath.getClassInfo(cpool.getClassName(superID));
/* interfaces */
int count = input.readUnsignedShort(); int count = input.readUnsignedShort();
interfaces = new ClassInfo[count]; interfaces = new ClassInfo[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
interfaces[i] = classpath.getClassInfo interfaces[i] = classpath.getClassInfo
(cpool.getClassName(input.readUnsignedShort())); (cpool.getClassName(input.readUnsignedShort()));
} }
}
/* fields */ /* fields */
if (howMuch >= PUBLICDECLARATIONS) { count = input.readUnsignedShort();
int count = input.readUnsignedShort();
fields = new FieldInfo[count]; fields = new FieldInfo[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
fields[i] = new FieldInfo(); fields[i] = new FieldInfo();
fields[i].read(cpool, input, howMuch); 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);
}
}
/* methods */ /* methods */
if (howMuch >= PUBLICDECLARATIONS) { count = input.readUnsignedShort();
int count = input.readUnsignedShort();
methods = new MethodInfo[count]; methods = new MethodInfo[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
methods[i] = new MethodInfo(); methods[i] = new MethodInfo();
methods[i].read(cpool, input, howMuch); 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);
}
}
/* initialize inner classes to empty array, in case there /* initialize inner classes to empty array, in case there
* is no InnerClasses attribute. * is no InnerClasses attribute.
@ -769,9 +760,19 @@ public final class ClassInfo extends BinaryInfo implements Comparable {
if (ci.status < OUTERCLASS) if (ci.status < OUTERCLASS)
ci.mergeOuterInfo(null, null, -1, false); ci.mergeOuterInfo(null, null, -1, false);
} }
/* Set status */
status = howMuch; 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) { private void reserveSmallConstants(GrowableConstantPool gcp) {
for (int i = 0; i < fields.length; i++) for (int i = 0; i < fields.length; i++)
fields[i].reserveSmallConstants(gcp); fields[i].reserveSmallConstants(gcp);
@ -780,8 +781,11 @@ public final class ClassInfo extends BinaryInfo implements Comparable {
methods[i].reserveSmallConstants(gcp); 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) { private void prepareWriting(GrowableConstantPool gcp) {
gcp.putClassName(name); gcp.putClassName(name);
gcp.putClassName(superclass.name); gcp.putClassName(superclass.name);
@ -828,6 +832,9 @@ public final class ClassInfo extends BinaryInfo implements Comparable {
prepareAttributes(gcp); prepareAttributes(gcp);
} }
/**
* Count the attributes needed by the class.
*/
protected int getAttributeCount() { protected int getAttributeCount() {
int count = super.getAttributeCount(); int count = super.getAttributeCount();
if (sourceFile != null) if (sourceFile != null)
@ -837,6 +844,10 @@ public final class ClassInfo extends BinaryInfo implements Comparable {
return count; return count;
} }
/**
* Write the attributes needed by the class, namely SourceFile
* and InnerClasses attributes.
*/
protected void writeAttributes(GrowableConstantPool gcp, protected void writeAttributes(GrowableConstantPool gcp,
DataOutputStream output) DataOutputStream output)
throws IOException { 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. * @param howMuch The amount of information that should be read, e.g.
* <code>HIERARCHY</code>. * <code>HIERARCHY</code>.
* @see #OUTERCLASS * @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. * informations bigger than "keep" and clean up the memory.
* @param keep tells how much info we should keep, can be * @param keep tells how much info we should keep, can be
* <code>NONE</code> or anything that <code>load</code> accepts. * <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 * Sets the name of this class info. Note that by changing the
* name you may overwrite an already loaded class. This can have * name you may overwrite an already loaded class. This can have
* ugly effects, as that overwritten class may still exist, but * ugly effects, as references to that overwritten class may still
* can't be loaded via the classPath. * exist.
*/ */
public void setName(String newName) { public void setName(String newName) {
/* The class name is used as index in the hash table. We have /* 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 * Checks if this class is a super class of child. This loads the
* complete hierarchy of child on demand and can throw an IOException * complete hierarchy of child on demand and can throw an IOException
* if some classes are not found or broken. * 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. * @param child the class that should be a child class of us.
* @return true if this is as super class of child, false otherwise * @return true if this is as super class of child, false otherwise
* @exception IOException if hierarchy of child could not be loaded. * @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 * 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 * 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 * 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. * @param clazz the class to be checked.
* @return true if this is a interface and is implemented by clazz, * @return true if this is a interface and is implemented by clazz,
* false otherwise * 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 (unified resource location), pointing to a directory </li>
* <li> A URL pointing to a jar or zip file. </li> * <li> A URL pointing to a jar or zip file. </li>
* <li> A Jar URL (see {@link java.net.JarURLConnection}), useful if * <li> A Jar URL (see {@link java.net.JarURLConnection}), useful if
* the jar file is not packed correctly</li> * the jar file is not packed correctly.</li>
* <li> The reflection URL <code>reflection:/</code> This location can * <li> The reflection URL <code>reflection:/</code>. This is a
* only load declarations of classes. If a security manager is * special location, which fills the ClassInfo with the information
* present, it can only load public declarations.</li> * 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> * </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> * to directories and the class file must have the <code>.class</code>
* extension. For example if the class path points to * extension. For example if the class path points to
* <code>/home/java</code>, the class <code>java.lang.Object</code> is * <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 * A class path can have another ClassPath as fallback. If
* ClassInfo.loadInfo is called and the class isn't found the fallback * ClassInfo.loadInfo is called and the class isn't found the fallback
* ClassPath is searched instead. This repeats until there is no * 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 * The main method for creating classes is {@link #getClassInfo}. The
* other available methods are useful to find other files in the * other available methods are useful to find other files in the
@ -86,51 +91,133 @@ import net.sf.jode.util.UnifyHash;
* them. * them.
* *
* @author Jochen Hoenicke * @author Jochen Hoenicke
* @version 1.1 */ * @version 1.1
*/
public class ClassPath { public class ClassPath {
/** /**
* We need a different pathSeparatorChar, since ':' (used for most * We need a different pathSeparatorChar, since ':' (used for UNIX
* UNIX System) is used a protocol separator in URLs. * systems) is also used as protocol separator in URLs. <br>
* *
* We currently allow both pathSeparatorChar and * We currently allow both pathSeparatorChar and
* altPathSeparatorChar and decide if it is a protocol separator * altPathSeparatorChar and decide if it is a protocol separator
* by context. This doesn't always work, so use * by context. This doesn't always work, so use
* <code>altPathSeparator</code>, or the ClassPath(String[]) * <code>altPathSeparator</code>, or better yet the
* constructor. * ClassPath(String[]) or ClassPath(Location[]) constructors.
*/ */
public static final char altPathSeparatorChar = ','; 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; 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; 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; 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; 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 throws IOException, ClassFormatException
{ {
String file = clazz.getName().replace('.', '/') + ".class"; String file = clazz.getName().replace('.', '/') + ".class";
if (!exists(file)) if (!exists(file))
return false; return false;
DataInputStream input = new DataInputStream DataInputStream input = new DataInputStream
(new BufferedInputStream (new BufferedInputStream(getFile(file)));
(getFile(file)));
clazz.read(input, howMuch); clazz.read(input, howMuch);
return true; return true;
} }
} }
private class ReflectionPath extends Path { private static class ReflectionLocation extends Location {
public boolean loadClass(ClassInfo classinfo, int howMuch) protected boolean loadClass(ClassInfo classinfo, int howMuch)
throws IOException, ClassFormatException throws IOException, ClassFormatException
{ {
if (howMuch > ClassInfo.DECLARATIONS) 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; private File dir;
public LocalPath(File path) { protected LocalLocation(File path) {
dir = path; dir = path;
} }
public boolean exists(String filename) { protected boolean exists(String filename) {
if (java.io.File.separatorChar != '/') if (java.io.File.separatorChar != '/')
filename = filename filename = filename
.replace('/', java.io.File.separatorChar); .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 != '/') if (java.io.File.separatorChar != '/')
filename = filename filename = filename
.replace('/', java.io.File.separatorChar); .replace('/', java.io.File.separatorChar);
return new File(dir, filename).isDirectory(); 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 != '/') if (java.io.File.separatorChar != '/')
filename = filename filename = filename
.replace('/', java.io.File.separatorChar); .replace('/', java.io.File.separatorChar);
@ -190,7 +277,7 @@ public class ClassPath {
return new FileInputStream(f); return new FileInputStream(f);
} }
public Enumeration listFiles(String directory) { protected Enumeration listFiles(String directory) {
if (File.separatorChar != '/') if (File.separatorChar != '/')
directory = directory directory = directory
.replace('/', File.separatorChar); .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 Hashtable entries = new Hashtable();
private ZipFile file; private ZipFile file;
private byte[] contents; private byte[] contents;
@ -261,7 +348,7 @@ public class ClassPath {
} while (name.length() > 0); } while (name.length() > 0);
} }
public ZipPath(ZipFile zipfile, String prefix) { ZipLocation(ZipFile zipfile, String prefix) {
this.file = zipfile; this.file = zipfile;
this.prefix = prefix; this.prefix = prefix;
@ -272,7 +359,7 @@ public class ClassPath {
} }
} }
public ZipPath(byte[] zipcontents, String prefix) ZipLocation(byte[] zipcontents, String prefix)
throws IOException throws IOException
{ {
this.contents = zipcontents; this.contents = zipcontents;
@ -290,7 +377,7 @@ public class ClassPath {
zis.close(); zis.close();
} }
public boolean exists(String filename) { protected boolean exists(String filename) {
if (entries.containsKey(filename)) if (entries.containsKey(filename))
return true; return true;
@ -307,11 +394,11 @@ public class ClassPath {
return false; return false;
} }
public boolean isDirectory(String filename) { protected boolean isDirectory(String filename) {
return entries.containsKey(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; String fullname = prefix != null ? prefix + filename : filename;
if (contents != null) { if (contents != null) {
ZipInputStream zis = new ZipInputStream ZipInputStream zis = new ZipInputStream
@ -353,7 +440,7 @@ public class ClassPath {
return null; return null;
} }
public Enumeration listFiles(String directory) { protected Enumeration listFiles(String directory) {
Vector direntries = (Vector) entries.get(directory); Vector direntries = (Vector) entries.get(directory);
if (direntries != null) if (direntries != null)
return direntries.elements(); return direntries.elements();
@ -365,14 +452,14 @@ public class ClassPath {
} }
} }
private class URLPath extends Path { private static class URLLocation extends Location {
private URL base; private URL base;
public URLPath(URL base) { public URLLocation(URL base) {
this.base = base; this.base = base;
} }
public boolean exists(String filename) { protected boolean exists(String filename) {
try { try {
URL url = new URL(base, filename); URL url = new URL(base, filename);
URLConnection conn = url.openConnection(); 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 { try {
URL url = new URL(base, filename); URL url = new URL(base, filename);
URLConnection conn = url.openConnection(); 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 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"; String file = clazz.getName().replace('.', '/') + ".class";
InputStream is = getFile(file); InputStream is = getFile(file);
if (is == null) if (is == null)
@ -405,7 +495,11 @@ public class ClassPath {
DataInputStream input = new DataInputStream DataInputStream input = new DataInputStream
(new BufferedInputStream(is)); (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; return true;
} }
@ -414,7 +508,7 @@ public class ClassPath {
} }
} }
private Path[] paths; private Location[] paths;
private UnifyHash classes = new UnifyHash(); private UnifyHash classes = new UnifyHash();
ClassPath fallback = null; ClassPath fallback = null;
@ -422,7 +516,8 @@ public class ClassPath {
/** /**
* Creates a new class path for the given path. See the class * Creates a new class path for the given path. See the class
* description for more information, which kind of paths are * 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 path An array of paths.
* @param fallback The fallback classpath. * @param fallback The fallback classpath.
*/ */
@ -441,6 +536,25 @@ public class ClassPath {
initPath(paths); 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 * Creates a new class path for the given path. See the class
* description for more information, which kind of paths are * description for more information, which kind of paths are
@ -468,7 +582,66 @@ public class ClassPath {
initPath(tokenizeClassPath(path)); 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 // Calculate a good approximation (rounded upwards) of the tokens
// in this path. // in this path.
int length = 1; int length = 1;
@ -528,30 +701,29 @@ public class ClassPath {
return tokens; return tokens;
} }
private byte[] readURLZip(URLConnection conn) { private static byte[] readURLZip(URLConnection conn) throws IOException {
int length = conn.getContentLength(); int length = conn.getContentLength();
if (length <= 0) if (length <= 0)
// Give a approximation if length is unknown // Give a approximation if length is unknown
length = 10240; length = 10240;
else else
// Increase the length by one, so we hopefully don't need // 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). // know when the end is reached).
length++; length++;
byte[] contents = new byte[length]; byte[] contents = new byte[length];
try {
InputStream is = conn.getInputStream(); InputStream is = conn.getInputStream();
int pos = 0; int pos = 0;
for (;;) { for (;;) {
// This is ugly, is.available() may return zero even // This is ugly, is.available() may return zero even
// if there are more bytes. // if there are more bytes.
int avail = Math.max(is.available(), 1); int avail = Math.max(is.available(), 1);
if (pos + is.available() > contents.length) { if (pos + avail > contents.length) {
// grow the byte array. // grow the byte array.
byte[] newarr = new byte byte[] newarr = new byte
[Math.max(2*contents.length, pos + is.available())]; [Math.max(2*contents.length, pos + avail)];
System.arraycopy(contents, 0, newarr, 0, pos); System.arraycopy(contents, 0, newarr, 0, pos);
contents = newarr; contents = newarr;
} }
@ -567,94 +739,28 @@ public class ClassPath {
contents = newarr; contents = newarr;
} }
return contents; return contents;
} catch (IOException ex) {
return null;
}
} }
private void initPath(String[] tokens) { private void initPath(String[] tokens) {
int length = tokens.length; int length = tokens.length;
paths = new Path[length]; paths = new Location[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
String path = tokens[i]; if (tokens[i] == null)
if (path == 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; 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 { try {
URL base = new URL(path); paths[i] = createLocation(tokens[i]);
try { } catch (MalformedURLException ex) {
URLConnection connection = base.openConnection(); GlobalOptions.err.println
if (zipPrefix != null ("Warning: Malformed URL "+ tokens[i] + ".");
|| 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) { } catch (IOException ex) {
GlobalOptions.err.println GlobalOptions.err.println
("Warning: IO exception while accessing " ("Warning: IO exception while accessing "
+path+"."); +tokens[i]+".");
} catch (SecurityException ex) { } catch (SecurityException ex) {
GlobalOptions.err.println GlobalOptions.err.println
("Warning: Security exception while accessing " ("Warning: Security exception while accessing "
+path+"."); +tokens[i]+".");
}
} 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 + ".");
}
} }
} }
} }
@ -663,10 +769,17 @@ public class ClassPath {
/** /**
* Creates a new class info for a class residing in this search * Creates a new class info for a class residing in this search
* path. This doesn't load the class immediately, this is done by * 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. * @param classname the dot-separated full qualified name of the class.
* For inner classes you must use the bytecode name with $, * 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. * @exception IllegalArgumentException if class name isn't valid.
*/ */
public ClassInfo getClassInfo(String classname) public ClassInfo getClassInfo(String classname)
@ -684,6 +797,44 @@ public class ClassPath {
return clazz; 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 * Updates the classes unify hash for a class renaming. This
* should be only called by {@link ClassInfo#setName}. * should be only called by {@link ClassInfo#setName}.
@ -740,7 +891,7 @@ public class ClassPath {
/** /**
* Searches for a file in the class path. * Searches for a file in the class path.
* @param filename the filename. The path components should be separated * @param filename the filename. The path components should be separated
* by <code>/</code>. * by "/".
* @return An InputStream for the file. * @return An InputStream for the file.
*/ */
public InputStream getFile(String filename) throws IOException { public InputStream getFile(String filename) throws IOException {
@ -748,6 +899,8 @@ public class ClassPath {
if (paths[i] != null && paths[i].exists(filename)) if (paths[i] != null && paths[i].exists(filename))
return paths[i].getFile(filename); return paths[i].getFile(filename);
} }
if (fallback != null)
return fallback.getFile(filename);
throw new FileNotFoundException(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 * Searches for a filename in the class path and tells if it is a
* directory. * directory.
* @param filename the filename. The path components should be separated * @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. * @return true, if filename exists and is a directory, false otherwise.
*/ */
public boolean isDirectory(String filename) { 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 * 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 * @param fqn the full qualified name. The components should be dot
* separated. * separated.
* @return true, if filename exists and is a package, false otherwise. * @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. * Get a list of all files in a given directory.
* @param dirName the directory name. The path components must * @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 * @return An enumeration with all files/directories in the given
* directory. If dirName doesn't denote a directory it returns null. * 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) boolean loadClass(ClassInfo clazz, int howMuch)
throws IOException, ClassFormatException throws IOException, ClassFormatException
{ {
@ -867,13 +1024,17 @@ public class ClassPath {
return false; return false;
} }
/**
* Returns a string representation of this classpath.
* @return a string useful for debugging purposes.
*/
public String toString() { public String toString() {
StringBuffer sb = new StringBuffer("ClassPath["); StringBuffer sb = new StringBuffer("ClassPath[");
for (int i = 0; i < paths.length; i++) { for (int i = 0; i < paths.length; i++) {
if (paths[i] != null) if (paths[i] != null)
sb.append(paths[i]).append(','); sb.append(paths[i]).append(',');
} }
sb.append(fallback).append(']'); sb.append("fallback=").append(fallback).append(']');
return sb.toString(); return sb.toString();
} }
} }

@ -30,9 +30,10 @@ import java.lang.UnsupportedOperationException;
///#enddef ///#enddef
/** /**
* This class represent the constant pool. You normally don't need to * This class represent the constant pool. Normally you wont need to
* access this class, except if you want to add your own custom * touch this class, as ClassInfo already does all the hard work. You
* attributes that use the constant pool. * will only need it if you want to add your own custom attributes
* that use the constant pool.
* *
* @author Jochen Hoenicke * @author Jochen Hoenicke
*/ */
@ -179,13 +180,16 @@ public class ConstantPool {
public String getClassName(int i) throws ClassFormatException { public String getClassName(int i) throws ClassFormatException {
if (tags[i] != CLASS) if (tags[i] != CLASS)
throw new ClassFormatException("Tag mismatch"); throw new ClassFormatException("Tag mismatch");
if (constants[i] == null) {
String clName = getUTF8(indices1[i]); String clName = getUTF8(indices1[i]);
try { try {
TypeSignature.checkTypeSig("L"+clName+";"); TypeSignature.checkTypeSig("L"+clName+";");
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
throw new ClassFormatException(ex.getMessage()); throw new ClassFormatException(ex.getMessage());
} }
return clName.replace('/','.').intern(); constants[i] = clName.replace('/','.').intern();
}
return (String) constants[i];
} }
/** /**

@ -26,6 +26,39 @@ import java.lang.reflect.Modifier;
import java.lang.Comparable; import java.lang.Comparable;
///#enddef ///#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 { public final class FieldInfo extends BinaryInfo implements Comparable {
int modifier; int modifier;
String name; String name;
@ -35,9 +68,20 @@ public final class FieldInfo extends BinaryInfo implements Comparable {
boolean syntheticFlag; boolean syntheticFlag;
boolean deprecatedFlag; boolean deprecatedFlag;
/**
* Creates a new empty field info.
*/
public FieldInfo() { 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) { public FieldInfo(String name, String typeSig, int modifier) {
this.name = name; this.name = name;
this.typeSig = typeSig; this.typeSig = typeSig;
@ -147,38 +191,81 @@ public final class FieldInfo extends BinaryInfo implements Comparable {
super.drop(keep); super.drop(keep);
} }
/**
* Gets the name of the field.
* @return the name.
*/
public String getName() { public String getName() {
return name; return name;
} }
/**
* Gets the type signature of the field.
* @return the type signature.
* @see TypeSignature
*/
public String getType() { public String getType() {
return typeSig; return typeSig;
} }
/**
* Gets the modifier of the field.
* @return the modifiers.
* @see Modifier
*/
public int getModifiers() { public int getModifiers() {
return modifier; return modifier;
} }
/**
* Tells whether this field is synthetic.
* @return true if the field is synthetic.
*/
public boolean isSynthetic() { public boolean isSynthetic() {
return syntheticFlag; return syntheticFlag;
} }
/**
* Tells whether this field is deprecated.
* @return true if the field is deprecated.
*/
public boolean isDeprecated() { public boolean isDeprecated() {
return deprecatedFlag; 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() { public Object getConstant() {
return constant; return constant;
} }
/**
* Sets the name of the field.
* @param newName the name.
*/
public void setName(String newName) { public void setName(String newName) {
name = newName; name = newName;
} }
/**
* Sets the type signature of the field.
* @param newType the type signature.
* @see TypeSignature
*/
public void setType(String newType) { public void setType(String newType) {
typeSig = newType; typeSig = newType;
} }
/**
* Sets the modifier of the field.
* @param newModifier the modifiers.
* @see Modifier
*/
public void setModifiers(int newModifier) { public void setModifiers(int newModifier) {
modifier = newModifier; modifier = newModifier;
} }

@ -23,11 +23,14 @@ import java.io.IOException;
import java.util.Hashtable; 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 * @author Jochen Hoenicke
*/ */
class GrowableConstantPool extends ConstantPool { public class GrowableConstantPool extends ConstantPool {
Hashtable entryToIndex = new Hashtable(); Hashtable entryToIndex = new Hashtable();
boolean written; boolean written;
@ -59,6 +62,9 @@ class GrowableConstantPool extends ConstantPool {
} }
} }
/**
* Create a new growable constant pool
*/
public GrowableConstantPool () { public GrowableConstantPool () {
count = 1; count = 1;
tags = new int[128]; tags = new int[128];
@ -68,11 +74,13 @@ class GrowableConstantPool extends ConstantPool {
written = false; written = false;
} }
public final void grow(int wantedSize) { private final void grow(int wantedSize) {
if (written) if (written)
throw new IllegalStateException("adding to written ConstantPool"); throw new IllegalStateException("adding to written ConstantPool");
if (wantedSize > 65535)
throw new IllegalArgumentException("Too many constants added");
if (tags.length < wantedSize) { 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]; int[] tmpints = new int[newSize];
System.arraycopy(tags, 0, tmpints, 0, count); System.arraycopy(tags, 0, tmpints, 0, count);
tags = tmpints; tags = tmpints;
@ -117,7 +125,7 @@ class GrowableConstantPool extends ConstantPool {
return newIndex; 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); Key key = new Key(tag, obj1, index2);
Integer indexObj = (Integer) entryToIndex.get(key); Integer indexObj = (Integer) entryToIndex.get(key);
if (indexObj != null) { if (indexObj != null) {
@ -135,16 +143,45 @@ class GrowableConstantPool extends ConstantPool {
return count++; 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) { public final int putUTF8(String utf) {
return putConstant(UTF8, 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) { public int putClassName(String name) {
name = name.replace('.','/'); name = name.replace('.','/');
TypeSignature.checkTypeSig("L"+name+";"); TypeSignature.checkTypeSig("L"+name+";");
return putIndexed(CLASS, name, putUTF8(name), 0); 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) { public int putClassType(String name) {
TypeSignature.checkTypeSig(name); TypeSignature.checkTypeSig(name);
if (name.charAt(0) == 'L') if (name.charAt(0) == 'L')
@ -154,6 +191,19 @@ class GrowableConstantPool extends ConstantPool {
return putIndexed(CLASS, name, putUTF8(name), 0); 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) { public int putRef(int tag, Reference ref) {
String className = ref.getClazz(); String className = ref.getClazz();
String typeSig = ref.getType(); String typeSig = ref.getType();
@ -172,10 +222,14 @@ class GrowableConstantPool extends ConstantPool {
} }
/** /**
* Puts a constant into this constant pool * Puts a "one slot" constant into this constant pool. If it
* @param c the constant, must be of type * already exists it will reuse the previous entry.
* Integer, Long, Float, Double or String * @param c the constant; must be of type
* @return the index into the pool of this constant. * 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) { public int putConstant(Object c) {
if (c instanceof String) { if (c instanceof String) {
@ -194,10 +248,13 @@ class GrowableConstantPool extends ConstantPool {
} }
/** /**
* Puts a constant into this constant pool * Puts a "double slot" constant into this constant pool. If it
* @param c the constant, must be of type * already exists it will reuse the previous entry.
* Integer, Long, Float, Double or String * @param c the constant; must be of type Long or Double
* @return the index into the pool of this constant. * @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) { public int putLongConstant(Object c) {
int tag; 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 * @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. * @return the reserved index into the pool of this constant.
*/ */
public int reserveConstant(Object c) { 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). * Writes the constant pool to the stream. This is normally called
* @param c the constant, must be of type * by {@link ClassInfo#write}.
* Integer, Long, Float, Double or String * @param stream the stream to write to.
* @return the reserved index into the pool of this constant. * @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) public void write(DataOutputStream stream)
throws IOException { throws IOException {
written = true; written = true;
if (count > 65536)
throw new ClassFormatError("Too many constants");
stream.writeShort(count); stream.writeShort(count);
for (int i=1; i< count; i++) { for (int i=1; i< count; i++) {
int tag = tags[i]; int tag = tags[i];

@ -20,11 +20,13 @@
package net.sf.jode.bytecode; 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 * <p> We only allow a subset of opcodes. Other opcodes are mapped to
* their simpler version. When writing the bytecode the shortest * their simpler version. Don't worry about this, when writing the
* possible bytecode is produced. * bytecode the shortest possible bytecode is produced. </p>
* *
* The opcodes we map are: * The opcodes we map are:
* <pre> * <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() { public final int getOpcode() {
return lineAndOpcode & 0xff; 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() { public final boolean hasLineNr() {
return lineAndOpcode >= 0; 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() { public final int getLineNr() {
return lineAndOpcode >> 8; 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) { public final void setLineNr(int nr) {
lineAndOpcode = (nr << 8) | (lineAndOpcode & 0xff); 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() { public boolean isStore() {
return false; return false;
} }
/**
* Checks whether this instruction accesses a local slot.
*/
public boolean hasLocal() { public boolean hasLocal() {
return false; 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() public int getLocalSlot()
{ {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
// UnsupportedOperationException would be more appropriate // 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() public LocalVariableInfo getLocalInfo()
{ {
throw new IllegalArgumentException(); 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) public void setLocalInfo(LocalVariableInfo info)
{ {
throw new IllegalArgumentException(); 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) public void setLocalSlot(int slot)
{ {
throw new IllegalArgumentException(); 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() 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) 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() 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) public void setDimensions(int dims)
{ {
throw new IllegalArgumentException(); 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() public Object getConstant()
{ {
throw new IllegalArgumentException(); 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) public void setConstant(Object constant)
{ {
throw new IllegalArgumentException(); 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() public Reference getReference()
{ {
throw new IllegalArgumentException(); 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) public void setReference(Reference ref)
{ {
throw new IllegalArgumentException(); 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() public String getClazzType()
{ {
throw new IllegalArgumentException(); 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) public void setClazzType(String type)
{ {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
/**
* Gets the values of a opc_lookupswitch opcode.
* @throws IllegalArgumentException if this instruction doesn't
* support this.
*/
public int[] getValues() public int[] getValues()
{ {
throw new IllegalArgumentException(); 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) public void setValues(int[] values)
{ {
throw new IllegalArgumentException(); 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() { public final boolean doesAlwaysJump() {
switch (getOpcode()) { switch (getOpcode()) {
case opc_ret: case opc_ret:
@ -380,6 +483,12 @@ public class Instruction implements Opcodes{
return toString(); 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() { public String toString() {
return opcodeString[getOpcode()]; return opcodeString[getOpcode()];
} }

@ -24,7 +24,17 @@ import java.util.Iterator;
///#enddef ///#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 { public final class LocalVariableInfo {
private String name, type; private String name, type;
@ -57,13 +67,24 @@ public final class LocalVariableInfo {
anonymous[i] = new LocalVariableInfo(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) { public static LocalVariableInfo getInfo(int slot) {
if (slot >= anonymous.length) if (slot >= anonymous.length)
grow(Math.max(slot + 1, anonymous.length * 2)); grow(Math.max(slot + 1, anonymous.length * 2));
return anonymous[slot]; 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) if (name == null && type == null)
return getInfo(slot); return getInfo(slot);
int hash = slot ^ name.hashCode() ^ type.hashCode(); int hash = slot ^ name.hashCode() ^ type.hashCode();
@ -80,18 +101,31 @@ public final class LocalVariableInfo {
return lvi; return lvi;
} }
/**
* Gets the slot number.
*/
public int getSlot() { public int getSlot() {
return slot; return slot;
} }
/**
* Gets the name.
*/
public String getName() { public String getName() {
return name; return name;
} }
/**
* Gets the type signature.
* @see TypeSignature
*/
public String getType() { public String getType() {
return type; return type;
} }
/**
* Gets a string representation for debugging purposes.
*/
public String toString() { public String toString() {
String result = ""+slot; String result = ""+slot;
if (name != null) if (name != null)

@ -27,8 +27,8 @@ import java.lang.Comparable;
///#enddef ///#enddef
/** /**
* <p>Represents a java bytecode method. A method consists of the following * Represents a java bytecode method. A method consists of the following
* parts:</p> * parts:
* *
* <dl> * <dl>
* *
@ -37,9 +37,13 @@ import java.lang.Comparable;
* <dt>type</dt><dd>The method's {@link TypeSignature type signature} * <dt>type</dt><dd>The method's {@link TypeSignature type signature}
* in bytecode format.</dd> * in bytecode format.</dd>
* *
* <dt>modifiers</dt><dd>The modifiers of the method like private, public etc. * <dt>modifiers</dt><dd>The modifiers of the field like private, public etc.
* These are created by or-ing the constants defined in * These are created by or-ing the constants {@link Modifier#PUBLIC},
* {@link java.lang.reflect.Modifier}. </dt> * {@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 * <dt>basicblocks</dt><dd>the bytecode of the method in form of
* {@link BasicBlocks basic blocks}, null if it is native or * {@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 * first element is the hint type of the return value, the
* remaining entries are the hint types of the parameters. All * remaining entries are the hint types of the parameters. All
* hint types may be null, if that parameter shouldn't be hinted. * 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(); 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 * make much sense to hint for byte, since its constant
* representation is more difficult than an int * representation is more difficult than an int
* representation. If you have more hints to suggest, please * 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 tCharHint = new IntegerType(IntegerType.IT_I, IntegerType.IT_C);
Type[] hintC = new Type[] { tCharHint }; Type[] hintC = new Type[] { tCharHint };
@ -1208,7 +1212,7 @@ public final class InvokeOperator extends Operator
} }
writer.endOp(); 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. * We still need to check for casts though.
*/ */
writer.breakOp(); writer.breakOp();

@ -36,8 +36,8 @@ import java.util.Iterator;
/** /**
* This class is a java virtual machine written in java :-). Well not * 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 * exactly. It is only a bytecode interpreter, you have to supply the
* written do deobfuscate Strings. * rest of the VM (the runtime environment).
* *
* @author Jochen Hoenicke * @author Jochen Hoenicke
*/ */

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

@ -35,7 +35,9 @@ import java.lang.reflect.InvocationTargetException;
* <li> array of other types are mapped to array of mapped other type </li> * <li> array of other types are mapped to array of mapped other type </li>
* </ul> * </ul>
* *
* @author Jochen Hoenicke */ * @author Jochen Hoenicke
* @see SimpleRuntimeEnvironment
*/
public interface RuntimeEnvironment { public interface RuntimeEnvironment {
/** /**
* Get the value of a field member. * 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.Method;
import java.lang.reflect.InvocationTargetException; 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 class SimpleRuntimeEnvironment implements RuntimeEnvironment {
public static Object fromReflectType(String typeSig, Object value) { public static Object fromReflectType(String typeSig, Object value) {

@ -65,240 +65,189 @@ public class RemovePopAnalyzer implements CodeTransformer, Opcodes {
public BlockInfo analyzeBlock(Block block, BlockInfo oldInfo) { 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]; * This method propagates pops through a dup instruction, eventually
* generating new code and a new set of popped instructions.
instr.getStackPopPush(poppush); *
* @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;
// Now check if the parameters of this instr are needed. /* Calculate which entries can be popped before this instruction,
boolean params_needed = false; * and update opcode.
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; int newPopped =
depth += poppush[1]; (((poppedEntries + 1) << depth) - 1) & (poppedEntries >> count);
break; int mask = ((1 << count) - 1);
int poppedDeep = poppedEntries & mask;
int poppedTop = (poppedEntries >> (depth + count)) & mask;
boolean swapAfterDup = false;
boolean swapBeforeDup = false;
default: for (int i = count+depth; i > depth; i--) {
/* Check if all results are needed, adjust depth */ if ((newPopped & (1 << i)) != 0)
for (int k = 0; k < poppush[1]; k++) { depth--;
if (!popped.get(depth++))
params_needed = true;
}
} }
if (params_needed) { // adjust the depth
/* mark params as needed */ for (int i = depth; i > 0; i--) {
for (int k = 0; k < poppush[0]; k++) if ((newPopped & (1 << i)) != 0)
popped.clear(--depth); depth--;
} else {
removed[j >> 5] |= 1 << (j & 31);
/* mark params as popped */
for (int k = 0; k < poppush[0]; k++)
popped.set(--depth);
}
} }
int[] poppedArr = new int[poppedLen]; // adjust the count and poppedDeep/3
if (blocks[i] != bb.getStartBlock()) { if ((poppedDeep & poppedTop & 1) != 0) {
/* Only can pop entries before this block if count--;
* this isn't the start block. poppedDeep >>= 1;
*/ poppedTop >>= 1;
for (int k = 0; k < block_pop; k++) { mask >>= 1;
if (popped.get(depth+k)) } else if ((poppedDeep & poppedTop & 2) != 0) {
poppedArr[k >> 5] |= 1 << (k & 31); count--;
} poppedDeep &= 1;
poppedTop &= 1;
mask &= 1;
} }
infos[i] = new BlockInfo(poppedArr, removed);
if (poppedDeep == mask
|| (depth == 0 && poppedTop == mask)) {
// dup was not necessary
return newPopped;
} }
/* Now start sharing poppedEntries as necessary. */ /* Now (poppedDeep & poppedTop) == 0 */
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; if (poppedTop > 0) {
boolean isAPred = false; /* Insert the pop for the top elements, we add
for (int k = 0; k < succs.length; k++) { * the dup later in front of these instructions.
if (succs[k] == blocks[i]) */
isAPred = true; if (poppedTop == 3) {
if (succs[k] != null && succs[k].getBlockNr() < i) newInstructions.addFirst
blockNr = succs[k].getBlockNr(); (Instruction.forOpcode(opc_pop2));
if (isAPred && blockNr >= 0) { } else {
int[] common = infos[blockNr].poppedEntries; newInstructions.addFirst
int[] my = infos[i].poppedEntries; (Instruction.forOpcode(opc_pop));
for (int k = 0; k < poppedLen; k++) if (count == 2 && poppedTop == 1)
common[k] &= my[k]; swapAfterDup = true;
infos[i].poppedEntries = common;
continue next_block;
}
}
} }
} }
/* Now propagate poppedEntries through blocks */ if (poppedDeep != 0) {
boolean changed = true; if (poppedDeep == 2) {
while (changed) { /* We swap before and after dupping to get to
changed = false; * poppedDeep = 1 case.
next_block: */
for (int i = 0; i < blocks.length; i++) { swapAfterDup = !swapAfterDup;
Block[] succs = blocks[i].getSuccs(); swapBeforeDup = true;
int[] succPops = null;
for (int j = 0; j < succs.length; j++) {
if (succs[j] != null) {
succPops = infos[succs[j].getBlockNr()].poppedEntries;
} }
/* The bottom most value is popped; decrease count
* and increase depth, so that it won't be created
* in the first place.
*/
depth++;
count--;
} }
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 &=
/* Now all pops are resolved */
/* Do a dup with count and depth now. */
for (int j = instrs.length; j-- > 0; ) { if (swapAfterDup)
Instruction instr = instrs[j]; newInstructions.addFirst
(Instruction.forOpcode(opc_swap));
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: if (depth < 3) {
/* Check if all results are needed, adjust depth */ newInstructions.addFirst
for (int k = 0; k < poppush[1]; k++) { (Instruction.forOpcode(opc_pop - 3 +
if (!popped.get(depth++)) depth + 3 * count));
params_needed = true;
}
/* 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);
} else { } else {
removed[j >> 5] |= 1 << (j & 31); // I hope that this will almost never happen.
/* mark params as popped */ // depth = 3, count = 1;
for (int k = 0; k < poppush[0]; k++) // Note that instructions are backwards.
popped.set(--depth); 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<
} }
if (blocks[j] == null) if (swapBeforeDup)
; newInstructions.addFirst
} (Instruction.forOpcode(opc_swap));
} return newPopped
}
return infos;
} }
public void transformCode(BasicBlocks bb) { /**
if (bb.getStartBlock() == null) * This method analyzes a block from end to start and removes the
return; * pop instructions together with their pushes. It propagates pops
* to the front removing various instructions on the fly, which may
BlockInfo[] infos = calcBlockInfos(bb); * generate new pops for their operands and so on.
*
int poppush[] = new int[2]; * @param block the block of code.
boolean poppedEntries[] = new boolean[bb.getMaxStack()]; * @param poppedEntries the stack slots that should be popped at
Block[] blocks = bb.getBlocks(); * the end of this block.
for (int i = 0; i < blocks.length; i++) { * @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(); LinkedList newInstructions = new LinkedList();
Instruction[] oldInstrs = blocks[i].getInstructions();
int instrNr = oldInstrs.length; int instrNr = oldInstrs.length;
int stackDepth = 0; int stackDepth = block.getStackHeight() + block.getStackDelta();
while (instrNr > 0) { while (instrNr > 0) {
Instruction instr = oldInstrs[instrNr]; Instruction instr = oldInstrs[--instrNr];
if (instr.getOpcode() == opc_nop) if (instr.getOpcode() == opc_nop)
continue; continue;
if (instr.getOpcode() == opc_pop) { if (instr.getOpcode() == opc_pop) {
poppedEntries[stackDepth++] = true; popsAtEnd.set(stackDepth++);
continue; continue;
} }
if (instr.getOpcode() == opc_pop2) { if (instr.getOpcode() == opc_pop2) {
poppedEntries[stackDepth++] = true; popsAtEnd.set(stackDepth++);
poppedEntries[stackDepth++] = true; popsAtEnd.set(stackDepth++);
continue; continue;
} }
instr.getStackPopPush(poppush); instr.getStackPopPush(poppush);
// First look if stackDepth was right
if (stackDepth < poppush[1]) { /* Check if this instr pushes a popped Entry. */
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_a_popped = false;
boolean push_all_popped = true; boolean push_all_popped = true;
for (int j=0; j < poppush[1]; j++) { for (int j=0; j < poppush[1]; j++) {
if (poppedEntries[j]) if (poppedEntries.get(j))
push_a_popped = true; push_a_popped = true;
else else
push_all_popped = false; push_all_popped = false;
} }
if (push_a_popped) {
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 */ /* We push an entry, that gets popped later */
int opcode = instr.getOpcode(); int opcode = instr.getOpcode();
switch (opcode) { switch (opcode) {
@ -308,200 +257,210 @@ public class RemovePopAnalyzer implements CodeTransformer, Opcodes {
case opc_dup2: case opc_dup2:
case opc_dup2_x1: case opc_dup2_x1:
case opc_dup2_x2: { case opc_dup2_x2: {
int popped = 0;
int count = (opcode - opc_dup)/3+1; for (int j = poppush[1] ; j > 0; j--) {
int depth = (opcode - opc_dup)%3; popped <<= 1;
stackDepth -= count; if (poppedEntries.get(--stackDepth)) {
int bottom = stackDepth - count - depth; popped |= 1;
poppedEntries.clear(stackDepth);
int popped1 = 0; }
int popped3 = 0; }
int newDepth = 0; popped = movePopsThroughDup(opcode, newInstructions,
popped);
// get the popped mask and adjust poppedEntries. for (int j=0; j < poppush[1]; j++) {
for (int j=0; j< count; j++) { if ((popped & 1) != 0)
if (poppedEntries[bottom + j]) poppedEntries.set(stackDepth);
popped1 |= 1<<j; stackDepth++;
if (poppedEntries[bottom + count + depth + j]) popped >>=1;
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; break;
}
// adjust the count case opc_swap:
if ((popped1 & popped3 & 1) != 0) { if (!push_all_popped) {
count--; // swap the popped status
popped1 >>= 1; if (poppedEntries.get(stackDepth - 1)) {
popped3 >>= 2; poppedEntries.clear(stackDepth - 1);
} else if ((popped1 & popped3 & 2) != 0) { poppedEntries.set(stackDepth - 2);
count--; } else {
popped1 &= 1; poppedEntries.set(stackDepth - 1);
popped3 &= 1; poppedEntries.clear(stackDepth - 2);
}
} }
if (popped1 == 1) { case opc_ldc2_w:
// count == 2, popped1 = 1, case opc_lload: case opc_dload:
depth++; case opc_i2l: case opc_i2d:
count--; case opc_f2l: case opc_f2d:
popped1 = 0; case opc_ldc:
popped3 >>= 1; 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);
} }
if (count == 2 && popped1 == 0 && popped3 > 0) {
// Do the normal dup2 and pop the right case opc_invokevirtual:
// element afterwards. case opc_invokespecial:
if (popped3 == 3) { case opc_invokestatic:
newInstructions.addFirst case opc_invokeinterface:
(Instruction.forOpcode(opc_pop2)); 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 { } else {
newInstructions.addFirst poppedEntries.clear(--stackDepth);
(Instruction.forOpcode(opc_pop)); poppedEntries.clear(--stackDepth);
if (popped3 == 1) newInstructions
newInstructions.addFirst .addFirst(Instruction.forOpcode(opc_pop2));
(Instruction.forOpcode(opc_swap));
} }
popped3 = 0; newInstructions.addFirst(instr);
default:
throw new InternalError("Unexpected opcode!");
} }
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;
} }
blocks[i].setCode((Instruction[]) newInstructions
.toArray(new Instruction[newInstructions.size()]),
blocks[i].getSuccs());
return poppedEntries
} }
if (count == 1) { /**
// Possible states: * This method analyzes a block from start to end and inserts the
// popped1 = 0; popped3 = 1; * pop instructions at the right places. It is used if a pop couldn't
// depth = 1 or depth = 2 * be removed for some reason.
if (depth == 1) { *
newInstructions.addFirst * @param block the block of code.
(Instruction.forOpcode(opc_swap)); * @param poppedEntries the stack slots that should be popped at
} else { * the end of this block.
newInstructions.addFirst * @return the stack slots that should be popped at the start of
(Instruction.forOpcode(opc_pop)); * the block.
newInstructions.addFirst(instr); */
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));
} }
break;
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;
} }
// count = 2; popped1 = 2 if (!push_a_popped) {
// Possible states: // Normal case:
// dpth/pop3 0 1 // add the instruction and adjust stack depth.
// 0 AB AAB AB newInstructions.addFirst(instr);
// 1 ABC BABC BAC stackDepth += poppush[0] - poppush[1];
// 2 ABCD CABCD CABD continue;
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) { /* We push an entry, that gets popped later */
} else if (depth == 1) { int opcode = instr.getOpcode();
newInstructions.addFirst switch (opcode) {
(Instruction.forOpcode(opc_pop)); //BAC case opc_dup:
newInstructions.addFirst case opc_dup_x1:
(Instruction.forOpcode(opc_dup_x2)); //BACB case opc_dup_x2:
newInstructions.addFirst case opc_dup2:
(Instruction.forOpcode(opc_swap)); //ACB case opc_dup2_x1:
} else { case opc_dup2_x2: {
newInstructions.addFirst int popped = 0;
(Instruction.forOpcode(opc_pop2)); //CABD for (int j = poppush[1] ; j > 0; j--) {
newInstructions.addFirst popped <<= 1;
(Instruction.forOpcode(opc_dup2_x1)); //CABDAB if (poppedEntries.get(--stackDepth)) {
newInstructions.addFirst popped |= 1;
(Instruction.forOpcode(opc_pop2)); //CDAB poppedEntries.clear(stackDepth);
newInstructions.addFirst
(Instruction.forOpcode(opc_dup2_x2)); //CDABCD
} }
} }
popped = movePopsThroughDup(opcode, newInstructions,
popped);
for (int j=0; j < poppush[1]; j++) {
if ((popped & 1) != 0)
poppedEntries.set(stackDepth);
stackDepth++;
popped >>=1;
}
break; break;
} }
case opc_swap: {
case opc_swap:
if (!push_all_popped) {
// swap the popped status // swap the popped status
boolean tmp = poppedEntries[stackDepth - 1]; if (poppedEntries.get(stackDepth - 1)) {
poppedEntries[stackDepth - 1] poppedEntries.clear(stackDepth - 1);
= poppedEntries[stackDepth - 2]; poppedEntries.set(stackDepth - 2);
poppedEntries[stackDepth - 2] = tmp; } else {
poppedEntries.set(stackDepth - 1);
poppedEntries.clear(stackDepth - 2);
}
} }
// Now the simple instructions, that can be removed.
// delta = -2;
case opc_ldc2_w: case opc_ldc2_w:
case opc_lload: case opc_dload: 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_i2l: case opc_i2d:
case opc_f2l: case opc_f2d: case opc_f2l: case opc_f2d:
case opc_ldc: case opc_ldc:
@ -541,14 +500,15 @@ public class RemovePopAnalyzer implements CodeTransformer, Opcodes {
case opc_getfield: case opc_getfield:
case opc_multianewarray: case opc_multianewarray:
/* The simple instructions, that can be removed. */
if (!push_all_popped) if (!push_all_popped)
throw new InternalError("pop half of a long"); throw new InternalError("pop half of a long");
if (poppush[0] < poppush[1]) { if (poppush[0] < poppush[1]) {
for (int j=0; j < poppush[0] - poppush[1]; j++) for (int j=0; j < poppush[0] - poppush[1]; j++)
poppedEntries[stackDepth++] = true; poppedEntries.set(stackDepth++);
} else if (poppush[0] < poppush[1]) { } else if (poppush[0] < poppush[1]) {
for (int j=0; j < poppush[0] - poppush[1]; j++) for (int j=0; j < poppush[0] - poppush[1]; j++)
poppedEntries[--stackDepth] = false; poppedEntries.clear(--stackDepth);
} }
case opc_invokevirtual: case opc_invokevirtual:
@ -556,15 +516,19 @@ public class RemovePopAnalyzer implements CodeTransformer, Opcodes {
case opc_invokestatic: case opc_invokestatic:
case opc_invokeinterface: case opc_invokeinterface:
case opc_checkcast: case opc_checkcast:
/* These instructions can't be removed, since
* they have side effects.
*/
if (!push_all_popped) if (!push_all_popped)
throw new InternalError("pop half of a long"); throw new InternalError("pop half of a long");
if (poppush[1] == 1) { if (poppush[1] == 1) {
poppedEntries[--stackDepth] = false; poppedEntries.clear(--stackDepth);
newInstructions newInstructions
.addFirst(Instruction.forOpcode(opc_pop)); .addFirst(Instruction.forOpcode(opc_pop));
} else { } else {
poppedEntries[--stackDepth] = false; poppedEntries.clear(--stackDepth);
poppedEntries[--stackDepth] = false; poppedEntries.clear(--stackDepth);
newInstructions newInstructions
.addFirst(Instruction.forOpcode(opc_pop2)); .addFirst(Instruction.forOpcode(opc_pop2));
} }
@ -572,16 +536,22 @@ public class RemovePopAnalyzer implements CodeTransformer, Opcodes {
default: default:
throw new InternalError("Unexpected opcode!"); 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++) { block.setCode((Instruction[]) newInstructions
// XXXX .toArray(new Instruction[newInstructions.size()]),
block.getSuccs());
} }
public void transformCode(BasicBlocks bb) {
if (bb.getStartBlock() == null)
return;
BlockInfo[] infos = calcBlockInfos(bb);
int poppush[] = new int[2];
boolean poppedEntries[] = new boolean[bb.getMaxStack()];
Block[] blocks = bb.getBlocks();
for (int i = 0; i < blocks.length; i++) {
blocks[i].setCode((Instruction[]) newInstructions blocks[i].setCode((Instruction[]) newInstructions
.toArray(oldInstrs), blocks[i].getSuccs()); .toArray(oldInstrs), blocks[i].getSuccs());
} }

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

Loading…
Cancel
Save