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