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