/** * Disassemble a stream of byte codes and return the string representation. * * @param stream data input stream * @return String representation of byte code */ private final String codeToHTML(ByteSequence bytes, int method_number) throws IOException { short opcode = (short) bytes.readUnsignedByte(); StringBuilder buf; String name, signature; int default_offset = 0, low, high; int index, class_index, vindex, constant; int[] jump_table; int no_pad_bytes = 0, offset; buf = new StringBuilder(256); buf.append("<TT>").append(OPCODE_NAMES[opcode]).append("</TT></TD><TD>"); /* Special case: Skip (0-3) padding bytes, i.e., the * following bytes are 4-byte-aligned */ if ((opcode == TABLESWITCH) || (opcode == LOOKUPSWITCH)) { int remainder = bytes.getIndex() % 4; no_pad_bytes = (remainder == 0) ? 0 : 4 - remainder; for (int i = 0; i < no_pad_bytes; i++) { bytes.readByte(); } // Both cases have a field default_offset in common default_offset = bytes.readInt(); } switch (opcode) { case TABLESWITCH: low = bytes.readInt(); high = bytes.readInt(); offset = bytes.getIndex() - 12 - no_pad_bytes - 1; default_offset += offset; buf.append("<TABLE BORDER=1><TR>"); // Print switch indices in first row (and default) jump_table = new int[high - low + 1]; for (int i = 0; i < jump_table.length; i++) { jump_table[i] = offset + bytes.readInt(); buf.append("<TH>").append(low + i).append("</TH>"); } buf.append("<TH>default</TH></TR>\n<TR>"); // Print target and default indices in second row for (int i = 0; i < jump_table.length; i++) { buf.append("<TD><A HREF=\"#code") .append(method_number) .append("@") .append(jump_table[i]) .append("\">") .append(jump_table[i]) .append("</A></TD>"); } buf.append("<TD><A HREF=\"#code") .append(method_number) .append("@") .append(default_offset) .append("\">") .append(default_offset) .append("</A></TD></TR>\n</TABLE>\n"); break; /* Lookup switch has variable length arguments. */ case LOOKUPSWITCH: int npairs = bytes.readInt(); offset = bytes.getIndex() - 8 - no_pad_bytes - 1; jump_table = new int[npairs]; default_offset += offset; buf.append("<TABLE BORDER=1><TR>"); // Print switch indices in first row (and default) for (int i = 0; i < npairs; i++) { int match = bytes.readInt(); jump_table[i] = offset + bytes.readInt(); buf.append("<TH>").append(match).append("</TH>"); } buf.append("<TH>default</TH></TR>\n<TR>"); // Print target and default indices in second row for (int i = 0; i < npairs; i++) { buf.append("<TD><A HREF=\"#code") .append(method_number) .append("@") .append(jump_table[i]) .append("\">") .append(jump_table[i]) .append("</A></TD>"); } buf.append("<TD><A HREF=\"#code") .append(method_number) .append("@") .append(default_offset) .append("\">") .append(default_offset) .append("</A></TD></TR>\n</TABLE>\n"); break; /* Two address bytes + offset from start of byte stream form the * jump target. */ case GOTO: case IFEQ: case IFGE: case IFGT: case IFLE: case IFLT: case IFNE: case IFNONNULL: case IFNULL: case IF_ACMPEQ: case IF_ACMPNE: case IF_ICMPEQ: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ICMPLT: case IF_ICMPNE: case JSR: index = (int) (bytes.getIndex() + bytes.readShort() - 1); buf.append("<A HREF=\"#code") .append(method_number) .append("@") .append(index) .append("\">") .append(index) .append("</A>"); break; /* Same for 32-bit wide jumps */ case GOTO_W: case JSR_W: int windex = bytes.getIndex() + bytes.readInt() - 1; buf.append("<A HREF=\"#code") .append(method_number) .append("@") .append(windex) .append("\">") .append(windex) .append("</A>"); break; /* Index byte references local variable (register) */ case ALOAD: case ASTORE: case DLOAD: case DSTORE: case FLOAD: case FSTORE: case ILOAD: case ISTORE: case LLOAD: case LSTORE: case RET: if (wide) { vindex = bytes.readShort(); wide = false; // Clear flag } else { vindex = bytes.readUnsignedByte(); } buf.append("%").append(vindex); break; /* * Remember wide byte which is used to form a 16-bit address in the * following instruction. Relies on that the method is called again with * the following opcode. */ case WIDE: wide = true; buf.append("(wide)"); break; /* Array of basic type. */ case NEWARRAY: buf.append("<FONT COLOR=\"#00FF00\">") .append(TYPE_NAMES[bytes.readByte()]) .append("</FONT>"); break; /* Access object/class fields. */ case GETFIELD: case GETSTATIC: case PUTFIELD: case PUTSTATIC: index = bytes.readShort(); ConstantFieldref c1 = (ConstantFieldref) constant_pool.getConstant(index, CONSTANT_Fieldref); class_index = c1.getClassIndex(); name = constant_pool.getConstantString(class_index, CONSTANT_Class); name = Utility.compactClassName(name, false); index = c1.getNameAndTypeIndex(); String field_name = constant_pool.constantToString(index, CONSTANT_NameAndType); if (name.equals(class_name)) { // Local field buf.append("<A HREF=\"") .append(class_name) .append("_methods.html#field") .append(field_name) .append("\" TARGET=Methods>") .append(field_name) .append("</A>\n"); } else { buf.append(constant_html.referenceConstant(class_index)).append(".").append(field_name); } break; /* Operands are references to classes in constant pool */ case CHECKCAST: case INSTANCEOF: case NEW: index = bytes.readShort(); buf.append(constant_html.referenceConstant(index)); break; /* Operands are references to methods in constant pool */ case INVOKESPECIAL: case INVOKESTATIC: case INVOKEVIRTUAL: case INVOKEINTERFACE: int m_index = bytes.readShort(); String str; if (opcode == INVOKEINTERFACE) { // Special treatment needed int nargs = bytes.readUnsignedByte(); // Redundant int reserved = bytes.readUnsignedByte(); // Reserved ConstantInterfaceMethodref c = (ConstantInterfaceMethodref) constant_pool.getConstant(m_index, CONSTANT_InterfaceMethodref); class_index = c.getClassIndex(); str = constant_pool.constantToString(c); index = c.getNameAndTypeIndex(); } else { ConstantMethodref c = (ConstantMethodref) constant_pool.getConstant(m_index, CONSTANT_Methodref); class_index = c.getClassIndex(); str = constant_pool.constantToString(c); index = c.getNameAndTypeIndex(); } name = Class2HTML.referenceClass(class_index); str = Class2HTML.toHTML( constant_pool.constantToString( constant_pool.getConstant(index, CONSTANT_NameAndType))); // Get signature, i.e., types ConstantNameAndType c2 = (ConstantNameAndType) constant_pool.getConstant(index, CONSTANT_NameAndType); signature = constant_pool.constantToString(c2.getSignatureIndex(), CONSTANT_Utf8); String[] args = Utility.methodSignatureArgumentTypes(signature, false); String type = Utility.methodSignatureReturnType(signature, false); buf.append(name) .append(".<A HREF=\"") .append(class_name) .append("_cp.html#cp") .append(m_index) .append("\" TARGET=ConstantPool>") .append(str) .append("</A>") .append("("); // List arguments for (int i = 0; i < args.length; i++) { buf.append(Class2HTML.referenceType(args[i])); if (i < args.length - 1) { buf.append(", "); } } // Attach return type buf.append("):").append(Class2HTML.referenceType(type)); break; /* Operands are references to items in constant pool */ case LDC_W: case LDC2_W: index = bytes.readShort(); buf.append("<A HREF=\"") .append(class_name) .append("_cp.html#cp") .append(index) .append("\" TARGET=\"ConstantPool\">") .append( Class2HTML.toHTML( constant_pool.constantToString( index, constant_pool.getConstant(index).getTag()))) .append("</a>"); break; case LDC: index = bytes.readUnsignedByte(); buf.append("<A HREF=\"") .append(class_name) .append("_cp.html#cp") .append(index) .append("\" TARGET=\"ConstantPool\">") .append( Class2HTML.toHTML( constant_pool.constantToString( index, constant_pool.getConstant(index).getTag()))) .append("</a>"); break; /* Array of references. */ case ANEWARRAY: index = bytes.readShort(); buf.append(constant_html.referenceConstant(index)); break; /* Multidimensional array of references. */ case MULTIANEWARRAY: index = bytes.readShort(); int dimensions = bytes.readByte(); buf.append(constant_html.referenceConstant(index)) .append(":") .append(dimensions) .append("-dimensional"); break; /* Increment local variable. */ case IINC: if (wide) { vindex = bytes.readShort(); constant = bytes.readShort(); wide = false; } else { vindex = bytes.readUnsignedByte(); constant = bytes.readByte(); } buf.append("%").append(vindex).append(" ").append(constant); break; default: if (NO_OF_OPERANDS[opcode] > 0) { for (int i = 0; i < TYPE_OF_OPERANDS[opcode].length; i++) { switch (TYPE_OF_OPERANDS[opcode][i]) { case T_BYTE: buf.append(bytes.readUnsignedByte()); break; case T_SHORT: // Either branch or index buf.append(bytes.readShort()); break; case T_INT: buf.append(bytes.readInt()); break; default: // Never reached System.err.println("Unreachable default case reached!"); System.exit(-1); } buf.append(" "); } } } buf.append("</TD>"); return buf.toString(); }
/** Write a single method with the byte code associated with it. */ private void writeMethod(Method method, int method_number) throws IOException { // Get raw signature String signature = method.getSignature(); // Get array of strings containing the argument types String[] args = Utility.methodSignatureArgumentTypes(signature, false); // Get return type string String type = Utility.methodSignatureReturnType(signature, false); // Get method name String name = method.getName(); String html_name = Class2HTML.toHTML(name); // Get method's access flags String access = Utility.accessToString(method.getAccessFlags()); access = Utility.replace(access, " ", " "); // Get the method's attributes, the Code Attribute in particular Attribute[] attributes = method.getAttributes(); file.print( "<P><B><FONT COLOR=\"#FF0000\">" + access + "</FONT> " + "<A NAME=method" + method_number + ">" + Class2HTML.referenceType(type) + "</A> <A HREF=\"" + class_name + "_methods.html#method" + method_number + "\" TARGET=Methods>" + html_name + "</A>("); for (int i = 0; i < args.length; i++) { file.print(Class2HTML.referenceType(args[i])); if (i < args.length - 1) { file.print(", "); } } file.println(")</B></P>"); Code c = null; byte[] code = null; if (attributes.length > 0) { file.print("<H4>Attributes</H4><UL>\n"); for (int i = 0; i < attributes.length; i++) { byte tag = attributes[i].getTag(); if (tag != ATTR_UNKNOWN) { file.print( "<LI><A HREF=\"" + class_name + "_attributes.html#method" + method_number + "@" + i + "\" TARGET=Attributes>" + ATTRIBUTE_NAMES[tag] + "</A></LI>\n"); } else { file.print("<LI>" + attributes[i] + "</LI>"); } if (tag == ATTR_CODE) { c = (Code) attributes[i]; Attribute[] attributes2 = c.getAttributes(); code = c.getCode(); file.print("<UL>"); for (int j = 0; j < attributes2.length; j++) { tag = attributes2[j].getTag(); file.print( "<LI><A HREF=\"" + class_name + "_attributes.html#" + "method" + method_number + "@" + i + "@" + j + "\" TARGET=Attributes>" + ATTRIBUTE_NAMES[tag] + "</A></LI>\n"); } file.print("</UL>"); } } file.println("</UL>"); } if (code != null) { // No code, an abstract method, e.g. // System.out.println(name + "\n" + Utility.codeToString(code, constant_pool, 0, -1)); // Print the byte code ByteSequence stream = new ByteSequence(code); stream.mark(stream.available()); findGotos(stream, method, c); stream.reset(); file.println( "<TABLE BORDER=0><TR><TH ALIGN=LEFT>Byte<BR>offset</TH>" + "<TH ALIGN=LEFT>Instruction</TH><TH ALIGN=LEFT>Argument</TH>"); for (int i = 0; stream.available() > 0; i++) { int offset = stream.getIndex(); String str = codeToHTML(stream, method_number); String anchor = ""; /* Set an anchor mark if this line is targetted by a goto, jsr, etc. * Defining an anchor for every line is very inefficient! */ if (goto_set.get(offset)) { anchor = "<A NAME=code" + method_number + "@" + offset + "></A>"; } String anchor2; if (stream.getIndex() == code.length) { anchor2 = "<A NAME=code" + method_number + "@" + code.length + ">" + offset + "</A>"; } else { anchor2 = "" + offset; } file.println("<TR VALIGN=TOP><TD>" + anchor2 + "</TD><TD>" + anchor + str + "</TR>"); } // Mark last line, may be targetted from Attributes window file.println("<TR><TD> </A></TD></TR>"); file.println("</TABLE>"); } }