1 /*
   2  * Copyright (c) 2010, 2015, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.ir.debug;
  27 
  28 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT;
  29 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.FLAGS_MASK;
  30 
  31 import java.io.File;
  32 import java.io.FileNotFoundException;
  33 import java.io.FileOutputStream;
  34 import java.io.PrintWriter;
  35 import java.util.HashMap;
  36 import java.util.HashSet;
  37 import java.util.Iterator;
  38 import java.util.LinkedHashSet;
  39 import java.util.List;
  40 import java.util.Map;
  41 import java.util.Set;
  42 import jdk.internal.org.objectweb.asm.Attribute;
  43 import jdk.internal.org.objectweb.asm.Handle;
  44 import jdk.internal.org.objectweb.asm.Label;
  45 import jdk.internal.org.objectweb.asm.Opcodes;
  46 import jdk.internal.org.objectweb.asm.Type;
  47 import jdk.internal.org.objectweb.asm.signature.SignatureReader;
  48 import jdk.internal.org.objectweb.asm.util.Printer;
  49 import jdk.internal.org.objectweb.asm.util.TraceSignatureVisitor;
  50 import jdk.nashorn.internal.runtime.ScriptEnvironment;
  51 import jdk.nashorn.internal.runtime.linker.Bootstrap;
  52 import jdk.nashorn.internal.runtime.linker.NameCodec;
  53 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
  54 
  55 /**
  56  * Pretty printer for --print-code.
  57  * Also supports dot formats if --print-code has arguments
  58  */
  59 public final class NashornTextifier extends Printer {
  60     private static final String BOOTSTRAP_CLASS_NAME = Bootstrap.class.getName().replace('.', '/');
  61 
  62     private String currentClassName;
  63     private Iterator<Label> labelIter;
  64     private Graph graph;
  65     private String currentBlock;
  66 
  67     // Following variables are used to govern the state of collapsing long sequences of NOP.
  68     /** True if the last instruction was a NOP. */
  69     private boolean lastWasNop = false;
  70     /** True if ellipse ("...") was emitted in place of a second NOP. */
  71     private boolean lastWasEllipse = false;
  72 
  73     private static final int INTERNAL_NAME = 0;
  74     private static final int FIELD_DESCRIPTOR = 1;
  75     private static final int FIELD_SIGNATURE = 2;
  76     private static final int METHOD_DESCRIPTOR = 3;
  77     private static final int METHOD_SIGNATURE = 4;
  78     private static final int CLASS_SIGNATURE = 5;
  79 
  80     private final String tab = "  ";
  81     private final String tab2 = "    ";
  82     private final String tab3 = "      ";
  83 
  84     private Map<Label, String> labelNames;
  85 
  86     private boolean localVarsStarted = false;
  87 
  88     private NashornClassReader cr;
  89     private ScriptEnvironment env;
  90 
  91     /**
  92      * Constructs a new {@link NashornTextifier}. <i>Subclasses must not use this
  93      * constructor</i>. Instead, they must use the {@link #NashornTextifier(int)}
  94      * version.
  95      * @param env script environment
  96      * @param cr a customized classreader for gathering, among other things, label
  97      * information
  98      */
  99     public NashornTextifier(final ScriptEnvironment env, final NashornClassReader cr) {
 100         this(Opcodes.ASM5);
 101         this.env = env;
 102         this.cr = cr;
 103     }
 104 
 105     private NashornTextifier(final ScriptEnvironment env, final NashornClassReader cr, final Iterator<Label> labelIter, final Graph graph) {
 106         this(env, cr);
 107         this.labelIter = labelIter;
 108         this.graph = graph;
 109     }
 110 
 111     /**
 112      * Constructs a new {@link NashornTextifier}.
 113      *
 114      * @param api
 115      *            the ASM API version implemented by this visitor. Must be one
 116      *            of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
 117      */
 118     protected NashornTextifier(final int api) {
 119         super(api);
 120     }
 121 
 122     @Override
 123     public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
 124         final int major = version & 0xFFFF;
 125         final int minor = version >>> 16;
 126 
 127         currentClassName = name;
 128 
 129         final StringBuilder sb = new StringBuilder();
 130         sb.append("// class version ").
 131             append(major).
 132             append('.').
 133             append(minor).append(" (").
 134             append(version).
 135             append(")\n");
 136 
 137         if ((access & Opcodes.ACC_DEPRECATED) != 0) {
 138             sb.append("// DEPRECATED\n");
 139         }
 140 
 141         sb.append("// access flags 0x"). //TODO TRANSLATE TO WHAT THEY MEAN
 142             append(Integer.toHexString(access).toUpperCase()).
 143             append('\n');
 144 
 145         appendDescriptor(sb, CLASS_SIGNATURE, signature);
 146         if (signature != null) {
 147             final TraceSignatureVisitor sv = new TraceSignatureVisitor(access);
 148             final SignatureReader r = new SignatureReader(signature);
 149             r.accept(sv);
 150             sb.append("// declaration: ").
 151                 append(name).
 152                 append(sv.getDeclaration()).
 153                 append('\n');
 154         }
 155 
 156         appendAccess(sb, access & ~Opcodes.ACC_SUPER);
 157         if ((access & Opcodes.ACC_ANNOTATION) != 0) {
 158             sb.append("@interface ");
 159         } else if ((access & Opcodes.ACC_INTERFACE) != 0) {
 160             sb.append("interface ");
 161         } else if ((access & Opcodes.ACC_ENUM) == 0) {
 162             sb.append("class ");
 163         }
 164         appendDescriptor(sb, INTERNAL_NAME, name);
 165 
 166         if (superName != null && !"java/lang/Object".equals(superName)) {
 167             sb.append(" extends ");
 168             appendDescriptor(sb, INTERNAL_NAME, superName);
 169             sb.append(' ');
 170         }
 171         if (interfaces != null && interfaces.length > 0) {
 172             sb.append(" implements ");
 173             for (final String interface1 : interfaces) {
 174                 appendDescriptor(sb, INTERNAL_NAME, interface1);
 175                 sb.append(' ');
 176             }
 177         }
 178         sb.append(" {\n");
 179 
 180         addText(sb);
 181     }
 182 
 183     @Override
 184     public void visitSource(final String file, final String debug) {
 185         final StringBuilder sb = new StringBuilder();
 186         if (file != null) {
 187             sb.append(tab).
 188                 append("// compiled from: ").
 189                 append(file).
 190                 append('\n');
 191         }
 192         if (debug != null) {
 193             sb.append(tab).
 194                 append("// debug info: ").
 195                 append(debug).
 196                 append('\n');
 197         }
 198         if (sb.length() > 0) {
 199             addText(sb);
 200         }
 201     }
 202 
 203     @Override
 204     public void visitOuterClass(final String owner, final String name, final String desc) {
 205         final StringBuilder sb = new StringBuilder();
 206         sb.append(tab).append("outer class ");
 207         appendDescriptor(sb, INTERNAL_NAME, owner);
 208         sb.append(' ');
 209         if (name != null) {
 210             sb.append(name).append(' ');
 211         }
 212         appendDescriptor(sb, METHOD_DESCRIPTOR, desc);
 213         sb.append('\n');
 214         addText(sb);
 215     }
 216 
 217     @Override
 218     public NashornTextifier visitField(final int access, final String name, final String desc, final String signature, final Object value) {
 219         final StringBuilder sb = new StringBuilder();
 220 //        sb.append('\n');
 221         if ((access & Opcodes.ACC_DEPRECATED) != 0) {
 222             sb.append(tab).append("// DEPRECATED\n");
 223         }
 224 
 225 /*        sb.append(tab).
 226             append("// access flags 0x").
 227             append(Integer.toHexString(access).toUpperCase()).
 228             append('\n');
 229 */
 230 
 231         if (signature != null) {
 232             sb.append(tab);
 233             appendDescriptor(sb, FIELD_SIGNATURE, signature);
 234 
 235             final TraceSignatureVisitor sv = new TraceSignatureVisitor(0);
 236             final SignatureReader r = new SignatureReader(signature);
 237             r.acceptType(sv);
 238             sb.append(tab).
 239                 append("// declaration: ").
 240                 append(sv.getDeclaration()).
 241                 append('\n');
 242         }
 243 
 244         sb.append(tab);
 245         appendAccess(sb, access);
 246 
 247         final String prunedDesc = desc.endsWith(";") ? desc.substring(0, desc.length() - 1) : desc;
 248         appendDescriptor(sb, FIELD_DESCRIPTOR, prunedDesc);
 249         sb.append(' ').append(name);
 250         if (value != null) {
 251             sb.append(" = ");
 252             if (value instanceof String) {
 253                 sb.append('\"').append(value).append('\"');
 254             } else {
 255                 sb.append(value);
 256             }
 257         }
 258 
 259         sb.append(";\n");
 260         addText(sb);
 261 
 262         final NashornTextifier t = createNashornTextifier();
 263         addText(t.getText());
 264 
 265         return t;
 266     }
 267 
 268     @Override
 269     public NashornTextifier visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
 270 
 271         graph = new Graph(name);
 272 
 273         final List<Label> extraLabels = cr.getExtraLabels(currentClassName, name, desc);
 274         this.labelIter = extraLabels == null ? null : extraLabels.iterator();
 275 
 276         final StringBuilder sb = new StringBuilder();
 277 
 278         sb.append('\n');
 279         if ((access & Opcodes.ACC_DEPRECATED) != 0) {
 280             sb.append(tab).
 281                 append("// DEPRECATED\n");
 282         }
 283 
 284         sb.append(tab).
 285             append("// access flags 0x").
 286             append(Integer.toHexString(access).toUpperCase()).
 287             append('\n');
 288 
 289         if (signature != null) {
 290             sb.append(tab);
 291             appendDescriptor(sb, METHOD_SIGNATURE, signature);
 292 
 293             final TraceSignatureVisitor v = new TraceSignatureVisitor(0);
 294             final SignatureReader r = new SignatureReader(signature);
 295             r.accept(v);
 296             final String genericDecl = v.getDeclaration();
 297             final String genericReturn = v.getReturnType();
 298             final String genericExceptions = v.getExceptions();
 299 
 300             sb.append(tab).
 301                 append("// declaration: ").
 302                 append(genericReturn).
 303                 append(' ').
 304                 append(name).
 305                 append(genericDecl);
 306 
 307             if (genericExceptions != null) {
 308                 sb.append(" throws ").append(genericExceptions);
 309             }
 310             sb.append('\n');
 311         }
 312 
 313         sb.append(tab);
 314         appendAccess(sb, access);
 315         if ((access & Opcodes.ACC_NATIVE) != 0) {
 316             sb.append("native ");
 317         }
 318         if ((access & Opcodes.ACC_VARARGS) != 0) {
 319             sb.append("varargs ");
 320         }
 321         if ((access & Opcodes.ACC_BRIDGE) != 0) {
 322             sb.append("bridge ");
 323         }
 324 
 325         sb.append(name);
 326         appendDescriptor(sb, METHOD_DESCRIPTOR, desc);
 327         if (exceptions != null && exceptions.length > 0) {
 328             sb.append(" throws ");
 329             for (final String exception : exceptions) {
 330                 appendDescriptor(sb, INTERNAL_NAME, exception);
 331                 sb.append(' ');
 332             }
 333         }
 334 
 335         sb.append('\n');
 336         addText(sb);
 337 
 338         final NashornTextifier t = createNashornTextifier();
 339         addText(t.getText());
 340         return t;
 341     }
 342 
 343     @Override
 344     public void visitClassEnd() {
 345         addText("}\n");
 346     }
 347 
 348     @Override
 349     public void visitFieldEnd() {
 350         //empty
 351     }
 352 
 353     @Override
 354     public void visitParameter(final String name, final int access) {
 355         final StringBuilder sb = new StringBuilder();
 356         sb.append(tab2).append("// parameter ");
 357         appendAccess(sb, access);
 358         sb.append(' ').append(name == null ? "<no name>" : name)
 359                 .append('\n');
 360         addText(sb);
 361     }
 362 
 363     @Override
 364     public void visitCode() {
 365         //empty
 366     }
 367 
 368     @Override
 369     public void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack) {
 370         final StringBuilder sb = new StringBuilder();
 371         sb.append("frame ");
 372         switch (type) {
 373         case Opcodes.F_NEW:
 374         case Opcodes.F_FULL:
 375             sb.append("full [");
 376             appendFrameTypes(sb, nLocal, local);
 377             sb.append("] [");
 378             appendFrameTypes(sb, nStack, stack);
 379             sb.append(']');
 380             break;
 381         case Opcodes.F_APPEND:
 382             sb.append("append [");
 383             appendFrameTypes(sb, nLocal, local);
 384             sb.append(']');
 385             break;
 386         case Opcodes.F_CHOP:
 387             sb.append("chop ").append(nLocal);
 388             break;
 389         case Opcodes.F_SAME:
 390             sb.append("same");
 391             break;
 392         case Opcodes.F_SAME1:
 393             sb.append("same1 ");
 394             appendFrameTypes(sb, 1, stack);
 395             break;
 396         default:
 397             assert false;
 398             break;
 399         }
 400         sb.append('\n');
 401         sb.append('\n');
 402         addText(sb);
 403     }
 404 
 405     private StringBuilder appendOpcode(final StringBuilder sb, final int opcode) {
 406         final Label next = getNextLabel();
 407         if (next instanceof NashornLabel) {
 408             final int bci = next.getOffset();
 409             if (bci != -1) {
 410                 final String bcis = "" + bci;
 411                 for (int i = 0; i < 5 - bcis.length(); i++) {
 412                     sb.append(' ');
 413                 }
 414                 sb.append(bcis);
 415                 sb.append(' ');
 416             } else {
 417                 sb.append("       ");
 418             }
 419         }
 420 
 421         return sb.append(tab2).append(OPCODES[opcode].toLowerCase());
 422     }
 423 
 424     private Label getNextLabel() {
 425         return labelIter == null ? null : labelIter.next();
 426     }
 427 
 428     @Override
 429     public void visitInsn(final int opcode) {
 430         if(opcode == Opcodes.NOP) {
 431             if(lastWasEllipse) {
 432                 getNextLabel();
 433                 return;
 434             } else if(lastWasNop) {
 435                 getNextLabel();
 436                 addText("          ...\n");
 437                 lastWasEllipse = true;
 438                 return;
 439             } else {
 440                 lastWasNop = true;
 441             }
 442         } else {
 443             lastWasNop = lastWasEllipse = false;
 444         }
 445         final StringBuilder sb = new StringBuilder();
 446         appendOpcode(sb, opcode).append('\n');
 447         addText(sb);
 448         checkNoFallThru(opcode, null);
 449     }
 450 
 451     @Override
 452     public void visitIntInsn(final int opcode, final int operand) {
 453         final StringBuilder sb = new StringBuilder();
 454         appendOpcode(sb, opcode)
 455                 .append(' ')
 456                 .append(opcode == Opcodes.NEWARRAY ? TYPES[operand] : Integer
 457                         .toString(operand)).append('\n');
 458         addText(sb);
 459     }
 460 
 461     @Override
 462     public void visitVarInsn(final int opcode, final int var) {
 463         final StringBuilder sb = new StringBuilder();
 464         appendOpcode(sb, opcode).append(' ').append(var).append('\n');
 465         addText(sb);
 466     }
 467 
 468     @Override
 469     public void visitTypeInsn(final int opcode, final String type) {
 470         final StringBuilder sb = new StringBuilder();
 471         appendOpcode(sb, opcode).append(' ');
 472         appendDescriptor(sb, INTERNAL_NAME, type);
 473         sb.append('\n');
 474         addText(sb);
 475     }
 476 
 477     @Override
 478     public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
 479         final StringBuilder sb = new StringBuilder();
 480         appendOpcode(sb, opcode).append(' ');
 481         appendDescriptor(sb, INTERNAL_NAME, owner);
 482         sb.append('.').append(name).append(" : ");
 483         appendDescriptor(sb, FIELD_DESCRIPTOR, desc);
 484         sb.append('\n');
 485         addText(sb);
 486     }
 487 
 488     @Override
 489     public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) {
 490         final StringBuilder sb = new StringBuilder();
 491         appendOpcode(sb, opcode).append(' ');
 492         appendDescriptor(sb, INTERNAL_NAME, owner);
 493         sb.append('.').append(name);
 494         appendDescriptor(sb, METHOD_DESCRIPTOR, desc);
 495         sb.append('\n');
 496         addText(sb);
 497     }
 498 
 499     @Override
 500     public void visitInvokeDynamicInsn(final String name, final String desc, final Handle bsm, final Object... bsmArgs) {
 501         final StringBuilder sb = new StringBuilder();
 502 
 503         appendOpcode(sb, Opcodes.INVOKEDYNAMIC).append(' ');
 504         final boolean isNashornBootstrap = isNashornBootstrap(bsm);
 505         if (isNashornBootstrap) {
 506             sb.append(NashornCallSiteDescriptor.getOperationName((Integer)bsmArgs[0]));
 507             final String decodedName = NameCodec.decode(name);
 508             if (!decodedName.isEmpty()) {
 509                 sb.append(':').append(decodedName);
 510             }
 511         } else {
 512             sb.append(name);
 513         }
 514         appendDescriptor(sb, METHOD_DESCRIPTOR, desc);
 515         final int len = sb.length();
 516         for (int i = 0; i < 80 - len ; i++) {
 517             sb.append(' ');
 518         }
 519         sb.append(" [");
 520         appendHandle(sb, bsm);
 521         if (bsmArgs.length == 0) {
 522             sb.append("none");
 523         } else {
 524             for (final Object cst : bsmArgs) {
 525                 if (cst instanceof String) {
 526                     appendStr(sb, (String)cst);
 527                 } else if (cst instanceof Type) {
 528                     sb.append(((Type)cst).getDescriptor()).append(".class");
 529                 } else if (cst instanceof Handle) {
 530                     appendHandle(sb, (Handle)cst);
 531                 } else if (cst instanceof Integer && isNashornBootstrap) {
 532                     final int c = (Integer)cst;
 533                     final int pp = c >> CALLSITE_PROGRAM_POINT_SHIFT;
 534                     if (pp != 0) {
 535                         sb.append(" pp=").append(pp);
 536                     }
 537                     sb.append(NashornCallSiteDescriptor.toString(c & FLAGS_MASK));
 538                 } else {
 539                     sb.append(cst);
 540                 }
 541                 sb.append(", ");
 542             }
 543             sb.setLength(sb.length() - 2);
 544         }
 545 
 546         sb.append("]\n");
 547         addText(sb);
 548     }
 549 
 550     private static boolean isNashornBootstrap(final Handle bsm) {
 551         return "bootstrap".equals(bsm.getName()) && BOOTSTRAP_CLASS_NAME.equals(bsm.getOwner());
 552     }
 553 
 554     private static boolean noFallThru(final int opcode) {
 555         switch (opcode) {
 556         case Opcodes.GOTO:
 557         case Opcodes.ATHROW:
 558         case Opcodes.ARETURN:
 559         case Opcodes.IRETURN:
 560         case Opcodes.LRETURN:
 561         case Opcodes.FRETURN:
 562         case Opcodes.DRETURN:
 563             return true;
 564         default:
 565             return false;
 566         }
 567     }
 568 
 569     private void checkNoFallThru(final int opcode, final String to) {
 570         if (noFallThru(opcode)) {
 571             graph.setNoFallThru(currentBlock);
 572         }
 573 
 574         if (currentBlock != null && to != null) {
 575             graph.addEdge(currentBlock, to);
 576         }
 577     }
 578 
 579     @Override
 580     public void visitJumpInsn(final int opcode, final Label label) {
 581         final StringBuilder sb = new StringBuilder();
 582         appendOpcode(sb, opcode).append(' ');
 583         final String to = appendLabel(sb, label);
 584         sb.append('\n');
 585         addText(sb);
 586         checkNoFallThru(opcode, to);
 587     }
 588 
 589     private void addText(final Object t) {
 590         text.add(t);
 591         if (currentBlock != null) {
 592             graph.addText(currentBlock, t.toString());
 593         }
 594     }
 595 
 596     @Override
 597     public void visitLabel(final Label label) {
 598         final StringBuilder sb = new StringBuilder();
 599         sb.append("\n");
 600         final String name = appendLabel(sb, label);
 601         sb.append(" [bci=");
 602         sb.append(label.info);
 603         sb.append("]");
 604         sb.append("\n");
 605 
 606         graph.addNode(name);
 607         if (currentBlock != null && !graph.isNoFallThru(currentBlock)) {
 608             graph.addEdge(currentBlock, name);
 609         }
 610         currentBlock = name;
 611         addText(sb);
 612     }
 613 
 614     @Override
 615     public void visitLdcInsn(final Object cst) {
 616         final StringBuilder sb = new StringBuilder();
 617         appendOpcode(sb, Opcodes.LDC).append(' ');
 618         if (cst instanceof String) {
 619             appendStr(sb, (String) cst);
 620         } else if (cst instanceof Type) {
 621             sb.append(((Type) cst).getDescriptor()).append(".class");
 622         } else {
 623             sb.append(cst);
 624         }
 625         sb.append('\n');
 626         addText(sb);
 627     }
 628 
 629     @Override
 630     public void visitIincInsn(final int var, final int increment) {
 631         final StringBuilder sb = new StringBuilder();
 632         appendOpcode(sb, Opcodes.IINC).append(' ');
 633         sb.append(var).append(' ')
 634                 .append(increment).append('\n');
 635         addText(sb);
 636     }
 637 
 638     @Override
 639     public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) {
 640         final StringBuilder sb = new StringBuilder();
 641         appendOpcode(sb, Opcodes.TABLESWITCH).append(' ');
 642         for (int i = 0; i < labels.length; ++i) {
 643             sb.append(tab3).append(min + i).append(": ");
 644             final String to = appendLabel(sb, labels[i]);
 645             graph.addEdge(currentBlock, to);
 646             sb.append('\n');
 647         }
 648         sb.append(tab3).append("default: ");
 649         appendLabel(sb, dflt);
 650         sb.append('\n');
 651         addText(sb);
 652     }
 653 
 654     @Override
 655     public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
 656         final StringBuilder sb = new StringBuilder();
 657         appendOpcode(sb, Opcodes.LOOKUPSWITCH).append(' ');
 658         for (int i = 0; i < labels.length; ++i) {
 659             sb.append(tab3).append(keys[i]).append(": ");
 660             final String to = appendLabel(sb, labels[i]);
 661             graph.addEdge(currentBlock, to);
 662             sb.append('\n');
 663         }
 664         sb.append(tab3).append("default: ");
 665         final String to = appendLabel(sb, dflt);
 666         graph.addEdge(currentBlock, to);
 667         sb.append('\n');
 668         addText(sb.toString());
 669     }
 670 
 671     @Override
 672     public void visitMultiANewArrayInsn(final String desc, final int dims) {
 673         final StringBuilder sb = new StringBuilder();
 674         appendOpcode(sb, Opcodes.MULTIANEWARRAY).append(' ');
 675         appendDescriptor(sb, FIELD_DESCRIPTOR, desc);
 676         sb.append(' ').append(dims).append('\n');
 677         addText(sb);
 678     }
 679 
 680     @Override
 681     public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) {
 682         final StringBuilder sb = new StringBuilder();
 683         sb.append(tab2).append("try ");
 684         final String from = appendLabel(sb, start);
 685         sb.append(' ');
 686         appendLabel(sb, end);
 687         sb.append(' ');
 688         final String to = appendLabel(sb, handler);
 689         sb.append(' ');
 690         appendDescriptor(sb, INTERNAL_NAME, type);
 691         sb.append('\n');
 692         addText(sb);
 693         graph.setIsCatch(to, type);
 694         graph.addTryCatch(from, to);
 695     }
 696 
 697     @Override
 698     public void visitLocalVariable(final String name, final String desc,final String signature, final Label start, final Label end, final int index) {
 699 
 700         final StringBuilder sb = new StringBuilder();
 701         if (!localVarsStarted) {
 702             text.add("\n");
 703             localVarsStarted = true;
 704             graph.addNode("vars");
 705             currentBlock = "vars";
 706         }
 707 
 708         sb.append(tab2).append("local ").append(name).append(' ');
 709         final int len = sb.length();
 710         for (int i = 0; i < 25 - len; i++) {
 711             sb.append(' ');
 712         }
 713         String label;
 714 
 715         label = appendLabel(sb, start);
 716         for (int i = 0; i < 5 - label.length(); i++) {
 717             sb.append(' ');
 718         }
 719         label = appendLabel(sb, end);
 720         for (int i = 0; i < 5 - label.length(); i++) {
 721             sb.append(' ');
 722         }
 723 
 724         sb.append(index).append(tab2);
 725 
 726         appendDescriptor(sb, FIELD_DESCRIPTOR, desc);
 727         sb.append('\n');
 728 
 729         if (signature != null) {
 730             sb.append(tab2);
 731             appendDescriptor(sb, FIELD_SIGNATURE, signature);
 732 
 733             final TraceSignatureVisitor sv = new TraceSignatureVisitor(0);
 734             final SignatureReader r = new SignatureReader(signature);
 735             r.acceptType(sv);
 736             sb.append(tab2).append("// declaration: ")
 737                     .append(sv.getDeclaration()).append('\n');
 738         }
 739         addText(sb.toString());
 740     }
 741 
 742     @Override
 743     public void visitLineNumber(final int line, final Label start) {
 744         final StringBuilder sb = new StringBuilder();
 745         sb.append("<line ");
 746         sb.append(line);
 747         sb.append(">\n");
 748         addText(sb.toString());
 749     }
 750 
 751     @Override
 752     public void visitMaxs(final int maxStack, final int maxLocals) {
 753         final StringBuilder sb = new StringBuilder();
 754         sb.append('\n');
 755         sb.append(tab2).append("max stack  = ").append(maxStack);
 756         sb.append(", max locals = ").append(maxLocals).append('\n');
 757         addText(sb.toString());
 758     }
 759 
 760     private void printToDir(final Graph g) {
 761         if (env._print_code_dir != null) {
 762             final File dir = new File(env._print_code_dir);
 763             if (!dir.exists() && !dir.mkdirs()) {
 764                 throw new RuntimeException(dir.toString());
 765             }
 766 
 767             File file;
 768             int uniqueId = 0;
 769             do {
 770                 final String fileName = g.getName() + (uniqueId == 0 ? "" : "_" + uniqueId) +  ".dot";
 771                 file = new File(dir, fileName);
 772                 uniqueId++;
 773             } while (file.exists());
 774 
 775             try (PrintWriter pw = new PrintWriter(new FileOutputStream(file))) {
 776                 pw.println(g);
 777             } catch (final FileNotFoundException e) {
 778                 throw new RuntimeException(e);
 779             }
 780         }
 781     }
 782 
 783     @Override
 784     public void visitMethodEnd() {
 785         //here we need to do several bytecode guesses best upon the ldc instructions.
 786         //for each instruction, assign bci. for an ldc/w/2w, guess a byte and keep
 787         //iterating. if the next label is wrong, backtrack.
 788         if (env._print_code_func == null || env._print_code_func.equals(graph.getName())) {
 789             if (env._print_code_dir != null) {
 790                 printToDir(graph);
 791             }
 792         }
 793     }
 794 
 795     /**
 796      * Creates a new TraceVisitor instance.
 797      *
 798      * @return a new TraceVisitor.
 799      */
 800     protected NashornTextifier createNashornTextifier() {
 801         return new NashornTextifier(env, cr, labelIter, graph);
 802     }
 803 
 804     private static void appendDescriptor(final StringBuilder sb, final int type, final String desc) {
 805         if (desc != null) {
 806             if (type == CLASS_SIGNATURE || type == FIELD_SIGNATURE || type == METHOD_SIGNATURE) {
 807                 sb.append("// signature ").append(desc).append('\n');
 808             } else {
 809                 appendShortDescriptor(sb, desc);
 810             }
 811         }
 812     }
 813 
 814     private String appendLabel(final StringBuilder sb, final Label l) {
 815         if (labelNames == null) {
 816             labelNames = new HashMap<>();
 817         }
 818         String name = labelNames.get(l);
 819         if (name == null) {
 820             name = "L" + labelNames.size();
 821             labelNames.put(l, name);
 822         }
 823         sb.append(name);
 824         return name;
 825     }
 826 
 827     private static void appendHandle(final StringBuilder sb, final Handle h) {
 828         switch (h.getTag()) {
 829         case Opcodes.H_GETFIELD:
 830             sb.append("getfield");
 831             break;
 832         case Opcodes.H_GETSTATIC:
 833             sb.append("getstatic");
 834             break;
 835         case Opcodes.H_PUTFIELD:
 836             sb.append("putfield");
 837             break;
 838         case Opcodes.H_PUTSTATIC:
 839             sb.append("putstatic");
 840             break;
 841         case Opcodes.H_INVOKEINTERFACE:
 842             sb.append("interface");
 843             break;
 844         case Opcodes.H_INVOKESPECIAL:
 845             sb.append("special");
 846             break;
 847         case Opcodes.H_INVOKESTATIC:
 848             sb.append("static");
 849             break;
 850         case Opcodes.H_INVOKEVIRTUAL:
 851             sb.append("virtual");
 852             break;
 853         case Opcodes.H_NEWINVOKESPECIAL:
 854             sb.append("new_special");
 855             break;
 856         default:
 857             assert false;
 858             break;
 859         }
 860         sb.append(" '");
 861         sb.append(h.getName());
 862         sb.append("'");
 863     }
 864 
 865     private static void appendAccess(final StringBuilder sb, final int access) {
 866         if ((access & Opcodes.ACC_PUBLIC) != 0) {
 867             sb.append("public ");
 868         }
 869         if ((access & Opcodes.ACC_PRIVATE) != 0) {
 870             sb.append("private ");
 871         }
 872         if ((access & Opcodes.ACC_PROTECTED) != 0) {
 873             sb.append("protected ");
 874         }
 875         if ((access & Opcodes.ACC_FINAL) != 0) {
 876             sb.append("final ");
 877         }
 878         if ((access & Opcodes.ACC_STATIC) != 0) {
 879             sb.append("static ");
 880         }
 881         if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) {
 882             sb.append("synchronized ");
 883         }
 884         if ((access & Opcodes.ACC_VOLATILE) != 0) {
 885             sb.append("volatile ");
 886         }
 887         if ((access & Opcodes.ACC_TRANSIENT) != 0) {
 888             sb.append("transient ");
 889         }
 890         if ((access & Opcodes.ACC_ABSTRACT) != 0) {
 891             sb.append("abstract ");
 892         }
 893         if ((access & Opcodes.ACC_STRICT) != 0) {
 894             sb.append("strictfp ");
 895         }
 896         if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
 897             sb.append("synthetic ");
 898         }
 899         if ((access & Opcodes.ACC_MANDATED) != 0) {
 900             sb.append("mandated ");
 901         }
 902         if ((access & Opcodes.ACC_ENUM) != 0) {
 903             sb.append("enum ");
 904         }
 905     }
 906 
 907     private void appendFrameTypes(final StringBuilder sb, final int n, final Object[] o) {
 908         for (int i = 0; i < n; ++i) {
 909             if (i > 0) {
 910                 sb.append(' ');
 911             }
 912             if (o[i] instanceof String) {
 913                 final String desc = (String) o[i];
 914                 if (desc.startsWith("[")) {
 915                     appendDescriptor(sb, FIELD_DESCRIPTOR, desc);
 916                 } else {
 917                     appendDescriptor(sb, INTERNAL_NAME, desc);
 918                 }
 919             } else if (o[i] instanceof Integer) {
 920                 switch (((Integer)o[i])) {
 921                 case 0:
 922                     appendDescriptor(sb, FIELD_DESCRIPTOR, "T");
 923                     break;
 924                 case 1:
 925                     appendDescriptor(sb, FIELD_DESCRIPTOR, "I");
 926                     break;
 927                 case 2:
 928                     appendDescriptor(sb, FIELD_DESCRIPTOR, "F");
 929                     break;
 930                 case 3:
 931                     appendDescriptor(sb, FIELD_DESCRIPTOR, "D");
 932                     break;
 933                 case 4:
 934                     appendDescriptor(sb, FIELD_DESCRIPTOR, "J");
 935                     break;
 936                 case 5:
 937                     appendDescriptor(sb, FIELD_DESCRIPTOR, "N");
 938                     break;
 939                 case 6:
 940                     appendDescriptor(sb, FIELD_DESCRIPTOR, "U");
 941                     break;
 942                 default:
 943                     assert false;
 944                     break;
 945                 }
 946             } else {
 947                 appendLabel(sb, (Label) o[i]);
 948             }
 949         }
 950     }
 951 
 952     private static void appendShortDescriptor(final StringBuilder sb, final String desc) {
 953         //final StringBuilder buf = new StringBuilder();
 954         if (desc.charAt(0) == '(') {
 955             for (int i = 0; i < desc.length(); i++) {
 956                 if (desc.charAt(i) == 'L') {
 957                     int slash = i;
 958                     while (desc.charAt(i) != ';') {
 959                         i++;
 960                         if (desc.charAt(i) == '/') {
 961                             slash = i;
 962                         }
 963                     }
 964                     sb.append(desc.substring(slash + 1, i)).append(';');
 965                 } else {
 966                     sb.append(desc.charAt(i));
 967                 }
 968             }
 969         } else {
 970             final int lastSlash = desc.lastIndexOf('/');
 971             final int lastBracket = desc.lastIndexOf('[');
 972             if(lastBracket != -1) {
 973                 sb.append(desc, 0, lastBracket + 1);
 974             }
 975             sb.append(lastSlash == -1 ? desc : desc.substring(lastSlash + 1));
 976         }
 977     }
 978 
 979     private static void appendStr(final StringBuilder sb, final String s) {
 980         sb.append('\"');
 981         for (int i = 0; i < s.length(); ++i) {
 982             final char c = s.charAt(i);
 983             if (c == '\n') {
 984                 sb.append("\\n");
 985             } else if (c == '\r') {
 986                 sb.append("\\r");
 987             } else if (c == '\\') {
 988                 sb.append("\\\\");
 989             } else if (c == '"') {
 990                 sb.append("\\\"");
 991             } else if (c < 0x20 || c > 0x7f) {
 992                 sb.append("\\u");
 993                 if (c < 0x10) {
 994                     sb.append("000");
 995                 } else if (c < 0x100) {
 996                     sb.append("00");
 997                 } else if (c < 0x1000) {
 998                     sb.append('0');
 999                 }
1000                 sb.append(Integer.toString(c, 16));
1001             } else {
1002                 sb.append(c);
1003             }
1004         }
1005         sb.append('\"');
1006     }
1007 
1008     private static class Graph {
1009         private final LinkedHashSet<String> nodes;
1010         private final Map<String, StringBuilder> contents;
1011         private final Map<String, Set<String>> edges;
1012         private final Set<String> hasPreds;
1013         private final Set<String> noFallThru;
1014         private final Map<String, String> catches;
1015         private final Map<String, Set<String>> exceptionMap; //maps catch nodes to all their trys that can reach them
1016         private final String name;
1017 
1018         private static final String LEFT_ALIGN      = "\\l";
1019         private static final String COLOR_CATCH     = "\"#ee9999\"";
1020         private static final String COLOR_ORPHAN    = "\"#9999bb\"";
1021         private static final String COLOR_DEFAULT   = "\"#99bb99\"";
1022         private static final String COLOR_LOCALVARS = "\"#999999\"";
1023 
1024         Graph(final String name) {
1025             this.name         = name;
1026             this.nodes        = new LinkedHashSet<>();
1027             this.contents     = new HashMap<>();
1028             this.edges        = new HashMap<>();
1029             this.hasPreds     = new HashSet<>();
1030             this.catches      = new HashMap<>();
1031             this.noFallThru   = new HashSet<>();
1032             this.exceptionMap = new HashMap<>();
1033          }
1034 
1035         void addEdge(final String from, final String to) {
1036             Set<String> edgeSet = edges.get(from);
1037             if (edgeSet == null) {
1038                 edgeSet = new LinkedHashSet<>();
1039                 edges.put(from, edgeSet);
1040             }
1041             edgeSet.add(to);
1042             hasPreds.add(to);
1043         }
1044 
1045         void addTryCatch(final String tryNode, final String catchNode) {
1046             Set<String> tryNodes = exceptionMap.get(catchNode);
1047             if (tryNodes == null) {
1048                 tryNodes = new HashSet<>();
1049                 exceptionMap.put(catchNode, tryNodes);
1050             }
1051             if (!tryNodes.contains(tryNode)) {
1052                 addEdge(tryNode, catchNode);
1053             }
1054             tryNodes.add(tryNode);
1055         }
1056 
1057         void addNode(final String node) {
1058             assert !nodes.contains(node);
1059             nodes.add(node);
1060         }
1061 
1062         void setNoFallThru(final String node) {
1063             noFallThru.add(node);
1064         }
1065 
1066         boolean isNoFallThru(final String node) {
1067             return noFallThru.contains(node);
1068         }
1069 
1070         void setIsCatch(final String node, final String exception) {
1071             catches.put(node, exception);
1072         }
1073 
1074         String getName() {
1075             return name;
1076         }
1077 
1078         void addText(final String node, final String text) {
1079             StringBuilder sb = contents.get(node);
1080             if (sb == null) {
1081                 sb = new StringBuilder();
1082             }
1083 
1084             for (int i = 0; i < text.length(); i++) {
1085                 switch (text.charAt(i)) {
1086                 case '\n':
1087                     sb.append(LEFT_ALIGN);
1088                     break;
1089                 case '"':
1090                     sb.append("'");
1091                     break;
1092                 default:
1093                     sb.append(text.charAt(i));
1094                     break;
1095                 }
1096            }
1097 
1098             contents.put(node, sb);
1099         }
1100 
1101         private static String dottyFriendly(final String name) {
1102             return name.replace(':', '_');
1103         }
1104 
1105         @Override
1106         public String toString() {
1107 
1108             final StringBuilder sb = new StringBuilder();
1109             sb.append("digraph ").append(dottyFriendly(name)).append(" {");
1110             sb.append("\n");
1111             sb.append("\tgraph [fontname=courier]\n");
1112             sb.append("\tnode [style=filled,color="+COLOR_DEFAULT+",fontname=courier]\n");
1113             sb.append("\tedge [fontname=courier]\n\n");
1114 
1115             for (final String node : nodes) {
1116                 sb.append("\t");
1117                 sb.append(node);
1118                 sb.append(" [");
1119                 sb.append("id=");
1120                 sb.append(node);
1121                 sb.append(", label=\"");
1122                 String c = contents.get(node).toString();
1123                 if (c.startsWith(LEFT_ALIGN)) {
1124                     c = c.substring(LEFT_ALIGN.length());
1125                 }
1126                 final String ex = catches.get(node);
1127                 if (ex != null) {
1128                     sb.append("*** CATCH: ").append(ex).append(" ***\\l");
1129                 }
1130                 sb.append(c);
1131                 sb.append("\"]\n");
1132             }
1133 
1134             for (final String from : edges.keySet()) {
1135                 for (final String to : edges.get(from)) {
1136                     sb.append("\t");
1137                     sb.append(from);
1138                     sb.append(" -> ");
1139                     sb.append(to);
1140                     sb.append("[label=\"");
1141                     sb.append(to);
1142                     sb.append("\"");
1143                     if (catches.get(to) != null) {
1144                         sb.append(", color=red, style=dashed");
1145                     }
1146                     sb.append(']');
1147                     sb.append(";\n");
1148                 }
1149             }
1150 
1151             sb.append("\n");
1152             for (final String node : nodes) {
1153                 sb.append("\t");
1154                 sb.append(node);
1155                 sb.append(" [shape=box");
1156                 if (catches.get(node) != null) {
1157                     sb.append(", color=" + COLOR_CATCH);
1158                 } else if ("vars".equals(node)) {
1159                     sb.append(", shape=hexagon, color=" + COLOR_LOCALVARS);
1160                 } else if (!hasPreds.contains(node)) {
1161                     sb.append(", color=" + COLOR_ORPHAN);
1162                 }
1163                 sb.append("]\n");
1164             }
1165 
1166             sb.append("}\n");
1167             return sb.toString();
1168         }
1169     }
1170 
1171     static class NashornLabel extends Label {
1172         final Label label;
1173         final int   bci;
1174         final int   opcode;
1175 
1176         NashornLabel(final Label label, final int bci) {
1177             this.label = label;
1178             this.bci   = bci;
1179             this.opcode = -1;
1180         }
1181 
1182         //not an ASM label
1183         NashornLabel(final int opcode, final int bci) {
1184             this.opcode = opcode;
1185             this.bci = bci;
1186             this.label = null;
1187         }
1188 
1189         Label getLabel() {
1190             return label;
1191         }
1192 
1193         @Override
1194         public int getOffset() {
1195             return bci;
1196         }
1197 
1198         @Override
1199         public String toString() {
1200             return "label " + bci;
1201         }
1202     }
1203 
1204     @Override
1205     public Printer visitAnnotationDefault() {
1206         throw new AssertionError();
1207     }
1208 
1209     @Override
1210     public Printer visitClassAnnotation(final String arg0, final boolean arg1) {
1211         return this;
1212     }
1213 
1214     @Override
1215     public void visitClassAttribute(final Attribute arg0) {
1216         throw new AssertionError();
1217     }
1218 
1219     @Override
1220     public Printer visitFieldAnnotation(final String arg0, final boolean arg1) {
1221         throw new AssertionError();
1222     }
1223 
1224     @Override
1225     public void visitFieldAttribute(final Attribute arg0) {
1226         throw new AssertionError();
1227     }
1228 
1229     @Override
1230     public Printer visitMethodAnnotation(final String arg0, final boolean arg1) {
1231         return this;
1232     }
1233 
1234     @Override
1235     public void visitMethodAttribute(final Attribute arg0) {
1236         throw new AssertionError();
1237     }
1238 
1239     @Override
1240     public Printer visitParameterAnnotation(final int arg0, final String arg1, final boolean arg2) {
1241         throw new AssertionError();
1242     }
1243 
1244     @Override
1245     public void visit(final String arg0, final Object arg1) {
1246         throw new AssertionError();
1247     }
1248 
1249     @Override
1250     public Printer visitAnnotation(final String arg0, final String arg1) {
1251         throw new AssertionError();
1252     }
1253 
1254     @Override
1255     public void visitAnnotationEnd() {
1256         //empty
1257     }
1258 
1259     @Override
1260     public Printer visitArray(final String arg0) {
1261         throw new AssertionError();
1262     }
1263 
1264     @Override
1265     public void visitEnum(final String arg0, final String arg1, final String arg2) {
1266         throw new AssertionError();
1267     }
1268 
1269     @Override
1270     public void visitInnerClass(final String arg0, final String arg1, final String arg2, final int arg3) {
1271         throw new AssertionError();
1272     }
1273 }