blob: c5dc56bf26fea023926ddb305dab608de5374f97 [file] [log] [blame]
/*
* 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 static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT;
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.FLAGS_MASK;
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.NashornCallSiteDescriptor;
/**
* Pretty printer for --print-code.
* Also supports dot formats if --print-code has arguments
*/
public final class NashornTextifier extends Printer {
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(' ');
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) {
final int c = (Integer)cst;
final int pp = c >> CALLSITE_PROGRAM_POINT_SHIFT;
if (pp != 0) {
sb.append(" pp=").append(pp);
}
sb.append(NashornCallSiteDescriptor.toString(c & FLAGS_MASK));
} else {
sb.append(cst);
}
sb.append(", ");
}
sb.setLength(sb.length() - 2);
}
sb.append("]\n");
addText(sb);
}
private static final 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]).intValue()) {
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 " + dottyFriendly(name) + " {");
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();
}
}