1 /* 2 * Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 package sun.jvm.hotspot.ui.classbrowser; 26 27 import java.io.*; 28 import java.util.*; 29 import sun.jvm.hotspot.asm.*; 30 import sun.jvm.hotspot.code.*; 31 import sun.jvm.hotspot.compiler.*; 32 import sun.jvm.hotspot.debugger.*; 33 import sun.jvm.hotspot.interpreter.*; 34 import sun.jvm.hotspot.memory.*; 35 import sun.jvm.hotspot.oops.*; 36 import sun.jvm.hotspot.runtime.*; 37 import sun.jvm.hotspot.tools.jcore.*; 38 import sun.jvm.hotspot.types.*; 39 import sun.jvm.hotspot.utilities.*; 40 41 public class HTMLGenerator implements /* imports */ ClassConstants { 42 static class Formatter { 43 boolean html; 44 StringBuffer buf = new StringBuffer(); 45 46 Formatter(boolean h) { 47 html = h; 48 } 49 50 void append(String s) { 51 buf.append(s); 52 } 53 54 void append(int s) { 55 buf.append(s); 56 } 57 58 void append(char s) { 59 buf.append(s); 60 } 61 62 void append(StringBuffer s) { 63 buf.append(s); 64 } 65 66 void append(Formatter s) { 67 buf.append(s); 68 } 69 70 StringBuffer getBuffer() { 71 return buf; 72 } 73 74 public String toString() { 75 return buf.toString(); 76 } 77 78 void wrap(String tag, String text) { 79 wrap(tag, tag, text); 80 } 81 void wrap(String before, String after, String text) { 82 beginTag(before); 83 append(text); 84 endTag(after); 85 } 86 87 // header tags 88 void h1(String s) { nl(); wrap("h1", s); nl(); } 89 void h2(String s) { nl(); wrap("h2", s); nl(); } 90 void h3(String s) { nl(); wrap("h3", s); nl(); } 91 void h4(String s) { nl(); wrap("h4", s); nl(); } 92 93 // list tags 94 void beginList() { beginTag("ul"); nl(); } 95 void endList() { endTag("ul"); nl(); } 96 void beginListItem() { beginTag("li"); } 97 void endListItem() { endTag("li"); nl(); } 98 void li(String s) { wrap("li", s); nl(); } 99 100 // table tags 101 void beginTable(int border) { 102 beginTag("table border='" + border + "'"); 103 } 104 void cell(String s) { wrap("td", s); } 105 void headerCell(String s) { wrap("th", s); } 106 void endTable() { endTag("table"); } 107 108 void link(String href, String text) { 109 wrap("a href='" + href + "'", "a", text); 110 } 111 void beginTag(String s) { 112 if (html) { append("<"); append(s); append(">"); } 113 } 114 void endTag(String s) { 115 if (html) { 116 append("</"); append(s); append(">"); 117 } else { 118 if (s.equals("table") || s.equals("tr")) { 119 nl(); 120 } 121 if (s.equals("td") || s.equals("th")) { 122 append(" "); 123 } 124 } 125 } 126 void bold(String s) { 127 wrap("b", s); 128 } 129 130 void nl() { 131 if (!html) buf.append("\n"); 132 } 133 134 void br() { 135 if (html) append("<br>"); 136 else append("\n"); 137 } 138 void genEmptyHTML() { 139 if (html) append("<html></html>"); 140 } 141 142 void genHTMLPrologue() { 143 if (html) append("<html><body>"); 144 } 145 146 void genHTMLPrologue(String title) { 147 if (html) { 148 append("<html><head><title>"); 149 append(title); 150 append("</title></head>"); 151 append("<body>"); 152 } 153 h2(title); 154 } 155 void genHTMLEpilogue() { 156 if (html) append("</body></html>"); 157 } 158 159 } 160 161 private static final String DUMP_KLASS_OUTPUT_DIR = "."; 162 private static final int NATIVE_CODE_SIZE = 200; 163 private final String spaces; 164 private final String tab; 165 166 private boolean genHTML = true; 167 168 public HTMLGenerator() { 169 this(true); 170 } 171 172 public HTMLGenerator(boolean html) { 173 genHTML = html; 174 if (html) { 175 spaces = " "; 176 tab = " "; 177 } else { 178 spaces = " "; 179 tab = " "; 180 } 181 } 182 183 protected String escapeHTMLSpecialChars(String value) { 184 if (!genHTML) return value; 185 186 Formatter buf = new Formatter(genHTML); 187 int len = value.length(); 188 for (int i=0; i < len; i++) { 189 char c = value.charAt(i); 190 switch (c) { 191 case '<': 192 buf.append("<"); 193 break; 194 case '>': 195 buf.append(">"); 196 break; 197 case '&': 198 buf.append("&"); 199 break; 200 default: 201 buf.append(c); 202 break; 203 } 204 } 205 return buf.toString(); 206 } 207 208 public String genHTMLForMessage(String message) { 209 Formatter buf = new Formatter(genHTML); 210 buf.genHTMLPrologue(message); 211 buf.genHTMLEpilogue(); 212 return buf.toString(); 213 } 214 215 public String genHTMLErrorMessage(Exception exp) { 216 exp.printStackTrace(); 217 return genHTMLForMessage(exp.getClass().getName() + " : " + exp.getMessage()); 218 } 219 220 public String genHTMLForWait(String message) { 221 Formatter buf = new Formatter(genHTML); 222 buf.genHTMLPrologue("Please wait .."); 223 buf.h2(message); 224 return buf.toString(); 225 } 226 227 protected String genKlassTitle(InstanceKlass klass) { 228 Formatter buf = new Formatter(genHTML); 229 AccessFlags acc = klass.getAccessFlagsObj(); 230 if (acc.isPublic()) { 231 buf.append("public "); 232 } else if (acc.isProtected()) { 233 buf.append("protected "); 234 } else if (acc.isPrivate()) { 235 buf.append("private "); 236 } 237 238 if (acc.isStatic()) { 239 buf.append("static "); 240 } 241 242 if (acc.isAbstract() ) { 243 buf.append("abstract "); 244 } else if (acc.isFinal()) { 245 buf.append("final "); 246 } 247 248 if (acc.isStrict()) { 249 buf.append("strict "); 250 } 251 252 // javac generated flags 253 if (acc.isEnum()) { 254 buf.append("[enum] "); 255 } 256 if (acc.isSynthetic()) { 257 buf.append("[synthetic] "); 258 } 259 260 if (klass.isInterface()) { 261 buf.append("interface"); 262 } else { 263 buf.append("class"); 264 } 265 266 buf.append(' '); 267 buf.append(klass.getName().asString().replace('/', '.')); 268 // is it generic? 269 Symbol genSig = klass.getGenericSignature(); 270 if (genSig != null) { 271 buf.append(" [signature "); 272 buf.append(escapeHTMLSpecialChars(genSig.asString())); 273 buf.append("] "); 274 } else { 275 buf.append(' '); 276 } 277 buf.append('@'); 278 buf.append(klass.getAddress().toString()); 279 return buf.toString(); 280 } 281 282 protected String genBaseHref() { 283 return ""; 284 } 285 286 protected String genKlassHref(InstanceKlass klass) { 287 return genBaseHref() + "klass=" + klass.getAddress(); 288 } 289 290 protected String genKlassLink(InstanceKlass klass) { 291 Formatter buf = new Formatter(genHTML); 292 buf.link(genKlassHref(klass), genKlassTitle(klass)); 293 return buf.toString(); 294 } 295 296 protected String genMethodModifierString(AccessFlags acc) { 297 Formatter buf = new Formatter(genHTML); 298 if (acc.isPrivate()) { 299 buf.append("private "); 300 } else if (acc.isProtected()) { 301 buf.append("protected "); 302 } else if (acc.isPublic()) { 303 buf.append("public "); 304 } 305 306 if (acc.isStatic()) { 307 buf.append("static "); 308 } else if (acc.isAbstract() ) { 309 buf.append("abstract "); 310 } else if (acc.isFinal()) { 311 buf.append("final "); 312 } 313 314 if (acc.isNative()) { 315 buf.append("native "); 316 } 317 318 if (acc.isStrict()) { 319 buf.append("strict "); 320 } 321 322 if (acc.isSynchronized()) { 323 buf.append("synchronized "); 324 } 325 326 // javac generated flags 327 if (acc.isBridge()) { 328 buf.append("[bridge] "); 329 } 330 331 if (acc.isSynthetic()) { 332 buf.append("[synthetic] "); 333 } 334 335 if (acc.isVarArgs()) { 336 buf.append("[varargs] "); 337 } 338 339 return buf.toString(); 340 } 341 342 protected String genMethodNameAndSignature(Method method) { 343 Formatter buf = new Formatter(genHTML); 344 buf.append(genMethodModifierString(method.getAccessFlagsObj())); 345 Symbol sig = method.getSignature(); 346 new SignatureConverter(sig, buf.getBuffer()).iterateReturntype(); 347 buf.append(" "); 348 String methodName = method.getName().asString(); 349 buf.append(escapeHTMLSpecialChars(methodName)); 350 buf.append('('); 351 new SignatureConverter(sig, buf.getBuffer()).iterateParameters(); 352 buf.append(')'); 353 // is it generic? 354 Symbol genSig = method.getGenericSignature(); 355 if (genSig != null) { 356 buf.append(" [signature "); 357 buf.append(escapeHTMLSpecialChars(genSig.asString())); 358 buf.append("] "); 359 } 360 return buf.toString().replace('/', '.'); 361 } 362 363 protected String genMethodTitle(Method method) { 364 Formatter buf = new Formatter(genHTML); 365 buf.append(genMethodNameAndSignature(method)); 366 buf.append(' '); 367 buf.append('@'); 368 buf.append(method.getAddress().toString()); 369 return buf.toString(); 370 } 371 372 protected String genMethodHref(Method m) { 373 return genBaseHref() + "method=" + m.getAddress(); 374 } 375 376 protected String genMethodLink(Method m) { 377 Formatter buf = new Formatter(genHTML); 378 buf.link(genMethodHref(m), genMethodTitle(m)); 379 return buf.toString(); 380 } 381 382 protected String genMethodAndKlassLink(Method m) { 383 Formatter buf = new Formatter(genHTML); 384 buf.append(genMethodLink(m)); 385 buf.append(" of "); 386 buf.append(genKlassLink((InstanceKlass) m.getMethodHolder())); 387 return buf.toString(); 388 } 389 390 protected String genNMethodHref(NMethod nm) { 391 return genBaseHref() + "nmethod=" + nm.getAddress(); 392 } 393 394 public String genNMethodTitle(NMethod nmethod) { 395 Formatter buf = new Formatter(genHTML); 396 Method m = nmethod.getMethod(); 397 398 buf.append("Disassembly for compiled method ["); 399 buf.append(genMethodTitle(m)); 400 buf.append(" ] "); 401 buf.append('@'); 402 buf.append(nmethod.getAddress().toString()); 403 return buf.toString(); 404 } 405 406 protected String genNMethodLink(NMethod nm) { 407 Formatter buf = new Formatter(genHTML); 408 buf.link(genNMethodHref(nm), genNMethodTitle(nm)); 409 return buf.toString(); 410 } 411 412 public String genCodeBlobTitle(CodeBlob blob) { 413 Formatter buf = new Formatter(genHTML); 414 buf.append("Disassembly for code blob " + blob.getName() + " ["); 415 buf.append(blob.getClass().getName()); 416 buf.append(" ] @"); 417 buf.append(blob.getAddress().toString()); 418 return buf.toString(); 419 } 420 421 protected BytecodeDisassembler createBytecodeDisassembler(Method m) { 422 return new BytecodeDisassembler(m); 423 } 424 425 private String genLowHighShort(int val) { 426 Formatter buf = new Formatter(genHTML); 427 buf.append('#'); 428 buf.append(Integer.toString(val & 0xFFFF)); 429 buf.append(" #"); 430 buf.append(Integer.toString((val >> 16) & 0xFFFF)); 431 return buf.toString(); 432 } 433 434 private String genListOfShort(short[] values) { 435 if (values == null || values.length == 0) return ""; 436 Formatter buf = new Formatter(genHTML); 437 buf.append('['); 438 for (int i = 0; i < values.length; i++) { 439 if (i > 0) buf.append(' '); 440 buf.append('#'); 441 buf.append(Integer.toString(values[i])); 442 } 443 buf.append(']'); 444 return buf.toString(); 445 } 446 447 protected String genHTMLTableForConstantPool(ConstantPool cpool) { 448 Formatter buf = new Formatter(genHTML); 449 buf.beginTable(1); 450 451 buf.beginTag("tr"); 452 buf.headerCell("Index"); 453 buf.headerCell("Constant Type"); 454 buf.headerCell("Constant Value"); 455 buf.endTag("tr"); 456 457 final int length = (int) cpool.getLength(); 458 // zero'th pool entry is always invalid. ignore it. 459 for (int index = 1; index < length; index++) { 460 buf.beginTag("tr"); 461 buf.cell(Integer.toString(index)); 462 463 int ctag = (int) cpool.getTags().at((int) index); 464 switch (ctag) { 465 case JVM_CONSTANT_Integer: 466 buf.cell("JVM_CONSTANT_Integer"); 467 buf.cell(Integer.toString(cpool.getIntAt(index))); 468 break; 469 470 case JVM_CONSTANT_Float: 471 buf.cell("JVM_CONSTANT_Float"); 472 buf.cell(Float.toString(cpool.getFloatAt(index))); 473 break; 474 475 case JVM_CONSTANT_Long: 476 buf.cell("JVM_CONSTANT_Long"); 477 buf.cell(Long.toString(cpool.getLongAt(index))); 478 // long entries occupy two slots 479 index++; 480 break; 481 482 case JVM_CONSTANT_Double: 483 buf.cell("JVM_CONSTANT_Double"); 484 buf.cell(Double.toString(cpool.getDoubleAt(index))); 485 // double entries occupy two slots 486 index++; 487 break; 488 489 case JVM_CONSTANT_UnresolvedClass: 490 buf.cell("JVM_CONSTANT_UnresolvedClass"); 491 buf.cell(cpool.getKlassNameAt(index).asString()); 492 break; 493 494 case JVM_CONSTANT_UnresolvedClassInError: 495 buf.cell("JVM_CONSTANT_UnresolvedClassInError"); 496 buf.cell(cpool.getSymbolAt(index).asString()); 497 break; 498 499 case JVM_CONSTANT_Class: 500 buf.cell("JVM_CONSTANT_Class"); 501 Klass klass = (Klass) cpool.getKlassAt(index); 502 if (klass instanceof InstanceKlass) { 503 buf.cell(genKlassLink((InstanceKlass) klass)); 504 } else { 505 buf.cell(klass.getName().asString().replace('/', '.')); 506 } 507 break; 508 509 case JVM_CONSTANT_Utf8: 510 buf.cell("JVM_CONSTANT_Utf8"); 511 buf.cell("\"" + 512 escapeHTMLSpecialChars(cpool.getSymbolAt(index).asString()) + 513 "\""); 514 break; 515 516 case JVM_CONSTANT_String: 517 buf.cell("JVM_CONSTANT_String"); 518 buf.cell("\"" + 519 escapeHTMLSpecialChars(cpool.getUnresolvedStringAt(index).asString()) + "\""); 520 break; 521 522 case JVM_CONSTANT_Fieldref: 523 buf.cell("JVM_CONSTANT_Fieldref"); 524 buf.cell(genLowHighShort(cpool.getIntAt(index))); 525 break; 526 527 case JVM_CONSTANT_Methodref: 528 buf.cell("JVM_CONSTANT_Methodref"); 529 buf.cell(genLowHighShort(cpool.getIntAt(index))); 530 break; 531 532 case JVM_CONSTANT_InterfaceMethodref: 533 buf.cell("JVM_CONSTANT_InterfaceMethodref"); 534 buf.cell(genLowHighShort(cpool.getIntAt(index))); 535 break; 536 537 case JVM_CONSTANT_NameAndType: 538 buf.cell("JVM_CONSTANT_NameAndType"); 539 buf.cell(genLowHighShort(cpool.getIntAt(index))); 540 break; 541 542 case JVM_CONSTANT_ClassIndex: 543 buf.cell("JVM_CONSTANT_ClassIndex"); 544 buf.cell(Integer.toString(cpool.getIntAt(index))); 545 break; 546 547 case JVM_CONSTANT_StringIndex: 548 buf.cell("JVM_CONSTANT_StringIndex"); 549 buf.cell(Integer.toString(cpool.getIntAt(index))); 550 break; 551 552 case JVM_CONSTANT_MethodHandle: 553 buf.cell("JVM_CONSTANT_MethodHandle"); 554 buf.cell(genLowHighShort(cpool.getIntAt(index))); 555 break; 556 557 case JVM_CONSTANT_MethodType: 558 buf.cell("JVM_CONSTANT_MethodType"); 559 buf.cell(Integer.toString(cpool.getIntAt(index))); 560 break; 561 562 case JVM_CONSTANT_InvokeDynamic: 563 buf.cell("JVM_CONSTANT_InvokeDynamic"); 564 buf.cell(genLowHighShort(cpool.getIntAt(index)) + 565 genListOfShort(cpool.getBootstrapSpecifierAt(index))); 566 break; 567 568 default: 569 throw new InternalError("unknown tag: " + ctag); 570 } 571 572 buf.endTag("tr"); 573 } 574 575 buf.endTable(); 576 return buf.toString(); 577 } 578 579 public String genHTML(ConstantPool cpool) { 580 try { 581 Formatter buf = new Formatter(genHTML); 582 buf.genHTMLPrologue(genConstantPoolTitle(cpool)); 583 buf.h3("Holder Class"); 584 buf.append(genKlassLink((InstanceKlass) cpool.getPoolHolder())); 585 buf.h3("Constants"); 586 buf.append(genHTMLTableForConstantPool(cpool)); 587 buf.genHTMLEpilogue(); 588 return buf.toString(); 589 } catch (Exception exp) { 590 return genHTMLErrorMessage(exp); 591 } 592 } 593 594 protected String genConstantPoolHref(ConstantPool cpool) { 595 return genBaseHref() + "cpool=" + cpool.getAddress(); 596 } 597 598 protected String genConstantPoolTitle(ConstantPool cpool) { 599 Formatter buf = new Formatter(genHTML); 600 buf.append("Constant Pool of ["); 601 buf.append(genKlassTitle((InstanceKlass) cpool.getPoolHolder())); 602 buf.append("] @"); 603 buf.append(cpool.getAddress().toString()); 604 return buf.toString(); 605 } 606 607 protected String genConstantPoolLink(ConstantPool cpool) { 608 Formatter buf = new Formatter(genHTML); 609 buf.link(genConstantPoolHref(cpool), genConstantPoolTitle(cpool)); 610 return buf.toString(); 611 } 612 613 public String genHTML(Method method) { 614 try { 615 final Formatter buf = new Formatter(genHTML); 616 buf.genHTMLPrologue(genMethodTitle(method)); 617 618 buf.h3("Holder Class"); 619 buf.append(genKlassLink((InstanceKlass) method.getMethodHolder())); 620 621 NMethod nmethod = method.getNativeMethod(); 622 if (nmethod != null) { 623 buf.h3("Compiled Code"); 624 buf.append(genNMethodLink(nmethod)); 625 } 626 627 boolean hasThrows = method.hasCheckedExceptions(); 628 ConstantPool cpool = ((InstanceKlass) method.getMethodHolder()).getConstants(); 629 if (hasThrows) { 630 buf.h3("Checked Exception(s)"); 631 CheckedExceptionElement[] exceptions = method.getCheckedExceptions(); 632 buf.beginTag("ul"); 633 for (int exp = 0; exp < exceptions.length; exp++) { 634 short cpIndex = (short) exceptions[exp].getClassCPIndex(); 635 ConstantPool.CPSlot obj = cpool.getSlotAt(cpIndex); 636 if (obj.isUnresolved()) { 637 buf.li((obj.getSymbol()).asString().replace('/', '.')); 638 } else { 639 buf.li(genKlassLink((InstanceKlass)obj.getKlass())); 640 } 641 } 642 buf.endTag("ul"); 643 } 644 645 if (method.isNative() || method.isAbstract()) { 646 buf.genHTMLEpilogue(); 647 return buf.toString(); 648 } 649 650 buf.h3("Bytecode"); 651 BytecodeDisassembler disasm = createBytecodeDisassembler(method); 652 final boolean hasLineNumbers = method.hasLineNumberTable(); 653 disasm.decode(new BytecodeVisitor() { 654 private Method method; 655 public void prologue(Method m) { 656 method = m; 657 buf.beginTable(0); 658 buf.beginTag("tr"); 659 if (hasLineNumbers) { 660 buf.headerCell("line"); 661 } 662 buf.headerCell("bci" + spaces); 663 buf.headerCell("bytecode"); 664 buf.endTag("tr"); 665 } 666 667 public void visit(Bytecode instr) { 668 int curBci = instr.bci(); 669 buf.beginTag("tr"); 670 if (hasLineNumbers) { 671 int lineNumber = method.getLineNumberFromBCI(curBci); 672 buf.cell(Integer.toString(lineNumber) + spaces); 673 } 674 buf.cell(Integer.toString(curBci) + spaces); 675 676 buf.beginTag("td"); 677 String instrStr = null; 678 try { 679 instrStr = escapeHTMLSpecialChars(instr.toString()); 680 } catch (RuntimeException re) { 681 buf.append("exception while printing " + instr.getBytecodeName()); 682 buf.endTag("td"); 683 buf.endTag("tr"); 684 re.printStackTrace(); 685 return; 686 } 687 688 if (instr instanceof BytecodeNew) { 689 BytecodeNew newBytecode = (BytecodeNew) instr; 690 InstanceKlass klass = newBytecode.getNewKlass(); 691 if (klass != null) { 692 buf.link(genKlassHref(klass), instrStr); 693 } else { 694 buf.append(instrStr); 695 } 696 } else if(instr instanceof BytecodeInvoke) { 697 BytecodeInvoke invokeBytecode = (BytecodeInvoke) instr; 698 Method m = invokeBytecode.getInvokedMethod(); 699 if (m != null) { 700 buf.link(genMethodHref(m), instrStr); 701 buf.append(" of "); 702 InstanceKlass klass = (InstanceKlass) m.getMethodHolder(); 703 buf.link(genKlassHref(klass), genKlassTitle(klass)); 704 } else { 705 buf.append(instrStr); 706 } 707 } else if (instr instanceof BytecodeGetPut) { 708 BytecodeGetPut getPut = (BytecodeGetPut) instr; 709 sun.jvm.hotspot.oops.Field f = getPut.getField(); 710 buf.append(instrStr); 711 if (f != null) { 712 InstanceKlass klass = f.getFieldHolder(); 713 buf.append(" of "); 714 buf.link(genKlassHref(klass), genKlassTitle(klass)); 715 } 716 } else if (instr instanceof BytecodeLoadConstant) { 717 BytecodeLoadConstant ldc = (BytecodeLoadConstant) instr; 718 if (ldc.isKlassConstant()) { 719 Object oop = ldc.getKlass(); 720 if (oop instanceof InstanceKlass) { 721 buf.append("<a href='"); 722 buf.append(genKlassHref((InstanceKlass) oop)); 723 buf.append("'>"); 724 buf.append(instrStr); 725 buf.append("</a>"); 726 } else { 727 // unresolved klass literal 728 buf.append(instrStr); 729 } 730 } else { 731 // not a klass literal 732 buf.append(instrStr); 733 } 734 } else { 735 buf.append(instrStr); 736 } 737 buf.endTag("td"); 738 buf.endTag("tr"); 739 } 740 741 public void epilogue() { 742 buf.endTable(); 743 } 744 }); 745 746 // display exception table for this method 747 boolean hasException = method.hasExceptionTable(); 748 if (hasException) { 749 ExceptionTableElement[] exceptionTable = method.getExceptionTable(); 750 int numEntries = exceptionTable.length; 751 if (numEntries != 0) { 752 buf.h4("Exception Table"); 753 buf.beginTable(1); 754 buf.beginTag("tr"); 755 buf.headerCell("start bci"); 756 buf.headerCell("end bci"); 757 buf.headerCell("handler bci"); 758 buf.headerCell("catch type"); 759 buf.endTag("tr"); 760 761 for (int e = 0; e < numEntries; e ++) { 762 buf.beginTag("tr"); 763 buf.cell(Integer.toString(exceptionTable[e].getStartPC())); 764 buf.cell(Integer.toString(exceptionTable[e].getEndPC())); 765 buf.cell(Integer.toString(exceptionTable[e].getHandlerPC())); 766 short cpIndex = (short) exceptionTable[e].getCatchTypeIndex(); 767 ConstantPool.CPSlot obj = cpIndex == 0? null : cpool.getSlotAt(cpIndex); 768 if (obj == null) { 769 buf.cell("Any"); 770 } else if (obj.isUnresolved()) { 771 buf.cell(obj.getSymbol().asString().replace('/', '.')); 772 } else { 773 buf.cell(genKlassLink((InstanceKlass)obj.getKlass())); 774 } 775 buf.endTag("tr"); 776 } 777 778 buf.endTable(); 779 } 780 } 781 782 // display constant pool hyperlink 783 buf.h3("Constant Pool"); 784 buf.append(genConstantPoolLink(cpool)); 785 buf.genHTMLEpilogue(); 786 return buf.toString(); 787 } catch (Exception exp) { 788 return genHTMLErrorMessage(exp); 789 } 790 } 791 792 protected SymbolFinder createSymbolFinder() { 793 return new DummySymbolFinder(); 794 } 795 796 // genHTML for a given address. Address may be a PC or 797 // Method* or Klass*. 798 799 public String genHTMLForAddress(String addrStr) { 800 return genHTML(parseAddress(addrStr)); 801 } 802 803 public String genHTML(sun.jvm.hotspot.debugger.Address pc) { 804 CodeBlob blob = null; 805 806 try { 807 blob = (CodeBlob)VM.getVM().getCodeCache().findBlobUnsafe(pc); 808 } catch (Exception exp) { 809 // ignore 810 } 811 812 if (blob != null) { 813 if (blob instanceof NMethod) { 814 return genHTML((NMethod)blob); 815 } else { 816 // may be interpreter code. 817 Interpreter interp = VM.getVM().getInterpreter(); 818 if (interp.contains(pc)) { 819 InterpreterCodelet codelet = interp.getCodeletContaining(pc); 820 if (codelet == null) { 821 return "Unknown location in the Interpreter: " + pc; 822 } 823 return genHTML(codelet); 824 } 825 return genHTML(blob); 826 } 827 } else if (VM.getVM().getCodeCache().contains(pc)) { 828 return "Unknown location in the CodeCache: " + pc; 829 } 830 831 // did not find nmethod. 832 // try Method*, Klass* and ConstantPool*. 833 try { 834 Metadata obj = Metadata.instantiateWrapperFor(pc); 835 if (obj != null) { 836 if (obj instanceof Method) { 837 return genHTML((Method) obj); 838 } else if (obj instanceof InstanceKlass) { 839 return genHTML((InstanceKlass) obj); 840 } else if (obj instanceof ConstantPool) { 841 return genHTML((ConstantPool) obj); 842 } 843 } 844 } catch (Exception exp) { 845 exp.printStackTrace(); 846 // ignore 847 } 848 849 // didn't find any. do raw disassembly. 850 return genHTMLForRawDisassembly(pc, null); 851 } 852 853 public String genHTMLForRawDisassembly(sun.jvm.hotspot.debugger.Address startPc, int size) { 854 try { 855 return genHTMLForRawDisassembly(startPc, size, null); 856 } catch (Exception exp) { 857 return genHTMLErrorMessage(exp); 858 } 859 } 860 861 protected String genHTMLForRawDisassembly(sun.jvm.hotspot.debugger.Address startPc, 862 String prevPCs) { 863 try { 864 return genHTMLForRawDisassembly(startPc, NATIVE_CODE_SIZE, prevPCs); 865 } catch (Exception exp) { 866 return genHTMLErrorMessage(exp); 867 } 868 } 869 870 protected String genPCHref(long targetPc) { 871 return genBaseHref() + "pc=0x" + Long.toHexString(targetPc); 872 } 873 874 protected String genMultPCHref(String pcs) { 875 StringBuffer buf = new StringBuffer(genBaseHref()); 876 buf.append("pc_multiple="); 877 buf.append(pcs); 878 return buf.toString(); 879 } 880 881 protected String genPCHref(Address addr) { 882 return genPCHref(addressToLong(addr)); 883 } 884 885 class HTMLDisassembler implements InstructionVisitor { 886 private int instrSize = 0; 887 private Formatter buf; 888 private SymbolFinder symFinder = createSymbolFinder(); 889 private long pc; 890 private OopMapSet oms; 891 private CodeBlob blob; 892 private NMethod nmethod; 893 894 HTMLDisassembler(Formatter buf, CodeBlob blob) { 895 this.buf = buf; 896 this.blob = blob; 897 if (blob != null) { 898 if (blob instanceof NMethod) { 899 nmethod = (NMethod)blob; 900 } 901 oms = blob.getOopMaps(); 902 } 903 } 904 905 public int getInstructionSize() { 906 return instrSize; 907 } 908 909 public void prologue() { 910 } 911 912 public void beginInstruction(long currentPc) { 913 pc = currentPc; 914 915 sun.jvm.hotspot.debugger.Address adr = longToAddress(pc); 916 if (nmethod != null) { 917 if (adr.equals(nmethod.getEntryPoint())) print("[Entry Point]\n"); 918 if (adr.equals(nmethod.getVerifiedEntryPoint())) print("[Verified Entry Point]\n"); 919 if (adr.equals(nmethod.exceptionBegin())) print("[Exception Handler]\n"); 920 if (adr.equals(nmethod.stubBegin()) && 921 !nmethod.stubBegin().equals(nmethod.stubEnd())) print("[Stub Code]\n"); 922 // if (adr.equals(nmethod.constsBegin())) print("[Constants]\n"); 923 } 924 925 buf.append(adr.toString()); 926 buf.append(':'); 927 buf.append(tab); 928 } 929 930 public void printAddress(long address) { 931 sun.jvm.hotspot.debugger.Address addr = longToAddress(address); 932 if (VM.getVM().getCodeCache().contains(addr)) { 933 buf.link(genPCHref(address), addr.toString()); 934 } else { 935 buf.append(addr.toString()); 936 } 937 } 938 939 public void print(String s) { 940 buf.append(s); 941 } 942 943 public void endInstruction(long endPc) { 944 instrSize += endPc - pc; 945 if (genHTML) buf.br(); 946 947 if (nmethod != null) { 948 ScopeDesc sd = nmethod.scope_desc_in(pc, endPc); 949 if (sd != null) { 950 buf.br(); 951 buf.append(genSafepointInfo(nmethod, sd)); 952 } 953 } 954 955 if (oms != null) { 956 long base = addressToLong(blob.codeBegin()); 957 for (int i = 0, imax = (int)oms.getSize(); i < imax; i++) { 958 OopMap om = oms.getMapAt(i); 959 long omspc = base + om.getOffset(); 960 if (omspc > pc) { 961 if (omspc <= endPc) { 962 buf.br(); 963 buf.append(genOopMapInfo(om)); 964 // st.move_to(column); 965 // visitor.print("; "); 966 // om.print_on(st); 967 } 968 break; 969 } 970 } 971 } 972 // follow each complete insn by a nice newline 973 buf.br(); 974 } 975 976 public void epilogue() { 977 } 978 }; 979 980 protected String genHTMLForRawDisassembly(sun.jvm.hotspot.debugger.Address addr, 981 int size, 982 String prevPCs) { 983 try { 984 final Formatter buf = new Formatter(genHTML); 985 buf.genHTMLPrologue("Disassembly @ " + addr); 986 987 if (prevPCs != null && genHTML) { 988 buf.beginTag("p"); 989 buf.link(genMultPCHref(prevPCs), "show previous code .."); 990 buf.endTag("p"); 991 } 992 993 994 buf.h3("Code"); 995 HTMLDisassembler visitor = new HTMLDisassembler(buf, null); 996 Disassembler.decode(visitor, null, addr, addr.addOffsetTo(size)); 997 998 if (genHTML) buf.beginTag("p"); 999 Formatter tmpBuf = new Formatter(genHTML); 1000 long startPc = addressToLong(addr); 1001 tmpBuf.append("0x"); 1002 tmpBuf.append(Long.toHexString(startPc + visitor.getInstructionSize()).toString()); 1003 tmpBuf.append(",0x"); 1004 tmpBuf.append(Long.toHexString(startPc)); 1005 if (prevPCs != null) { 1006 tmpBuf.append(','); 1007 tmpBuf.append(prevPCs); 1008 } 1009 if (genHTML) { 1010 buf.link(genMultPCHref(tmpBuf.toString()), "show more code .."); 1011 buf.endTag("p"); 1012 } 1013 1014 buf.genHTMLEpilogue(); 1015 return buf.toString(); 1016 } catch (Exception exp) { 1017 return genHTMLErrorMessage(exp); 1018 } 1019 } 1020 1021 protected String genSafepointInfo(NMethod nm, ScopeDesc sd) { 1022 Formatter buf = new Formatter(genHTML); 1023 Formatter tabs = new Formatter(genHTML); 1024 tabs.append(tab + tab + tab); // Initial indent for debug info 1025 1026 buf.beginTag("pre"); 1027 genScope(buf, tabs, sd); 1028 1029 // Reset indent for scalar replaced objects 1030 tabs = new Formatter(genHTML); 1031 tabs.append(tab + tab + tab); // Initial indent for debug info 1032 1033 genScObjInfo(buf, tabs, sd); 1034 buf.endTag("pre"); 1035 1036 return buf.toString(); 1037 } 1038 1039 protected void genScope(Formatter buf, Formatter tabs, ScopeDesc sd) { 1040 if (sd == null) { 1041 return; 1042 } 1043 1044 genScope(buf, tabs, sd.sender()); 1045 1046 buf.append(tabs); 1047 Method m = sd.getMethod(); 1048 buf.append(genMethodAndKlassLink(m)); 1049 int bci = sd.getBCI(); 1050 buf.append(" @ bci = "); 1051 buf.append(Integer.toString(bci)); 1052 1053 int line = m.getLineNumberFromBCI(bci); 1054 if (line != -1) { 1055 buf.append(", line = "); 1056 buf.append(Integer.toString(line)); 1057 } 1058 1059 List locals = sd.getLocals(); 1060 if (locals != null) { 1061 buf.br(); 1062 buf.append(tabs); 1063 buf.append(genHTMLForLocals(sd, locals)); 1064 } 1065 1066 List expressions = sd.getExpressions(); 1067 if (expressions != null) { 1068 buf.br(); 1069 buf.append(tabs); 1070 buf.append(genHTMLForExpressions(sd, expressions)); 1071 } 1072 1073 List monitors = sd.getMonitors(); 1074 if (monitors != null) { 1075 buf.br(); 1076 buf.append(tabs); 1077 buf.append(genHTMLForMonitors(sd, monitors)); 1078 } 1079 1080 buf.br(); 1081 tabs.append(tab); 1082 } 1083 1084 protected void genScObjInfo(Formatter buf, Formatter tabs, ScopeDesc sd) { 1085 if (sd == null) { 1086 return; 1087 } 1088 1089 List objects = sd.getObjects(); 1090 if (objects == null) { 1091 return; 1092 } 1093 int length = objects.size(); 1094 for (int i = 0; i < length; i++) { 1095 buf.append(tabs); 1096 ObjectValue ov = (ObjectValue)objects.get(i); 1097 buf.append("ScObj" + i); 1098 ScopeValue sv = ov.getKlass(); 1099 if (Assert.ASSERTS_ENABLED) { 1100 Assert.that(sv.isConstantOop(), "scalar replaced object klass must be constant oop"); 1101 } 1102 ConstantOopReadValue klv = (ConstantOopReadValue)sv; 1103 OopHandle klHandle = klv.getValue(); 1104 if (Assert.ASSERTS_ENABLED) { 1105 Assert.that(klHandle != null, "scalar replaced object klass must be not NULL"); 1106 } 1107 Oop obj = VM.getVM().getObjectHeap().newOop(klHandle); 1108 // Obj is a Java mirror 1109 Klass klass = java_lang_Class.asKlass(obj); 1110 if (klass instanceof InstanceKlass) { 1111 InstanceKlass kls = (InstanceKlass) klass; 1112 buf.append(" " + kls.getName().asString() + "={"); 1113 int flen = ov.fieldsSize(); 1114 1115 U2Array klfields = kls.getFields(); 1116 int klen = (int) klfields.length(); 1117 int findex = 0; 1118 for (int index = 0; index < klen; index++) { 1119 int accsFlags = kls.getFieldAccessFlags(index); 1120 Symbol f_name = kls.getFieldName(index); 1121 AccessFlags access = new AccessFlags(accsFlags); 1122 if (!access.isStatic()) { 1123 ScopeValue svf = ov.getFieldAt(findex++); 1124 String fstr = scopeValueAsString(sd, svf); 1125 buf.append(" [" + f_name.asString() + " :"+ index + "]=(#" + fstr + ")"); 1126 } 1127 } 1128 buf.append(" }"); 1129 } else { 1130 buf.append(" "); 1131 int flen = ov.fieldsSize(); 1132 if (klass instanceof TypeArrayKlass) { 1133 TypeArrayKlass kls = (TypeArrayKlass) klass; 1134 buf.append(kls.getElementTypeName() + "[" + flen + "]"); 1135 } else if (klass instanceof ObjArrayKlass) { 1136 ObjArrayKlass kls = (ObjArrayKlass) klass; 1137 Klass elobj = kls.getBottomKlass(); 1138 if (elobj instanceof InstanceKlass) { 1139 buf.append(elobj.getName().asString()); 1140 } else if (elobj instanceof TypeArrayKlass) { 1141 TypeArrayKlass elkls = (TypeArrayKlass) elobj; 1142 buf.append(elkls.getElementTypeName()); 1143 } else { 1144 if (Assert.ASSERTS_ENABLED) { 1145 Assert.that(false, "unknown scalar replaced object klass!"); 1146 } 1147 } 1148 buf.append("[" + flen + "]"); 1149 int ndim = (int) kls.getDimension(); 1150 while (--ndim > 0) { 1151 buf.append("[]"); 1152 } 1153 } else { 1154 if (Assert.ASSERTS_ENABLED) { 1155 Assert.that(false, "unknown scalar replaced object klass!"); 1156 } 1157 } 1158 buf.append("={"); 1159 for (int findex = 0; findex < flen; findex++) { 1160 ScopeValue svf = ov.getFieldAt(findex); 1161 String fstr = scopeValueAsString(sd, svf); 1162 buf.append(" [" + findex + "]=(#" + fstr + ")"); 1163 } 1164 buf.append(" }"); 1165 } 1166 buf.br(); 1167 } 1168 } 1169 1170 protected String genHTMLForOopMap(OopMap map) { 1171 final int stack0 = VMRegImpl.getStack0().getValue(); 1172 Formatter buf = new Formatter(genHTML); 1173 1174 final class OopMapValueIterator { 1175 final Formatter iterate(OopMapStream oms, String type, boolean printContentReg) { 1176 Formatter tmpBuf = new Formatter(genHTML); 1177 boolean found = false; 1178 tmpBuf.beginTag("tr"); 1179 tmpBuf.beginTag("td"); 1180 tmpBuf.append(type); 1181 for (; ! oms.isDone(); oms.next()) { 1182 OopMapValue omv = oms.getCurrent(); 1183 if (omv == null) { 1184 continue; 1185 } 1186 found = true; 1187 VMReg vmReg = omv.getReg(); 1188 int reg = vmReg.getValue(); 1189 if (reg < stack0) { 1190 tmpBuf.append(VMRegImpl.getRegisterName(reg)); 1191 } else { 1192 tmpBuf.append('['); 1193 tmpBuf.append(Integer.toString((reg - stack0) * 4)); 1194 tmpBuf.append(']'); 1195 } 1196 if (printContentReg) { 1197 tmpBuf.append(" = "); 1198 VMReg vmContentReg = omv.getContentReg(); 1199 int contentReg = vmContentReg.getValue(); 1200 if (contentReg < stack0) { 1201 tmpBuf.append(VMRegImpl.getRegisterName(contentReg)); 1202 } else { 1203 tmpBuf.append('['); 1204 tmpBuf.append(Integer.toString((contentReg - stack0) * 4)); 1205 tmpBuf.append(']'); 1206 } 1207 } 1208 tmpBuf.append(spaces); 1209 } 1210 tmpBuf.endTag("td"); 1211 tmpBuf.endTag("tr"); 1212 return found ? tmpBuf : new Formatter(genHTML); 1213 } 1214 } 1215 1216 buf.beginTable(0); 1217 1218 OopMapValueIterator omvIterator = new OopMapValueIterator(); 1219 OopMapStream oms = new OopMapStream(map, OopMapValue.OopTypes.OOP_VALUE); 1220 buf.append(omvIterator.iterate(oms, "Oops:", false)); 1221 1222 oms = new OopMapStream(map, OopMapValue.OopTypes.NARROWOOP_VALUE); 1223 buf.append(omvIterator.iterate(oms, "NarrowOops:", false)); 1224 1225 oms = new OopMapStream(map, OopMapValue.OopTypes.VALUE_VALUE); 1226 buf.append(omvIterator.iterate(oms, "Values:", false)); 1227 1228 oms = new OopMapStream(map, OopMapValue.OopTypes.CALLEE_SAVED_VALUE); 1229 buf.append(omvIterator.iterate(oms, "Callee saved:", true)); 1230 1231 oms = new OopMapStream(map, OopMapValue.OopTypes.DERIVED_OOP_VALUE); 1232 buf.append(omvIterator.iterate(oms, "Derived oops:", true)); 1233 1234 buf.endTag("table"); 1235 return buf.toString(); 1236 } 1237 1238 1239 protected String genOopMapInfo(NMethod nmethod, PCDesc pcDesc) { 1240 OopMapSet mapSet = nmethod.getOopMaps(); 1241 if (mapSet == null || (mapSet.getSize() <= 0)) 1242 return ""; 1243 int pcOffset = pcDesc.getPCOffset(); 1244 OopMap map = mapSet.findMapAtOffset(pcOffset, VM.getVM().isDebugging()); 1245 if (map == null) { 1246 throw new IllegalArgumentException("no oopmap at safepoint!"); 1247 } 1248 1249 return genOopMapInfo(map); 1250 } 1251 1252 protected String genOopMapInfo(OopMap map) { 1253 Formatter buf = new Formatter(genHTML); 1254 buf.beginTag("pre"); 1255 buf.append("OopMap: "); 1256 buf.br(); 1257 buf.append(genHTMLForOopMap(map)); 1258 buf.endTag("pre"); 1259 1260 return buf.toString(); 1261 } 1262 1263 protected String locationAsString(Location loc) { 1264 Formatter buf = new Formatter(genHTML); 1265 if (loc.isIllegal()) { 1266 buf.append("illegal"); 1267 } else { 1268 Location.Where w = loc.getWhere(); 1269 Location.Type type = loc.getType(); 1270 1271 if (w == Location.Where.ON_STACK) { 1272 buf.append("stack[" + loc.getStackOffset() + "]"); 1273 } else if (w == Location.Where.IN_REGISTER) { 1274 boolean isFloat = (type == Location.Type.FLOAT_IN_DBL || 1275 type == Location.Type.DBL); 1276 int regNum = loc.getRegisterNumber(); 1277 VMReg vmReg = new VMReg(regNum); 1278 buf.append(VMRegImpl.getRegisterName(vmReg.getValue())); 1279 } 1280 1281 buf.append(", "); 1282 if (type == Location.Type.NORMAL) { 1283 buf.append("normal"); 1284 } else if (type == Location.Type.OOP) { 1285 buf.append("oop"); 1286 } else if (type == Location.Type.NARROWOOP) { 1287 buf.append("narrowoop"); 1288 } else if (type == Location.Type.INT_IN_LONG) { 1289 buf.append("int"); 1290 } else if (type == Location.Type.LNG) { 1291 buf.append("long"); 1292 } else if (type == Location.Type.FLOAT_IN_DBL) { 1293 buf.append("float"); 1294 } else if (type == Location.Type.DBL) { 1295 buf.append("double"); 1296 } else if (type == Location.Type.ADDR) { 1297 buf.append("address"); 1298 } else if (type == Location.Type.INVALID) { 1299 buf.append("invalid"); 1300 } 1301 } 1302 return buf.toString(); 1303 } 1304 1305 private String scopeValueAsString(ScopeDesc sd, ScopeValue sv) { 1306 Formatter buf = new Formatter(genHTML); 1307 if (sv.isConstantInt()) { 1308 buf.append("int "); 1309 ConstantIntValue intValue = (ConstantIntValue) sv; 1310 buf.append(Integer.toString(intValue.getValue())); 1311 } else if (sv.isConstantLong()) { 1312 buf.append("long "); 1313 ConstantLongValue longValue = (ConstantLongValue) sv; 1314 buf.append(Long.toString(longValue.getValue())); 1315 buf.append("L"); 1316 } else if (sv.isConstantDouble()) { 1317 buf.append("double "); 1318 ConstantDoubleValue dblValue = (ConstantDoubleValue) sv; 1319 buf.append(Double.toString(dblValue.getValue())); 1320 buf.append("D"); 1321 } else if (sv.isConstantOop()) { 1322 buf.append("oop "); 1323 ConstantOopReadValue oopValue = (ConstantOopReadValue) sv; 1324 OopHandle oopHandle = oopValue.getValue(); 1325 if (oopHandle != null) { 1326 buf.append(oopHandle.toString()); 1327 } else { 1328 buf.append("null"); 1329 } 1330 } else if (sv.isLocation()) { 1331 LocationValue lvalue = (LocationValue) sv; 1332 Location loc = lvalue.getLocation(); 1333 if (loc != null) { 1334 buf.append(locationAsString(loc)); 1335 } else { 1336 buf.append("null"); 1337 } 1338 } else if (sv.isObject()) { 1339 ObjectValue ov = (ObjectValue)sv; 1340 buf.append("#ScObj" + sd.getObjects().indexOf(ov)); 1341 } else { 1342 buf.append("unknown scope value " + sv); 1343 } 1344 return buf.toString(); 1345 } 1346 1347 protected String genHTMLForScopeValues(ScopeDesc sd, boolean locals, List values) { 1348 int length = values.size(); 1349 Formatter buf = new Formatter(genHTML); 1350 buf.append(locals? "locals " : "expressions "); 1351 for (int i = 0; i < length; i++) { 1352 ScopeValue sv = (ScopeValue) values.get(i); 1353 if (sv == null) { 1354 continue; 1355 } 1356 buf.append('('); 1357 if (locals) { 1358 Symbol name = sd.getMethod().getLocalVariableName(sd.getBCI(), i); 1359 if (name != null) { 1360 buf.append("'"); 1361 buf.append(name.asString()); 1362 buf.append('\''); 1363 } else { 1364 buf.append("["); 1365 buf.append(Integer.toString(i)); 1366 buf.append(']'); 1367 } 1368 } else { 1369 buf.append("["); 1370 buf.append(Integer.toString(i)); 1371 buf.append(']'); 1372 } 1373 1374 buf.append(", "); 1375 buf.append(scopeValueAsString(sd, sv)); 1376 buf.append(") "); 1377 } 1378 1379 return buf.toString(); 1380 } 1381 1382 protected String genHTMLForLocals(ScopeDesc sd, List locals) { 1383 return genHTMLForScopeValues(sd, true, locals); 1384 } 1385 1386 protected String genHTMLForExpressions(ScopeDesc sd, List expressions) { 1387 return genHTMLForScopeValues(sd, false, expressions); 1388 } 1389 1390 protected String genHTMLForMonitors(ScopeDesc sd, List monitors) { 1391 int length = monitors.size(); 1392 Formatter buf = new Formatter(genHTML); 1393 buf.append("monitors "); 1394 for (int i = 0; i < length; i++) { 1395 MonitorValue mv = (MonitorValue) monitors.get(i); 1396 if (mv == null) { 1397 continue; 1398 } 1399 buf.append("(owner = "); 1400 ScopeValue owner = mv.owner(); 1401 if (owner != null) { 1402 buf.append(scopeValueAsString(sd, owner)); 1403 } else { 1404 buf.append("null"); 1405 } 1406 buf.append(", lock = "); 1407 1408 Location loc = mv.basicLock(); 1409 if (loc != null) { 1410 buf.append(locationAsString(loc)); 1411 } else { 1412 buf.append("null"); 1413 } 1414 buf.append(") "); 1415 } 1416 return buf.toString(); 1417 } 1418 1419 public String genHTML(final NMethod nmethod) { 1420 try { 1421 final Formatter buf = new Formatter(genHTML); 1422 buf.genHTMLPrologue(genNMethodTitle(nmethod)); 1423 buf.h3("Method"); 1424 buf.append(genMethodAndKlassLink(nmethod.getMethod())); 1425 1426 buf.h3("Compiled Code"); 1427 Disassembler.decode(new HTMLDisassembler(buf, nmethod), nmethod); 1428 buf.genHTMLEpilogue(); 1429 return buf.toString(); 1430 } catch (Exception exp) { 1431 return genHTMLErrorMessage(exp); 1432 } 1433 } 1434 1435 public String genHTML(final CodeBlob blob) { 1436 try { 1437 final Formatter buf = new Formatter(genHTML); 1438 buf.genHTMLPrologue(genCodeBlobTitle(blob)); 1439 buf.h3("CodeBlob"); 1440 1441 buf.h3("Compiled Code"); 1442 Disassembler.decode(new HTMLDisassembler(buf, blob), blob); 1443 1444 buf.genHTMLEpilogue(); 1445 return buf.toString(); 1446 } catch (Exception exp) { 1447 return genHTMLErrorMessage(exp); 1448 } 1449 } 1450 1451 protected String genInterpreterCodeletTitle(InterpreterCodelet codelet) { 1452 Formatter buf = new Formatter(genHTML); 1453 buf.append("Interpreter codelet ["); 1454 buf.append(codelet.codeBegin().toString()); 1455 buf.append(','); 1456 buf.append(codelet.codeEnd().toString()); 1457 buf.append(") - "); 1458 buf.append(codelet.getDescription()); 1459 return buf.toString(); 1460 } 1461 1462 protected String genInterpreterCodeletLinkPageHref(StubQueue stubq) { 1463 return genBaseHref() + "interp_codelets"; 1464 } 1465 1466 public String genInterpreterCodeletLinksPage() { 1467 Formatter buf = new Formatter(genHTML); 1468 buf.genHTMLPrologue("Interpreter Codelets"); 1469 buf.beginTag("ul"); 1470 1471 Interpreter interp = VM.getVM().getInterpreter(); 1472 StubQueue code = interp.getCode(); 1473 InterpreterCodelet stub = (InterpreterCodelet) code.getFirst(); 1474 while (stub != null) { 1475 buf.beginTag("li"); 1476 sun.jvm.hotspot.debugger.Address addr = stub.codeBegin(); 1477 buf.link(genPCHref(addressToLong(addr)), stub.getDescription() + " @" + addr); 1478 buf.endTag("li"); 1479 stub = (InterpreterCodelet) code.getNext(stub); 1480 } 1481 1482 buf.endTag("ul"); 1483 buf.genHTMLEpilogue(); 1484 return buf.toString(); 1485 } 1486 1487 public String genHTML(InterpreterCodelet codelet) { 1488 Formatter buf = new Formatter(genHTML); 1489 buf.genHTMLPrologue(genInterpreterCodeletTitle(codelet)); 1490 Interpreter interp = VM.getVM().getInterpreter(); 1491 StubQueue stubq = interp.getCode(); 1492 1493 if (genHTML) { 1494 buf.beginTag("h3"); 1495 buf.link(genInterpreterCodeletLinkPageHref(stubq), "View links for all codelets"); 1496 buf.endTag("h3"); 1497 buf.br(); 1498 } 1499 1500 Stub prev = stubq.getPrev(codelet); 1501 if (prev != null) { 1502 if (genHTML) { 1503 buf.beginTag("h3"); 1504 buf.link(genPCHref(addressToLong(prev.codeBegin())), "View Previous Codelet"); 1505 buf.endTag("h3"); 1506 buf.br(); 1507 } else { 1508 buf.h3("Previous Codelet = 0x" + Long.toHexString(addressToLong(prev.codeBegin()))); 1509 } 1510 } 1511 1512 buf.h3("Code"); 1513 Disassembler.decode(new HTMLDisassembler(buf, null), null, 1514 codelet.codeBegin(), codelet.codeEnd()); 1515 1516 Stub next = stubq.getNext(codelet); 1517 if (next != null) { 1518 if (genHTML) { 1519 buf.beginTag("h3"); 1520 buf.link(genPCHref(addressToLong(next.codeBegin())), "View Next Codelet"); 1521 buf.endTag("h3"); 1522 } else { 1523 buf.h3("Next Codelet = 0x" + Long.toHexString(addressToLong(next.codeBegin()))); 1524 } 1525 } 1526 1527 buf.genHTMLEpilogue(); 1528 return buf.toString(); 1529 } 1530 1531 protected String genDumpKlassesTitle(InstanceKlass[] klasses) { 1532 return (klasses.length == 1) ? "Create .class for this class" 1533 : "Create .class for all classes"; 1534 } 1535 1536 protected String genDumpKlassesHref(InstanceKlass[] klasses) { 1537 StringBuffer buf = new StringBuffer(genBaseHref()); 1538 buf.append("jcore_multiple="); 1539 for (int k = 0; k < klasses.length; k++) { 1540 buf.append(klasses[k].getAddress().toString()); 1541 buf.append(','); 1542 } 1543 return buf.toString(); 1544 } 1545 1546 protected String genDumpKlassesLink(InstanceKlass[] klasses) { 1547 if (!genHTML) return ""; 1548 1549 Formatter buf = new Formatter(genHTML); 1550 buf.link(genDumpKlassesHref(klasses), genDumpKlassesTitle(klasses)); 1551 return buf.toString(); 1552 } 1553 1554 public String genHTMLForKlassNames(InstanceKlass[] klasses) { 1555 try { 1556 Formatter buf = new Formatter(genHTML); 1557 buf.genHTMLPrologue(); 1558 buf.h3(genDumpKlassesLink(klasses)); 1559 1560 buf.append(genHTMLListForKlassNames(klasses)); 1561 buf.genHTMLEpilogue(); 1562 return buf.toString(); 1563 } catch (Exception exp) { 1564 return genHTMLErrorMessage(exp); 1565 } 1566 } 1567 1568 protected String genHTMLListForKlassNames(InstanceKlass[] klasses) { 1569 final Formatter buf = new Formatter(genHTML); 1570 buf.beginTable(0); 1571 for (int i = 0; i < klasses.length; i++) { 1572 InstanceKlass ik = klasses[i]; 1573 buf.beginTag("tr"); 1574 buf.cell(genKlassLink(ik)); 1575 buf.endTag("tr"); 1576 } 1577 1578 buf.endTable(); 1579 return buf.toString(); 1580 } 1581 1582 public String genHTMLForMethodNames(InstanceKlass klass) { 1583 try { 1584 Formatter buf = new Formatter(genHTML); 1585 buf.genHTMLPrologue(); 1586 buf.append(genHTMLListForMethods(klass)); 1587 buf.genHTMLEpilogue(); 1588 return buf.toString(); 1589 } catch (Exception exp) { 1590 return genHTMLErrorMessage(exp); 1591 } 1592 } 1593 1594 protected String genHTMLListForMethods(InstanceKlass klass) { 1595 Formatter buf = new Formatter(genHTML); 1596 MethodArray methods = klass.getMethods(); 1597 int numMethods = methods.length(); 1598 if (numMethods != 0) { 1599 buf.h3("Methods"); 1600 buf.beginTag("ul"); 1601 for (int m = 0; m < numMethods; m++) { 1602 Method mtd = methods.at(m); 1603 buf.li(genMethodLink(mtd) + ";"); 1604 } 1605 buf.endTag("ul"); 1606 } 1607 return buf.toString(); 1608 } 1609 1610 protected String genHTMLListForInterfaces(InstanceKlass klass) { 1611 try { 1612 Formatter buf = new Formatter(genHTML); 1613 KlassArray interfaces = klass.getLocalInterfaces(); 1614 int numInterfaces = interfaces.length(); 1615 if (numInterfaces != 0) { 1616 buf.h3("Interfaces"); 1617 buf.beginTag("ul"); 1618 for (int i = 0; i < numInterfaces; i++) { 1619 InstanceKlass inf = (InstanceKlass) interfaces.getAt(i); 1620 buf.li(genKlassLink(inf)); 1621 } 1622 buf.endTag("ul"); 1623 } 1624 return buf.toString(); 1625 } catch (Exception exp) { 1626 return genHTMLErrorMessage(exp); 1627 } 1628 } 1629 1630 protected String genFieldModifierString(AccessFlags acc) { 1631 Formatter buf = new Formatter(genHTML); 1632 if (acc.isPrivate()) { 1633 buf.append("private "); 1634 } else if (acc.isProtected()) { 1635 buf.append("protected "); 1636 } else if (acc.isPublic()) { 1637 buf.append("public "); 1638 } 1639 1640 if (acc.isStatic()) { 1641 buf.append("static "); 1642 } 1643 1644 if (acc.isFinal()) { 1645 buf.append("final "); 1646 } 1647 if (acc.isVolatile()) { 1648 buf.append("volatile "); 1649 } 1650 if (acc.isTransient()) { 1651 buf.append("transient "); 1652 } 1653 1654 // javac generated flags 1655 if (acc.isSynthetic()) { 1656 buf.append("[synthetic] "); 1657 } 1658 return buf.toString(); 1659 } 1660 1661 public String genHTMLForFieldNames(InstanceKlass klass) { 1662 try { 1663 Formatter buf = new Formatter(genHTML); 1664 buf.genHTMLPrologue(); 1665 buf.append(genHTMLListForFields(klass)); 1666 buf.genHTMLEpilogue(); 1667 return buf.toString(); 1668 } catch (Exception exp) { 1669 return genHTMLErrorMessage(exp); 1670 } 1671 } 1672 1673 protected String genHTMLListForFields(InstanceKlass klass) { 1674 Formatter buf = new Formatter(genHTML); 1675 U2Array fields = klass.getFields(); 1676 int numFields = klass.getAllFieldsCount(); 1677 if (numFields != 0) { 1678 buf.h3("Fields"); 1679 buf.beginList(); 1680 for (int f = 0; f < numFields; f++) { 1681 sun.jvm.hotspot.oops.Field field = klass.getFieldByIndex(f); 1682 String f_name = ((NamedFieldIdentifier)field.getID()).getName(); 1683 Symbol f_sig = field.getSignature(); 1684 Symbol f_genSig = field.getGenericSignature(); 1685 AccessFlags acc = field.getAccessFlagsObj(); 1686 1687 buf.beginListItem(); 1688 buf.append(genFieldModifierString(acc)); 1689 buf.append(' '); 1690 Formatter sigBuf = new Formatter(genHTML); 1691 new SignatureConverter(f_sig, sigBuf.getBuffer()).dispatchField(); 1692 buf.append(sigBuf.toString().replace('/', '.')); 1693 buf.append(' '); 1694 buf.append(f_name); 1695 buf.append(';'); 1696 // is it generic? 1697 if (f_genSig != null) { 1698 buf.append(" [signature "); 1699 buf.append(escapeHTMLSpecialChars(f_genSig.asString())); 1700 buf.append("] "); 1701 } 1702 buf.append(" (offset = " + field.getOffset() + ")"); 1703 buf.endListItem(); 1704 } 1705 buf.endList(); 1706 } 1707 return buf.toString(); 1708 } 1709 1710 protected String genKlassHierarchyHref(InstanceKlass klass) { 1711 return genBaseHref() + "hierarchy=" + klass.getAddress(); 1712 } 1713 1714 protected String genKlassHierarchyTitle(InstanceKlass klass) { 1715 Formatter buf = new Formatter(genHTML); 1716 buf.append("Class Hierarchy of "); 1717 buf.append(genKlassTitle(klass)); 1718 return buf.toString(); 1719 } 1720 1721 protected String genKlassHierarchyLink(InstanceKlass klass) { 1722 Formatter buf = new Formatter(genHTML); 1723 buf.link(genKlassHierarchyHref(klass), genKlassHierarchyTitle(klass)); 1724 return buf.toString(); 1725 } 1726 1727 protected String genHTMLListForSubKlasses(InstanceKlass klass) { 1728 Formatter buf = new Formatter(genHTML); 1729 Klass subklass = klass.getSubklassKlass(); 1730 if (subklass != null) { 1731 buf.beginList(); 1732 while (subklass != null) { 1733 if (subklass instanceof InstanceKlass) { 1734 buf.li(genKlassLink((InstanceKlass)subklass)); 1735 } 1736 subklass = subklass.getNextSiblingKlass(); 1737 } 1738 buf.endList(); 1739 } 1740 return buf.toString(); 1741 } 1742 1743 public String genHTMLForKlassHierarchy(InstanceKlass klass) { 1744 Formatter buf = new Formatter(genHTML); 1745 buf.genHTMLPrologue(genKlassHierarchyTitle(klass)); 1746 1747 1748 buf.beginTag("pre"); 1749 buf.append(genKlassLink(klass)); 1750 buf.br(); 1751 StringBuffer tabs = new StringBuffer(tab); 1752 InstanceKlass superKlass = klass; 1753 while ( (superKlass = (InstanceKlass) superKlass.getSuper()) != null ) { 1754 buf.append(tabs); 1755 buf.append(genKlassLink(superKlass)); 1756 tabs.append(tab); 1757 buf.br(); 1758 } 1759 buf.endTag("pre"); 1760 1761 // generate subklass list 1762 Klass subklass = klass.getSubklassKlass(); 1763 if (subklass != null) { 1764 buf.h3("Direct Subclasses"); 1765 buf.append(genHTMLListForSubKlasses(klass)); 1766 } 1767 1768 buf.genHTMLEpilogue(); 1769 return buf.toString(); 1770 } 1771 1772 protected String genDumpKlassHref(InstanceKlass klass) { 1773 return genBaseHref() + "jcore=" + klass.getAddress(); 1774 } 1775 1776 protected String genDumpKlassLink(InstanceKlass klass) { 1777 if (!genHTML) return ""; 1778 1779 Formatter buf = new Formatter(genHTML); 1780 buf.link(genDumpKlassHref(klass), "Create .class File"); 1781 return buf.toString(); 1782 } 1783 1784 public String genHTML(InstanceKlass klass) { 1785 Formatter buf = new Formatter(genHTML); 1786 buf.genHTMLPrologue(genKlassTitle(klass)); 1787 InstanceKlass superKlass = (InstanceKlass) klass.getSuper(); 1788 1789 if (genHTML) { 1790 // super class tree and subclass list 1791 buf.beginTag("h3"); 1792 buf.link(genKlassHierarchyHref(klass), "View Class Hierarchy"); 1793 buf.endTag("h3"); 1794 } 1795 1796 // jcore - create .class link 1797 buf.h3(genDumpKlassLink(klass)); 1798 1799 // super class 1800 if (superKlass != null) { 1801 buf.h3("Super Class"); 1802 buf.append(genKlassLink(superKlass)); 1803 } 1804 1805 // interfaces 1806 buf.append(genHTMLListForInterfaces(klass)); 1807 1808 // fields 1809 buf.append(genHTMLListForFields(klass)); 1810 1811 // methods 1812 buf.append(genHTMLListForMethods(klass)); 1813 1814 // constant pool link 1815 buf.h3("Constant Pool"); 1816 buf.append(genConstantPoolLink(klass.getConstants())); 1817 1818 buf.genHTMLEpilogue(); 1819 return buf.toString(); 1820 } 1821 1822 protected sun.jvm.hotspot.debugger.Address parseAddress(String address) { 1823 VM vm = VM.getVM(); 1824 sun.jvm.hotspot.debugger.Address addr = vm.getDebugger().parseAddress(address); 1825 return addr; 1826 } 1827 1828 protected long addressToLong(sun.jvm.hotspot.debugger.Address addr) { 1829 return VM.getVM().getDebugger().getAddressValue(addr); 1830 } 1831 1832 protected sun.jvm.hotspot.debugger.Address longToAddress(long addr) { 1833 return parseAddress("0x" + Long.toHexString(addr)); 1834 } 1835 1836 protected Oop getOopAtAddress(sun.jvm.hotspot.debugger.Address addr) { 1837 OopHandle oopHandle = addr.addOffsetToAsOopHandle(0); 1838 return VM.getVM().getObjectHeap().newOop(oopHandle); 1839 } 1840 1841 protected Oop getOopAtAddress(String address) { 1842 sun.jvm.hotspot.debugger.Address addr = parseAddress(address); 1843 return getOopAtAddress(addr); 1844 } 1845 1846 protected Klass getKlassAtAddress(String address) { 1847 sun.jvm.hotspot.debugger.Address addr = parseAddress(address); 1848 return (Klass)Metadata.instantiateWrapperFor(addr); 1849 } 1850 1851 protected Method getMethodAtAddress(String address) { 1852 sun.jvm.hotspot.debugger.Address addr = parseAddress(address); 1853 return (Method)Metadata.instantiateWrapperFor(addr); 1854 } 1855 1856 protected ConstantPool getConstantPoolAtAddress(String address) { 1857 sun.jvm.hotspot.debugger.Address addr = parseAddress(address); 1858 return (ConstantPool) Metadata.instantiateWrapperFor(addr); 1859 } 1860 1861 private void dumpKlass(InstanceKlass kls) throws IOException { 1862 String klassName = kls.getName().asString(); 1863 klassName = klassName.replace('/', File.separatorChar); 1864 int index = klassName.lastIndexOf(File.separatorChar); 1865 File dir = null; 1866 if (index != -1) { 1867 String dirName = klassName.substring(0, index); 1868 dir = new File(DUMP_KLASS_OUTPUT_DIR, dirName); 1869 } else { 1870 dir = new File(DUMP_KLASS_OUTPUT_DIR); 1871 } 1872 1873 dir.mkdirs(); 1874 File f = new File(dir, klassName.substring(klassName.lastIndexOf(File.separatorChar) + 1) 1875 + ".class"); 1876 f.createNewFile(); 1877 FileOutputStream fis = new FileOutputStream(f); 1878 ClassWriter cw = new ClassWriter(kls, fis); 1879 cw.write(); 1880 } 1881 1882 public String genDumpKlass(InstanceKlass kls) { 1883 try { 1884 dumpKlass(kls); 1885 Formatter buf = new Formatter(genHTML); 1886 buf.genHTMLPrologue(genKlassTitle(kls)); 1887 buf.append(".class created for "); 1888 buf.append(genKlassLink(kls)); 1889 buf.genHTMLEpilogue(); 1890 return buf.toString(); 1891 } catch(IOException exp) { 1892 return genHTMLErrorMessage(exp); 1893 } 1894 } 1895 1896 protected String genJavaStackTraceTitle(JavaThread thread) { 1897 Formatter buf = new Formatter(genHTML); 1898 buf.append("Java Stack Trace for "); 1899 buf.append(thread.getThreadName()); 1900 return buf.toString(); 1901 } 1902 1903 public String genHTMLForJavaStackTrace(JavaThread thread) { 1904 Formatter buf = new Formatter(genHTML); 1905 buf.genHTMLPrologue(genJavaStackTraceTitle(thread)); 1906 1907 buf.append("Thread state = "); 1908 buf.append(thread.getThreadState().toString()); 1909 buf.br(); 1910 buf.beginTag("pre"); 1911 for (JavaVFrame vf = thread.getLastJavaVFrameDbg(); vf != null; vf = vf.javaSender()) { 1912 Method method = vf.getMethod(); 1913 buf.append(" - "); 1914 buf.append(genMethodLink(method)); 1915 buf.append(" @bci = " + vf.getBCI()); 1916 1917 int lineNumber = method.getLineNumberFromBCI(vf.getBCI()); 1918 if (lineNumber != -1) { 1919 buf.append(", line = "); 1920 buf.append(lineNumber); 1921 } 1922 1923 sun.jvm.hotspot.debugger.Address pc = vf.getFrame().getPC(); 1924 if (pc != null) { 1925 buf.append(", pc = "); 1926 buf.link(genPCHref(addressToLong(pc)), pc.toString()); 1927 } 1928 1929 if (vf.isCompiledFrame()) { 1930 buf.append(" (Compiled"); 1931 } 1932 else if (vf.isInterpretedFrame()) { 1933 buf.append(" (Interpreted"); 1934 } 1935 1936 if (vf.mayBeImpreciseDbg()) { 1937 buf.append("; information may be imprecise"); 1938 } 1939 buf.append(")"); 1940 buf.br(); 1941 } 1942 1943 buf.endTag("pre"); 1944 buf.genHTMLEpilogue(); 1945 return buf.toString(); 1946 } 1947 1948 public String genHTMLForHyperlink(String href) { 1949 if (href.startsWith("klass=")) { 1950 href = href.substring(href.indexOf('=') + 1); 1951 Klass k = getKlassAtAddress(href); 1952 if (Assert.ASSERTS_ENABLED) { 1953 Assert.that(k instanceof InstanceKlass, "class= href with improper InstanceKlass!"); 1954 } 1955 return genHTML((InstanceKlass) k); 1956 } else if (href.startsWith("method=")) { 1957 href = href.substring(href.indexOf('=') + 1); 1958 Method obj = getMethodAtAddress(href); 1959 if (Assert.ASSERTS_ENABLED) { 1960 Assert.that(obj instanceof Method, "method= href with improper Method!"); 1961 } 1962 return genHTML(obj); 1963 } else if (href.startsWith("nmethod=")) { 1964 String addr = href.substring(href.indexOf('=') + 1); 1965 Object obj = VMObjectFactory.newObject(NMethod.class, parseAddress(addr)); 1966 if (Assert.ASSERTS_ENABLED) { 1967 Assert.that(obj instanceof NMethod, "nmethod= href with improper NMethod!"); 1968 } 1969 return genHTML((NMethod) obj); 1970 } else if (href.startsWith("pc=")) { 1971 String address = href.substring(href.indexOf('=') + 1); 1972 return genHTML(parseAddress(address)); 1973 } else if (href.startsWith("pc_multiple=")) { 1974 int indexOfComma = href.indexOf(','); 1975 if (indexOfComma == -1) { 1976 String firstPC = href.substring(href.indexOf('=') + 1); 1977 return genHTMLForRawDisassembly(parseAddress(firstPC), null); 1978 } else { 1979 String firstPC = href.substring(href.indexOf('=') + 1, indexOfComma); 1980 return genHTMLForRawDisassembly(parseAddress(firstPC), href.substring(indexOfComma + 1)); 1981 } 1982 } else if (href.startsWith("interp_codelets")) { 1983 return genInterpreterCodeletLinksPage(); 1984 } else if (href.startsWith("hierarchy=")) { 1985 href = href.substring(href.indexOf('=') + 1); 1986 Klass obj = getKlassAtAddress(href); 1987 if (Assert.ASSERTS_ENABLED) { 1988 Assert.that(obj instanceof InstanceKlass, "class= href with improper InstanceKlass!"); 1989 } 1990 return genHTMLForKlassHierarchy((InstanceKlass) obj); 1991 } else if (href.startsWith("cpool=")) { 1992 href = href.substring(href.indexOf('=') + 1); 1993 ConstantPool obj = getConstantPoolAtAddress(href); 1994 if (Assert.ASSERTS_ENABLED) { 1995 Assert.that(obj instanceof ConstantPool, "cpool= href with improper ConstantPool!"); 1996 } 1997 return genHTML(obj); 1998 } else if (href.startsWith("jcore=")) { 1999 href = href.substring(href.indexOf('=') + 1); 2000 Klass obj = getKlassAtAddress(href); 2001 if (Assert.ASSERTS_ENABLED) { 2002 Assert.that(obj instanceof InstanceKlass, "jcore= href with improper InstanceKlass!"); 2003 } 2004 return genDumpKlass((InstanceKlass) obj); 2005 } else if (href.startsWith("jcore_multiple=")) { 2006 href = href.substring(href.indexOf('=') + 1); 2007 Formatter buf = new Formatter(genHTML); 2008 buf.genHTMLPrologue(); 2009 StringTokenizer st = new StringTokenizer(href, ","); 2010 while (st.hasMoreTokens()) { 2011 Klass obj = getKlassAtAddress(st.nextToken()); 2012 if (Assert.ASSERTS_ENABLED) { 2013 Assert.that(obj instanceof InstanceKlass, "jcore_multiple= href with improper InstanceKlass!"); 2014 } 2015 2016 InstanceKlass kls = (InstanceKlass) obj; 2017 try { 2018 dumpKlass(kls); 2019 buf.append(".class created for "); 2020 buf.append(genKlassLink(kls)); 2021 } catch(Exception exp) { 2022 buf.bold("can't .class for " + 2023 genKlassTitle(kls) + 2024 " : " + 2025 exp.getMessage()); 2026 } 2027 buf.br(); 2028 } 2029 2030 buf.genHTMLEpilogue(); 2031 return buf.toString(); 2032 } else { 2033 if (Assert.ASSERTS_ENABLED) { 2034 Assert.that(false, "unknown href link!"); 2035 } 2036 return null; 2037 } 2038 } 2039 }