CodeHTML.java

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

  18. import java.io.IOException;
  19. import java.io.PrintWriter;
  20. import java.nio.charset.Charset;
  21. import java.util.BitSet;

  22. import org.apache.bcel.Const;
  23. import org.apache.bcel.classfile.Attribute;
  24. import org.apache.bcel.classfile.Code;
  25. import org.apache.bcel.classfile.CodeException;
  26. import org.apache.bcel.classfile.ConstantFieldref;
  27. import org.apache.bcel.classfile.ConstantInterfaceMethodref;
  28. import org.apache.bcel.classfile.ConstantInvokeDynamic;
  29. import org.apache.bcel.classfile.ConstantMethodref;
  30. import org.apache.bcel.classfile.ConstantNameAndType;
  31. import org.apache.bcel.classfile.ConstantPool;
  32. import org.apache.bcel.classfile.LocalVariableTable;
  33. import org.apache.bcel.classfile.Method;
  34. import org.apache.bcel.classfile.Utility;

  35. /**
  36.  * Convert code into HTML file.
  37.  */
  38. final class CodeHTML {

  39.     private static boolean wide;
  40.     private final String className; // name of current class
  41.     // private Method[] methods; // Methods to print
  42.     private final PrintWriter printWriter; // file to write to
  43.     private BitSet gotoSet;
  44.     private final ConstantPool constantPool;
  45.     private final ConstantHTML constantHtml;

  46.     CodeHTML(final String dir, final String className, final Method[] methods, final ConstantPool constantPool, final ConstantHTML constantHtml,
  47.         final Charset charset) throws IOException {
  48.         this.className = className;
  49. //        this.methods = methods;
  50.         this.constantPool = constantPool;
  51.         this.constantHtml = constantHtml;
  52.         try (PrintWriter newPrintWriter = new PrintWriter(dir + className + "_code.html", charset.name())) {
  53.             printWriter = newPrintWriter;
  54.             printWriter.print("<HTML><head><meta charset=\"");
  55.             printWriter.print(charset.name());
  56.             printWriter.println("\"></head>");
  57.             printWriter.println("<BODY BGCOLOR=\"#C0C0C0\">");
  58.             for (int i = 0; i < methods.length; i++) {
  59.                 writeMethod(methods[i], i);
  60.             }
  61.             printWriter.println("</BODY></HTML>");
  62.         }
  63.     }

  64.     /**
  65.      * Disassemble a stream of byte codes and return the string representation.
  66.      *
  67.      * @param stream data input stream
  68.      * @return String representation of byte code
  69.      */
  70.     private String codeToHTML(final ByteSequence bytes, final int methodNumber) throws IOException {
  71.         final short opcode = (short) bytes.readUnsignedByte();
  72.         String name;
  73.         String signature;
  74.         int defaultOffset = 0;
  75.         int low;
  76.         int high;
  77.         int index;
  78.         int classIndex;
  79.         int vindex;
  80.         int constant;
  81.         int[] jumpTable;
  82.         int noPadBytes = 0;
  83.         int offset;
  84.         final StringBuilder buf = new StringBuilder(256); // CHECKSTYLE IGNORE MagicNumber
  85.         buf.append("<TT>").append(Const.getOpcodeName(opcode)).append("</TT></TD><TD>");
  86.         /*
  87.          * Special case: Skip (0-3) padding bytes, i.e., the following bytes are 4-byte-aligned
  88.          */
  89.         if (opcode == Const.TABLESWITCH || opcode == Const.LOOKUPSWITCH) {
  90.             final int remainder = bytes.getIndex() % 4;
  91.             noPadBytes = remainder == 0 ? 0 : 4 - remainder;
  92.             for (int i = 0; i < noPadBytes; i++) {
  93.                 bytes.readByte();
  94.             }
  95.             // Both cases have a field default_offset in common
  96.             defaultOffset = bytes.readInt();
  97.         }
  98.         switch (opcode) {
  99.         case Const.TABLESWITCH:
  100.             low = bytes.readInt();
  101.             high = bytes.readInt();
  102.             offset = bytes.getIndex() - 12 - noPadBytes - 1;
  103.             defaultOffset += offset;
  104.             buf.append("<TABLE BORDER=1><TR>");
  105.             // Print switch indices in first row (and default)
  106.             jumpTable = new int[high - low + 1];
  107.             for (int i = 0; i < jumpTable.length; i++) {
  108.                 jumpTable[i] = offset + bytes.readInt();
  109.                 buf.append("<TH>").append(low + i).append("</TH>");
  110.             }
  111.             buf.append("<TH>default</TH></TR>\n<TR>");
  112.             // Print target and default indices in second row
  113.             for (final int element : jumpTable) {
  114.                 buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(element).append("\">").append(element).append("</A></TD>");
  115.             }
  116.             buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(defaultOffset).append("\">").append(defaultOffset)
  117.                 .append("</A></TD></TR>\n</TABLE>\n");
  118.             break;
  119.         /*
  120.          * Lookup switch has variable length arguments.
  121.          */
  122.         case Const.LOOKUPSWITCH:
  123.             final int npairs = bytes.readInt();
  124.             offset = bytes.getIndex() - 8 - noPadBytes - 1;
  125.             jumpTable = new int[npairs];
  126.             defaultOffset += offset;
  127.             buf.append("<TABLE BORDER=1><TR>");
  128.             // Print switch indices in first row (and default)
  129.             for (int i = 0; i < npairs; i++) {
  130.                 final int match = bytes.readInt();
  131.                 jumpTable[i] = offset + bytes.readInt();
  132.                 buf.append("<TH>").append(match).append("</TH>");
  133.             }
  134.             buf.append("<TH>default</TH></TR>\n<TR>");
  135.             // Print target and default indices in second row
  136.             for (int i = 0; i < npairs; i++) {
  137.                 buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(jumpTable[i]).append("\">").append(jumpTable[i])
  138.                     .append("</A></TD>");
  139.             }
  140.             buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(defaultOffset).append("\">").append(defaultOffset)
  141.                 .append("</A></TD></TR>\n</TABLE>\n");
  142.             break;
  143.         /*
  144.          * Two address bytes + offset from start of byte stream form the jump target.
  145.          */
  146.         case Const.GOTO:
  147.         case Const.IFEQ:
  148.         case Const.IFGE:
  149.         case Const.IFGT:
  150.         case Const.IFLE:
  151.         case Const.IFLT:
  152.         case Const.IFNE:
  153.         case Const.IFNONNULL:
  154.         case Const.IFNULL:
  155.         case Const.IF_ACMPEQ:
  156.         case Const.IF_ACMPNE:
  157.         case Const.IF_ICMPEQ:
  158.         case Const.IF_ICMPGE:
  159.         case Const.IF_ICMPGT:
  160.         case Const.IF_ICMPLE:
  161.         case Const.IF_ICMPLT:
  162.         case Const.IF_ICMPNE:
  163.         case Const.JSR:
  164.             index = bytes.getIndex() + bytes.readShort() - 1;
  165.             buf.append("<A HREF=\"#code").append(methodNumber).append("@").append(index).append("\">").append(index).append("</A>");
  166.             break;
  167.         /*
  168.          * Same for 32-bit wide jumps
  169.          */
  170.         case Const.GOTO_W:
  171.         case Const.JSR_W:
  172.             final int windex = bytes.getIndex() + bytes.readInt() - 1;
  173.             buf.append("<A HREF=\"#code").append(methodNumber).append("@").append(windex).append("\">").append(windex).append("</A>");
  174.             break;
  175.         /*
  176.          * Index byte references local variable (register)
  177.          */
  178.         case Const.ALOAD:
  179.         case Const.ASTORE:
  180.         case Const.DLOAD:
  181.         case Const.DSTORE:
  182.         case Const.FLOAD:
  183.         case Const.FSTORE:
  184.         case Const.ILOAD:
  185.         case Const.ISTORE:
  186.         case Const.LLOAD:
  187.         case Const.LSTORE:
  188.         case Const.RET:
  189.             if (wide) {
  190.                 vindex = bytes.readShort();
  191.                 wide = false; // Clear flag
  192.             } else {
  193.                 vindex = bytes.readUnsignedByte();
  194.             }
  195.             buf.append("%").append(vindex);
  196.             break;
  197.         /*
  198.          * Remember wide byte which is used to form a 16-bit address in the following instruction. Relies on that the method is
  199.          * called again with the following opcode.
  200.          */
  201.         case Const.WIDE:
  202.             wide = true;
  203.             buf.append("(wide)");
  204.             break;
  205.         /*
  206.          * Array of basic type.
  207.          */
  208.         case Const.NEWARRAY:
  209.             buf.append("<FONT COLOR=\"#00FF00\">").append(Const.getTypeName(bytes.readByte())).append("</FONT>");
  210.             break;
  211.         /*
  212.          * Access object/class fields.
  213.          */
  214.         case Const.GETFIELD:
  215.         case Const.GETSTATIC:
  216.         case Const.PUTFIELD:
  217.         case Const.PUTSTATIC:
  218.             index = bytes.readShort();
  219.             final ConstantFieldref c1 = constantPool.getConstant(index, Const.CONSTANT_Fieldref, ConstantFieldref.class);
  220.             classIndex = c1.getClassIndex();
  221.             name = constantPool.getConstantString(classIndex, Const.CONSTANT_Class);
  222.             name = Utility.compactClassName(name, false);
  223.             index = c1.getNameAndTypeIndex();
  224.             final String fieldName = constantPool.constantToString(index, Const.CONSTANT_NameAndType);
  225.             if (name.equals(className)) { // Local field
  226.                 buf.append("<A HREF=\"").append(className).append("_methods.html#field").append(fieldName).append("\" TARGET=Methods>").append(fieldName)
  227.                     .append("</A>\n");
  228.             } else {
  229.                 buf.append(constantHtml.referenceConstant(classIndex)).append(".").append(fieldName);
  230.             }
  231.             break;
  232.         /*
  233.          * Operands are references to classes in constant pool
  234.          */
  235.         case Const.CHECKCAST:
  236.         case Const.INSTANCEOF:
  237.         case Const.NEW:
  238.             index = bytes.readShort();
  239.             buf.append(constantHtml.referenceConstant(index));
  240.             break;
  241.         /*
  242.          * Operands are references to methods in constant pool
  243.          */
  244.         case Const.INVOKESPECIAL:
  245.         case Const.INVOKESTATIC:
  246.         case Const.INVOKEVIRTUAL:
  247.         case Const.INVOKEINTERFACE:
  248.         case Const.INVOKEDYNAMIC:
  249.             final int mIndex = bytes.readShort();
  250.             String str;
  251.             if (opcode == Const.INVOKEINTERFACE) { // Special treatment needed
  252.                 bytes.readUnsignedByte(); // Redundant
  253.                 bytes.readUnsignedByte(); // Reserved
  254. //                    int nargs = bytes.readUnsignedByte(); // Redundant
  255. //                    int reserved = bytes.readUnsignedByte(); // Reserved
  256.                 final ConstantInterfaceMethodref c = constantPool.getConstant(mIndex, Const.CONSTANT_InterfaceMethodref, ConstantInterfaceMethodref.class);
  257.                 classIndex = c.getClassIndex();
  258.                 index = c.getNameAndTypeIndex();
  259.                 name = Class2HTML.referenceClass(classIndex);
  260.             } else if (opcode == Const.INVOKEDYNAMIC) { // Special treatment needed
  261.                 bytes.readUnsignedByte(); // Reserved
  262.                 bytes.readUnsignedByte(); // Reserved
  263.                 final ConstantInvokeDynamic c = constantPool.getConstant(mIndex, Const.CONSTANT_InvokeDynamic, ConstantInvokeDynamic.class);
  264.                 index = c.getNameAndTypeIndex();
  265.                 name = "#" + c.getBootstrapMethodAttrIndex();
  266.             } else {
  267.                 // UNDONE: Java8 now allows INVOKESPECIAL and INVOKESTATIC to
  268.                 // reference EITHER a Methodref OR an InterfaceMethodref.
  269.                 // Not sure if that affects this code or not. (markro)
  270.                 final ConstantMethodref c = constantPool.getConstant(mIndex, Const.CONSTANT_Methodref, ConstantMethodref.class);
  271.                 classIndex = c.getClassIndex();
  272.                 index = c.getNameAndTypeIndex();
  273.                 name = Class2HTML.referenceClass(classIndex);
  274.             }
  275.             str = Class2HTML.toHTML(constantPool.constantToString(constantPool.getConstant(index, Const.CONSTANT_NameAndType)));
  276.             // Get signature, i.e., types
  277.             final ConstantNameAndType c2 = constantPool.getConstant(index, Const.CONSTANT_NameAndType, ConstantNameAndType.class);
  278.             signature = constantPool.constantToString(c2.getSignatureIndex(), Const.CONSTANT_Utf8);
  279.             final String[] args = Utility.methodSignatureArgumentTypes(signature, false);
  280.             final String type = Utility.methodSignatureReturnType(signature, false);
  281.             buf.append(name).append(".<A HREF=\"").append(className).append("_cp.html#cp").append(mIndex).append("\" TARGET=ConstantPool>").append(str)
  282.                 .append("</A>").append("(");
  283.             // List arguments
  284.             for (int i = 0; i < args.length; i++) {
  285.                 buf.append(Class2HTML.referenceType(args[i]));
  286.                 if (i < args.length - 1) {
  287.                     buf.append(", ");
  288.                 }
  289.             }
  290.             // Attach return type
  291.             buf.append("):").append(Class2HTML.referenceType(type));
  292.             break;
  293.         /*
  294.          * Operands are references to items in constant pool
  295.          */
  296.         case Const.LDC_W:
  297.         case Const.LDC2_W:
  298.             index = bytes.readShort();
  299.             buf.append("<A HREF=\"").append(className).append("_cp.html#cp").append(index).append("\" TARGET=\"ConstantPool\">")
  300.                 .append(Class2HTML.toHTML(constantPool.constantToString(index, constantPool.getConstant(index).getTag()))).append("</a>");
  301.             break;
  302.         case Const.LDC:
  303.             index = bytes.readUnsignedByte();
  304.             buf.append("<A HREF=\"").append(className).append("_cp.html#cp").append(index).append("\" TARGET=\"ConstantPool\">")
  305.                 .append(Class2HTML.toHTML(constantPool.constantToString(index, constantPool.getConstant(index).getTag()))).append("</a>");
  306.             break;
  307.         /*
  308.          * Array of references.
  309.          */
  310.         case Const.ANEWARRAY:
  311.             index = bytes.readShort();
  312.             buf.append(constantHtml.referenceConstant(index));
  313.             break;
  314.         /*
  315.          * Multidimensional array of references.
  316.          */
  317.         case Const.MULTIANEWARRAY:
  318.             index = bytes.readShort();
  319.             final int dimensions = bytes.readByte();
  320.             buf.append(constantHtml.referenceConstant(index)).append(":").append(dimensions).append("-dimensional");
  321.             break;
  322.         /*
  323.          * Increment local variable.
  324.          */
  325.         case Const.IINC:
  326.             if (wide) {
  327.                 vindex = bytes.readShort();
  328.                 constant = bytes.readShort();
  329.                 wide = false;
  330.             } else {
  331.                 vindex = bytes.readUnsignedByte();
  332.                 constant = bytes.readByte();
  333.             }
  334.             buf.append("%").append(vindex).append(" ").append(constant);
  335.             break;
  336.         default:
  337.             if (Const.getNoOfOperands(opcode) > 0) {
  338.                 for (int i = 0; i < Const.getOperandTypeCount(opcode); i++) {
  339.                     switch (Const.getOperandType(opcode, i)) {
  340.                     case Const.T_BYTE:
  341.                         buf.append(bytes.readUnsignedByte());
  342.                         break;
  343.                     case Const.T_SHORT: // Either branch or index
  344.                         buf.append(bytes.readShort());
  345.                         break;
  346.                     case Const.T_INT:
  347.                         buf.append(bytes.readInt());
  348.                         break;
  349.                     default: // Never reached
  350.                         throw new IllegalStateException("Unreachable default case reached! " + Const.getOperandType(opcode, i));
  351.                     }
  352.                     buf.append("&nbsp;");
  353.                 }
  354.             }
  355.         }
  356.         buf.append("</TD>");
  357.         return buf.toString();
  358.     }

  359.     /**
  360.      * Find all target addresses in code, so that they can be marked with &lt;A NAME = ...&gt;. Target addresses are kept in
  361.      * an BitSet object.
  362.      */
  363.     private void findGotos(final ByteSequence bytes, final Code code) throws IOException {
  364.         int index;
  365.         gotoSet = new BitSet(bytes.available());
  366.         int opcode;
  367.         /*
  368.          * First get Code attribute from method and the exceptions handled (try .. catch) in this method. We only need the line
  369.          * number here.
  370.          */
  371.         if (code != null) {
  372.             final CodeException[] ce = code.getExceptionTable();
  373.             for (final CodeException cex : ce) {
  374.                 gotoSet.set(cex.getStartPC());
  375.                 gotoSet.set(cex.getEndPC());
  376.                 gotoSet.set(cex.getHandlerPC());
  377.             }
  378.             // Look for local variables and their range
  379.             final Attribute[] attributes = code.getAttributes();
  380.             for (final Attribute attribute : attributes) {
  381.                 if (attribute.getTag() == Const.ATTR_LOCAL_VARIABLE_TABLE) {
  382.                     ((LocalVariableTable) attribute).forEach(var -> {
  383.                         final int start = var.getStartPC();
  384.                         gotoSet.set(start);
  385.                         gotoSet.set(start + var.getLength());
  386.                     });
  387.                     break;
  388.                 }
  389.             }
  390.         }
  391.         // Get target addresses from GOTO, JSR, TABLESWITCH, etc.
  392.         while (bytes.available() > 0) {
  393.             opcode = bytes.readUnsignedByte();
  394.             // System.out.println(getOpcodeName(opcode));
  395.             switch (opcode) {
  396.             case Const.TABLESWITCH:
  397.             case Const.LOOKUPSWITCH:
  398.                 // bytes.readByte(); // Skip already read byte
  399.                 final int remainder = bytes.getIndex() % 4;
  400.                 final int noPadBytes = remainder == 0 ? 0 : 4 - remainder;
  401.                 int defaultOffset;
  402.                 int offset;
  403.                 for (int j = 0; j < noPadBytes; j++) {
  404.                     bytes.readByte();
  405.                 }
  406.                 // Both cases have a field default_offset in common
  407.                 defaultOffset = bytes.readInt();
  408.                 if (opcode == Const.TABLESWITCH) {
  409.                     final int low = bytes.readInt();
  410.                     final int high = bytes.readInt();
  411.                     offset = bytes.getIndex() - 12 - noPadBytes - 1;
  412.                     defaultOffset += offset;
  413.                     gotoSet.set(defaultOffset);
  414.                     for (int j = 0; j < high - low + 1; j++) {
  415.                         index = offset + bytes.readInt();
  416.                         gotoSet.set(index);
  417.                     }
  418.                 } else { // LOOKUPSWITCH
  419.                     final int npairs = bytes.readInt();
  420.                     offset = bytes.getIndex() - 8 - noPadBytes - 1;
  421.                     defaultOffset += offset;
  422.                     gotoSet.set(defaultOffset);
  423.                     for (int j = 0; j < npairs; j++) {
  424. //                            int match = bytes.readInt();
  425.                         bytes.readInt();
  426.                         index = offset + bytes.readInt();
  427.                         gotoSet.set(index);
  428.                     }
  429.                 }
  430.                 break;
  431.             case Const.GOTO:
  432.             case Const.IFEQ:
  433.             case Const.IFGE:
  434.             case Const.IFGT:
  435.             case Const.IFLE:
  436.             case Const.IFLT:
  437.             case Const.IFNE:
  438.             case Const.IFNONNULL:
  439.             case Const.IFNULL:
  440.             case Const.IF_ACMPEQ:
  441.             case Const.IF_ACMPNE:
  442.             case Const.IF_ICMPEQ:
  443.             case Const.IF_ICMPGE:
  444.             case Const.IF_ICMPGT:
  445.             case Const.IF_ICMPLE:
  446.             case Const.IF_ICMPLT:
  447.             case Const.IF_ICMPNE:
  448.             case Const.JSR:
  449.                 // bytes.readByte(); // Skip already read byte
  450.                 index = bytes.getIndex() + bytes.readShort() - 1;
  451.                 gotoSet.set(index);
  452.                 break;
  453.             case Const.GOTO_W:
  454.             case Const.JSR_W:
  455.                 // bytes.readByte(); // Skip already read byte
  456.                 index = bytes.getIndex() + bytes.readInt() - 1;
  457.                 gotoSet.set(index);
  458.                 break;
  459.             default:
  460.                 bytes.unreadByte();
  461.                 codeToHTML(bytes, 0); // Ignore output
  462.             }
  463.         }
  464.     }

  465.     /**
  466.      * Write a single method with the byte code associated with it.
  467.      */
  468.     private void writeMethod(final Method method, final int methodNumber) throws IOException {
  469.         // Get raw signature
  470.         final String signature = method.getSignature();
  471.         // Get array of strings containing the argument types
  472.         final String[] args = Utility.methodSignatureArgumentTypes(signature, false);
  473.         // Get return type string
  474.         final String type = Utility.methodSignatureReturnType(signature, false);
  475.         // Get method name
  476.         final String name = method.getName();
  477.         final String htmlName = Class2HTML.toHTML(name);
  478.         // Get method's access flags
  479.         String access = Utility.accessToString(method.getAccessFlags());
  480.         access = Utility.replace(access, " ", "&nbsp;");
  481.         // Get the method's attributes, the Code Attribute in particular
  482.         final Attribute[] attributes = method.getAttributes();
  483.         printWriter.print("<P><B><FONT COLOR=\"#FF0000\">" + access + "</FONT>&nbsp;" + "<A NAME=method" + methodNumber + ">" + Class2HTML.referenceType(type)
  484.             + "</A>&nbsp<A HREF=\"" + className + "_methods.html#method" + methodNumber + "\" TARGET=Methods>" + htmlName + "</A>(");
  485.         for (int i = 0; i < args.length; i++) {
  486.             printWriter.print(Class2HTML.referenceType(args[i]));
  487.             if (i < args.length - 1) {
  488.                 printWriter.print(",&nbsp;");
  489.             }
  490.         }
  491.         printWriter.println(")</B></P>");
  492.         Code c = null;
  493.         byte[] code = null;
  494.         if (attributes.length > 0) {
  495.             printWriter.print("<H4>Attributes</H4><UL>\n");
  496.             for (int i = 0; i < attributes.length; i++) {
  497.                 byte tag = attributes[i].getTag();
  498.                 if (tag != Const.ATTR_UNKNOWN) {
  499.                     printWriter.print("<LI><A HREF=\"" + className + "_attributes.html#method" + methodNumber + "@" + i + "\" TARGET=Attributes>"
  500.                         + Const.getAttributeName(tag) + "</A></LI>\n");
  501.                 } else {
  502.                     printWriter.print("<LI>" + attributes[i] + "</LI>");
  503.                 }
  504.                 if (tag == Const.ATTR_CODE) {
  505.                     c = (Code) attributes[i];
  506.                     final Attribute[] attributes2 = c.getAttributes();
  507.                     code = c.getCode();
  508.                     printWriter.print("<UL>");
  509.                     for (int j = 0; j < attributes2.length; j++) {
  510.                         tag = attributes2[j].getTag();
  511.                         printWriter.print("<LI><A HREF=\"" + className + "_attributes.html#" + "method" + methodNumber + "@" + i + "@" + j
  512.                             + "\" TARGET=Attributes>" + Const.getAttributeName(tag) + "</A></LI>\n");
  513.                     }
  514.                     printWriter.print("</UL>");
  515.                 }
  516.             }
  517.             printWriter.println("</UL>");
  518.         }
  519.         if (code != null) { // No code, an abstract method, e.g.
  520.             // System.out.println(name + "\n" + Utility.codeToString(code, constantPool, 0, -1));
  521.             // Print the byte code
  522.             try (ByteSequence stream = new ByteSequence(code)) {
  523.                 stream.mark(stream.available());
  524.                 findGotos(stream, c);
  525.                 stream.reset();
  526.                 printWriter.println("<TABLE BORDER=0><TR><TH ALIGN=LEFT>Byte<BR>offset</TH>" + "<TH ALIGN=LEFT>Instruction</TH><TH ALIGN=LEFT>Argument</TH>");
  527.                 while (stream.available() > 0) {
  528.                     final int offset = stream.getIndex();
  529.                     final String str = codeToHTML(stream, methodNumber);
  530.                     String anchor = "";
  531.                     /*
  532.                      * Sets an anchor mark if this line is targetted by a goto, jsr, etc. Defining an anchor for every line is very
  533.                      * inefficient!
  534.                      */
  535.                     if (gotoSet.get(offset)) {
  536.                         anchor = "<A NAME=code" + methodNumber + "@" + offset + "></A>";
  537.                     }
  538.                     String anchor2;
  539.                     if (stream.getIndex() == code.length) {
  540.                         anchor2 = "<A NAME=code" + methodNumber + "@" + code.length + ">" + offset + "</A>";
  541.                     } else {
  542.                         anchor2 = "" + offset;
  543.                     }
  544.                     printWriter.println("<TR VALIGN=TOP><TD>" + anchor2 + "</TD><TD>" + anchor + str + "</TR>");
  545.                 }
  546.             }
  547.             // Mark last line, may be targetted from Attributes window
  548.             printWriter.println("<TR><TD> </A></TD></TR>");
  549.             printWriter.println("</TABLE>");
  550.         }
  551.     }
  552. }