1 /*
   2  * Copyright (c) 2010, 2013, 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.NashornCallSiteDescriptor;
  52 
  53 /**
  54  * Pretty printer for --print-code.
  55  * Also supports dot formats if --print-code has arguments
  56  */
  57 public final class NashornTextifier extends Printer {
  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.ASM5);
  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         sb.append(name);
 502         appendDescriptor(sb, METHOD_DESCRIPTOR, desc);
 503         final int len = sb.length();
 504         for (int i = 0; i < 80 - len ; i++) {
 505             sb.append(' ');
 506         }
 507         sb.append(" [");
 508         appendHandle(sb, bsm);
 509         if (bsmArgs.length == 0) {
 510             sb.append("none");
 511         } else {
 512             for (final Object cst : bsmArgs) {
 513                 if (cst instanceof String) {
 514                     appendStr(sb, (String)cst);
 515                 } else if (cst instanceof Type) {
 516                     sb.append(((Type)cst).getDescriptor()).append(".class");
 517                 } else if (cst instanceof Handle) {
 518                     appendHandle(sb, (Handle)cst);
 519                 } else if (cst instanceof Integer) {
 520                     final int c = (Integer)cst;
 521                     final int pp = c >> CALLSITE_PROGRAM_POINT_SHIFT;
 522                     if (pp != 0) {
 523                         sb.append(" pp=").append(pp);
 524                     }
 525                     sb.append(NashornCallSiteDescriptor.toString(c & FLAGS_MASK));
 526                 } else {
 527                     sb.append(cst);
 528                 }
 529                 sb.append(", ");
 530             }
 531             sb.setLength(sb.length() - 2);
 532         }
 533 
 534         sb.append("]\n");
 535         addText(sb);
 536     }
 537 
 538     private static final boolean noFallThru(final int opcode) {
 539         switch (opcode) {
 540         case Opcodes.GOTO:
 541         case Opcodes.ATHROW:
 542         case Opcodes.ARETURN:
 543         case Opcodes.IRETURN:
 544         case Opcodes.LRETURN:
 545         case Opcodes.FRETURN:
 546         case Opcodes.DRETURN:
 547             return true;
 548         default:
 549             return false;
 550         }
 551     }
 552 
 553     private void checkNoFallThru(final int opcode, final String to) {
 554         if (noFallThru(opcode)) {
 555             graph.setNoFallThru(currentBlock);
 556         }
 557 
 558         if (currentBlock != null && to != null) {
 559             graph.addEdge(currentBlock, to);
 560         }
 561     }
 562 
 563     @Override
 564     public void visitJumpInsn(final int opcode, final Label label) {
 565         final StringBuilder sb = new StringBuilder();
 566         appendOpcode(sb, opcode).append(' ');
 567         final String to = appendLabel(sb, label);
 568         sb.append('\n');
 569         addText(sb);
 570         checkNoFallThru(opcode, to);
 571     }
 572 
 573     private void addText(final Object t) {
 574         text.add(t);
 575         if (currentBlock != null) {
 576             graph.addText(currentBlock, t.toString());
 577         }
 578     }
 579 
 580     @Override
 581     public void visitLabel(final Label label) {
 582         final StringBuilder sb = new StringBuilder();
 583         sb.append("\n");
 584         final String name = appendLabel(sb, label);
 585         sb.append(" [bci=");
 586         sb.append(label.info);
 587         sb.append("]");
 588         sb.append("\n");
 589 
 590         graph.addNode(name);
 591         if (currentBlock != null && !graph.isNoFallThru(currentBlock)) {
 592             graph.addEdge(currentBlock, name);
 593         }
 594         currentBlock = name;
 595         addText(sb);
 596     }
 597 
 598     @Override
 599     public void visitLdcInsn(final Object cst) {
 600         final StringBuilder sb = new StringBuilder();
 601         appendOpcode(sb, Opcodes.LDC).append(' ');
 602         if (cst instanceof String) {
 603             appendStr(sb, (String) cst);
 604         } else if (cst instanceof Type) {
 605             sb.append(((Type) cst).getDescriptor()).append(".class");
 606         } else {
 607             sb.append(cst);
 608         }
 609         sb.append('\n');
 610         addText(sb);
 611     }
 612 
 613     @Override
 614     public void visitIincInsn(final int var, final int increment) {
 615         final StringBuilder sb = new StringBuilder();
 616         appendOpcode(sb, Opcodes.IINC).append(' ');
 617         sb.append(var).append(' ')
 618                 .append(increment).append('\n');
 619         addText(sb);
 620     }
 621 
 622     @Override
 623     public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) {
 624         final StringBuilder sb = new StringBuilder();
 625         appendOpcode(sb, Opcodes.TABLESWITCH).append(' ');
 626         for (int i = 0; i < labels.length; ++i) {
 627             sb.append(tab3).append(min + i).append(": ");
 628             final String to = appendLabel(sb, labels[i]);
 629             graph.addEdge(currentBlock, to);
 630             sb.append('\n');
 631         }
 632         sb.append(tab3).append("default: ");
 633         appendLabel(sb, dflt);
 634         sb.append('\n');
 635         addText(sb);
 636     }
 637 
 638     @Override
 639     public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
 640         final StringBuilder sb = new StringBuilder();
 641         appendOpcode(sb, Opcodes.LOOKUPSWITCH).append(' ');
 642         for (int i = 0; i < labels.length; ++i) {
 643             sb.append(tab3).append(keys[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         final String to = appendLabel(sb, dflt);
 650         graph.addEdge(currentBlock, to);
 651         sb.append('\n');
 652         addText(sb.toString());
 653     }
 654 
 655     @Override
 656     public void visitMultiANewArrayInsn(final String desc, final int dims) {
 657         final StringBuilder sb = new StringBuilder();
 658         appendOpcode(sb, Opcodes.MULTIANEWARRAY).append(' ');
 659         appendDescriptor(sb, FIELD_DESCRIPTOR, desc);
 660         sb.append(' ').append(dims).append('\n');
 661         addText(sb);
 662     }
 663 
 664     @Override
 665     public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) {
 666         final StringBuilder sb = new StringBuilder();
 667         sb.append(tab2).append("try ");
 668         final String from = appendLabel(sb, start);
 669         sb.append(' ');
 670         appendLabel(sb, end);
 671         sb.append(' ');
 672         final String to = appendLabel(sb, handler);
 673         sb.append(' ');
 674         appendDescriptor(sb, INTERNAL_NAME, type);
 675         sb.append('\n');
 676         addText(sb);
 677         graph.setIsCatch(to, type);
 678         graph.addTryCatch(from, to);
 679     }
 680 
 681     @Override
 682     public void visitLocalVariable(final String name, final String desc,final String signature, final Label start, final Label end, final int index) {
 683 
 684         final StringBuilder sb = new StringBuilder();
 685         if (!localVarsStarted) {
 686             text.add("\n");
 687             localVarsStarted = true;
 688             graph.addNode("vars");
 689             currentBlock = "vars";
 690         }
 691 
 692         sb.append(tab2).append("local ").append(name).append(' ');
 693         final int len = sb.length();
 694         for (int i = 0; i < 25 - len; i++) {
 695             sb.append(' ');
 696         }
 697         String label;
 698 
 699         label = appendLabel(sb, start);
 700         for (int i = 0; i < 5 - label.length(); i++) {
 701             sb.append(' ');
 702         }
 703         label = appendLabel(sb, end);
 704         for (int i = 0; i < 5 - label.length(); i++) {
 705             sb.append(' ');
 706         }
 707 
 708         sb.append(index).append(tab2);
 709 
 710         appendDescriptor(sb, FIELD_DESCRIPTOR, desc);
 711         sb.append('\n');
 712 
 713         if (signature != null) {
 714             sb.append(tab2);
 715             appendDescriptor(sb, FIELD_SIGNATURE, signature);
 716 
 717             final TraceSignatureVisitor sv = new TraceSignatureVisitor(0);
 718             final SignatureReader r = new SignatureReader(signature);
 719             r.acceptType(sv);
 720             sb.append(tab2).append("// declaration: ")
 721                     .append(sv.getDeclaration()).append('\n');
 722         }
 723         addText(sb.toString());
 724     }
 725 
 726     @Override
 727     public void visitLineNumber(final int line, final Label start) {
 728         final StringBuilder sb = new StringBuilder();
 729         sb.append("<line ");
 730         sb.append(line);
 731         sb.append(">\n");
 732         addText(sb.toString());
 733     }
 734 
 735     @Override
 736     public void visitMaxs(final int maxStack, final int maxLocals) {
 737         final StringBuilder sb = new StringBuilder();
 738         sb.append('\n');
 739         sb.append(tab2).append("max stack  = ").append(maxStack);
 740         sb.append(", max locals = ").append(maxLocals).append('\n');
 741         addText(sb.toString());
 742     }
 743 
 744     private void printToDir(final Graph g) {
 745         if (env._print_code_dir != null) {
 746             final File dir = new File(env._print_code_dir);
 747             if (!dir.exists() && !dir.mkdirs()) {
 748                 throw new RuntimeException(dir.toString());
 749             }
 750 
 751             File file;
 752             int uniqueId = 0;
 753             do {
 754                 final String fileName = g.getName() + (uniqueId == 0 ? "" : "_" + uniqueId) +  ".dot";
 755                 file = new File(dir, fileName);
 756                 uniqueId++;
 757             } while (file.exists());
 758 
 759             try (PrintWriter pw = new PrintWriter(new FileOutputStream(file))) {
 760                 pw.println(g);
 761             } catch (final FileNotFoundException e) {
 762                 throw new RuntimeException(e);
 763             }
 764         }
 765     }
 766 
 767     @Override
 768     public void visitMethodEnd() {
 769         //here we need to do several bytecode guesses best upon the ldc instructions.
 770         //for each instruction, assign bci. for an ldc/w/2w, guess a byte and keep
 771         //iterating. if the next label is wrong, backtrack.
 772         if (env._print_code_func == null || env._print_code_func.equals(graph.getName())) {
 773             if (env._print_code_dir != null) {
 774                 printToDir(graph);
 775             }
 776         }
 777     }
 778 
 779     /**
 780      * Creates a new TraceVisitor instance.
 781      *
 782      * @return a new TraceVisitor.
 783      */
 784     protected NashornTextifier createNashornTextifier() {
 785         return new NashornTextifier(env, cr, labelIter, graph);
 786     }
 787 
 788     private static void appendDescriptor(final StringBuilder sb, final int type, final String desc) {
 789         if (desc != null) {
 790             if (type == CLASS_SIGNATURE || type == FIELD_SIGNATURE || type == METHOD_SIGNATURE) {
 791                 sb.append("// signature ").append(desc).append('\n');
 792             } else {
 793                 appendShortDescriptor(sb, desc);
 794             }
 795         }
 796     }
 797 
 798     private String appendLabel(final StringBuilder sb, final Label l) {
 799         if (labelNames == null) {
 800             labelNames = new HashMap<>();
 801         }
 802         String name = labelNames.get(l);
 803         if (name == null) {
 804             name = "L" + labelNames.size();
 805             labelNames.put(l, name);
 806         }
 807         sb.append(name);
 808         return name;
 809     }
 810 
 811     private static void appendHandle(final StringBuilder sb, final Handle h) {
 812         switch (h.getTag()) {
 813         case Opcodes.H_GETFIELD:
 814             sb.append("getfield");
 815             break;
 816         case Opcodes.H_GETSTATIC:
 817             sb.append("getstatic");
 818             break;
 819         case Opcodes.H_PUTFIELD:
 820             sb.append("putfield");
 821             break;
 822         case Opcodes.H_PUTSTATIC:
 823             sb.append("putstatic");
 824             break;
 825         case Opcodes.H_INVOKEINTERFACE:
 826             sb.append("interface");
 827             break;
 828         case Opcodes.H_INVOKESPECIAL:
 829             sb.append("special");
 830             break;
 831         case Opcodes.H_INVOKESTATIC:
 832             sb.append("static");
 833             break;
 834         case Opcodes.H_INVOKEVIRTUAL:
 835             sb.append("virtual");
 836             break;
 837         case Opcodes.H_NEWINVOKESPECIAL:
 838             sb.append("new_special");
 839             break;
 840         default:
 841             assert false;
 842             break;
 843         }
 844         sb.append(" '");
 845         sb.append(h.getName());
 846         sb.append("'");
 847     }
 848 
 849     private static void appendAccess(final StringBuilder sb, final int access) {
 850         if ((access & Opcodes.ACC_PUBLIC) != 0) {
 851             sb.append("public ");
 852         }
 853         if ((access & Opcodes.ACC_PRIVATE) != 0) {
 854             sb.append("private ");
 855         }
 856         if ((access & Opcodes.ACC_PROTECTED) != 0) {
 857             sb.append("protected ");
 858         }
 859         if ((access & Opcodes.ACC_FINAL) != 0) {
 860             sb.append("final ");
 861         }
 862         if ((access & Opcodes.ACC_STATIC) != 0) {
 863             sb.append("static ");
 864         }
 865         if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) {
 866             sb.append("synchronized ");
 867         }
 868         if ((access & Opcodes.ACC_VOLATILE) != 0) {
 869             sb.append("volatile ");
 870         }
 871         if ((access & Opcodes.ACC_TRANSIENT) != 0) {
 872             sb.append("transient ");
 873         }
 874         if ((access & Opcodes.ACC_ABSTRACT) != 0) {
 875             sb.append("abstract ");
 876         }
 877         if ((access & Opcodes.ACC_STRICT) != 0) {
 878             sb.append("strictfp ");
 879         }
 880         if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
 881             sb.append("synthetic ");
 882         }
 883         if ((access & Opcodes.ACC_MANDATED) != 0) {
 884             sb.append("mandated ");
 885         }
 886         if ((access & Opcodes.ACC_ENUM) != 0) {
 887             sb.append("enum ");
 888         }
 889     }
 890 
 891     private void appendFrameTypes(final StringBuilder sb, final int n, final Object[] o) {
 892         for (int i = 0; i < n; ++i) {
 893             if (i > 0) {
 894                 sb.append(' ');
 895             }
 896             if (o[i] instanceof String) {
 897                 final String desc = (String) o[i];
 898                 if (desc.startsWith("[")) {
 899                     appendDescriptor(sb, FIELD_DESCRIPTOR, desc);
 900                 } else {
 901                     appendDescriptor(sb, INTERNAL_NAME, desc);
 902                 }
 903             } else if (o[i] instanceof Integer) {
 904                 switch (((Integer)o[i]).intValue()) {
 905                 case 0:
 906                     appendDescriptor(sb, FIELD_DESCRIPTOR, "T");
 907                     break;
 908                 case 1:
 909                     appendDescriptor(sb, FIELD_DESCRIPTOR, "I");
 910                     break;
 911                 case 2:
 912                     appendDescriptor(sb, FIELD_DESCRIPTOR, "F");
 913                     break;
 914                 case 3:
 915                     appendDescriptor(sb, FIELD_DESCRIPTOR, "D");
 916                     break;
 917                 case 4:
 918                     appendDescriptor(sb, FIELD_DESCRIPTOR, "J");
 919                     break;
 920                 case 5:
 921                     appendDescriptor(sb, FIELD_DESCRIPTOR, "N");
 922                     break;
 923                 case 6:
 924                     appendDescriptor(sb, FIELD_DESCRIPTOR, "U");
 925                     break;
 926                 default:
 927                     assert false;
 928                     break;
 929                 }
 930             } else {
 931                 appendLabel(sb, (Label) o[i]);
 932             }
 933         }
 934     }
 935 
 936     private static void appendShortDescriptor(final StringBuilder sb, final String desc) {
 937         //final StringBuilder buf = new StringBuilder();
 938         if (desc.charAt(0) == '(') {
 939             for (int i = 0; i < desc.length(); i++) {
 940                 if (desc.charAt(i) == 'L') {
 941                     int slash = i;
 942                     while (desc.charAt(i) != ';') {
 943                         i++;
 944                         if (desc.charAt(i) == '/') {
 945                             slash = i;
 946                         }
 947                     }
 948                     sb.append(desc.substring(slash + 1, i)).append(';');
 949                 } else {
 950                     sb.append(desc.charAt(i));
 951                 }
 952             }
 953         } else {
 954             final int lastSlash = desc.lastIndexOf('/');
 955             final int lastBracket = desc.lastIndexOf('[');
 956             if(lastBracket != -1) {
 957                 sb.append(desc, 0, lastBracket + 1);
 958             }
 959             sb.append(lastSlash == -1 ? desc : desc.substring(lastSlash + 1));
 960         }
 961     }
 962 
 963     private static void appendStr(final StringBuilder sb, final String s) {
 964         sb.append('\"');
 965         for (int i = 0; i < s.length(); ++i) {
 966             final char c = s.charAt(i);
 967             if (c == '\n') {
 968                 sb.append("\\n");
 969             } else if (c == '\r') {
 970                 sb.append("\\r");
 971             } else if (c == '\\') {
 972                 sb.append("\\\\");
 973             } else if (c == '"') {
 974                 sb.append("\\\"");
 975             } else if (c < 0x20 || c > 0x7f) {
 976                 sb.append("\\u");
 977                 if (c < 0x10) {
 978                     sb.append("000");
 979                 } else if (c < 0x100) {
 980                     sb.append("00");
 981                 } else if (c < 0x1000) {
 982                     sb.append('0');
 983                 }
 984                 sb.append(Integer.toString(c, 16));
 985             } else {
 986                 sb.append(c);
 987             }
 988         }
 989         sb.append('\"');
 990     }
 991 
 992     private static class Graph {
 993         private final LinkedHashSet<String> nodes;
 994         private final Map<String, StringBuilder> contents;
 995         private final Map<String, Set<String>> edges;
 996         private final Set<String> hasPreds;
 997         private final Set<String> noFallThru;
 998         private final Map<String, String> catches;
 999         private final Map<String, Set<String>> exceptionMap; //maps catch nodes to all their trys that can reach them
1000         private final String name;
1001 
1002         private static final String LEFT_ALIGN      = "\\l";
1003         private static final String COLOR_CATCH     = "\"#ee9999\"";
1004         private static final String COLOR_ORPHAN    = "\"#9999bb\"";
1005         private static final String COLOR_DEFAULT   = "\"#99bb99\"";
1006         private static final String COLOR_LOCALVARS = "\"#999999\"";
1007 
1008         Graph(final String name) {
1009             this.name         = name;
1010             this.nodes        = new LinkedHashSet<>();
1011             this.contents     = new HashMap<>();
1012             this.edges        = new HashMap<>();
1013             this.hasPreds     = new HashSet<>();
1014             this.catches      = new HashMap<>();
1015             this.noFallThru   = new HashSet<>();
1016             this.exceptionMap = new HashMap<>();
1017          }
1018 
1019         void addEdge(final String from, final String to) {
1020             Set<String> edgeSet = edges.get(from);
1021             if (edgeSet == null) {
1022                 edgeSet = new LinkedHashSet<>();
1023                 edges.put(from, edgeSet);
1024             }
1025             edgeSet.add(to);
1026             hasPreds.add(to);
1027         }
1028 
1029         void addTryCatch(final String tryNode, final String catchNode) {
1030             Set<String> tryNodes = exceptionMap.get(catchNode);
1031             if (tryNodes == null) {
1032                 tryNodes = new HashSet<>();
1033                 exceptionMap.put(catchNode, tryNodes);
1034             }
1035             if (!tryNodes.contains(tryNode)) {
1036                 addEdge(tryNode, catchNode);
1037             }
1038             tryNodes.add(tryNode);
1039         }
1040 
1041         void addNode(final String node) {
1042             assert !nodes.contains(node);
1043             nodes.add(node);
1044         }
1045 
1046         void setNoFallThru(final String node) {
1047             noFallThru.add(node);
1048         }
1049 
1050         boolean isNoFallThru(final String node) {
1051             return noFallThru.contains(node);
1052         }
1053 
1054         void setIsCatch(final String node, final String exception) {
1055             catches.put(node, exception);
1056         }
1057 
1058         String getName() {
1059             return name;
1060         }
1061 
1062         void addText(final String node, final String text) {
1063             StringBuilder sb = contents.get(node);
1064             if (sb == null) {
1065                 sb = new StringBuilder();
1066             }
1067 
1068             for (int i = 0; i < text.length(); i++) {
1069                 switch (text.charAt(i)) {
1070                 case '\n':
1071                     sb.append(LEFT_ALIGN);
1072                     break;
1073                 case '"':
1074                     sb.append("'");
1075                     break;
1076                 default:
1077                     sb.append(text.charAt(i));
1078                     break;
1079                 }
1080            }
1081 
1082             contents.put(node, sb);
1083         }
1084 
1085         private static String dottyFriendly(final String name) {
1086             return name.replace(':', '_');
1087         }
1088 
1089         @Override
1090         public String toString() {
1091 
1092             final StringBuilder sb = new StringBuilder();
1093             sb.append("digraph " + dottyFriendly(name) + " {");
1094             sb.append("\n");
1095             sb.append("\tgraph [fontname=courier]\n");
1096             sb.append("\tnode [style=filled,color="+COLOR_DEFAULT+",fontname=courier]\n");
1097             sb.append("\tedge [fontname=courier]\n\n");
1098 
1099             for (final String node : nodes) {
1100                 sb.append("\t");
1101                 sb.append(node);
1102                 sb.append(" [");
1103                 sb.append("id=");
1104                 sb.append(node);
1105                 sb.append(", label=\"");
1106                 String c = contents.get(node).toString();
1107                 if (c.startsWith(LEFT_ALIGN)) {
1108                     c = c.substring(LEFT_ALIGN.length());
1109                 }
1110                 final String ex = catches.get(node);
1111                 if (ex != null) {
1112                     sb.append("*** CATCH: ").append(ex).append(" ***\n");
1113                 }
1114                 sb.append(c);
1115                 sb.append("\"]\n");
1116             }
1117 
1118             for (final String from : edges.keySet()) {
1119                 for (final String to : edges.get(from)) {
1120                     sb.append("\t");
1121                     sb.append(from);
1122                     sb.append(" -> ");
1123                     sb.append(to);
1124                     sb.append("[label=\"");
1125                     sb.append(to);
1126                     sb.append("\"");
1127                     if (catches.get(to) != null) {
1128                         sb.append(", color=red, style=dashed");
1129                     }
1130                     sb.append(']');
1131                     sb.append(";\n");
1132                 }
1133             }
1134 
1135             sb.append("\n");
1136             for (final String node : nodes) {
1137                 sb.append("\t");
1138                 sb.append(node);
1139                 sb.append(" [shape=box");
1140                 if (catches.get(node) != null) {
1141                     sb.append(", color=" + COLOR_CATCH);
1142                 } else if ("vars".equals(node)) {
1143                     sb.append(", shape=hexagon, color=" + COLOR_LOCALVARS);
1144                 } else if (!hasPreds.contains(node)) {
1145                     sb.append(", color=" + COLOR_ORPHAN);
1146                 }
1147                 sb.append("]\n");
1148             }
1149 
1150             sb.append("}\n");
1151             return sb.toString();
1152         }
1153     }
1154 
1155     static class NashornLabel extends Label {
1156         final Label label;
1157         final int   bci;
1158         final int   opcode;
1159 
1160         NashornLabel(final Label label, final int bci) {
1161             this.label = label;
1162             this.bci   = bci;
1163             this.opcode = -1;
1164         }
1165 
1166         //not an ASM label
1167         NashornLabel(final int opcode, final int bci) {
1168             this.opcode = opcode;
1169             this.bci = bci;
1170             this.label = null;
1171         }
1172 
1173         Label getLabel() {
1174             return label;
1175         }
1176 
1177         @Override
1178         public int getOffset() {
1179             return bci;
1180         }
1181 
1182         @Override
1183         public String toString() {
1184             return "label " + bci;
1185         }
1186     }
1187 
1188     @Override
1189     public Printer visitAnnotationDefault() {
1190         throw new AssertionError();
1191     }
1192 
1193     @Override
1194     public Printer visitClassAnnotation(final String arg0, final boolean arg1) {
1195         return this;
1196     }
1197 
1198     @Override
1199     public void visitClassAttribute(final Attribute arg0) {
1200         throw new AssertionError();
1201     }
1202 
1203     @Override
1204     public Printer visitFieldAnnotation(final String arg0, final boolean arg1) {
1205         throw new AssertionError();
1206     }
1207 
1208     @Override
1209     public void visitFieldAttribute(final Attribute arg0) {
1210         throw new AssertionError();
1211     }
1212 
1213     @Override
1214     public Printer visitMethodAnnotation(final String arg0, final boolean arg1) {
1215         return this;
1216     }
1217 
1218     @Override
1219     public void visitMethodAttribute(final Attribute arg0) {
1220         throw new AssertionError();
1221     }
1222 
1223     @Override
1224     public Printer visitParameterAnnotation(final int arg0, final String arg1, final boolean arg2) {
1225         throw new AssertionError();
1226     }
1227 
1228     @Override
1229     public void visit(final String arg0, final Object arg1) {
1230         throw new AssertionError();
1231     }
1232 
1233     @Override
1234     public Printer visitAnnotation(final String arg0, final String arg1) {
1235         throw new AssertionError();
1236     }
1237 
1238     @Override
1239     public void visitAnnotationEnd() {
1240         //empty
1241     }
1242 
1243     @Override
1244     public Printer visitArray(final String arg0) {
1245         throw new AssertionError();
1246     }
1247 
1248     @Override
1249     public void visitEnum(final String arg0, final String arg1, final String arg2) {
1250         throw new AssertionError();
1251     }
1252 
1253     @Override
1254     public void visitInnerClass(final String arg0, final String arg1, final String arg2, final int arg3) {
1255         throw new AssertionError();
1256     }
1257 }