1 16 17 package org.cojen.classfile; 18 19 import java.io.PrintWriter ; 20 import java.util.ArrayList ; 21 import java.util.Arrays ; 22 import java.util.Comparator ; 23 import java.util.Date ; 24 import java.util.HashMap ; 25 import java.util.Iterator ; 26 import java.util.List ; 27 import java.util.Map ; 28 29 import org.cojen.classfile.attribute.Annotation; 30 import org.cojen.classfile.attribute.CodeAttr; 31 import org.cojen.classfile.attribute.SignatureAttr; 32 import org.cojen.classfile.constant.ConstantClassInfo; 33 import org.cojen.classfile.constant.ConstantDoubleInfo; 34 import org.cojen.classfile.constant.ConstantFieldInfo; 35 import org.cojen.classfile.constant.ConstantFloatInfo; 36 import org.cojen.classfile.constant.ConstantIntegerInfo; 37 import org.cojen.classfile.constant.ConstantInterfaceMethodInfo; 38 import org.cojen.classfile.constant.ConstantLongInfo; 39 import org.cojen.classfile.constant.ConstantMethodInfo; 40 import org.cojen.classfile.constant.ConstantNameAndTypeInfo; 41 import org.cojen.classfile.constant.ConstantStringInfo; 42 import org.cojen.classfile.constant.ConstantUTFInfo; 43 44 49 class AssemblyStylePrinter implements DisassemblyTool.Printer { 50 private ClassFile mClassFile; 51 private ConstantPool mCp; 52 private PrintWriter mOut; 53 54 private byte[] mByteCodes; 55 private int mAddress; 57 58 private Map mLabels; 60 61 private ExceptionHandler[] mExceptionHandlers; 62 63 private Map mCatchLocations; 65 66 public AssemblyStylePrinter() { 67 } 68 69 public void disassemble(ClassFile cf, PrintWriter out) { 70 disassemble(cf, out, ""); 71 } 72 73 private void disassemble(ClassFile cf, PrintWriter out, String indent) { 74 mClassFile = cf; 75 mCp = cf.getConstantPool(); 76 mOut = out; 77 78 if (indent.length() == 0 || mClassFile.getSourceFile() != null || 79 mClassFile.isDeprecated() || mClassFile.isSynthetic()) { 80 81 println(indent, "/**"); 82 83 boolean addBreak = false; 84 85 if (indent.length() == 0) { 86 print(indent, " * Disassembled on "); 87 print(new Date ()); 88 println("."); 89 addBreak = true; 90 } 91 92 if (indent.length() == 0 && mClassFile.getTarget() != null) { 93 if (addBreak) { 94 println(indent, " * "); 95 addBreak = false; 96 } 97 print(indent, " * @target "); 98 println(CodeAssemblerPrinter.escape(mClassFile.getTarget())); 99 } 100 101 if (mClassFile.getSourceFile() != null) { 102 if (addBreak) { 103 println(indent, " * "); 104 addBreak = false; 105 } 106 print(indent, " * @source "); 107 println(CodeAssemblerPrinter.escape(mClassFile.getSourceFile())); 108 } 109 110 if (mClassFile.isInnerClass()) { 111 if (addBreak) { 112 println(indent, " * "); 113 addBreak = false; 114 } 115 if (mClassFile.getInnerClassName() == null) { 116 println(indent, " * @anonymous"); 117 } else { 118 print(indent, " * @name "); 119 println(CodeAssemblerPrinter.escape(mClassFile.getInnerClassName())); 120 } 121 } 122 123 if (mClassFile.isDeprecated()) { 124 if (addBreak) { 125 println(indent, " * "); 126 addBreak = false; 127 } 128 println(indent, " * @deprecated"); 129 } 130 131 if (mClassFile.isSynthetic()) { 132 if (addBreak) { 133 println(indent, " * "); 134 addBreak = false; 135 } 136 println(indent, " * @synthetic"); 137 } 138 139 SignatureAttr sig = mClassFile.getSignatureAttr(); 141 if (sig != null) { 142 if (addBreak) { 143 println(indent, " * "); 144 addBreak = false; 145 } 146 println(indent, " * @signature " + sig.getSignature().getValue()); 147 } 148 149 println(indent, " */"); 150 } 151 152 disassemble(indent, mClassFile.getRuntimeVisibleAnnotations()); 153 disassemble(indent, mClassFile.getRuntimeInvisibleAnnotations()); 154 155 print(indent); 156 157 disassemble(mClassFile.getModifiers()); 158 boolean isInterface = mClassFile.getModifiers().isInterface(); 159 if (!isInterface) { 160 print("class "); 161 } 162 print(mClassFile.getClassName()); 163 164 if (mClassFile.getSuperClassName() != null) { 165 print(" extends "); 166 print(mClassFile.getSuperClassName()); 167 } 168 169 String innerIndent = indent + " "; 170 171 String [] interfaces = mClassFile.getInterfaces(); 172 if (interfaces.length == 0) { 173 println(" {"); 174 } else { 175 println(); 176 for (int i=0; i<interfaces.length; i++) { 177 if (i == 0) { 178 print(innerIndent, "implements "); 179 } else { 180 println(","); 181 print(innerIndent, " "); 182 } 183 print(interfaces[i]); 184 } 185 println(); 186 println(indent, "{"); 187 } 188 189 FieldInfo[] fields = mClassFile.getFields(); 190 MethodInfo[] methods = mClassFile.getMethods(); 191 MethodInfo[] ctors = mClassFile.getConstructors(); 192 MethodInfo init = mClassFile.getInitializer(); 193 194 Object [] members = new Object [fields.length + methods.length + 195 ctors.length + ((init == null) ? 0 : 1)]; 196 197 int m = 0; 198 199 for (int i=0; i<fields.length; i++) { 200 members[m++] = fields[i]; 201 } 202 203 for (int i=0; i<methods.length; i++) { 204 members[m++] = methods[i]; 205 } 206 207 for (int i=0; i<ctors.length; i++) { 208 members[m++] = ctors[i]; 209 } 210 211 if (init != null) { 212 members[m++] = init; 213 } 214 215 sortMembers(members); 216 217 for (int i=0; i<members.length; i++) { 218 if (i > 0) { 219 println(); 220 } 221 if (members[i] instanceof FieldInfo) { 222 disassemble(innerIndent, (FieldInfo)members[i]); 223 } else { 224 disassemble(innerIndent, (MethodInfo)members[i]); 225 } 226 } 227 228 mByteCodes = null; 229 mLabels = null; 230 mExceptionHandlers = null; 231 mCatchLocations = null; 232 233 ClassFile[] innerClasses = mClassFile.getInnerClasses(); 234 235 for (int i=0; i<innerClasses.length; i++) { 236 if (i > 0 || members.length > 0) { 237 println(); 238 } 239 AssemblyStylePrinter printer = new AssemblyStylePrinter(); 240 printer.disassemble(innerClasses[i], mOut, innerIndent); 241 } 242 243 println(indent, "}"); 244 245 mOut.flush(); 246 mOut = null; 247 } 248 249 private void disassemble(String indent, FieldInfo field) { 250 SignatureAttr sig = field.getSignatureAttr(); 251 if (field.isDeprecated() || field.isSynthetic() || sig != null) { 252 println(indent, "/**"); 253 if (field.isDeprecated()) { 254 println(indent, " * @deprecated"); 255 } 256 if (field.isSynthetic()) { 257 println(indent, " * @synthetic"); 258 } 259 if (sig != null) { 260 println(indent, " * @signature " + sig.getSignature().getValue()); 261 } 262 println(indent, " */"); 263 } 264 265 disassemble(indent, field.getRuntimeVisibleAnnotations()); 266 disassemble(indent, field.getRuntimeInvisibleAnnotations()); 267 268 print(indent); 269 disassemble(field.getModifiers()); 270 disassemble(field.getType()); 271 print(" "); 272 print(field.getName()); 273 ConstantInfo constant = field.getConstantValue(); 274 if (constant != null) { 275 print(" = "); 276 disassemble(constant); 277 } 278 println(";"); 279 } 280 281 private void disassemble(String indent, MethodInfo method) { 282 SignatureAttr sig = method.getSignatureAttr(); 283 if (method.isDeprecated() || method.isSynthetic() || sig != null) { 284 println(indent, "/**"); 285 if (method.isDeprecated()) { 286 println(indent, " * @deprecated"); 287 } 288 if (method.isSynthetic()) { 289 println(indent, " * @synthetic"); 290 } 291 if (sig != null) { 292 println(indent, " * @signature " + sig.getSignature().getValue()); 293 } 294 println(indent, " */"); 295 } 296 297 disassemble(indent, method.getRuntimeVisibleAnnotations()); 298 disassemble(indent, method.getRuntimeInvisibleAnnotations()); 299 300 print(indent); 301 302 MethodDesc md = method.getMethodDescriptor(); 303 304 if ("<clinit>".equals(method.getName()) && 305 md.getReturnType() == TypeDesc.VOID && 306 md.getParameterCount() == 0 && 307 (method.getModifiers().isStatic()) && 308 (!method.getModifiers().isAbstract()) && 309 method.getExceptions().length == 0) { 310 311 print("static"); 313 } else { 314 Modifiers modifiers = method.getModifiers(); 315 boolean varargs = modifiers.isVarArgs(); 316 if (varargs) { 317 modifiers = modifiers.toVarArgs(false); 319 } 320 disassemble(modifiers); 321 print(md.toMethodSignature(method.getName(), varargs)); 322 } 323 324 CodeAttr code = method.getCodeAttr(); 325 326 TypeDesc[] exceptions = method.getExceptions(); 327 if (exceptions.length == 0) { 328 if (code == null) { 329 println(";"); 330 } else { 331 println(" {"); 332 } 333 } else { 334 println(); 335 for (int i=0; i<exceptions.length; i++) { 336 if (i == 0) { 337 print(indent + " ", "throws "); 338 } else { 339 println(","); 340 print(indent + " ", " "); 341 } 342 print(exceptions[i].getFullName()); 343 } 344 if (code == null) { 345 println(";"); 346 } else { 347 println(); 348 println(indent, "{"); 349 } 350 } 351 352 if (code != null) { 353 disassemble(indent + " ", code); 354 println(indent, "}"); 355 } 356 } 357 358 private void disassemble(Modifiers modifiers) { 359 print(modifiers); 360 if (modifiers.getBitmask() != 0) { 361 print(" "); 362 } 363 } 364 365 private void disassemble(ConstantInfo constant) { 366 disassemble(constant, false); 367 } 368 369 private void disassemble(ConstantInfo constant, boolean showClassLiteral) { 370 if (constant instanceof ConstantStringInfo) { 371 print("\""); 372 String value = ((ConstantStringInfo)constant).getValue(); 373 print(CodeAssemblerPrinter.escape(value)); 374 print("\""); 375 } else if (constant instanceof ConstantIntegerInfo) { 376 print(String.valueOf(((ConstantIntegerInfo)constant).getValue())); 377 } else if (constant instanceof ConstantLongInfo) { 378 print(String.valueOf(((ConstantLongInfo)constant).getValue())); 379 print("L"); 380 } else if (constant instanceof ConstantFloatInfo) { 381 float value = ((ConstantFloatInfo)constant).getValue(); 382 if (value != value) { 383 print("0.0f/0.0f"); 384 } else if (value == Float.NEGATIVE_INFINITY) { 385 print("-1.0f/0.0f"); 386 } else if (value == Float.POSITIVE_INFINITY) { 387 print("1.0f/0.0f"); 388 } else { 389 print(String.valueOf(value)); 390 print("f"); 391 } 392 } else if (constant instanceof ConstantDoubleInfo) { 393 double value = ((ConstantDoubleInfo)constant).getValue(); 394 if (value != value) { 395 print("0.0d/0.0d"); 396 } else if (value == Float.NEGATIVE_INFINITY) { 397 print("-1.0d/0.0d"); 398 } else if (value == Float.POSITIVE_INFINITY) { 399 print("1.0d/0.0d"); 400 } else { 401 print(String.valueOf(value)); 402 print("d"); 403 } 404 } else if (constant instanceof ConstantClassInfo) { 405 ConstantClassInfo cci = (ConstantClassInfo)constant; 406 disassemble(cci.getType()); 407 if (showClassLiteral) { 408 print(".class"); 409 } 410 } else if (constant instanceof ConstantUTFInfo) { 411 print("\""); 412 String value = ((ConstantUTFInfo)constant).getValue(); 413 print(CodeAssemblerPrinter.escape(value)); 414 print("\""); 415 } else { 416 print(constant); 417 } 418 } 419 420 private void disassemble(TypeDesc type) { 421 print(type.getFullName()); 422 } 423 424 private void disassemble(String indent, Annotation[] annotations) { 425 for (int i=0; i<annotations.length; i++) { 426 print(indent); 427 disassemble(indent, annotations[i]); 428 println(); 429 } 430 } 431 432 private void disassemble(String indent, Annotation ann) { 433 print("@"); 434 print(ann.getType().getFullName()); 435 Map mvMap = ann.getMemberValues(); 436 if (mvMap.size() == 0) { 437 return; 438 } 439 print("("); 440 Iterator it = mvMap.entrySet().iterator(); 441 int ordinal = 0; 442 while (it.hasNext()) { 443 if (ordinal++ > 0) { 444 print(", "); 445 } 446 Map.Entry entry = (Map.Entry )it.next(); 447 String name = (String )entry.getKey(); 448 if (!"value".equals(name)) { 449 print(name); 450 print("="); 451 } 452 disassemble(indent, (Annotation.MemberValue)entry.getValue()); 453 } 454 print(")"); 455 } 456 457 private void disassemble(String indent, Annotation.MemberValue mv) { 458 Object value = mv.getValue(); 459 switch (mv.getTag()) { 460 default: 461 print("?"); 462 break; 463 case Annotation.MEMBER_TAG_BOOLEAN: 464 ConstantIntegerInfo ci = (ConstantIntegerInfo)value; 465 print(ci.getValue() == 0 ? "false" : "true"); 466 break; 467 case Annotation.MEMBER_TAG_BYTE: 468 case Annotation.MEMBER_TAG_SHORT: 469 case Annotation.MEMBER_TAG_INT: 470 case Annotation.MEMBER_TAG_LONG: 471 case Annotation.MEMBER_TAG_FLOAT: 472 case Annotation.MEMBER_TAG_DOUBLE: 473 case Annotation.MEMBER_TAG_STRING: 474 case Annotation.MEMBER_TAG_CLASS: 475 disassemble((ConstantInfo)value, true); 476 break; 477 case Annotation.MEMBER_TAG_CHAR: { 478 print("'"); 479 char c = (char) ((ConstantIntegerInfo)value).getValue(); 480 print(CodeAssemblerPrinter.escape(String.valueOf(c), true)); 481 print("'"); 482 break; 483 } 484 case Annotation.MEMBER_TAG_ENUM: 485 Annotation.EnumConstValue ecv = (Annotation.EnumConstValue)value; 486 print(TypeDesc.forDescriptor(ecv.getTypeName().getValue()).getFullName()); 487 print("."); 488 print(ecv.getConstName().getValue()); 489 break; 490 case Annotation.MEMBER_TAG_ANNOTATION: 491 disassemble(indent, (Annotation)value); 492 break; 493 case Annotation.MEMBER_TAG_ARRAY: 494 Annotation.MemberValue[] mvs = (Annotation.MemberValue[])value; 495 496 String originalIndent = indent; 497 boolean multiLine = false; 498 if (mvs.length > 0) { 499 if (mvs.length > 4 || 500 (mvs.length > 1 && mvs[0].getTag() == Annotation.MEMBER_TAG_ENUM) || 501 mvs[0].getTag() == Annotation.MEMBER_TAG_ARRAY || 502 mvs[0].getTag() == Annotation.MEMBER_TAG_ANNOTATION) { 503 504 multiLine = true; 505 indent = indent + " "; 506 } 507 } 508 509 if (multiLine || mvs.length != 1) { 510 print("{"); 511 if (multiLine) { 512 println(); 513 } 514 } 515 516 for (int j=0; j<mvs.length; j++) { 517 if (multiLine) { 518 print(indent); 519 } 520 disassemble(indent, mvs[j]); 521 if (j + 1 < mvs.length) { 522 print(","); 523 if (!multiLine) { 524 print(" "); 525 } 526 } 527 if (multiLine) { 528 println(); 529 } 530 } 531 532 indent = originalIndent; 533 534 if (multiLine || mvs.length != 1) { 535 if (multiLine) { 536 print(indent); 537 } 538 print("}"); 539 } 540 541 break; 542 } 543 } 544 545 private void disassemble(String indent, CodeAttr code) { 546 CodeBuffer buffer = code.getCodeBuffer(); 547 mExceptionHandlers = buffer.getExceptionHandlers(); 548 print(indent); 549 print("// max stack: "); 550 println(String.valueOf(buffer.getMaxStackDepth())); 551 print(indent); 552 print("// max locals: "); 553 println(String.valueOf(buffer.getMaxLocals())); 554 555 mByteCodes = buffer.getByteCodes(); 556 557 gatherLabels(); 558 559 Location currentLoc = new Location() { 560 public int getLocation() { 561 return mAddress; 562 } 563 564 public int compareTo(Object obj) { 565 if (this == obj) { 566 return 0; 567 } 568 Location other = (Location)obj; 569 570 int loca = getLocation(); 571 int locb = other.getLocation(); 572 573 if (loca < locb) { 574 return -1; 575 } else if (loca > locb) { 576 return 1; 577 } else { 578 return 0; 579 } 580 } 581 }; 582 583 int currentLine = -1; 584 585 for (mAddress = 0; mAddress < mByteCodes.length; mAddress++) { 586 int nextLine = code.getLineNumber(currentLoc); 587 if (nextLine != currentLine) { 588 if ((currentLine = nextLine) >= 0) { 589 println(indent, "// line " + currentLine); 590 } 591 } 592 593 locateLabel(indent); 595 596 byte opcode = mByteCodes[mAddress]; 597 598 String mnemonic; 599 try { 600 mnemonic = Opcode.getMnemonic(opcode); 601 } catch (IllegalArgumentException e) { 602 mnemonic = String.valueOf(opcode & 0xff); 603 } 604 605 print(indent, mnemonic); 606 607 switch (opcode) { 608 609 default: 610 break; 611 612 614 case Opcode.NOP: 615 case Opcode.BREAKPOINT: 616 case Opcode.ACONST_NULL: 617 case Opcode.ICONST_M1: 618 case Opcode.ICONST_0: 619 case Opcode.ICONST_1: 620 case Opcode.ICONST_2: 621 case Opcode.ICONST_3: 622 case Opcode.ICONST_4: 623 case Opcode.ICONST_5: 624 case Opcode.LCONST_0: 625 case Opcode.LCONST_1: 626 case Opcode.FCONST_0: 627 case Opcode.FCONST_1: 628 case Opcode.FCONST_2: 629 case Opcode.DCONST_0: 630 case Opcode.DCONST_1: 631 case Opcode.POP: 632 case Opcode.POP2: 633 case Opcode.DUP: 634 case Opcode.DUP_X1: 635 case Opcode.DUP_X2: 636 case Opcode.DUP2: 637 case Opcode.DUP2_X1: 638 case Opcode.DUP2_X2: 639 case Opcode.SWAP: 640 case Opcode.IADD: case Opcode.LADD: 641 case Opcode.FADD: case Opcode.DADD: 642 case Opcode.ISUB: case Opcode.LSUB: 643 case Opcode.FSUB: case Opcode.DSUB: 644 case Opcode.IMUL: case Opcode.LMUL: 645 case Opcode.FMUL: case Opcode.DMUL: 646 case Opcode.IDIV: case Opcode.LDIV: 647 case Opcode.FDIV: case Opcode.DDIV: 648 case Opcode.IREM: case Opcode.LREM: 649 case Opcode.FREM: case Opcode.DREM: 650 case Opcode.INEG: case Opcode.LNEG: 651 case Opcode.FNEG: case Opcode.DNEG: 652 case Opcode.ISHL: case Opcode.LSHL: 653 case Opcode.ISHR: case Opcode.LSHR: 654 case Opcode.IUSHR: case Opcode.LUSHR: 655 case Opcode.IAND: case Opcode.LAND: 656 case Opcode.IOR: case Opcode.LOR: 657 case Opcode.IXOR: case Opcode.LXOR: 658 case Opcode.FCMPL: case Opcode.DCMPL: 659 case Opcode.FCMPG: case Opcode.DCMPG: 660 case Opcode.LCMP: 661 case Opcode.I2L: 662 case Opcode.I2F: 663 case Opcode.I2D: 664 case Opcode.L2I: 665 case Opcode.L2F: 666 case Opcode.L2D: 667 case Opcode.F2I: 668 case Opcode.F2L: 669 case Opcode.F2D: 670 case Opcode.D2I: 671 case Opcode.D2L: 672 case Opcode.D2F: 673 case Opcode.I2B: 674 case Opcode.I2C: 675 case Opcode.I2S: 676 case Opcode.IRETURN: 677 case Opcode.LRETURN: 678 case Opcode.FRETURN: 679 case Opcode.DRETURN: 680 case Opcode.ARETURN: 681 case Opcode.RETURN: 682 case Opcode.IALOAD: 683 case Opcode.LALOAD: 684 case Opcode.FALOAD: 685 case Opcode.DALOAD: 686 case Opcode.AALOAD: 687 case Opcode.BALOAD: 688 case Opcode.CALOAD: 689 case Opcode.SALOAD: 690 case Opcode.IASTORE: 691 case Opcode.LASTORE: 692 case Opcode.FASTORE: 693 case Opcode.DASTORE: 694 case Opcode.AASTORE: 695 case Opcode.BASTORE: 696 case Opcode.CASTORE: 697 case Opcode.SASTORE: 698 case Opcode.ARRAYLENGTH: 699 case Opcode.ATHROW: 700 case Opcode.MONITORENTER: 701 case Opcode.MONITOREXIT: 702 case Opcode.ILOAD_0: case Opcode.ISTORE_0: 703 case Opcode.ILOAD_1: case Opcode.ISTORE_1: 704 case Opcode.ILOAD_2: case Opcode.ISTORE_2: 705 case Opcode.ILOAD_3: case Opcode.ISTORE_3: 706 case Opcode.LLOAD_0: case Opcode.LSTORE_0: 707 case Opcode.LLOAD_1: case Opcode.LSTORE_1: 708 case Opcode.LLOAD_2: case Opcode.LSTORE_2: 709 case Opcode.LLOAD_3: case Opcode.LSTORE_3: 710 case Opcode.FLOAD_0: case Opcode.FSTORE_0: 711 case Opcode.FLOAD_1: case Opcode.FSTORE_1: 712 case Opcode.FLOAD_2: case Opcode.FSTORE_2: 713 case Opcode.FLOAD_3: case Opcode.FSTORE_3: 714 case Opcode.DLOAD_0: case Opcode.DSTORE_0: 715 case Opcode.DLOAD_1: case Opcode.DSTORE_1: 716 case Opcode.DLOAD_2: case Opcode.DSTORE_2: 717 case Opcode.DLOAD_3: case Opcode.DSTORE_3: 718 case Opcode.ALOAD_0: case Opcode.ASTORE_0: 719 case Opcode.ALOAD_1: case Opcode.ASTORE_1: 720 case Opcode.ALOAD_2: case Opcode.ASTORE_2: 721 case Opcode.ALOAD_3: case Opcode.ASTORE_3: 722 println(); 723 continue; 724 725 } 727 728 print(" "); 730 731 int index; 732 ConstantInfo constant; 733 734 switch (opcode) { 735 default: 736 break; 737 738 740 case Opcode.LDC: 741 case Opcode.LDC_W: 742 case Opcode.LDC2_W: 743 switch (opcode) { 744 case Opcode.LDC: 745 index = readUnsignedByte(); 746 break; 747 case Opcode.LDC_W: 748 case Opcode.LDC2_W: 749 index = readUnsignedShort(); 750 break; 751 default: 752 index = 0; 753 break; 754 } 755 756 disassemble(getConstant(index), true); 757 break; 758 759 case Opcode.NEW: 760 case Opcode.ANEWARRAY: 761 case Opcode.CHECKCAST: 762 case Opcode.INSTANCEOF: 763 constant = getConstant(readUnsignedShort()); 764 if (constant instanceof ConstantClassInfo) { 765 disassemble(constant); 766 } else { 767 print(constant); 768 } 769 break; 770 case Opcode.MULTIANEWARRAY: 771 constant = getConstant(readUnsignedShort()); 772 int dims = readUnsignedByte(); 773 if (constant instanceof ConstantClassInfo) { 774 disassemble(constant); 775 } else { 776 print(constant); 777 } 778 print(" "); 779 print(String.valueOf(dims)); 780 break; 781 782 case Opcode.GETSTATIC: 783 case Opcode.PUTSTATIC: 784 case Opcode.GETFIELD: 785 case Opcode.PUTFIELD: 786 constant = getConstant(readUnsignedShort()); 787 if (constant instanceof ConstantFieldInfo) { 788 ConstantFieldInfo field = (ConstantFieldInfo)constant; 789 Descriptor type = field.getNameAndType().getType(); 790 if (type instanceof TypeDesc) { 791 disassemble((TypeDesc)type); 792 } else { 793 print(type); 794 } 795 print(" "); 796 print(field.getParentClass().getType().getFullName()); 797 print("."); 798 print(field.getNameAndType().getName()); 799 } else { 800 print(constant); 801 } 802 break; 803 804 case Opcode.INVOKEVIRTUAL: 805 case Opcode.INVOKESPECIAL: 806 case Opcode.INVOKESTATIC: 807 case Opcode.INVOKEINTERFACE: 808 constant = getConstant(readUnsignedShort()); 809 810 String className; 811 ConstantNameAndTypeInfo nameAndType; 812 813 if (opcode == Opcode.INVOKEINTERFACE) { 814 readShort(); 816 if (!(constant instanceof ConstantInterfaceMethodInfo)) { 817 print(constant); 818 break; 819 } 820 ConstantInterfaceMethodInfo method = 821 (ConstantInterfaceMethodInfo)constant; 822 className = 823 method.getParentClass().getType().getFullName(); 824 nameAndType = method.getNameAndType(); 825 } else { 826 if (!(constant instanceof ConstantMethodInfo)) { 827 print(constant); 828 break; 829 } 830 ConstantMethodInfo method = (ConstantMethodInfo)constant; 831 className = 832 method.getParentClass().getType().getFullName(); 833 nameAndType = method.getNameAndType(); 834 } 835 836 Descriptor type = nameAndType.getType(); 837 if (!(type instanceof MethodDesc)) { 838 print(type); 839 break; 840 } 841 disassemble(((MethodDesc)type).getReturnType()); 842 print(" "); 843 print(className); 844 print("."); 845 print(nameAndType.getName()); 846 847 print("("); 848 TypeDesc[] params = ((MethodDesc)type).getParameterTypes(); 849 for (int i=0; i<params.length; i++) { 850 if (i > 0) { 851 print(", "); 852 } 853 disassemble(params[i]); 854 } 855 print(")"); 856 break; 857 858 860 862 case Opcode.ILOAD: case Opcode.ISTORE: 863 case Opcode.LLOAD: case Opcode.LSTORE: 864 case Opcode.FLOAD: case Opcode.FSTORE: 865 case Opcode.DLOAD: case Opcode.DSTORE: 866 case Opcode.ALOAD: case Opcode.ASTORE: 867 case Opcode.RET: 868 print(String.valueOf(readUnsignedByte())); 869 break; 870 case Opcode.IINC: 871 print(String.valueOf(readUnsignedByte())); 872 print(" "); 873 int incValue = readByte(); 874 if (incValue >= 0) { 875 print("+"); 876 } 877 print(String.valueOf(incValue)); 878 break; 879 880 882 case Opcode.GOTO: 884 case Opcode.JSR: 885 case Opcode.IFNULL: 886 case Opcode.IFNONNULL: 887 case Opcode.IF_ACMPEQ: 888 case Opcode.IF_ACMPNE: 889 case Opcode.IFEQ: 890 case Opcode.IFNE: 891 case Opcode.IFLT: 892 case Opcode.IFGE: 893 case Opcode.IFGT: 894 case Opcode.IFLE: 895 case Opcode.IF_ICMPEQ: 896 case Opcode.IF_ICMPNE: 897 case Opcode.IF_ICMPLT: 898 case Opcode.IF_ICMPGE: 899 case Opcode.IF_ICMPGT: 900 case Opcode.IF_ICMPLE: 901 print(getLabel(mAddress + readShort())); 902 break; 903 case Opcode.GOTO_W: 904 case Opcode.JSR_W: 905 print(getLabel(mAddress + readInt())); 906 break; 907 908 910 case Opcode.BIPUSH: 912 int value = readByte(); 913 print(String.valueOf(value)); 914 printCharLiteral(value); 915 break; 916 case Opcode.SIPUSH: 917 value = readShort(); 918 print(String.valueOf(value)); 919 printCharLiteral(value); 920 break; 921 922 case Opcode.NEWARRAY: 923 int atype = readByte(); 924 switch (atype) { 925 case 4: print("boolean"); 927 break; 928 case 5: print("char"); 930 break; 931 case 6: print("float"); 933 break; 934 case 7: print("double"); 936 break; 937 case 8: print("byte"); 939 break; 940 case 9: print("short"); 942 break; 943 case 10: print("int"); 945 break; 946 case 11: print("long"); 948 break; 949 default: 950 print("T_" + atype); 951 break; 952 } 953 break; 954 955 case Opcode.TABLESWITCH: 956 case Opcode.LOOKUPSWITCH: 957 int opcodeAddress = mAddress; 958 while (((mAddress + 1) & 3) != 0) { 960 ++mAddress; 961 } 962 String defaultLocation = getLabel(opcodeAddress + readInt()); 963 int[] cases; 964 String [] locations; 965 966 if (opcode == Opcode.TABLESWITCH) { 967 int lowValue = readInt(); 968 int highValue = readInt(); 969 int caseCount = highValue - lowValue + 1; 970 print("// " + caseCount + " cases"); 971 try { 972 cases = new int[caseCount]; 973 } catch (NegativeArraySizeException e) { 974 break; 975 } 976 locations = new String [caseCount]; 977 for (int i=0; i<caseCount; i++) { 978 cases[i] = lowValue + i; 979 locations[i] = getLabel(opcodeAddress + readInt()); 980 } 981 } else { 982 int caseCount = readInt(); 983 print("// " + caseCount + " cases"); 984 try { 985 cases = new int[caseCount]; 986 } catch (NegativeArraySizeException e) { 987 break; 988 } 989 locations = new String [caseCount]; 990 for (int i=0; i<caseCount; i++) { 991 cases[i] = readInt(); 992 locations[i] = getLabel(opcodeAddress + readInt()); 993 } 994 } 995 996 println(); 997 998 print(indent, " default: goto "); 999 println(defaultLocation); 1000 1001 String prefix = indent + " " + "case "; 1002 for (int i=0; i<cases.length; i++) { 1003 print(prefix + cases[i]); 1004 print(": goto "); 1005 print(locations[i]); 1006 printCharLiteral(cases[i]); 1007 println(); 1008 } 1009 1010 break; 1011 1012 case Opcode.WIDE: 1013 opcode = mByteCodes[++mAddress]; 1014 print(Opcode.getMnemonic(opcode)); 1015 print(" "); 1016 1017 switch (opcode) { 1018 1019 default: 1020 break; 1021 1022 case Opcode.ILOAD: case Opcode.ISTORE: 1023 case Opcode.LLOAD: case Opcode.LSTORE: 1024 case Opcode.FLOAD: case Opcode.FSTORE: 1025 case Opcode.DLOAD: case Opcode.DSTORE: 1026 case Opcode.ALOAD: case Opcode.ASTORE: 1027 case Opcode.RET: 1028 print(String.valueOf(readUnsignedShort())); 1029 break; 1030 case Opcode.IINC: 1031 print(String.valueOf(readUnsignedShort())); 1032 print(" "); 1033 incValue = readShort(); 1034 if (incValue >= 0) { 1035 print("+"); 1036 } 1037 print(String.valueOf(incValue)); 1038 break; 1039 } 1040 1041 break; 1042 } 1044 println(); 1045 } } 1047 1048 private void gatherLabels() { 1049 mLabels = new HashMap (); 1050 mCatchLocations = new HashMap (mExceptionHandlers.length * 2 + 1); 1051 1052 for (int i = mExceptionHandlers.length - 1; i >= 0; i--) { 1054 ExceptionHandler handler = mExceptionHandlers[i]; 1055 createLabel(new Integer (handler.getStartLocation().getLocation())); 1056 createLabel(new Integer (handler.getEndLocation().getLocation())); 1057 Integer labelKey = 1058 new Integer (handler.getCatchLocation().getLocation()); 1059 createLabel(labelKey); 1060 List list = (List )mCatchLocations.get(labelKey); 1061 if (list == null) { 1062 list = new ArrayList (2); 1063 mCatchLocations.put(labelKey, list); 1064 } 1065 list.add(handler); 1066 } 1067 1068 for (mAddress = 0; mAddress < mByteCodes.length; mAddress++) { 1070 byte opcode = mByteCodes[mAddress]; 1071 1072 switch (opcode) { 1073 1074 default: 1075 break; 1076 1077 1079 case Opcode.GOTO: 1080 case Opcode.JSR: 1081 case Opcode.IFNULL: 1082 case Opcode.IFNONNULL: 1083 case Opcode.IF_ACMPEQ: 1084 case Opcode.IF_ACMPNE: 1085 case Opcode.IFEQ: 1086 case Opcode.IFNE: 1087 case Opcode.IFLT: 1088 case Opcode.IFGE: 1089 case Opcode.IFGT: 1090 case Opcode.IFLE: 1091 case Opcode.IF_ICMPEQ: 1092 case Opcode.IF_ICMPNE: 1093 case Opcode.IF_ICMPLT: 1094 case Opcode.IF_ICMPGE: 1095 case Opcode.IF_ICMPGT: 1096 case Opcode.IF_ICMPLE: 1097 createLabel(new Integer (mAddress + readShort())); 1098 break; 1099 1100 case Opcode.GOTO_W: 1101 case Opcode.JSR_W: 1102 createLabel(new Integer (mAddress + readInt())); 1103 break; 1104 1105 case Opcode.TABLESWITCH: 1106 case Opcode.LOOKUPSWITCH: 1107 int opcodeAddress = mAddress; 1108 while (((mAddress + 1) & 3) != 0) { 1110 ++mAddress; 1111 } 1112 1113 1115 1116 createLabel(new Integer (opcodeAddress + readInt())); 1117 1118 if (opcode == Opcode.TABLESWITCH) { 1119 int lowValue = readInt(); 1120 int highValue = readInt(); 1121 int caseCount = highValue - lowValue + 1; 1122 1123 for (int i=0; i<caseCount; i++) { 1124 createLabel(new Integer (opcodeAddress + readInt())); 1126 } 1127 } else { 1128 int caseCount = readInt(); 1129 1130 for (int i=0; i<caseCount; i++) { 1131 mAddress += 4; 1133 createLabel(new Integer (opcodeAddress + readInt())); 1135 } 1136 } 1137 break; 1138 1139 1142 1144 case Opcode.NOP: 1145 case Opcode.BREAKPOINT: 1146 case Opcode.ACONST_NULL: 1147 case Opcode.ICONST_M1: 1148 case Opcode.ICONST_0: 1149 case Opcode.ICONST_1: 1150 case Opcode.ICONST_2: 1151 case Opcode.ICONST_3: 1152 case Opcode.ICONST_4: 1153 case Opcode.ICONST_5: 1154 case Opcode.LCONST_0: 1155 case Opcode.LCONST_1: 1156 case Opcode.FCONST_0: 1157 case Opcode.FCONST_1: 1158 case Opcode.FCONST_2: 1159 case Opcode.DCONST_0: 1160 case Opcode.DCONST_1: 1161 case Opcode.POP: 1162 case Opcode.POP2: 1163 case Opcode.DUP: 1164 case Opcode.DUP_X1: 1165 case Opcode.DUP_X2: 1166 case Opcode.DUP2: 1167 case Opcode.DUP2_X1: 1168 case Opcode.DUP2_X2: 1169 case Opcode.SWAP: 1170 case Opcode.IADD: case Opcode.LADD: 1171 case Opcode.FADD: case Opcode.DADD: 1172 case Opcode.ISUB: case Opcode.LSUB: 1173 case Opcode.FSUB: case Opcode.DSUB: 1174 case Opcode.IMUL: case Opcode.LMUL: 1175 case Opcode.FMUL: case Opcode.DMUL: 1176 case Opcode.IDIV: case Opcode.LDIV: 1177 case Opcode.FDIV: case Opcode.DDIV: 1178 case Opcode.IREM: case Opcode.LREM: 1179 case Opcode.FREM: case Opcode.DREM: 1180 case Opcode.INEG: case Opcode.LNEG: 1181 case Opcode.FNEG: case Opcode.DNEG: 1182 case Opcode.ISHL: case Opcode.LSHL: 1183 case Opcode.ISHR: case Opcode.LSHR: 1184 case Opcode.IUSHR: case Opcode.LUSHR: 1185 case Opcode.IAND: case Opcode.LAND: 1186 case Opcode.IOR: case Opcode.LOR: 1187 case Opcode.IXOR: case Opcode.LXOR: 1188 case Opcode.FCMPL: case Opcode.DCMPL: 1189 case Opcode.FCMPG: case Opcode.DCMPG: 1190 case Opcode.LCMP: 1191 case Opcode.I2L: 1192 case Opcode.I2F: 1193 case Opcode.I2D: 1194 case Opcode.L2I: 1195 case Opcode.L2F: 1196 case Opcode.L2D: 1197 case Opcode.F2I: 1198 case Opcode.F2L: 1199 case Opcode.F2D: 1200 case Opcode.D2I: 1201 case Opcode.D2L: 1202 case Opcode.D2F: 1203 case Opcode.I2B: 1204 case Opcode.I2C: 1205 case Opcode.I2S: 1206 case Opcode.IRETURN: 1207 case Opcode.LRETURN: 1208 case Opcode.FRETURN: 1209 case Opcode.DRETURN: 1210 case Opcode.ARETURN: 1211 case Opcode.RETURN: 1212 case Opcode.IALOAD: 1213 case Opcode.LALOAD: 1214 case Opcode.FALOAD: 1215 case Opcode.DALOAD: 1216 case Opcode.AALOAD: 1217 case Opcode.BALOAD: 1218 case Opcode.CALOAD: 1219 case Opcode.SALOAD: 1220 case Opcode.IASTORE: 1221 case Opcode.LASTORE: 1222 case Opcode.FASTORE: 1223 case Opcode.DASTORE: 1224 case Opcode.AASTORE: 1225 case Opcode.BASTORE: 1226 case Opcode.CASTORE: 1227 case Opcode.SASTORE: 1228 case Opcode.ARRAYLENGTH: 1229 case Opcode.ATHROW: 1230 case Opcode.MONITORENTER: 1231 case Opcode.MONITOREXIT: 1232 case Opcode.ILOAD_0: case Opcode.ISTORE_0: 1233 case Opcode.ILOAD_1: case Opcode.ISTORE_1: 1234 case Opcode.ILOAD_2: case Opcode.ISTORE_2: 1235 case Opcode.ILOAD_3: case Opcode.ISTORE_3: 1236 case Opcode.LLOAD_0: case Opcode.LSTORE_0: 1237 case Opcode.LLOAD_1: case Opcode.LSTORE_1: 1238 case Opcode.LLOAD_2: case Opcode.LSTORE_2: 1239 case Opcode.LLOAD_3: case Opcode.LSTORE_3: 1240 case Opcode.FLOAD_0: case Opcode.FSTORE_0: 1241 case Opcode.FLOAD_1: case Opcode.FSTORE_1: 1242 case Opcode.FLOAD_2: case Opcode.FSTORE_2: 1243 case Opcode.FLOAD_3: case Opcode.FSTORE_3: 1244 case Opcode.DLOAD_0: case Opcode.DSTORE_0: 1245 case Opcode.DLOAD_1: case Opcode.DSTORE_1: 1246 case Opcode.DLOAD_2: case Opcode.DSTORE_2: 1247 case Opcode.DLOAD_3: case Opcode.DSTORE_3: 1248 case Opcode.ALOAD_0: case Opcode.ASTORE_0: 1249 case Opcode.ALOAD_1: case Opcode.ASTORE_1: 1250 case Opcode.ALOAD_2: case Opcode.ASTORE_2: 1251 case Opcode.ALOAD_3: case Opcode.ASTORE_3: 1252 break; 1253 1254 1256 case Opcode.LDC: 1257 case Opcode.ILOAD: case Opcode.ISTORE: 1258 case Opcode.LLOAD: case Opcode.LSTORE: 1259 case Opcode.FLOAD: case Opcode.FSTORE: 1260 case Opcode.DLOAD: case Opcode.DSTORE: 1261 case Opcode.ALOAD: case Opcode.ASTORE: 1262 case Opcode.RET: 1263 case Opcode.IINC: 1264 case Opcode.BIPUSH: 1265 case Opcode.NEWARRAY: 1266 mAddress += 1; 1267 break; 1268 1269 1271 case Opcode.LDC_W: 1272 case Opcode.LDC2_W: 1273 case Opcode.NEW: 1274 case Opcode.ANEWARRAY: 1275 case Opcode.CHECKCAST: 1276 case Opcode.INSTANCEOF: 1277 case Opcode.GETSTATIC: 1278 case Opcode.PUTSTATIC: 1279 case Opcode.GETFIELD: 1280 case Opcode.PUTFIELD: 1281 case Opcode.INVOKEVIRTUAL: 1282 case Opcode.INVOKESPECIAL: 1283 case Opcode.INVOKESTATIC: 1284 case Opcode.SIPUSH: 1285 mAddress += 2; 1286 break; 1287 1288 1290 case Opcode.MULTIANEWARRAY: 1291 mAddress += 3; 1292 break; 1293 1294 1296 case Opcode.INVOKEINTERFACE: 1297 mAddress += 4; 1298 break; 1299 1300 1302 case Opcode.WIDE: 1303 opcode = mByteCodes[++mAddress]; 1304 mAddress += 2; 1305 if (opcode == Opcode.IINC) { 1306 mAddress += 2; 1307 } 1308 break; 1309 } } 1312 Integer [] keys = new Integer [mLabels.size()]; 1313 mLabels.keySet().toArray(keys); 1314 Arrays.sort(keys); 1315 for (int i=0; i<keys.length; i++) { 1316 mLabels.put(keys[i], "L" + (i + 1) + '_' + keys[i]); 1317 } 1318 } 1319 1320 private void createLabel(Integer labelKey) { 1321 mLabels.put(labelKey, labelKey); 1322 } 1323 1324 private ConstantInfo getConstant(int index) { 1325 try { 1326 return mCp.getConstant(index); 1327 } catch (IndexOutOfBoundsException e) { 1328 return null; 1329 } 1330 } 1331 1332 private String getLabel(int address) { 1333 Object label = mLabels.get(new Integer (address)); 1334 if (label == null || (!(label instanceof String ))) { 1335 return "L?_" + address; 1336 } else { 1337 return (String )label; 1338 } 1339 } 1340 1341 private void locateLabel(String indent) { 1342 Integer labelKey = new Integer (mAddress); 1343 Object labelValue = mLabels.get(labelKey); 1344 1345 if (labelValue == null) { 1346 return; 1347 } 1348 1349 int len = indent.length() - 4; 1350 if (len > 0) { 1351 print(indent.substring(0, len)); 1352 } 1353 1354 print(labelValue); 1355 println(":"); 1356 1357 List handlers = (List )mCatchLocations.get(labelKey); 1358 1359 if (handlers != null) { 1360 for (int i=0; i<handlers.size(); i++) { 1361 ExceptionHandler handler = (ExceptionHandler)handlers.get(i); 1362 print(indent, "try ("); 1363 print(getLabel(handler.getStartLocation().getLocation())); 1364 print(".."); 1365 print(getLabel(handler.getEndLocation().getLocation())); 1366 print(") catch ("); 1367 if (handler.getCatchType() == null) { 1368 print("..."); 1369 } else { 1370 disassemble(handler.getCatchType()); 1371 } 1372 println(")"); 1373 } 1374 } 1375 } 1376 1377 private int readByte() { 1378 return mByteCodes[++mAddress]; 1379 } 1380 1381 private int readUnsignedByte() { 1382 return mByteCodes[++mAddress] & 0xff; 1383 } 1384 1385 private int readShort() { 1386 return (mByteCodes[++mAddress] << 8) | (mByteCodes[++mAddress] & 0xff); 1387 } 1388 1389 private int readUnsignedShort() { 1390 return 1391 ((mByteCodes[++mAddress] & 0xff) << 8) | 1392 ((mByteCodes[++mAddress] & 0xff) << 0); 1393 } 1394 1395 private int readInt() { 1396 return 1397 (mByteCodes[++mAddress] << 24) | 1398 ((mByteCodes[++mAddress] & 0xff) << 16) | 1399 ((mByteCodes[++mAddress] & 0xff) << 8) | 1400 ((mByteCodes[++mAddress] & 0xff) << 0); 1401 } 1402 1403 private void print(Object text) { 1404 mOut.print(text); 1405 } 1406 1407 private void println(Object text) { 1408 mOut.println(text); 1409 } 1410 1411 private void print(String indent, Object text) { 1412 mOut.print(indent); 1413 mOut.print(text); 1414 } 1415 1416 private void println(String indent, Object text) { 1417 mOut.print(indent); 1418 mOut.println(text); 1419 } 1420 1421 private void println() { 1422 mOut.println(); 1423 } 1424 1425 private void printCharLiteral(int value) { 1426 if (value >= 0 && value <= 65535) { 1427 int type = Character.getType((char)value); 1428 switch (type) { 1429 case Character.UPPERCASE_LETTER: 1430 case Character.LOWERCASE_LETTER: 1431 case Character.TITLECASE_LETTER: 1432 case Character.MODIFIER_LETTER: 1433 case Character.OTHER_LETTER: 1434 case Character.NON_SPACING_MARK: 1435 case Character.ENCLOSING_MARK: 1436 case Character.COMBINING_SPACING_MARK: 1437 case Character.DECIMAL_DIGIT_NUMBER: 1438 case Character.LETTER_NUMBER: 1439 case Character.OTHER_NUMBER: 1440 case Character.DASH_PUNCTUATION: 1441 case Character.START_PUNCTUATION: 1442 case Character.END_PUNCTUATION: 1443 case Character.CONNECTOR_PUNCTUATION: 1444 case Character.OTHER_PUNCTUATION: 1445 case Character.MATH_SYMBOL: 1446 case Character.CURRENCY_SYMBOL: 1447 case Character.MODIFIER_SYMBOL: 1448 case Character.OTHER_SYMBOL: 1449 print(" // '"); 1450 print(String.valueOf((char)value)); 1451 print("'"); 1452 } 1453 } 1454 } 1455 1456 private void sortMembers(Object [] members) { 1457 Arrays.sort(members, new MemberComparator()); 1458 } 1459 1460 1490 private class MemberComparator implements Comparator { 1491 public int compare(Object a, Object b) { 1492 Modifiers aFlags, bFlags; 1493 1494 if (a instanceof FieldInfo) { 1495 aFlags = ((FieldInfo)a).getModifiers(); 1496 } else { 1497 aFlags = ((MethodInfo)a).getModifiers(); 1498 } 1499 1500 if (b instanceof FieldInfo) { 1501 bFlags = ((FieldInfo)b).getModifiers(); 1502 } else { 1503 bFlags = ((MethodInfo)b).getModifiers(); 1504 } 1505 1506 if (aFlags.isStatic()) { 1508 if (!bFlags.isStatic()) { 1509 return -1; 1510 } 1511 } else { 1512 if (bFlags.isStatic()) { 1513 return 1; 1514 } 1515 } 1516 1517 if (a instanceof FieldInfo) { 1519 if (b instanceof MethodInfo) { 1520 return -1; 1521 } 1522 } else { 1523 if (!(b instanceof MethodInfo)) { 1524 return 1; 1525 } 1526 1527 String aName = ((MethodInfo)a).getName(); 1529 String bName = ((MethodInfo)b).getName(); 1530 1531 if ("<init>".equals(aName) || "<clinit>".equals(aName)) { 1532 if ("<init>".equals(bName) || "<clinit>".equals(bName)) { 1533 } else { 1534 return -1; 1535 } 1536 } else { 1537 if ("<init>".equals(bName) || "<clinit>".equals(bName)) { 1538 return 1; 1539 } 1540 } 1541 } 1542 1543 int aValue, bValue; 1545 if (aFlags.isPublic()) { 1546 aValue = 0; 1547 } else if (aFlags.isProtected()) { 1548 aValue = 4; 1549 } else if (!aFlags.isPrivate()) { 1550 aValue = 8; 1551 } else { 1552 aValue = 12; 1553 } 1554 1555 if (bFlags.isPublic()) { 1556 bValue = 0; 1557 } else if (bFlags.isProtected()) { 1558 bValue = 4; 1559 } else if (!bFlags.isPrivate()) { 1560 bValue = 8; 1561 } else { 1562 bValue = 12; 1563 } 1564 1565 aValue += (aFlags.isFinal()) ? 0 : 2; 1567 bValue += (bFlags.isFinal()) ? 0 : 2; 1568 1569 aValue += (aFlags.isTransient()) ? 1 : 0; 1571 bValue += (bFlags.isTransient()) ? 1 : 0; 1572 1573 if (aValue < bValue) { 1574 return -1; 1575 } else if (aValue > bValue) { 1576 return 1; 1577 } 1578 1579 return 0; 1580 } 1581 } 1582} 1583 | Popular Tags |