/* * 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. * * @version $Id$ * */ 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(""); for (int i = 0; i < methods.length; i++) { writeMethod(methods[i], i); } file.println(""); file.close(); } /** * 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("").append(Const.getOpcodeName(opcode)).append(""); /* 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(""); // 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(""); } buf.append("\n"); // Print target and default indices in second row for (final int element : jump_table) { buf.append(""); } buf.append("\n
").append(low + i).append("default
").append(element).append("").append(default_offset).append( "
\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(""); // 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(""); } buf.append("\n"); // Print target and default indices in second row for (int i = 0; i < npairs; i++) { buf.append(""); } buf.append("\n
").append(match).append("default
").append(jump_table[i]).append("").append(default_offset).append( "
\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("").append(index).append(""); 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("").append(windex).append(""); 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("").append(Const.getTypeName(bytes.readByte())).append( ""); 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("").append(field_name) .append("\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(".").append(str).append( "").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("").append( Class2HTML.toHTML(constant_pool.constantToString(index, constant_pool.getConstant(index).getTag()))).append(""); break; case Const.LDC: index = bytes.readUnsignedByte(); buf.append("").append( Class2HTML.toHTML(constant_pool.constantToString(index, constant_pool.getConstant(index).getTag()))).append(""); 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(" "); } } } buf.append(""); 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. */ 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. */ 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, " ", " "); // Get the method's attributes, the Code Attribute in particular final Attribute[] attributes = method.getAttributes(); file.print("

" + access + " " + "" + Class2HTML.referenceType(type) + " " + html_name + "("); for (int i = 0; i < args.length; i++) { file.print(Class2HTML.referenceType(args[i])); if (i < args.length - 1) { file.print(", "); } } file.println(")

"); Code c = null; byte[] code = null; if (attributes.length > 0) { file.print("

Attributes

"); } 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("" + ""); 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 = ""; } String anchor2; if (stream.getIndex() == code.length) { anchor2 = "" + offset + ""; } else { anchor2 = "" + offset; } file.println(""); } } // Mark last line, may be targetted from Attributes window file.println(""); file.println("
Byte
offset
InstructionArgument
" + anchor2 + "" + anchor + str + "
"); } } }