/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.bcel.util;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.BitSet;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.CodeException;
import org.apache.bcel.classfile.ConstantFieldref;
import org.apache.bcel.classfile.ConstantInterfaceMethodref;
import org.apache.bcel.classfile.ConstantInvokeDynamic;
import org.apache.bcel.classfile.ConstantMethodref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.LocalVariable;
import org.apache.bcel.classfile.LocalVariableTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.Utility;

Convert code into HTML file.
/** * Convert code into HTML file. * * */
final class CodeHTML { private final String class_name; // name of current class // private Method[] methods; // Methods to print private final PrintWriter file; // file to write to private BitSet goto_set; private final ConstantPool constant_pool; private final ConstantHTML constant_html; private static boolean wide = false; CodeHTML(final String dir, final String class_name, final Method[] methods, final ConstantPool constant_pool, final ConstantHTML constant_html) throws IOException { this.class_name = class_name; // this.methods = methods; this.constant_pool = constant_pool; this.constant_html = constant_html; file = new PrintWriter(new FileOutputStream(dir + class_name + "_code.html")); file.println("<HTML><BODY BGCOLOR=\"#C0C0C0\">"); for (int i = 0; i < methods.length; i++) { writeMethod(methods[i], i); } file.println("</BODY></HTML>"); file.close(); }
Disassemble a stream of byte codes and return the string representation.
Params:
  • stream – data input stream
Returns:String representation of byte code
/** * Disassemble a stream of byte codes and return the * string representation. * * @param stream data input stream * @return String representation of byte code */
private String codeToHTML( final ByteSequence bytes, final int method_number ) throws IOException { final short opcode = (short) bytes.readUnsignedByte(); String name; String signature; int default_offset = 0; int low; int high; int index; int class_index; int vindex; int constant; int[] jump_table; int no_pad_bytes = 0; int offset; final StringBuilder buf = new StringBuilder(256); // CHECKSTYLE IGNORE MagicNumber buf.append("<TT>").append(Const.getOpcodeName(opcode)).append("</TT></TD><TD>"); /* Special case: Skip (0-3) padding bytes, i.e., the * following bytes are 4-byte-aligned */ if ((opcode == Const.TABLESWITCH) || (opcode == Const.LOOKUPSWITCH)) { final 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 Const.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 (final int element : jump_table) { buf.append("<TD><A HREF=\"#code").append(method_number).append("@").append( element).append("\">").append(element).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 Const.LOOKUPSWITCH: final 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++) { final 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 Const.GOTO: case Const.IFEQ: case Const.IFGE: case Const.IFGT: case Const.IFLE: case Const.IFLT: case Const.IFNE: case Const.IFNONNULL: case Const.IFNULL: case Const.IF_ACMPEQ: case Const.IF_ACMPNE: case Const.IF_ICMPEQ: case Const.IF_ICMPGE: case Const.IF_ICMPGT: case Const.IF_ICMPLE: case Const.IF_ICMPLT: case Const.IF_ICMPNE: case Const.JSR: index = 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 Const.GOTO_W: case Const.JSR_W: final 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 Const.ALOAD: case Const.ASTORE: case Const.DLOAD: case Const.DSTORE: case Const.FLOAD: case Const.FSTORE: case Const.ILOAD: case Const.ISTORE: case Const.LLOAD: case Const.LSTORE: case Const.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 Const.WIDE: wide = true; buf.append("(wide)"); break; /* Array of basic type. */ case Const.NEWARRAY: buf.append("<FONT COLOR=\"#00FF00\">").append(Const.getTypeName(bytes.readByte())).append( "</FONT>"); break; /* Access object/class fields. */ case Const.GETFIELD: case Const.GETSTATIC: case Const.PUTFIELD: case Const.PUTSTATIC: index = bytes.readShort(); final ConstantFieldref c1 = (ConstantFieldref) constant_pool.getConstant(index, Const.CONSTANT_Fieldref); class_index = c1.getClassIndex(); name = constant_pool.getConstantString(class_index, Const.CONSTANT_Class); name = Utility.compactClassName(name, false); index = c1.getNameAndTypeIndex(); final String field_name = constant_pool.constantToString(index, Const.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 Const.CHECKCAST: case Const.INSTANCEOF: case Const.NEW: index = bytes.readShort(); buf.append(constant_html.referenceConstant(index)); break; /* Operands are references to methods in constant pool */ case Const.INVOKESPECIAL: case Const.INVOKESTATIC: case Const.INVOKEVIRTUAL: case Const.INVOKEINTERFACE: case Const.INVOKEDYNAMIC: final int m_index = bytes.readShort(); String str; if (opcode == Const.INVOKEINTERFACE) { // Special treatment needed bytes.readUnsignedByte(); // Redundant bytes.readUnsignedByte(); // Reserved // int nargs = bytes.readUnsignedByte(); // Redundant // int reserved = bytes.readUnsignedByte(); // Reserved final ConstantInterfaceMethodref c = (ConstantInterfaceMethodref) constant_pool .getConstant(m_index, Const.CONSTANT_InterfaceMethodref); class_index = c.getClassIndex(); index = c.getNameAndTypeIndex(); name = Class2HTML.referenceClass(class_index); } else if (opcode == Const.INVOKEDYNAMIC) { // Special treatment needed bytes.readUnsignedByte(); // Reserved bytes.readUnsignedByte(); // Reserved final ConstantInvokeDynamic c = (ConstantInvokeDynamic) constant_pool .getConstant(m_index, Const.CONSTANT_InvokeDynamic); index = c.getNameAndTypeIndex(); name = "#" + c.getBootstrapMethodAttrIndex(); } else { // UNDONE: Java8 now allows INVOKESPECIAL and INVOKESTATIC to // reference EITHER a Methodref OR an InterfaceMethodref. // Not sure if that affects this code or not. (markro) final ConstantMethodref c = (ConstantMethodref) constant_pool.getConstant(m_index, Const.CONSTANT_Methodref); class_index = c.getClassIndex(); index = c.getNameAndTypeIndex(); name = Class2HTML.referenceClass(class_index); } str = Class2HTML.toHTML(constant_pool.constantToString(constant_pool.getConstant( index, Const.CONSTANT_NameAndType))); // Get signature, i.e., types final ConstantNameAndType c2 = (ConstantNameAndType) constant_pool.getConstant(index, Const.CONSTANT_NameAndType); signature = constant_pool.constantToString(c2.getSignatureIndex(), Const.CONSTANT_Utf8); final String[] args = Utility.methodSignatureArgumentTypes(signature, false); final 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 Const.LDC_W: case Const.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 Const.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 Const.ANEWARRAY: index = bytes.readShort(); buf.append(constant_html.referenceConstant(index)); break; /* Multidimensional array of references. */ case Const.MULTIANEWARRAY: index = bytes.readShort(); final int dimensions = bytes.readByte(); buf.append(constant_html.referenceConstant(index)).append(":").append(dimensions) .append("-dimensional"); break; /* Increment local variable. */ case Const.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 (Const.getNoOfOperands(opcode) > 0) { for (int i = 0; i < Const.getOperandTypeCount(opcode); i++) { switch (Const.getOperandType(opcode, i)) { case Const.T_BYTE: buf.append(bytes.readUnsignedByte()); break; case Const.T_SHORT: // Either branch or index buf.append(bytes.readShort()); break; case Const.T_INT: buf.append(bytes.readInt()); break; default: // Never reached throw new IllegalStateException("Unreachable default case reached! "+Const.getOperandType(opcode, i)); } buf.append("&nbsp;"); } } } buf.append("</TD>"); return buf.toString(); }
Find all target addresses in code, so that they can be marked with <A NAME = ...>. Target addresses are kept in an BitSet object.
/** * Find all target addresses in code, so that they can be marked * with &lt;A NAME = ...&gt;. Target addresses are kept in an BitSet object. */
private void findGotos( final ByteSequence bytes, final Code code ) throws IOException { int index; goto_set = new BitSet(bytes.available()); int opcode; /* First get Code attribute from method and the exceptions handled * (try .. catch) in this method. We only need the line number here. */ if (code != null) { final CodeException[] ce = code.getExceptionTable(); for (final CodeException cex : ce) { goto_set.set(cex.getStartPC()); goto_set.set(cex.getEndPC()); goto_set.set(cex.getHandlerPC()); } // Look for local variables and their range final Attribute[] attributes = code.getAttributes(); for (final Attribute attribute : attributes) { if (attribute.getTag() == Const.ATTR_LOCAL_VARIABLE_TABLE) { final LocalVariable[] vars = ((LocalVariableTable) attribute) .getLocalVariableTable(); for (final LocalVariable var : vars) { final int start = var.getStartPC(); final int end = start + var.getLength(); goto_set.set(start); goto_set.set(end); } break; } } } // Get target addresses from GOTO, JSR, TABLESWITCH, etc. for (; bytes.available() > 0;) { opcode = bytes.readUnsignedByte(); //System.out.println(getOpcodeName(opcode)); switch (opcode) { case Const.TABLESWITCH: case Const.LOOKUPSWITCH: //bytes.readByte(); // Skip already read byte final int remainder = bytes.getIndex() % 4; final int no_pad_bytes = (remainder == 0) ? 0 : 4 - remainder; int default_offset; int offset; for (int j = 0; j < no_pad_bytes; j++) { bytes.readByte(); } // Both cases have a field default_offset in common default_offset = bytes.readInt(); if (opcode == Const.TABLESWITCH) { final int low = bytes.readInt(); final int high = bytes.readInt(); offset = bytes.getIndex() - 12 - no_pad_bytes - 1; default_offset += offset; goto_set.set(default_offset); for (int j = 0; j < (high - low + 1); j++) { index = offset + bytes.readInt(); goto_set.set(index); } } else { // LOOKUPSWITCH final int npairs = bytes.readInt(); offset = bytes.getIndex() - 8 - no_pad_bytes - 1; default_offset += offset; goto_set.set(default_offset); for (int j = 0; j < npairs; j++) { // int match = bytes.readInt(); bytes.readInt(); index = offset + bytes.readInt(); goto_set.set(index); } } break; case Const.GOTO: case Const.IFEQ: case Const.IFGE: case Const.IFGT: case Const.IFLE: case Const.IFLT: case Const.IFNE: case Const.IFNONNULL: case Const.IFNULL: case Const.IF_ACMPEQ: case Const.IF_ACMPNE: case Const.IF_ICMPEQ: case Const.IF_ICMPGE: case Const.IF_ICMPGT: case Const.IF_ICMPLE: case Const.IF_ICMPLT: case Const.IF_ICMPNE: case Const.JSR: //bytes.readByte(); // Skip already read byte index = bytes.getIndex() + bytes.readShort() - 1; goto_set.set(index); break; case Const.GOTO_W: case Const.JSR_W: //bytes.readByte(); // Skip already read byte index = bytes.getIndex() + bytes.readInt() - 1; goto_set.set(index); break; default: bytes.unreadByte(); codeToHTML(bytes, 0); // Ignore output } } }
Write a single method with the byte code associated with it.
/** * Write a single method with the byte code associated with it. */
private void writeMethod( final Method method, final int method_number ) throws IOException { // Get raw signature final String signature = method.getSignature(); // Get array of strings containing the argument types final String[] args = Utility.methodSignatureArgumentTypes(signature, false); // Get return type string final String type = Utility.methodSignatureReturnType(signature, false); // Get method name final String name = method.getName(); final String html_name = Class2HTML.toHTML(name); // Get method's access flags String access = Utility.accessToString(method.getAccessFlags()); access = Utility.replace(access, " ", "&nbsp;"); // Get the method's attributes, the Code Attribute in particular final Attribute[] attributes = method.getAttributes(); file.print("<P><B><FONT COLOR=\"#FF0000\">" + access + "</FONT>&nbsp;" + "<A NAME=method" + method_number + ">" + Class2HTML.referenceType(type) + "</A>&nbsp<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(",&nbsp;"); } } 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 != Const.ATTR_UNKNOWN) { file.print("<LI><A HREF=\"" + class_name + "_attributes.html#method" + method_number + "@" + i + "\" TARGET=Attributes>" + Const.getAttributeName(tag) + "</A></LI>\n"); } else { file.print("<LI>" + attributes[i] + "</LI>"); } if (tag == Const.ATTR_CODE) { c = (Code) attributes[i]; final 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>" + Const.getAttributeName(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 try (ByteSequence stream = new ByteSequence(code)) { stream.mark(stream.available()); findGotos(stream, 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 (; stream.available() > 0;) { final int offset = stream.getIndex(); final 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>"); } } }