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