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(" "); 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 <A NAME = ...>. 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, " ", " "); 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> " + "<A NAME=method" 510 + method_number + ">" + Class2HTML.referenceType(type) + "</A> <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(", "); 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