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