View Javadoc

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