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