View Javadoc
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  
19  import java.io.IOException;
20  import java.io.PrintWriter;
21  import java.nio.charset.Charset;
22  import java.util.BitSet;
23  
24  import org.apache.bcel.Const;
25  import org.apache.bcel.classfile.Attribute;
26  import org.apache.bcel.classfile.Code;
27  import org.apache.bcel.classfile.CodeException;
28  import org.apache.bcel.classfile.ConstantFieldref;
29  import org.apache.bcel.classfile.ConstantInterfaceMethodref;
30  import org.apache.bcel.classfile.ConstantInvokeDynamic;
31  import org.apache.bcel.classfile.ConstantMethodref;
32  import org.apache.bcel.classfile.ConstantNameAndType;
33  import org.apache.bcel.classfile.ConstantPool;
34  import org.apache.bcel.classfile.LocalVariableTable;
35  import org.apache.bcel.classfile.Method;
36  import org.apache.bcel.classfile.Utility;
37  
38  /**
39   * Convert code into HTML file.
40   */
41  final class CodeHTML {
42  
43      private static boolean wide;
44      private final String className; // name of current class
45      // private Method[] methods; // Methods to print
46      private final PrintWriter printWriter; // file to write to
47      private BitSet gotoSet;
48      private final ConstantPool constantPool;
49      private final ConstantHTML constantHtml;
50  
51      CodeHTML(final String dir, final String className, final Method[] methods, final ConstantPool constantPool, final ConstantHTML constantHtml,
52          final Charset charset) throws IOException {
53          this.className = className;
54  //        this.methods = methods;
55          this.constantPool = constantPool;
56          this.constantHtml = constantHtml;
57          try (PrintWriter newPrintWriter = new PrintWriter(dir + className + "_code.html", charset.name())) {
58              printWriter = newPrintWriter;
59              printWriter.print("<HTML><head><meta charset=\"");
60              printWriter.print(charset.name());
61              printWriter.println("\"></head>");
62              printWriter.println("<BODY BGCOLOR=\"#C0C0C0\">");
63              for (int i = 0; i < methods.length; i++) {
64                  writeMethod(methods[i], i);
65              }
66              printWriter.println("</BODY></HTML>");
67          }
68      }
69  
70      /**
71       * Disassemble a stream of byte codes and return the string representation.
72       *
73       * @param stream data input stream
74       * @return String representation of byte code
75       */
76      private String codeToHTML(final ByteSequence bytes, final int methodNumber) throws IOException {
77          final short opcode = (short) bytes.readUnsignedByte();
78          String name;
79          String signature;
80          int defaultOffset = 0;
81          int low;
82          int high;
83          int index;
84          int classIndex;
85          int vindex;
86          int constant;
87          int[] jumpTable;
88          int noPadBytes = 0;
89          int offset;
90          final StringBuilder buf = new StringBuilder(256); // CHECKSTYLE IGNORE MagicNumber
91          buf.append("<TT>").append(Const.getOpcodeName(opcode)).append("</TT></TD><TD>");
92          /*
93           * Special case: Skip (0-3) padding bytes, i.e., the following bytes are 4-byte-aligned
94           */
95          if (opcode == Const.TABLESWITCH || opcode == Const.LOOKUPSWITCH) {
96              final int remainder = bytes.getIndex() % 4;
97              noPadBytes = remainder == 0 ? 0 : 4 - remainder;
98              for (int i = 0; i < noPadBytes; i++) {
99                  bytes.readByte();
100             }
101             // Both cases have a field default_offset in common
102             defaultOffset = bytes.readInt();
103         }
104         switch (opcode) {
105         case Const.TABLESWITCH:
106             low = bytes.readInt();
107             high = bytes.readInt();
108             offset = bytes.getIndex() - 12 - noPadBytes - 1;
109             defaultOffset += offset;
110             buf.append("<TABLE BORDER=1><TR>");
111             // Print switch indices in first row (and default)
112             jumpTable = new int[high - low + 1];
113             for (int i = 0; i < jumpTable.length; i++) {
114                 jumpTable[i] = offset + bytes.readInt();
115                 buf.append("<TH>").append(low + i).append("</TH>");
116             }
117             buf.append("<TH>default</TH></TR>\n<TR>");
118             // Print target and default indices in second row
119             for (final int element : jumpTable) {
120                 buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(element).append("\">").append(element).append("</A></TD>");
121             }
122             buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(defaultOffset).append("\">").append(defaultOffset)
123                 .append("</A></TD></TR>\n</TABLE>\n");
124             break;
125         /*
126          * Lookup switch has variable length arguments.
127          */
128         case Const.LOOKUPSWITCH:
129             final int npairs = bytes.readInt();
130             offset = bytes.getIndex() - 8 - noPadBytes - 1;
131             jumpTable = new int[npairs];
132             defaultOffset += offset;
133             buf.append("<TABLE BORDER=1><TR>");
134             // Print switch indices in first row (and default)
135             for (int i = 0; i < npairs; i++) {
136                 final int match = bytes.readInt();
137                 jumpTable[i] = offset + bytes.readInt();
138                 buf.append("<TH>").append(match).append("</TH>");
139             }
140             buf.append("<TH>default</TH></TR>\n<TR>");
141             // Print target and default indices in second row
142             for (int i = 0; i < npairs; i++) {
143                 buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(jumpTable[i]).append("\">").append(jumpTable[i])
144                     .append("</A></TD>");
145             }
146             buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(defaultOffset).append("\">").append(defaultOffset)
147                 .append("</A></TD></TR>\n</TABLE>\n");
148             break;
149         /*
150          * Two address bytes + offset from start of byte stream form the jump target.
151          */
152         case Const.GOTO:
153         case Const.IFEQ:
154         case Const.IFGE:
155         case Const.IFGT:
156         case Const.IFLE:
157         case Const.IFLT:
158         case Const.IFNE:
159         case Const.IFNONNULL:
160         case Const.IFNULL:
161         case Const.IF_ACMPEQ:
162         case Const.IF_ACMPNE:
163         case Const.IF_ICMPEQ:
164         case Const.IF_ICMPGE:
165         case Const.IF_ICMPGT:
166         case Const.IF_ICMPLE:
167         case Const.IF_ICMPLT:
168         case Const.IF_ICMPNE:
169         case Const.JSR:
170             index = bytes.getIndex() + bytes.readShort() - 1;
171             buf.append("<A HREF=\"#code").append(methodNumber).append("@").append(index).append("\">").append(index).append("</A>");
172             break;
173         /*
174          * Same for 32-bit wide jumps
175          */
176         case Const.GOTO_W:
177         case Const.JSR_W:
178             final int windex = bytes.getIndex() + bytes.readInt() - 1;
179             buf.append("<A HREF=\"#code").append(methodNumber).append("@").append(windex).append("\">").append(windex).append("</A>");
180             break;
181         /*
182          * Index byte references local variable (register)
183          */
184         case Const.ALOAD:
185         case Const.ASTORE:
186         case Const.DLOAD:
187         case Const.DSTORE:
188         case Const.FLOAD:
189         case Const.FSTORE:
190         case Const.ILOAD:
191         case Const.ISTORE:
192         case Const.LLOAD:
193         case Const.LSTORE:
194         case Const.RET:
195             if (wide) {
196                 vindex = bytes.readShort();
197                 wide = false; // Clear flag
198             } else {
199                 vindex = bytes.readUnsignedByte();
200             }
201             buf.append("%").append(vindex);
202             break;
203         /*
204          * Remember wide byte which is used to form a 16-bit address in the following instruction. Relies on that the method is
205          * called again with the following opcode.
206          */
207         case Const.WIDE:
208             wide = true;
209             buf.append("(wide)");
210             break;
211         /*
212          * Array of basic type.
213          */
214         case Const.NEWARRAY:
215             buf.append("<FONT COLOR=\"#00FF00\">").append(Const.getTypeName(bytes.readByte())).append("</FONT>");
216             break;
217         /*
218          * Access object/class fields.
219          */
220         case Const.GETFIELD:
221         case Const.GETSTATIC:
222         case Const.PUTFIELD:
223         case Const.PUTSTATIC:
224             index = bytes.readShort();
225             final ConstantFieldref c1 = constantPool.getConstant(index, Const.CONSTANT_Fieldref, ConstantFieldref.class);
226             classIndex = c1.getClassIndex();
227             name = constantPool.getConstantString(classIndex, Const.CONSTANT_Class);
228             name = Utility.compactClassName(name, false);
229             index = c1.getNameAndTypeIndex();
230             final String fieldName = constantPool.constantToString(index, Const.CONSTANT_NameAndType);
231             if (name.equals(className)) { // Local field
232                 buf.append("<A HREF=\"").append(className).append("_methods.html#field").append(fieldName).append("\" TARGET=Methods>").append(fieldName)
233                     .append("</A>\n");
234             } else {
235                 buf.append(constantHtml.referenceConstant(classIndex)).append(".").append(fieldName);
236             }
237             break;
238         /*
239          * Operands are references to classes in constant pool
240          */
241         case Const.CHECKCAST:
242         case Const.INSTANCEOF:
243         case Const.NEW:
244             index = bytes.readShort();
245             buf.append(constantHtml.referenceConstant(index));
246             break;
247         /*
248          * Operands are references to methods in constant pool
249          */
250         case Const.INVOKESPECIAL:
251         case Const.INVOKESTATIC:
252         case Const.INVOKEVIRTUAL:
253         case Const.INVOKEINTERFACE:
254         case Const.INVOKEDYNAMIC:
255             final int mIndex = bytes.readShort();
256             String str;
257             if (opcode == Const.INVOKEINTERFACE) { // Special treatment needed
258                 bytes.readUnsignedByte(); // Redundant
259                 bytes.readUnsignedByte(); // Reserved
260 //                    int nargs = bytes.readUnsignedByte(); // Redundant
261 //                    int reserved = bytes.readUnsignedByte(); // Reserved
262                 final ConstantInterfaceMethodref c = constantPool.getConstant(mIndex, Const.CONSTANT_InterfaceMethodref, ConstantInterfaceMethodref.class);
263                 classIndex = c.getClassIndex();
264                 index = c.getNameAndTypeIndex();
265                 name = Class2HTML.referenceClass(classIndex);
266             } else if (opcode == Const.INVOKEDYNAMIC) { // Special treatment needed
267                 bytes.readUnsignedByte(); // Reserved
268                 bytes.readUnsignedByte(); // Reserved
269                 final ConstantInvokeDynamic c = constantPool.getConstant(mIndex, Const.CONSTANT_InvokeDynamic, ConstantInvokeDynamic.class);
270                 index = c.getNameAndTypeIndex();
271                 name = "#" + c.getBootstrapMethodAttrIndex();
272             } else {
273                 // UNDONE: Java8 now allows INVOKESPECIAL and INVOKESTATIC to
274                 // reference EITHER a Methodref OR an InterfaceMethodref.
275                 // Not sure if that affects this code or not. (markro)
276                 final ConstantMethodref c = constantPool.getConstant(mIndex, Const.CONSTANT_Methodref, ConstantMethodref.class);
277                 classIndex = c.getClassIndex();
278                 index = c.getNameAndTypeIndex();
279                 name = Class2HTML.referenceClass(classIndex);
280             }
281             str = Class2HTML.toHTML(constantPool.constantToString(constantPool.getConstant(index, Const.CONSTANT_NameAndType)));
282             // Get signature, i.e., types
283             final ConstantNameAndType c2 = constantPool.getConstant(index, Const.CONSTANT_NameAndType, ConstantNameAndType.class);
284             signature = constantPool.constantToString(c2.getSignatureIndex(), Const.CONSTANT_Utf8);
285             final String[] args = Utility.methodSignatureArgumentTypes(signature, false);
286             final String type = Utility.methodSignatureReturnType(signature, false);
287             buf.append(name).append(".<A HREF=\"").append(className).append("_cp.html#cp").append(mIndex).append("\" TARGET=ConstantPool>").append(str)
288                 .append("</A>").append("(");
289             // List arguments
290             for (int i = 0; i < args.length; i++) {
291                 buf.append(Class2HTML.referenceType(args[i]));
292                 if (i < args.length - 1) {
293                     buf.append(", ");
294                 }
295             }
296             // Attach return type
297             buf.append("):").append(Class2HTML.referenceType(type));
298             break;
299         /*
300          * Operands are references to items in constant pool
301          */
302         case Const.LDC_W:
303         case Const.LDC2_W:
304             index = bytes.readShort();
305             buf.append("<A HREF=\"").append(className).append("_cp.html#cp").append(index).append("\" TARGET=\"ConstantPool\">")
306                 .append(Class2HTML.toHTML(constantPool.constantToString(index, constantPool.getConstant(index).getTag()))).append("</a>");
307             break;
308         case Const.LDC:
309             index = bytes.readUnsignedByte();
310             buf.append("<A HREF=\"").append(className).append("_cp.html#cp").append(index).append("\" TARGET=\"ConstantPool\">")
311                 .append(Class2HTML.toHTML(constantPool.constantToString(index, constantPool.getConstant(index).getTag()))).append("</a>");
312             break;
313         /*
314          * Array of references.
315          */
316         case Const.ANEWARRAY:
317             index = bytes.readShort();
318             buf.append(constantHtml.referenceConstant(index));
319             break;
320         /*
321          * Multidimensional array of references.
322          */
323         case Const.MULTIANEWARRAY:
324             index = bytes.readShort();
325             final int dimensions = bytes.readByte();
326             buf.append(constantHtml.referenceConstant(index)).append(":").append(dimensions).append("-dimensional");
327             break;
328         /*
329          * Increment local variable.
330          */
331         case Const.IINC:
332             if (wide) {
333                 vindex = bytes.readShort();
334                 constant = bytes.readShort();
335                 wide = false;
336             } else {
337                 vindex = bytes.readUnsignedByte();
338                 constant = bytes.readByte();
339             }
340             buf.append("%").append(vindex).append(" ").append(constant);
341             break;
342         default:
343             if (Const.getNoOfOperands(opcode) > 0) {
344                 for (int i = 0; i < Const.getOperandTypeCount(opcode); i++) {
345                     switch (Const.getOperandType(opcode, i)) {
346                     case Const.T_BYTE:
347                         buf.append(bytes.readUnsignedByte());
348                         break;
349                     case Const.T_SHORT: // Either branch or index
350                         buf.append(bytes.readShort());
351                         break;
352                     case Const.T_INT:
353                         buf.append(bytes.readInt());
354                         break;
355                     default: // Never reached
356                         throw new IllegalStateException("Unreachable default case reached! " + Const.getOperandType(opcode, i));
357                     }
358                     buf.append("&nbsp;");
359                 }
360             }
361         }
362         buf.append("</TD>");
363         return buf.toString();
364     }
365 
366     /**
367      * Find all target addresses in code, so that they can be marked with &lt;A NAME = ...&gt;. Target addresses are kept in
368      * an BitSet object.
369      */
370     private void findGotos(final ByteSequence bytes, final Code code) throws IOException {
371         int index;
372         gotoSet = new BitSet(bytes.available());
373         int opcode;
374         /*
375          * First get Code attribute from method and the exceptions handled (try .. catch) in this method. We only need the line
376          * number here.
377          */
378         if (code != null) {
379             final CodeException[] ce = code.getExceptionTable();
380             for (final CodeException cex : ce) {
381                 gotoSet.set(cex.getStartPC());
382                 gotoSet.set(cex.getEndPC());
383                 gotoSet.set(cex.getHandlerPC());
384             }
385             // Look for local variables and their range
386             final Attribute[] attributes = code.getAttributes();
387             for (final Attribute attribute : attributes) {
388                 if (attribute.getTag() == Const.ATTR_LOCAL_VARIABLE_TABLE) {
389                     ((LocalVariableTable) attribute).forEach(var -> {
390                         final int start = var.getStartPC();
391                         gotoSet.set(start);
392                         gotoSet.set(start + var.getLength());
393                     });
394                     break;
395                 }
396             }
397         }
398         // Get target addresses from GOTO, JSR, TABLESWITCH, etc.
399         while (bytes.available() > 0) {
400             opcode = bytes.readUnsignedByte();
401             // System.out.println(getOpcodeName(opcode));
402             switch (opcode) {
403             case Const.TABLESWITCH:
404             case Const.LOOKUPSWITCH:
405                 // bytes.readByte(); // Skip already read byte
406                 final int remainder = bytes.getIndex() % 4;
407                 final int noPadBytes = remainder == 0 ? 0 : 4 - remainder;
408                 int defaultOffset;
409                 int offset;
410                 for (int j = 0; j < noPadBytes; j++) {
411                     bytes.readByte();
412                 }
413                 // Both cases have a field default_offset in common
414                 defaultOffset = bytes.readInt();
415                 if (opcode == Const.TABLESWITCH) {
416                     final int low = bytes.readInt();
417                     final int high = bytes.readInt();
418                     offset = bytes.getIndex() - 12 - noPadBytes - 1;
419                     defaultOffset += offset;
420                     gotoSet.set(defaultOffset);
421                     for (int j = 0; j < high - low + 1; j++) {
422                         index = offset + bytes.readInt();
423                         gotoSet.set(index);
424                     }
425                 } else { // LOOKUPSWITCH
426                     final int npairs = bytes.readInt();
427                     offset = bytes.getIndex() - 8 - noPadBytes - 1;
428                     defaultOffset += offset;
429                     gotoSet.set(defaultOffset);
430                     for (int j = 0; j < npairs; j++) {
431 //                            int match = bytes.readInt();
432                         bytes.readInt();
433                         index = offset + bytes.readInt();
434                         gotoSet.set(index);
435                     }
436                 }
437                 break;
438             case Const.GOTO:
439             case Const.IFEQ:
440             case Const.IFGE:
441             case Const.IFGT:
442             case Const.IFLE:
443             case Const.IFLT:
444             case Const.IFNE:
445             case Const.IFNONNULL:
446             case Const.IFNULL:
447             case Const.IF_ACMPEQ:
448             case Const.IF_ACMPNE:
449             case Const.IF_ICMPEQ:
450             case Const.IF_ICMPGE:
451             case Const.IF_ICMPGT:
452             case Const.IF_ICMPLE:
453             case Const.IF_ICMPLT:
454             case Const.IF_ICMPNE:
455             case Const.JSR:
456                 // bytes.readByte(); // Skip already read byte
457                 index = bytes.getIndex() + bytes.readShort() - 1;
458                 gotoSet.set(index);
459                 break;
460             case Const.GOTO_W:
461             case Const.JSR_W:
462                 // bytes.readByte(); // Skip already read byte
463                 index = bytes.getIndex() + bytes.readInt() - 1;
464                 gotoSet.set(index);
465                 break;
466             default:
467                 bytes.unreadByte();
468                 codeToHTML(bytes, 0); // Ignore output
469             }
470         }
471     }
472 
473     /**
474      * Write a single method with the byte code associated with it.
475      */
476     private void writeMethod(final Method method, final int methodNumber) throws IOException {
477         // Get raw signature
478         final String signature = method.getSignature();
479         // Get array of strings containing the argument types
480         final String[] args = Utility.methodSignatureArgumentTypes(signature, false);
481         // Get return type string
482         final String type = Utility.methodSignatureReturnType(signature, false);
483         // Get method name
484         final String name = method.getName();
485         final String htmlName = Class2HTML.toHTML(name);
486         // Get method's access flags
487         String access = Utility.accessToString(method.getAccessFlags());
488         access = Utility.replace(access, " ", "&nbsp;");
489         // Get the method's attributes, the Code Attribute in particular
490         final Attribute[] attributes = method.getAttributes();
491         printWriter.print("<P><B><FONT COLOR=\"#FF0000\">" + access + "</FONT>&nbsp;" + "<A NAME=method" + methodNumber + ">" + Class2HTML.referenceType(type)
492             + "</A>&nbsp<A HREF=\"" + className + "_methods.html#method" + methodNumber + "\" TARGET=Methods>" + htmlName + "</A>(");
493         for (int i = 0; i < args.length; i++) {
494             printWriter.print(Class2HTML.referenceType(args[i]));
495             if (i < args.length - 1) {
496                 printWriter.print(",&nbsp;");
497             }
498         }
499         printWriter.println(")</B></P>");
500         Code c = null;
501         byte[] code = null;
502         if (attributes.length > 0) {
503             printWriter.print("<H4>Attributes</H4><UL>\n");
504             for (int i = 0; i < attributes.length; i++) {
505                 byte tag = attributes[i].getTag();
506                 if (tag != Const.ATTR_UNKNOWN) {
507                     printWriter.print("<LI><A HREF=\"" + className + "_attributes.html#method" + methodNumber + "@" + i + "\" TARGET=Attributes>"
508                         + Const.getAttributeName(tag) + "</A></LI>\n");
509                 } else {
510                     printWriter.print("<LI>" + attributes[i] + "</LI>");
511                 }
512                 if (tag == Const.ATTR_CODE) {
513                     c = (Code) attributes[i];
514                     final Attribute[] attributes2 = c.getAttributes();
515                     code = c.getCode();
516                     printWriter.print("<UL>");
517                     for (int j = 0; j < attributes2.length; j++) {
518                         tag = attributes2[j].getTag();
519                         printWriter.print("<LI><A HREF=\"" + className + "_attributes.html#" + "method" + methodNumber + "@" + i + "@" + j
520                             + "\" TARGET=Attributes>" + Const.getAttributeName(tag) + "</A></LI>\n");
521                     }
522                     printWriter.print("</UL>");
523                 }
524             }
525             printWriter.println("</UL>");
526         }
527         if (code != null) { // No code, an abstract method, e.g.
528             // System.out.println(name + "\n" + Utility.codeToString(code, constantPool, 0, -1));
529             // Print the byte code
530             try (ByteSequence stream = new ByteSequence(code)) {
531                 stream.mark(stream.available());
532                 findGotos(stream, c);
533                 stream.reset();
534                 printWriter.println("<TABLE BORDER=0><TR><TH ALIGN=LEFT>Byte<BR>offset</TH>" + "<TH ALIGN=LEFT>Instruction</TH><TH ALIGN=LEFT>Argument</TH>");
535                 while (stream.available() > 0) {
536                     final int offset = stream.getIndex();
537                     final String str = codeToHTML(stream, methodNumber);
538                     String anchor = "";
539                     /*
540                      * Sets an anchor mark if this line is targetted by a goto, jsr, etc. Defining an anchor for every line is very
541                      * inefficient!
542                      */
543                     if (gotoSet.get(offset)) {
544                         anchor = "<A NAME=code" + methodNumber + "@" + offset + "></A>";
545                     }
546                     String anchor2;
547                     if (stream.getIndex() == code.length) {
548                         anchor2 = "<A NAME=code" + methodNumber + "@" + code.length + ">" + offset + "</A>";
549                     } else {
550                         anchor2 = "" + offset;
551                     }
552                     printWriter.println("<TR VALIGN=TOP><TD>" + anchor2 + "</TD><TD>" + anchor + str + "</TR>");
553                 }
554             }
555             // Mark last line, may be targetted from Attributes window
556             printWriter.println("<TR><TD> </A></TD></TR>");
557             printWriter.println("</TABLE>");
558         }
559     }
560 }