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