1 /*
2  * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.nashorn.internal.ir.debug;
27 
28 import java.io.File;
29 import java.io.FileNotFoundException;
30 import java.io.FileOutputStream;
31 import java.io.PrintWriter;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.LinkedHashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import jdk.internal.org.objectweb.asm.Attribute;
40 import jdk.internal.org.objectweb.asm.Handle;
41 import jdk.internal.org.objectweb.asm.Label;
42 import jdk.internal.org.objectweb.asm.Opcodes;
43 import jdk.internal.org.objectweb.asm.Type;
44 import jdk.internal.org.objectweb.asm.signature.SignatureReader;
45 import jdk.internal.org.objectweb.asm.util.Printer;
46 import jdk.internal.org.objectweb.asm.util.TraceSignatureVisitor;
47 import jdk.nashorn.internal.runtime.ScriptEnvironment;
48 import jdk.nashorn.internal.runtime.linker.Bootstrap;
49 import jdk.nashorn.internal.runtime.linker.NameCodec;
50 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
51 
52 /**
53  * Pretty printer for --print-code.
54  * Also supports dot formats if --print-code has arguments
55  */
56 public final class NashornTextifier extends Printer {
57     private static final String BOOTSTRAP_CLASS_NAME = Bootstrap.class.getName().replace('.', '/');
58 
59     private String currentClassName;
60     private Iterator<Label> labelIter;
61     private Graph graph;
62     private String currentBlock;
63 
64     // Following variables are used to govern the state of collapsing long sequences of NOP.
65     /** True if the last instruction was a NOP. */
66     private boolean lastWasNop = false;
67     /** True if ellipse ("...") was emitted in place of a second NOP. */
68     private boolean lastWasEllipse = false;
69 
70     private static final int INTERNAL_NAME = 0;
71     private static final int FIELD_DESCRIPTOR = 1;
72     private static final int FIELD_SIGNATURE = 2;
73     private static final int METHOD_DESCRIPTOR = 3;
74     private static final int METHOD_SIGNATURE = 4;
75     private static final int CLASS_SIGNATURE = 5;
76 
77     private final String tab = "  ";
78     private final String tab2 = "    ";
79     private final String tab3 = "      ";
80 
81     private Map<Label, String> labelNames;
82 
83     private boolean localVarsStarted = false;
84 
85     private NashornClassReader cr;
86     private ScriptEnvironment env;
87 
88     /**
89      * Constructs a new {@link NashornTextifier}. <i>Subclasses must not use this
90      * constructor</i>. Instead, they must use the {@link #NashornTextifier(int)}
91      * version.
92      * @param env script environment
93      * @param cr a customized classreader for gathering, among other things, label
94      * information
95      */
NashornTextifier(final ScriptEnvironment env, final NashornClassReader cr)96     public NashornTextifier(final ScriptEnvironment env, final NashornClassReader cr) {
97         this(Opcodes.ASM7);
98         this.env = env;
99         this.cr = cr;
100     }
101 
NashornTextifier(final ScriptEnvironment env, final NashornClassReader cr, final Iterator<Label> labelIter, final Graph graph)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      */
NashornTextifier(final int api)115     protected NashornTextifier(final int api) {
116         super(api);
117     }
118 
119     @Override
visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces)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
visitSource(final String file, final String debug)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
visitOuterClass(final String owner, final String name, final String desc)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
visitField(final int access, final String name, final String desc, final String signature, final Object value)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
visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions)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
visitClassEnd()341     public void visitClassEnd() {
342         addText("}\n");
343     }
344 
345     @Override
visitFieldEnd()346     public void visitFieldEnd() {
347         //empty
348     }
349 
350     @Override
visitParameter(final String name, final int access)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
visitCode()361     public void visitCode() {
362         //empty
363     }
364 
365     @Override
visitFrame(final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack)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 
appendOpcode(final StringBuilder sb, final int opcode)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 
getNextLabel()421     private Label getNextLabel() {
422         return labelIter == null ? null : labelIter.next();
423     }
424 
425     @Override
visitInsn(final int opcode)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
visitIntInsn(final int opcode, final int operand)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
visitVarInsn(final int opcode, final int var)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
visitTypeInsn(final int opcode, final String type)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
visitFieldInsn(final int opcode, final String owner, final String name, final String desc)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
visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf)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
visitInvokeDynamicInsn(final String name, final String desc, final Handle bsm, final Object... bsmArgs)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 
isNashornBootstrap(final Handle bsm)545     private static boolean isNashornBootstrap(final Handle bsm) {
546         return "bootstrap".equals(bsm.getName()) && BOOTSTRAP_CLASS_NAME.equals(bsm.getOwner());
547     }
548 
isNashornMathBootstrap(final Handle bsm)549     private static boolean isNashornMathBootstrap(final Handle bsm) {
550         return "mathBootstrap".equals(bsm.getName()) && BOOTSTRAP_CLASS_NAME.equals(bsm.getOwner());
551     }
552 
noFallThru(final int opcode)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 
checkNoFallThru(final int opcode, final String to)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
visitJumpInsn(final int opcode, final Label label)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 
addText(final Object t)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
visitLabel(final Label label)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
visitLdcInsn(final Object cst)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
visitIincInsn(final int var, final int increment)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
visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels)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
visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels)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
visitMultiANewArrayInsn(final String desc, final int dims)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
visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type)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
visitLocalVariable(final String name, final String desc,final String signature, final Label start, final Label end, final int index)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
visitLineNumber(final int line, final Label start)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
visitMaxs(final int maxStack, final int maxLocals)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 
printToDir(final Graph g)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
visitMethodEnd()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      */
createNashornTextifier()799     protected NashornTextifier createNashornTextifier() {
800         return new NashornTextifier(env, cr, labelIter, graph);
801     }
802 
appendDescriptor(final StringBuilder sb, final int type, final String desc)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 
appendLabel(final StringBuilder sb, final Label l)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 
appendHandle(final StringBuilder sb, final Handle h)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 
appendAccess(final StringBuilder sb, final int access)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 
appendFrameTypes(final StringBuilder sb, final int n, final Object[] o)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 
appendShortDescriptor(final StringBuilder sb, final String desc)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 
appendStr(final StringBuilder sb, final String s)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 
Graph(final String name)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 
addEdge(final String from, final String to)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 
addTryCatch(final String tryNode, final String catchNode)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 
addNode(final String node)1056         void addNode(final String node) {
1057             assert !nodes.contains(node);
1058             nodes.add(node);
1059         }
1060 
setNoFallThru(final String node)1061         void setNoFallThru(final String node) {
1062             noFallThru.add(node);
1063         }
1064 
isNoFallThru(final String node)1065         boolean isNoFallThru(final String node) {
1066             return noFallThru.contains(node);
1067         }
1068 
setIsCatch(final String node, final String exception)1069         void setIsCatch(final String node, final String exception) {
1070             catches.put(node, exception);
1071         }
1072 
getName()1073         String getName() {
1074             return name;
1075         }
1076 
addText(final String node, final String text)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 
dottyFriendly(final String name)1100         private static String dottyFriendly(final String name) {
1101             return name.replace(':', '_');
1102         }
1103 
1104         @Override
toString()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 
NashornLabel(final Label label, final int bci)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
NashornLabel(final int opcode, final int bci)1182         NashornLabel(final int opcode, final int bci) {
1183             this.opcode = opcode;
1184             this.bci = bci;
1185             this.label = null;
1186         }
1187 
getLabel()1188         Label getLabel() {
1189             return label;
1190         }
1191 
1192         @Override
getOffset()1193         public int getOffset() {
1194             return bci;
1195         }
1196 
1197         @Override
toString()1198         public String toString() {
1199             return "label " + bci;
1200         }
1201     }
1202 
1203     @Override
visitAnnotationDefault()1204     public Printer visitAnnotationDefault() {
1205         throw new AssertionError();
1206     }
1207 
1208     @Override
visitClassAnnotation(final String arg0, final boolean arg1)1209     public Printer visitClassAnnotation(final String arg0, final boolean arg1) {
1210         return this;
1211     }
1212 
1213     @Override
visitClassAttribute(final Attribute arg0)1214     public void visitClassAttribute(final Attribute arg0) {
1215         throw new AssertionError();
1216     }
1217 
1218     @Override
visitFieldAnnotation(final String arg0, final boolean arg1)1219     public Printer visitFieldAnnotation(final String arg0, final boolean arg1) {
1220         throw new AssertionError();
1221     }
1222 
1223     @Override
visitFieldAttribute(final Attribute arg0)1224     public void visitFieldAttribute(final Attribute arg0) {
1225         throw new AssertionError();
1226     }
1227 
1228     @Override
visitMethodAnnotation(final String arg0, final boolean arg1)1229     public Printer visitMethodAnnotation(final String arg0, final boolean arg1) {
1230         return this;
1231     }
1232 
1233     @Override
visitMethodAttribute(final Attribute arg0)1234     public void visitMethodAttribute(final Attribute arg0) {
1235         throw new AssertionError();
1236     }
1237 
1238     @Override
visitParameterAnnotation(final int arg0, final String arg1, final boolean arg2)1239     public Printer visitParameterAnnotation(final int arg0, final String arg1, final boolean arg2) {
1240         throw new AssertionError();
1241     }
1242 
1243     @Override
visit(final String arg0, final Object arg1)1244     public void visit(final String arg0, final Object arg1) {
1245         throw new AssertionError();
1246     }
1247 
1248     @Override
visitAnnotation(final String arg0, final String arg1)1249     public Printer visitAnnotation(final String arg0, final String arg1) {
1250         throw new AssertionError();
1251     }
1252 
1253     @Override
visitAnnotationEnd()1254     public void visitAnnotationEnd() {
1255         //empty
1256     }
1257 
1258     @Override
visitArray(final String arg0)1259     public Printer visitArray(final String arg0) {
1260         throw new AssertionError();
1261     }
1262 
1263     @Override
visitEnum(final String arg0, final String arg1, final String arg2)1264     public void visitEnum(final String arg0, final String arg1, final String arg2) {
1265         throw new AssertionError();
1266     }
1267 
1268     @Override
visitInnerClass(final String arg0, final String arg1, final String arg2, final int arg3)1269     public void visitInnerClass(final String arg0, final String arg1, final String arg2, final int arg3) {
1270         throw new AssertionError();
1271     }
1272 }
1273