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