NashornTextifier.java revision 1298:776551a5b3a2
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.org.objectweb.asm.Attribute;
43import jdk.internal.org.objectweb.asm.Handle;
44import jdk.internal.org.objectweb.asm.Label;
45import jdk.internal.org.objectweb.asm.Opcodes;
46import jdk.internal.org.objectweb.asm.Type;
47import jdk.internal.org.objectweb.asm.signature.SignatureReader;
48import jdk.internal.org.objectweb.asm.util.Printer;
49import jdk.internal.org.objectweb.asm.util.TraceSignatureVisitor;
50import jdk.nashorn.internal.runtime.ScriptEnvironment;
51import 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 */
57public 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(" ***\\l");
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}
1258