| /* |
| * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.ir.debug; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.PrintWriter; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import jdk.internal.org.objectweb.asm.Attribute; |
| import jdk.internal.org.objectweb.asm.Handle; |
| import jdk.internal.org.objectweb.asm.Label; |
| import jdk.internal.org.objectweb.asm.Opcodes; |
| import jdk.internal.org.objectweb.asm.Type; |
| import jdk.internal.org.objectweb.asm.signature.SignatureReader; |
| import jdk.internal.org.objectweb.asm.util.Printer; |
| import jdk.internal.org.objectweb.asm.util.TraceSignatureVisitor; |
| import jdk.nashorn.internal.runtime.ScriptEnvironment; |
| import jdk.nashorn.internal.runtime.linker.Bootstrap; |
| import jdk.nashorn.internal.runtime.linker.NameCodec; |
| import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; |
| |
| /** |
| * Pretty printer for --print-code. |
| * Also supports dot formats if --print-code has arguments |
| */ |
| public final class NashornTextifier extends Printer { |
| private static final String BOOTSTRAP_CLASS_NAME = Bootstrap.class.getName().replace('.', '/'); |
| |
| private String currentClassName; |
| private Iterator<Label> labelIter; |
| private Graph graph; |
| private String currentBlock; |
| |
| // Following variables are used to govern the state of collapsing long sequences of NOP. |
| /** True if the last instruction was a NOP. */ |
| private boolean lastWasNop = false; |
| /** True if ellipse ("...") was emitted in place of a second NOP. */ |
| private boolean lastWasEllipse = false; |
| |
| private static final int INTERNAL_NAME = 0; |
| private static final int FIELD_DESCRIPTOR = 1; |
| private static final int FIELD_SIGNATURE = 2; |
| private static final int METHOD_DESCRIPTOR = 3; |
| private static final int METHOD_SIGNATURE = 4; |
| private static final int CLASS_SIGNATURE = 5; |
| |
| private final String tab = " "; |
| private final String tab2 = " "; |
| private final String tab3 = " "; |
| |
| private Map<Label, String> labelNames; |
| |
| private boolean localVarsStarted = false; |
| |
| private NashornClassReader cr; |
| private ScriptEnvironment env; |
| |
| /** |
| * Constructs a new {@link NashornTextifier}. <i>Subclasses must not use this |
| * constructor</i>. Instead, they must use the {@link #NashornTextifier(int)} |
| * version. |
| * @param env script environment |
| * @param cr a customized classreader for gathering, among other things, label |
| * information |
| */ |
| public NashornTextifier(final ScriptEnvironment env, final NashornClassReader cr) { |
| this(Opcodes.ASM5); |
| this.env = env; |
| this.cr = cr; |
| } |
| |
| private NashornTextifier(final ScriptEnvironment env, final NashornClassReader cr, final Iterator<Label> labelIter, final Graph graph) { |
| this(env, cr); |
| this.labelIter = labelIter; |
| this.graph = graph; |
| } |
| |
| /** |
| * Constructs a new {@link NashornTextifier}. |
| * |
| * @param api |
| * the ASM API version implemented by this visitor. Must be one |
| * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. |
| */ |
| protected NashornTextifier(final int api) { |
| super(api); |
| } |
| |
| @Override |
| public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { |
| final int major = version & 0xFFFF; |
| final int minor = version >>> 16; |
| |
| currentClassName = name; |
| |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("// class version "). |
| append(major). |
| append('.'). |
| append(minor).append(" ("). |
| append(version). |
| append(")\n"); |
| |
| if ((access & Opcodes.ACC_DEPRECATED) != 0) { |
| sb.append("// DEPRECATED\n"); |
| } |
| |
| sb.append("// access flags 0x"). //TODO TRANSLATE TO WHAT THEY MEAN |
| append(Integer.toHexString(access).toUpperCase()). |
| append('\n'); |
| |
| appendDescriptor(sb, CLASS_SIGNATURE, signature); |
| if (signature != null) { |
| final TraceSignatureVisitor sv = new TraceSignatureVisitor(access); |
| final SignatureReader r = new SignatureReader(signature); |
| r.accept(sv); |
| sb.append("// declaration: "). |
| append(name). |
| append(sv.getDeclaration()). |
| append('\n'); |
| } |
| |
| appendAccess(sb, access & ~Opcodes.ACC_SUPER); |
| if ((access & Opcodes.ACC_ANNOTATION) != 0) { |
| sb.append("@interface "); |
| } else if ((access & Opcodes.ACC_INTERFACE) != 0) { |
| sb.append("interface "); |
| } else if ((access & Opcodes.ACC_ENUM) == 0) { |
| sb.append("class "); |
| } |
| appendDescriptor(sb, INTERNAL_NAME, name); |
| |
| if (superName != null && !"java/lang/Object".equals(superName)) { |
| sb.append(" extends "); |
| appendDescriptor(sb, INTERNAL_NAME, superName); |
| sb.append(' '); |
| } |
| if (interfaces != null && interfaces.length > 0) { |
| sb.append(" implements "); |
| for (final String interface1 : interfaces) { |
| appendDescriptor(sb, INTERNAL_NAME, interface1); |
| sb.append(' '); |
| } |
| } |
| sb.append(" {\n"); |
| |
| addText(sb); |
| } |
| |
| @Override |
| public void visitSource(final String file, final String debug) { |
| final StringBuilder sb = new StringBuilder(); |
| if (file != null) { |
| sb.append(tab). |
| append("// compiled from: "). |
| append(file). |
| append('\n'); |
| } |
| if (debug != null) { |
| sb.append(tab). |
| append("// debug info: "). |
| append(debug). |
| append('\n'); |
| } |
| if (sb.length() > 0) { |
| addText(sb); |
| } |
| } |
| |
| @Override |
| public void visitOuterClass(final String owner, final String name, final String desc) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append(tab).append("outer class "); |
| appendDescriptor(sb, INTERNAL_NAME, owner); |
| sb.append(' '); |
| if (name != null) { |
| sb.append(name).append(' '); |
| } |
| appendDescriptor(sb, METHOD_DESCRIPTOR, desc); |
| sb.append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public NashornTextifier visitField(final int access, final String name, final String desc, final String signature, final Object value) { |
| final StringBuilder sb = new StringBuilder(); |
| // sb.append('\n'); |
| if ((access & Opcodes.ACC_DEPRECATED) != 0) { |
| sb.append(tab).append("// DEPRECATED\n"); |
| } |
| |
| /* sb.append(tab). |
| append("// access flags 0x"). |
| append(Integer.toHexString(access).toUpperCase()). |
| append('\n'); |
| */ |
| |
| if (signature != null) { |
| sb.append(tab); |
| appendDescriptor(sb, FIELD_SIGNATURE, signature); |
| |
| final TraceSignatureVisitor sv = new TraceSignatureVisitor(0); |
| final SignatureReader r = new SignatureReader(signature); |
| r.acceptType(sv); |
| sb.append(tab). |
| append("// declaration: "). |
| append(sv.getDeclaration()). |
| append('\n'); |
| } |
| |
| sb.append(tab); |
| appendAccess(sb, access); |
| |
| final String prunedDesc = desc.endsWith(";") ? desc.substring(0, desc.length() - 1) : desc; |
| appendDescriptor(sb, FIELD_DESCRIPTOR, prunedDesc); |
| sb.append(' ').append(name); |
| if (value != null) { |
| sb.append(" = "); |
| if (value instanceof String) { |
| sb.append('\"').append(value).append('\"'); |
| } else { |
| sb.append(value); |
| } |
| } |
| |
| sb.append(";\n"); |
| addText(sb); |
| |
| final NashornTextifier t = createNashornTextifier(); |
| addText(t.getText()); |
| |
| return t; |
| } |
| |
| @Override |
| public NashornTextifier visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { |
| |
| graph = new Graph(name); |
| |
| final List<Label> extraLabels = cr.getExtraLabels(currentClassName, name, desc); |
| this.labelIter = extraLabels == null ? null : extraLabels.iterator(); |
| |
| final StringBuilder sb = new StringBuilder(); |
| |
| sb.append('\n'); |
| if ((access & Opcodes.ACC_DEPRECATED) != 0) { |
| sb.append(tab). |
| append("// DEPRECATED\n"); |
| } |
| |
| sb.append(tab). |
| append("// access flags 0x"). |
| append(Integer.toHexString(access).toUpperCase()). |
| append('\n'); |
| |
| if (signature != null) { |
| sb.append(tab); |
| appendDescriptor(sb, METHOD_SIGNATURE, signature); |
| |
| final TraceSignatureVisitor v = new TraceSignatureVisitor(0); |
| final SignatureReader r = new SignatureReader(signature); |
| r.accept(v); |
| final String genericDecl = v.getDeclaration(); |
| final String genericReturn = v.getReturnType(); |
| final String genericExceptions = v.getExceptions(); |
| |
| sb.append(tab). |
| append("// declaration: "). |
| append(genericReturn). |
| append(' '). |
| append(name). |
| append(genericDecl); |
| |
| if (genericExceptions != null) { |
| sb.append(" throws ").append(genericExceptions); |
| } |
| sb.append('\n'); |
| } |
| |
| sb.append(tab); |
| appendAccess(sb, access); |
| if ((access & Opcodes.ACC_NATIVE) != 0) { |
| sb.append("native "); |
| } |
| if ((access & Opcodes.ACC_VARARGS) != 0) { |
| sb.append("varargs "); |
| } |
| if ((access & Opcodes.ACC_BRIDGE) != 0) { |
| sb.append("bridge "); |
| } |
| |
| sb.append(name); |
| appendDescriptor(sb, METHOD_DESCRIPTOR, desc); |
| if (exceptions != null && exceptions.length > 0) { |
| sb.append(" throws "); |
| for (final String exception : exceptions) { |
| appendDescriptor(sb, INTERNAL_NAME, exception); |
| sb.append(' '); |
| } |
| } |
| |
| sb.append('\n'); |
| addText(sb); |
| |
| final NashornTextifier t = createNashornTextifier(); |
| addText(t.getText()); |
| return t; |
| } |
| |
| @Override |
| public void visitClassEnd() { |
| addText("}\n"); |
| } |
| |
| @Override |
| public void visitFieldEnd() { |
| //empty |
| } |
| |
| @Override |
| public void visitParameter(final String name, final int access) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append(tab2).append("// parameter "); |
| appendAccess(sb, access); |
| sb.append(' ').append(name == null ? "<no name>" : name) |
| .append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitCode() { |
| //empty |
| } |
| |
| @Override |
| public void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("frame "); |
| switch (type) { |
| case Opcodes.F_NEW: |
| case Opcodes.F_FULL: |
| sb.append("full ["); |
| appendFrameTypes(sb, nLocal, local); |
| sb.append("] ["); |
| appendFrameTypes(sb, nStack, stack); |
| sb.append(']'); |
| break; |
| case Opcodes.F_APPEND: |
| sb.append("append ["); |
| appendFrameTypes(sb, nLocal, local); |
| sb.append(']'); |
| break; |
| case Opcodes.F_CHOP: |
| sb.append("chop ").append(nLocal); |
| break; |
| case Opcodes.F_SAME: |
| sb.append("same"); |
| break; |
| case Opcodes.F_SAME1: |
| sb.append("same1 "); |
| appendFrameTypes(sb, 1, stack); |
| break; |
| default: |
| assert false; |
| break; |
| } |
| sb.append('\n'); |
| sb.append('\n'); |
| addText(sb); |
| } |
| |
| private StringBuilder appendOpcode(final StringBuilder sb, final int opcode) { |
| final Label next = getNextLabel(); |
| if (next instanceof NashornLabel) { |
| final int bci = next.getOffset(); |
| if (bci != -1) { |
| final String bcis = "" + bci; |
| for (int i = 0; i < 5 - bcis.length(); i++) { |
| sb.append(' '); |
| } |
| sb.append(bcis); |
| sb.append(' '); |
| } else { |
| sb.append(" "); |
| } |
| } |
| |
| return sb.append(tab2).append(OPCODES[opcode].toLowerCase()); |
| } |
| |
| private Label getNextLabel() { |
| return labelIter == null ? null : labelIter.next(); |
| } |
| |
| @Override |
| public void visitInsn(final int opcode) { |
| if(opcode == Opcodes.NOP) { |
| if(lastWasEllipse) { |
| getNextLabel(); |
| return; |
| } else if(lastWasNop) { |
| getNextLabel(); |
| addText(" ...\n"); |
| lastWasEllipse = true; |
| return; |
| } else { |
| lastWasNop = true; |
| } |
| } else { |
| lastWasNop = lastWasEllipse = false; |
| } |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, opcode).append('\n'); |
| addText(sb); |
| checkNoFallThru(opcode, null); |
| } |
| |
| @Override |
| public void visitIntInsn(final int opcode, final int operand) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, opcode) |
| .append(' ') |
| .append(opcode == Opcodes.NEWARRAY ? TYPES[operand] : Integer |
| .toString(operand)).append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitVarInsn(final int opcode, final int var) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, opcode).append(' ').append(var).append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitTypeInsn(final int opcode, final String type) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, opcode).append(' '); |
| appendDescriptor(sb, INTERNAL_NAME, type); |
| sb.append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, opcode).append(' '); |
| appendDescriptor(sb, INTERNAL_NAME, owner); |
| sb.append('.').append(name).append(" : "); |
| appendDescriptor(sb, FIELD_DESCRIPTOR, desc); |
| sb.append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, opcode).append(' '); |
| appendDescriptor(sb, INTERNAL_NAME, owner); |
| sb.append('.').append(name); |
| appendDescriptor(sb, METHOD_DESCRIPTOR, desc); |
| sb.append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitInvokeDynamicInsn(final String name, final String desc, final Handle bsm, final Object... bsmArgs) { |
| final StringBuilder sb = new StringBuilder(); |
| |
| appendOpcode(sb, Opcodes.INVOKEDYNAMIC).append(' '); |
| final boolean isNashornBootstrap = isNashornBootstrap(bsm); |
| final boolean isNashornMathBootstrap = isNashornMathBootstrap(bsm); |
| if (isNashornBootstrap) { |
| sb.append(NashornCallSiteDescriptor.getOperationName((Integer)bsmArgs[0])); |
| final String decodedName = NameCodec.decode(name); |
| if (!decodedName.isEmpty()) { |
| sb.append(':').append(decodedName); |
| } |
| } else { |
| sb.append(name); |
| } |
| appendDescriptor(sb, METHOD_DESCRIPTOR, desc); |
| final int len = sb.length(); |
| for (int i = 0; i < 80 - len ; i++) { |
| sb.append(' '); |
| } |
| sb.append(" ["); |
| appendHandle(sb, bsm); |
| if (bsmArgs.length == 0) { |
| sb.append("none"); |
| } else { |
| for (final Object cst : bsmArgs) { |
| if (cst instanceof String) { |
| appendStr(sb, (String)cst); |
| } else if (cst instanceof Type) { |
| sb.append(((Type)cst).getDescriptor()).append(".class"); |
| } else if (cst instanceof Handle) { |
| appendHandle(sb, (Handle)cst); |
| } else if (cst instanceof Integer && isNashornBootstrap) { |
| NashornCallSiteDescriptor.appendFlags((Integer) cst, sb); |
| } else if (cst instanceof Integer && isNashornMathBootstrap) { |
| sb.append(" pp=").append(cst); |
| } else { |
| sb.append(cst); |
| } |
| sb.append(", "); |
| } |
| sb.setLength(sb.length() - 2); |
| } |
| |
| sb.append("]\n"); |
| addText(sb); |
| } |
| |
| private static boolean isNashornBootstrap(final Handle bsm) { |
| return "bootstrap".equals(bsm.getName()) && BOOTSTRAP_CLASS_NAME.equals(bsm.getOwner()); |
| } |
| |
| private static boolean isNashornMathBootstrap(final Handle bsm) { |
| return "mathBootstrap".equals(bsm.getName()) && BOOTSTRAP_CLASS_NAME.equals(bsm.getOwner()); |
| } |
| |
| private static boolean noFallThru(final int opcode) { |
| switch (opcode) { |
| case Opcodes.GOTO: |
| case Opcodes.ATHROW: |
| case Opcodes.ARETURN: |
| case Opcodes.IRETURN: |
| case Opcodes.LRETURN: |
| case Opcodes.FRETURN: |
| case Opcodes.DRETURN: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private void checkNoFallThru(final int opcode, final String to) { |
| if (noFallThru(opcode)) { |
| graph.setNoFallThru(currentBlock); |
| } |
| |
| if (currentBlock != null && to != null) { |
| graph.addEdge(currentBlock, to); |
| } |
| } |
| |
| @Override |
| public void visitJumpInsn(final int opcode, final Label label) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, opcode).append(' '); |
| final String to = appendLabel(sb, label); |
| sb.append('\n'); |
| addText(sb); |
| checkNoFallThru(opcode, to); |
| } |
| |
| private void addText(final Object t) { |
| text.add(t); |
| if (currentBlock != null) { |
| graph.addText(currentBlock, t.toString()); |
| } |
| } |
| |
| @Override |
| public void visitLabel(final Label label) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("\n"); |
| final String name = appendLabel(sb, label); |
| sb.append(" [bci="); |
| sb.append(label.info); |
| sb.append("]"); |
| sb.append("\n"); |
| |
| graph.addNode(name); |
| if (currentBlock != null && !graph.isNoFallThru(currentBlock)) { |
| graph.addEdge(currentBlock, name); |
| } |
| currentBlock = name; |
| addText(sb); |
| } |
| |
| @Override |
| public void visitLdcInsn(final Object cst) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, Opcodes.LDC).append(' '); |
| if (cst instanceof String) { |
| appendStr(sb, (String) cst); |
| } else if (cst instanceof Type) { |
| sb.append(((Type) cst).getDescriptor()).append(".class"); |
| } else { |
| sb.append(cst); |
| } |
| sb.append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitIincInsn(final int var, final int increment) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, Opcodes.IINC).append(' '); |
| sb.append(var).append(' ') |
| .append(increment).append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, Opcodes.TABLESWITCH).append(' '); |
| for (int i = 0; i < labels.length; ++i) { |
| sb.append(tab3).append(min + i).append(": "); |
| final String to = appendLabel(sb, labels[i]); |
| graph.addEdge(currentBlock, to); |
| sb.append('\n'); |
| } |
| sb.append(tab3).append("default: "); |
| appendLabel(sb, dflt); |
| sb.append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, Opcodes.LOOKUPSWITCH).append(' '); |
| for (int i = 0; i < labels.length; ++i) { |
| sb.append(tab3).append(keys[i]).append(": "); |
| final String to = appendLabel(sb, labels[i]); |
| graph.addEdge(currentBlock, to); |
| sb.append('\n'); |
| } |
| sb.append(tab3).append("default: "); |
| final String to = appendLabel(sb, dflt); |
| graph.addEdge(currentBlock, to); |
| sb.append('\n'); |
| addText(sb.toString()); |
| } |
| |
| @Override |
| public void visitMultiANewArrayInsn(final String desc, final int dims) { |
| final StringBuilder sb = new StringBuilder(); |
| appendOpcode(sb, Opcodes.MULTIANEWARRAY).append(' '); |
| appendDescriptor(sb, FIELD_DESCRIPTOR, desc); |
| sb.append(' ').append(dims).append('\n'); |
| addText(sb); |
| } |
| |
| @Override |
| public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append(tab2).append("try "); |
| final String from = appendLabel(sb, start); |
| sb.append(' '); |
| appendLabel(sb, end); |
| sb.append(' '); |
| final String to = appendLabel(sb, handler); |
| sb.append(' '); |
| appendDescriptor(sb, INTERNAL_NAME, type); |
| sb.append('\n'); |
| addText(sb); |
| graph.setIsCatch(to, type); |
| graph.addTryCatch(from, to); |
| } |
| |
| @Override |
| public void visitLocalVariable(final String name, final String desc,final String signature, final Label start, final Label end, final int index) { |
| |
| final StringBuilder sb = new StringBuilder(); |
| if (!localVarsStarted) { |
| text.add("\n"); |
| localVarsStarted = true; |
| graph.addNode("vars"); |
| currentBlock = "vars"; |
| } |
| |
| sb.append(tab2).append("local ").append(name).append(' '); |
| final int len = sb.length(); |
| for (int i = 0; i < 25 - len; i++) { |
| sb.append(' '); |
| } |
| String label; |
| |
| label = appendLabel(sb, start); |
| for (int i = 0; i < 5 - label.length(); i++) { |
| sb.append(' '); |
| } |
| label = appendLabel(sb, end); |
| for (int i = 0; i < 5 - label.length(); i++) { |
| sb.append(' '); |
| } |
| |
| sb.append(index).append(tab2); |
| |
| appendDescriptor(sb, FIELD_DESCRIPTOR, desc); |
| sb.append('\n'); |
| |
| if (signature != null) { |
| sb.append(tab2); |
| appendDescriptor(sb, FIELD_SIGNATURE, signature); |
| |
| final TraceSignatureVisitor sv = new TraceSignatureVisitor(0); |
| final SignatureReader r = new SignatureReader(signature); |
| r.acceptType(sv); |
| sb.append(tab2).append("// declaration: ") |
| .append(sv.getDeclaration()).append('\n'); |
| } |
| addText(sb.toString()); |
| } |
| |
| @Override |
| public void visitLineNumber(final int line, final Label start) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("<line "); |
| sb.append(line); |
| sb.append(">\n"); |
| addText(sb.toString()); |
| } |
| |
| @Override |
| public void visitMaxs(final int maxStack, final int maxLocals) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append('\n'); |
| sb.append(tab2).append("max stack = ").append(maxStack); |
| sb.append(", max locals = ").append(maxLocals).append('\n'); |
| addText(sb.toString()); |
| } |
| |
| private void printToDir(final Graph g) { |
| if (env._print_code_dir != null) { |
| final File dir = new File(env._print_code_dir); |
| if (!dir.exists() && !dir.mkdirs()) { |
| throw new RuntimeException(dir.toString()); |
| } |
| |
| File file; |
| int uniqueId = 0; |
| do { |
| final String fileName = g.getName() + (uniqueId == 0 ? "" : "_" + uniqueId) + ".dot"; |
| file = new File(dir, fileName); |
| uniqueId++; |
| } while (file.exists()); |
| |
| try (PrintWriter pw = new PrintWriter(new FileOutputStream(file))) { |
| pw.println(g); |
| } catch (final FileNotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| |
| @Override |
| public void visitMethodEnd() { |
| //here we need to do several bytecode guesses best upon the ldc instructions. |
| //for each instruction, assign bci. for an ldc/w/2w, guess a byte and keep |
| //iterating. if the next label is wrong, backtrack. |
| if (env._print_code_func == null || env._print_code_func.equals(graph.getName())) { |
| if (env._print_code_dir != null) { |
| printToDir(graph); |
| } |
| } |
| } |
| |
| /** |
| * Creates a new TraceVisitor instance. |
| * |
| * @return a new TraceVisitor. |
| */ |
| protected NashornTextifier createNashornTextifier() { |
| return new NashornTextifier(env, cr, labelIter, graph); |
| } |
| |
| private static void appendDescriptor(final StringBuilder sb, final int type, final String desc) { |
| if (desc != null) { |
| if (type == CLASS_SIGNATURE || type == FIELD_SIGNATURE || type == METHOD_SIGNATURE) { |
| sb.append("// signature ").append(desc).append('\n'); |
| } else { |
| appendShortDescriptor(sb, desc); |
| } |
| } |
| } |
| |
| private String appendLabel(final StringBuilder sb, final Label l) { |
| if (labelNames == null) { |
| labelNames = new HashMap<>(); |
| } |
| String name = labelNames.get(l); |
| if (name == null) { |
| name = "L" + labelNames.size(); |
| labelNames.put(l, name); |
| } |
| sb.append(name); |
| return name; |
| } |
| |
| private static void appendHandle(final StringBuilder sb, final Handle h) { |
| switch (h.getTag()) { |
| case Opcodes.H_GETFIELD: |
| sb.append("getfield"); |
| break; |
| case Opcodes.H_GETSTATIC: |
| sb.append("getstatic"); |
| break; |
| case Opcodes.H_PUTFIELD: |
| sb.append("putfield"); |
| break; |
| case Opcodes.H_PUTSTATIC: |
| sb.append("putstatic"); |
| break; |
| case Opcodes.H_INVOKEINTERFACE: |
| sb.append("interface"); |
| break; |
| case Opcodes.H_INVOKESPECIAL: |
| sb.append("special"); |
| break; |
| case Opcodes.H_INVOKESTATIC: |
| sb.append("static"); |
| break; |
| case Opcodes.H_INVOKEVIRTUAL: |
| sb.append("virtual"); |
| break; |
| case Opcodes.H_NEWINVOKESPECIAL: |
| sb.append("new_special"); |
| break; |
| default: |
| assert false; |
| break; |
| } |
| sb.append(" '"); |
| sb.append(h.getName()); |
| sb.append("'"); |
| } |
| |
| private static void appendAccess(final StringBuilder sb, final int access) { |
| if ((access & Opcodes.ACC_PUBLIC) != 0) { |
| sb.append("public "); |
| } |
| if ((access & Opcodes.ACC_PRIVATE) != 0) { |
| sb.append("private "); |
| } |
| if ((access & Opcodes.ACC_PROTECTED) != 0) { |
| sb.append("protected "); |
| } |
| if ((access & Opcodes.ACC_FINAL) != 0) { |
| sb.append("final "); |
| } |
| if ((access & Opcodes.ACC_STATIC) != 0) { |
| sb.append("static "); |
| } |
| if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) { |
| sb.append("synchronized "); |
| } |
| if ((access & Opcodes.ACC_VOLATILE) != 0) { |
| sb.append("volatile "); |
| } |
| if ((access & Opcodes.ACC_TRANSIENT) != 0) { |
| sb.append("transient "); |
| } |
| if ((access & Opcodes.ACC_ABSTRACT) != 0) { |
| sb.append("abstract "); |
| } |
| if ((access & Opcodes.ACC_STRICT) != 0) { |
| sb.append("strictfp "); |
| } |
| if ((access & Opcodes.ACC_SYNTHETIC) != 0) { |
| sb.append("synthetic "); |
| } |
| if ((access & Opcodes.ACC_MANDATED) != 0) { |
| sb.append("mandated "); |
| } |
| if ((access & Opcodes.ACC_ENUM) != 0) { |
| sb.append("enum "); |
| } |
| } |
| |
| private void appendFrameTypes(final StringBuilder sb, final int n, final Object[] o) { |
| for (int i = 0; i < n; ++i) { |
| if (i > 0) { |
| sb.append(' '); |
| } |
| if (o[i] instanceof String) { |
| final String desc = (String) o[i]; |
| if (desc.startsWith("[")) { |
| appendDescriptor(sb, FIELD_DESCRIPTOR, desc); |
| } else { |
| appendDescriptor(sb, INTERNAL_NAME, desc); |
| } |
| } else if (o[i] instanceof Integer) { |
| switch (((Integer)o[i])) { |
| case 0: |
| appendDescriptor(sb, FIELD_DESCRIPTOR, "T"); |
| break; |
| case 1: |
| appendDescriptor(sb, FIELD_DESCRIPTOR, "I"); |
| break; |
| case 2: |
| appendDescriptor(sb, FIELD_DESCRIPTOR, "F"); |
| break; |
| case 3: |
| appendDescriptor(sb, FIELD_DESCRIPTOR, "D"); |
| break; |
| case 4: |
| appendDescriptor(sb, FIELD_DESCRIPTOR, "J"); |
| break; |
| case 5: |
| appendDescriptor(sb, FIELD_DESCRIPTOR, "N"); |
| break; |
| case 6: |
| appendDescriptor(sb, FIELD_DESCRIPTOR, "U"); |
| break; |
| default: |
| assert false; |
| break; |
| } |
| } else { |
| appendLabel(sb, (Label) o[i]); |
| } |
| } |
| } |
| |
| private static void appendShortDescriptor(final StringBuilder sb, final String desc) { |
| //final StringBuilder buf = new StringBuilder(); |
| if (desc.charAt(0) == '(') { |
| for (int i = 0; i < desc.length(); i++) { |
| if (desc.charAt(i) == 'L') { |
| int slash = i; |
| while (desc.charAt(i) != ';') { |
| i++; |
| if (desc.charAt(i) == '/') { |
| slash = i; |
| } |
| } |
| sb.append(desc.substring(slash + 1, i)).append(';'); |
| } else { |
| sb.append(desc.charAt(i)); |
| } |
| } |
| } else { |
| final int lastSlash = desc.lastIndexOf('/'); |
| final int lastBracket = desc.lastIndexOf('['); |
| if(lastBracket != -1) { |
| sb.append(desc, 0, lastBracket + 1); |
| } |
| sb.append(lastSlash == -1 ? desc : desc.substring(lastSlash + 1)); |
| } |
| } |
| |
| private static void appendStr(final StringBuilder sb, final String s) { |
| sb.append('\"'); |
| for (int i = 0; i < s.length(); ++i) { |
| final char c = s.charAt(i); |
| if (c == '\n') { |
| sb.append("\\n"); |
| } else if (c == '\r') { |
| sb.append("\\r"); |
| } else if (c == '\\') { |
| sb.append("\\\\"); |
| } else if (c == '"') { |
| sb.append("\\\""); |
| } else if (c < 0x20 || c > 0x7f) { |
| sb.append("\\u"); |
| if (c < 0x10) { |
| sb.append("000"); |
| } else if (c < 0x100) { |
| sb.append("00"); |
| } else if (c < 0x1000) { |
| sb.append('0'); |
| } |
| sb.append(Integer.toString(c, 16)); |
| } else { |
| sb.append(c); |
| } |
| } |
| sb.append('\"'); |
| } |
| |
| private static class Graph { |
| private final LinkedHashSet<String> nodes; |
| private final Map<String, StringBuilder> contents; |
| private final Map<String, Set<String>> edges; |
| private final Set<String> hasPreds; |
| private final Set<String> noFallThru; |
| private final Map<String, String> catches; |
| private final Map<String, Set<String>> exceptionMap; //maps catch nodes to all their trys that can reach them |
| private final String name; |
| |
| private static final String LEFT_ALIGN = "\\l"; |
| private static final String COLOR_CATCH = "\"#ee9999\""; |
| private static final String COLOR_ORPHAN = "\"#9999bb\""; |
| private static final String COLOR_DEFAULT = "\"#99bb99\""; |
| private static final String COLOR_LOCALVARS = "\"#999999\""; |
| |
| Graph(final String name) { |
| this.name = name; |
| this.nodes = new LinkedHashSet<>(); |
| this.contents = new HashMap<>(); |
| this.edges = new HashMap<>(); |
| this.hasPreds = new HashSet<>(); |
| this.catches = new HashMap<>(); |
| this.noFallThru = new HashSet<>(); |
| this.exceptionMap = new HashMap<>(); |
| } |
| |
| void addEdge(final String from, final String to) { |
| Set<String> edgeSet = edges.get(from); |
| if (edgeSet == null) { |
| edgeSet = new LinkedHashSet<>(); |
| edges.put(from, edgeSet); |
| } |
| edgeSet.add(to); |
| hasPreds.add(to); |
| } |
| |
| void addTryCatch(final String tryNode, final String catchNode) { |
| Set<String> tryNodes = exceptionMap.get(catchNode); |
| if (tryNodes == null) { |
| tryNodes = new HashSet<>(); |
| exceptionMap.put(catchNode, tryNodes); |
| } |
| if (!tryNodes.contains(tryNode)) { |
| addEdge(tryNode, catchNode); |
| } |
| tryNodes.add(tryNode); |
| } |
| |
| void addNode(final String node) { |
| assert !nodes.contains(node); |
| nodes.add(node); |
| } |
| |
| void setNoFallThru(final String node) { |
| noFallThru.add(node); |
| } |
| |
| boolean isNoFallThru(final String node) { |
| return noFallThru.contains(node); |
| } |
| |
| void setIsCatch(final String node, final String exception) { |
| catches.put(node, exception); |
| } |
| |
| String getName() { |
| return name; |
| } |
| |
| void addText(final String node, final String text) { |
| StringBuilder sb = contents.get(node); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| } |
| |
| for (int i = 0; i < text.length(); i++) { |
| switch (text.charAt(i)) { |
| case '\n': |
| sb.append(LEFT_ALIGN); |
| break; |
| case '"': |
| sb.append("'"); |
| break; |
| default: |
| sb.append(text.charAt(i)); |
| break; |
| } |
| } |
| |
| contents.put(node, sb); |
| } |
| |
| private static String dottyFriendly(final String name) { |
| return name.replace(':', '_'); |
| } |
| |
| @Override |
| public String toString() { |
| |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("digraph ").append(dottyFriendly(name)).append(" {"); |
| sb.append("\n"); |
| sb.append("\tgraph [fontname=courier]\n"); |
| sb.append("\tnode [style=filled,color="+COLOR_DEFAULT+",fontname=courier]\n"); |
| sb.append("\tedge [fontname=courier]\n\n"); |
| |
| for (final String node : nodes) { |
| sb.append("\t"); |
| sb.append(node); |
| sb.append(" ["); |
| sb.append("id="); |
| sb.append(node); |
| sb.append(", label=\""); |
| String c = contents.get(node).toString(); |
| if (c.startsWith(LEFT_ALIGN)) { |
| c = c.substring(LEFT_ALIGN.length()); |
| } |
| final String ex = catches.get(node); |
| if (ex != null) { |
| sb.append("*** CATCH: ").append(ex).append(" ***\\l"); |
| } |
| sb.append(c); |
| sb.append("\"]\n"); |
| } |
| |
| for (final String from : edges.keySet()) { |
| for (final String to : edges.get(from)) { |
| sb.append("\t"); |
| sb.append(from); |
| sb.append(" -> "); |
| sb.append(to); |
| sb.append("[label=\""); |
| sb.append(to); |
| sb.append("\""); |
| if (catches.get(to) != null) { |
| sb.append(", color=red, style=dashed"); |
| } |
| sb.append(']'); |
| sb.append(";\n"); |
| } |
| } |
| |
| sb.append("\n"); |
| for (final String node : nodes) { |
| sb.append("\t"); |
| sb.append(node); |
| sb.append(" [shape=box"); |
| if (catches.get(node) != null) { |
| sb.append(", color=" + COLOR_CATCH); |
| } else if ("vars".equals(node)) { |
| sb.append(", shape=hexagon, color=" + COLOR_LOCALVARS); |
| } else if (!hasPreds.contains(node)) { |
| sb.append(", color=" + COLOR_ORPHAN); |
| } |
| sb.append("]\n"); |
| } |
| |
| sb.append("}\n"); |
| return sb.toString(); |
| } |
| } |
| |
| static class NashornLabel extends Label { |
| final Label label; |
| final int bci; |
| final int opcode; |
| |
| NashornLabel(final Label label, final int bci) { |
| this.label = label; |
| this.bci = bci; |
| this.opcode = -1; |
| } |
| |
| //not an ASM label |
| NashornLabel(final int opcode, final int bci) { |
| this.opcode = opcode; |
| this.bci = bci; |
| this.label = null; |
| } |
| |
| Label getLabel() { |
| return label; |
| } |
| |
| @Override |
| public int getOffset() { |
| return bci; |
| } |
| |
| @Override |
| public String toString() { |
| return "label " + bci; |
| } |
| } |
| |
| @Override |
| public Printer visitAnnotationDefault() { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public Printer visitClassAnnotation(final String arg0, final boolean arg1) { |
| return this; |
| } |
| |
| @Override |
| public void visitClassAttribute(final Attribute arg0) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public Printer visitFieldAnnotation(final String arg0, final boolean arg1) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public void visitFieldAttribute(final Attribute arg0) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public Printer visitMethodAnnotation(final String arg0, final boolean arg1) { |
| return this; |
| } |
| |
| @Override |
| public void visitMethodAttribute(final Attribute arg0) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public Printer visitParameterAnnotation(final int arg0, final String arg1, final boolean arg2) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public void visit(final String arg0, final Object arg1) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public Printer visitAnnotation(final String arg0, final String arg1) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public void visitAnnotationEnd() { |
| //empty |
| } |
| |
| @Override |
| public Printer visitArray(final String arg0) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public void visitEnum(final String arg0, final String arg1, final String arg2) { |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public void visitInnerClass(final String arg0, final String arg1, final String arg2, final int arg3) { |
| throw new AssertionError(); |
| } |
| } |