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