blob: 52d5a7174f1806c706702899dcdfcfbc2ec7255c [file] [log] [blame]
/*
* Copyright (c) 2012, 2016, 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 com.sun.hotspot.igv.data.serialization;
import com.sun.hotspot.igv.data.*;
import com.sun.hotspot.igv.data.Properties;
import com.sun.hotspot.igv.data.services.GroupCallback;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.SwingUtilities;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class BinaryParser implements GraphParser {
private static final int BEGIN_GROUP = 0x00;
private static final int BEGIN_GRAPH = 0x01;
private static final int CLOSE_GROUP = 0x02;
private static final int POOL_NEW = 0x00;
private static final int POOL_STRING = 0x01;
private static final int POOL_ENUM = 0x02;
private static final int POOL_CLASS = 0x03;
private static final int POOL_METHOD = 0x04;
private static final int POOL_NULL = 0x05;
private static final int POOL_NODE_CLASS = 0x06;
private static final int POOL_FIELD = 0x07;
private static final int POOL_SIGNATURE = 0x08;
private static final int KLASS = 0x00;
private static final int ENUM_KLASS = 0x01;
private static final int PROPERTY_POOL = 0x00;
private static final int PROPERTY_INT = 0x01;
private static final int PROPERTY_LONG = 0x02;
private static final int PROPERTY_DOUBLE = 0x03;
private static final int PROPERTY_FLOAT = 0x04;
private static final int PROPERTY_TRUE = 0x05;
private static final int PROPERTY_FALSE = 0x06;
private static final int PROPERTY_ARRAY = 0x07;
private static final int PROPERTY_SUBGRAPH = 0x08;
private static final String NO_BLOCK = "noBlock";
private static final Charset utf8 = Charset.forName("UTF-8");
private final GroupCallback callback;
private final List<Object> constantPool;
private final ByteBuffer buffer;
private final ReadableByteChannel channel;
private final GraphDocument rootDocument;
private final Deque<Folder> folderStack;
private final Deque<byte[]> hashStack;
private final ParseMonitor monitor;
private MessageDigest digest;
private enum Length {
S,
M,
L
}
private interface LengthToString {
String toString(Length l);
}
private static abstract class Member implements LengthToString {
public final Klass holder;
public final int accessFlags;
public final String name;
public Member(Klass holder, String name, int accessFlags) {
this.holder = holder;
this.accessFlags = accessFlags;
this.name = name;
}
}
private static class Method extends Member {
public final Signature signature;
public final byte[] code;
public Method(String name, Signature signature, byte[] code, Klass holder, int accessFlags) {
super(holder, name, accessFlags);
this.signature = signature;
this.code = code;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(holder).append('.').append(name).append('(');
for (int i = 0; i < signature.argTypes.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(signature.argTypes[i]);
}
sb.append(')');
return sb.toString();
}
@Override
public String toString(Length l) {
switch(l) {
case M:
return holder.toString(Length.L) + "." + name;
case S:
return holder.toString(Length.S) + "." + name;
default:
case L:
return toString();
}
}
}
private static class Signature {
public final String returnType;
public final String[] argTypes;
public Signature(String returnType, String[] argTypes) {
this.returnType = returnType;
this.argTypes = argTypes;
}
}
private static class Field extends Member {
public final String type;
public Field(String type, Klass holder, String name, int accessFlags) {
super(holder, name, accessFlags);
this.type = type;
}
@Override
public String toString() {
return holder + "." + name;
}
@Override
public String toString(Length l) {
switch(l) {
case M:
return holder.toString(Length.L) + "." + name;
case S:
return holder.toString(Length.S) + "." + name;
default:
case L:
return toString();
}
}
}
private static class Klass implements LengthToString {
public final String name;
public final String simpleName;
public Klass(String name) {
this.name = name;
String simple;
try {
simple = name.substring(name.lastIndexOf('.') + 1);
} catch (IndexOutOfBoundsException ioobe) {
simple = name;
}
this.simpleName = simple;
}
@Override
public String toString() {
return name;
}
@Override
public String toString(Length l) {
switch(l) {
case S:
return simpleName;
default:
case L:
case M:
return toString();
}
}
}
private static class EnumKlass extends Klass {
public final String[] values;
public EnumKlass(String name, String[] values) {
super(name);
this.values = values;
}
}
private static class Port {
public final boolean isList;
public final String name;
private Port(boolean isList, String name) {
this.isList = isList;
this.name = name;
}
}
private static class TypedPort extends Port {
public final EnumValue type;
private TypedPort(boolean isList, String name, EnumValue type) {
super(isList, name);
this.type = type;
}
}
private static class NodeClass {
public final String className;
public final String nameTemplate;
public final List<TypedPort> inputs;
public final List<Port> sux;
private NodeClass(String className, String nameTemplate, List<TypedPort> inputs, List<Port> sux) {
this.className = className;
this.nameTemplate = nameTemplate;
this.inputs = inputs;
this.sux = sux;
}
@Override
public String toString() {
return className;
}
}
private static class EnumValue implements LengthToString {
public EnumKlass enumKlass;
public int ordinal;
public EnumValue(EnumKlass enumKlass, int ordinal) {
this.enumKlass = enumKlass;
this.ordinal = ordinal;
}
@Override
public String toString() {
return enumKlass.simpleName + "." + enumKlass.values[ordinal];
}
@Override
public String toString(Length l) {
switch(l) {
case S:
return enumKlass.values[ordinal];
default:
case M:
case L:
return toString();
}
}
}
public BinaryParser(ReadableByteChannel channel, ParseMonitor monitor, GraphDocument rootDocument, GroupCallback callback) {
this.callback = callback;
constantPool = new ArrayList<>();
buffer = ByteBuffer.allocateDirect(256 * 1024);
buffer.flip();
this.channel = channel;
this.rootDocument = rootDocument;
folderStack = new LinkedList<>();
hashStack = new LinkedList<>();
this.monitor = monitor;
try {
this.digest = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
}
}
private void fill() throws IOException {
// All the data between lastPosition and position has been
// used so add it to the digest.
int position = buffer.position();
buffer.position(lastPosition);
byte[] remaining = new byte[position - buffer.position()];
buffer.get(remaining);
digest.update(remaining);
assert position == buffer.position();
buffer.compact();
if (channel.read(buffer) < 0) {
throw new EOFException();
}
buffer.flip();
lastPosition = buffer.position();
}
private void ensureAvailable(int i) throws IOException {
if (i > buffer.capacity()) {
throw new IllegalArgumentException(String.format("Can not request %d bytes: buffer capacity is %d", i, buffer.capacity()));
}
while (buffer.remaining() < i) {
fill();
}
}
private int readByte() throws IOException {
ensureAvailable(1);
return ((int)buffer.get()) & 0xff;
}
private int readInt() throws IOException {
ensureAvailable(4);
return buffer.getInt();
}
private char readShort() throws IOException {
ensureAvailable(2);
return buffer.getChar();
}
private long readLong() throws IOException {
ensureAvailable(8);
return buffer.getLong();
}
private double readDouble() throws IOException {
ensureAvailable(8);
return buffer.getDouble();
}
private float readFloat() throws IOException {
ensureAvailable(4);
return buffer.getFloat();
}
private String readString() throws IOException {
return new String(readBytes(), utf8).intern();
}
private byte[] readBytes() throws IOException {
int len = readInt();
if (len < 0) {
return null;
}
byte[] b = new byte[len];
int bytesRead = 0;
while (bytesRead < b.length) {
int toRead = Math.min(b.length - bytesRead, buffer.capacity());
ensureAvailable(toRead);
buffer.get(b, bytesRead, toRead);
bytesRead += toRead;
}
return b;
}
private String readIntsToString() throws IOException {
int len = readInt();
if (len < 0) {
return "null";
}
ensureAvailable(len * 4);
StringBuilder sb = new StringBuilder().append('[');
for (int i = 0; i < len; i++) {
sb.append(buffer.getInt());
if (i < len - 1) {
sb.append(", ");
}
}
sb.append(']');
return sb.toString().intern();
}
private String readDoublesToString() throws IOException {
int len = readInt();
if (len < 0) {
return "null";
}
ensureAvailable(len * 8);
StringBuilder sb = new StringBuilder().append('[');
for (int i = 0; i < len; i++) {
sb.append(buffer.getDouble());
if (i < len - 1) {
sb.append(", ");
}
}
sb.append(']');
return sb.toString().intern();
}
private String readPoolObjectsToString() throws IOException {
int len = readInt();
if (len < 0) {
return "null";
}
StringBuilder sb = new StringBuilder().append('[');
for (int i = 0; i < len; i++) {
sb.append(readPoolObject(Object.class));
if (i < len - 1) {
sb.append(", ");
}
}
sb.append(']');
return sb.toString().intern();
}
private <T> T readPoolObject(Class<T> klass) throws IOException {
int type = readByte();
if (type == POOL_NULL) {
return null;
}
if (type == POOL_NEW) {
return (T) addPoolEntry(klass);
}
assert assertObjectType(klass, type);
char index = readShort();
if (index < 0 || index >= constantPool.size()) {
throw new IOException("Invalid constant pool index : " + index);
}
Object obj = constantPool.get(index);
return (T) obj;
}
private boolean assertObjectType(Class<?> klass, int type) {
switch(type) {
case POOL_CLASS:
return klass.isAssignableFrom(EnumKlass.class);
case POOL_ENUM:
return klass.isAssignableFrom(EnumValue.class);
case POOL_METHOD:
return klass.isAssignableFrom(Method.class);
case POOL_STRING:
return klass.isAssignableFrom(String.class);
case POOL_NODE_CLASS:
return klass.isAssignableFrom(NodeClass.class);
case POOL_FIELD:
return klass.isAssignableFrom(Field.class);
case POOL_SIGNATURE:
return klass.isAssignableFrom(Signature.class);
case POOL_NULL:
return true;
default:
return false;
}
}
private Object addPoolEntry(Class<?> klass) throws IOException {
char index = readShort();
int type = readByte();
assert assertObjectType(klass, type) : "Wrong object type : " + klass + " != " + type;
Object obj;
switch(type) {
case POOL_CLASS: {
String name = readString();
int klasstype = readByte();
if (klasstype == ENUM_KLASS) {
int len = readInt();
String[] values = new String[len];
for (int i = 0; i < len; i++) {
values[i] = readPoolObject(String.class);
}
obj = new EnumKlass(name, values);
} else if (klasstype == KLASS) {
obj = new Klass(name);
} else {
throw new IOException("unknown klass type : " + klasstype);
}
break;
}
case POOL_ENUM: {
EnumKlass enumClass = readPoolObject(EnumKlass.class);
int ordinal = readInt();
obj = new EnumValue(enumClass, ordinal);
break;
}
case POOL_NODE_CLASS: {
String className = readString();
String nameTemplate = readString();
int inputCount = readShort();
List<TypedPort> inputs = new ArrayList<>(inputCount);
for (int i = 0; i < inputCount; i++) {
boolean isList = readByte() != 0;
String name = readPoolObject(String.class);
EnumValue inputType = readPoolObject(EnumValue.class);
inputs.add(new TypedPort(isList, name, inputType));
}
int suxCount = readShort();
List<Port> sux = new ArrayList<>(suxCount);
for (int i = 0; i < suxCount; i++) {
boolean isList = readByte() != 0;
String name = readPoolObject(String.class);
sux.add(new Port(isList, name));
}
obj = new NodeClass(className, nameTemplate, inputs, sux);
break;
}
case POOL_METHOD: {
Klass holder = readPoolObject(Klass.class);
String name = readPoolObject(String.class);
Signature sign = readPoolObject(Signature.class);
int flags = readInt();
byte[] code = readBytes();
obj = new Method(name, sign, code, holder, flags);
break;
}
case POOL_FIELD: {
Klass holder = readPoolObject(Klass.class);
String name = readPoolObject(String.class);
String fType = readPoolObject(String.class);
int flags = readInt();
obj = new Field(fType, holder, name, flags);
break;
}
case POOL_SIGNATURE: {
int argc = readShort();
String[] args = new String[argc];
for (int i = 0; i < argc; i++) {
args[i] = readPoolObject(String.class);
}
String returnType = readPoolObject(String.class);
obj = new Signature(returnType, args);
break;
}
case POOL_STRING: {
obj = readString();
break;
}
default:
throw new IOException("unknown pool type");
}
while (constantPool.size() <= index) {
constantPool.add(null);
}
constantPool.set(index, obj);
return obj;
}
private Object readPropertyObject() throws IOException {
int type = readByte();
switch (type) {
case PROPERTY_INT:
return readInt();
case PROPERTY_LONG:
return readLong();
case PROPERTY_FLOAT:
return readFloat();
case PROPERTY_DOUBLE:
return readDouble();
case PROPERTY_TRUE:
return Boolean.TRUE;
case PROPERTY_FALSE:
return Boolean.FALSE;
case PROPERTY_POOL:
return readPoolObject(Object.class);
case PROPERTY_ARRAY:
int subType = readByte();
switch(subType) {
case PROPERTY_INT:
return readIntsToString();
case PROPERTY_DOUBLE:
return readDoublesToString();
case PROPERTY_POOL:
return readPoolObjectsToString();
default:
throw new IOException("Unknown type");
}
case PROPERTY_SUBGRAPH:
InputGraph graph = parseGraph("");
new Group(null).addElement(graph);
return graph;
default:
throw new IOException("Unknown type");
}
}
@Override
public GraphDocument parse() throws IOException {
folderStack.push(rootDocument);
hashStack.push(null);
if (monitor != null) {
monitor.setState("Starting parsing");
}
try {
while(true) {
parseRoot();
}
} catch (EOFException e) {
}
if (monitor != null) {
monitor.setState("Finished parsing");
}
return rootDocument;
}
private void parseRoot() throws IOException {
int type = readByte();
switch(type) {
case BEGIN_GRAPH: {
final Folder parent = folderStack.peek();
final InputGraph graph = parseGraph();
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run() {
parent.addElement(graph);
}
});
break;
}
case BEGIN_GROUP: {
final Folder parent = folderStack.peek();
final Group group = parseGroup(parent);
if (callback == null || parent instanceof Group) {
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run() {
parent.addElement(group);
}
});
}
folderStack.push(group);
hashStack.push(null);
if (callback != null && parent instanceof GraphDocument) {
callback.started(group);
}
break;
}
case CLOSE_GROUP: {
if (folderStack.isEmpty()) {
throw new IOException("Unbalanced groups");
}
folderStack.pop();
hashStack.pop();
break;
}
default:
throw new IOException("unknown root : " + type);
}
}
private Group parseGroup(Folder parent) throws IOException {
String name = readPoolObject(String.class);
String shortName = readPoolObject(String.class);
if (monitor != null) {
monitor.setState(shortName);
}
Method method = readPoolObject(Method.class);
int bci = readInt();
Group group = new Group(parent);
group.getProperties().setProperty("name", name);
parseProperties(group.getProperties());
if (method != null) {
InputMethod inMethod = new InputMethod(group, method.name, shortName, bci);
inMethod.setBytecodes("TODO");
group.setMethod(inMethod);
}
return group;
}
int lastPosition = 0;
private InputGraph parseGraph() throws IOException {
if (monitor != null) {
monitor.updateProgress();
}
String title = readPoolObject(String.class);
digest.reset();
lastPosition = buffer.position();
InputGraph graph = parseGraph(title);
int position = buffer.position();
buffer.position(lastPosition);
byte[] remaining = new byte[position - buffer.position()];
buffer.get(remaining);
digest.update(remaining);
assert position == buffer.position();
lastPosition = buffer.position();
byte[] d = digest.digest();
byte[] hash = hashStack.peek();
if (hash != null && Arrays.equals(hash, d)) {
graph.getProperties().setProperty("_isDuplicate", "true");
} else {
hashStack.pop();
hashStack.push(d);
}
return graph;
}
private void parseProperties(Properties properties) throws IOException {
int propCount = readShort();
for (int j = 0; j < propCount; j++) {
String key = readPoolObject(String.class);
Object value = readPropertyObject();
properties.setProperty(key, value != null ? value.toString() : "null");
}
}
private InputGraph parseGraph(String title) throws IOException {
InputGraph graph = new InputGraph(title);
parseProperties(graph.getProperties());
parseNodes(graph);
parseBlocks(graph);
graph.ensureNodesInBlocks();
for (InputNode node : graph.getNodes()) {
node.internProperties();
}
return graph;
}
private void parseBlocks(InputGraph graph) throws IOException {
int blockCount = readInt();
List<Edge> edges = new LinkedList<>();
for (int i = 0; i < blockCount; i++) {
int id = readInt();
String name = id >= 0 ? Integer.toString(id) : NO_BLOCK;
InputBlock block = graph.addBlock(name);
int nodeCount = readInt();
for (int j = 0; j < nodeCount; j++) {
int nodeId = readInt();
if (nodeId < 0) {
continue;
}
final Properties properties = graph.getNode(nodeId).getProperties();
final String oldBlock = properties.get("block");
if(oldBlock != null) {
properties.setProperty("block", oldBlock + ", " + name);
} else {
block.addNode(nodeId);
properties.setProperty("block", name);
}
}
int edgeCount = readInt();
for (int j = 0; j < edgeCount; j++) {
int to = readInt();
edges.add(new Edge(id, to));
}
}
for (Edge e : edges) {
String fromName = e.from >= 0 ? Integer.toString(e.from) : NO_BLOCK;
String toName = e.to >= 0 ? Integer.toString(e.to) : NO_BLOCK;
graph.addBlockEdge(graph.getBlock(fromName), graph.getBlock(toName));
}
}
private void parseNodes(InputGraph graph) throws IOException {
int count = readInt();
Map<String, Object> props = new HashMap<>();
List<Edge> inputEdges = new ArrayList<>(count);
List<Edge> succEdges = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
int id = readInt();
InputNode node = new InputNode(id);
final Properties properties = node.getProperties();
NodeClass nodeClass = readPoolObject(NodeClass.class);
int preds = readByte();
if (preds > 0) {
properties.setProperty("hasPredecessor", "true");
}
properties.setProperty("idx", Integer.toString(id));
int propCount = readShort();
for (int j = 0; j < propCount; j++) {
String key = readPoolObject(String.class);
if (key.equals("hasPredecessor") || key.equals("name") || key.equals("class") || key.equals("id") || key.equals("idx")) {
key = "!data." + key;
}
Object value = readPropertyObject();
if (value instanceof InputGraph) {
InputGraph subgraph = (InputGraph) value;
subgraph.getProperties().setProperty("name", node.getId() + ":" + key);
node.addSubgraph((InputGraph) value);
} else {
properties.setProperty(key, value != null ? value.toString() : "null");
props.put(key, value);
}
}
ArrayList<Edge> currentEdges = new ArrayList<>();
int portNum = 0;
for (TypedPort p : nodeClass.inputs) {
if (p.isList) {
int size = readShort();
for (int j = 0; j < size; j++) {
int in = readInt();
if (in >= 0) {
Edge e = new Edge(in, id, (char) (preds + portNum), p.name + "[" + j + "]", p.type.toString(Length.S), true);
currentEdges.add(e);
inputEdges.add(e);
portNum++;
}
}
} else {
int in = readInt();
if (in >= 0) {
Edge e = new Edge(in, id, (char) (preds + portNum), p.name, p.type.toString(Length.S), true);
currentEdges.add(e);
inputEdges.add(e);
portNum++;
}
}
}
portNum = 0;
for (Port p : nodeClass.sux) {
if (p.isList) {
int size = readShort();
for (int j = 0; j < size; j++) {
int sux = readInt();
if (sux >= 0) {
Edge e = new Edge(id, sux, (char) portNum, p.name + "[" + j + "]", "Successor", false);
currentEdges.add(e);
succEdges.add(e);
portNum++;
}
}
} else {
int sux = readInt();
if (sux >= 0) {
Edge e = new Edge(id, sux, (char) portNum, p.name, "Successor", false);
currentEdges.add(e);
succEdges.add(e);
portNum++;
}
}
}
properties.setProperty("name", createName(currentEdges, props, nodeClass.nameTemplate));
properties.setProperty("class", nodeClass.className);
switch (nodeClass.className) {
case "BeginNode":
properties.setProperty("shortName", "B");
break;
case "EndNode":
properties.setProperty("shortName", "E");
break;
}
graph.addNode(node);
props.clear();
}
Set<InputNode> nodesWithSuccessor = new HashSet<>();
for (Edge e : succEdges) {
assert !e.input;
char fromIndex = e.num;
nodesWithSuccessor.add(graph.getNode(e.from));
char toIndex = 0;
graph.addEdge(InputEdge.createImmutable(fromIndex, toIndex, e.from, e.to, e.label, e.type));
}
for (Edge e : inputEdges) {
assert e.input;
char fromIndex = (char) (nodesWithSuccessor.contains(graph.getNode(e.from)) ? 1 : 0);
char toIndex = e.num;
graph.addEdge(InputEdge.createImmutable(fromIndex, toIndex, e.from, e.to, e.label, e.type));
}
}
static final Pattern templatePattern = Pattern.compile("\\{(p|i)#([a-zA-Z0-9$_]+)(/(l|m|s))?\\}");
private String createName(List<Edge> edges, Map<String, Object> properties, String template) {
Matcher m = templatePattern.matcher(template);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String name = m.group(2);
String type = m.group(1);
String result;
switch (type) {
case "i":
StringBuilder inputString = new StringBuilder();
for(Edge edge : edges) {
if (edge.label.startsWith(name) && (name.length() == edge.label.length() || edge.label.charAt(name.length()) == '[')) {
if (inputString.length() > 0) {
inputString.append(", ");
}
inputString.append(edge.from);
}
}
result = inputString.toString();
break;
case "p":
Object prop = properties.get(name);
String length = m.group(4);
if (prop == null) {
result = "?";
} else if (length != null && prop instanceof LengthToString) {
LengthToString lengthProp = (LengthToString) prop;
switch(length) {
default:
case "l":
result = lengthProp.toString(Length.L);
break;
case "m":
result = lengthProp.toString(Length.M);
break;
case "s":
result = lengthProp.toString(Length.S);
break;
}
} else {
result = prop.toString();
}
break;
default:
result = "#?#";
break;
}
result = result.replace("\\", "\\\\");
result = result.replace("$", "\\$");
m.appendReplacement(sb, result);
}
m.appendTail(sb);
return sb.toString().intern();
}
private static class Edge {
final int from;
final int to;
final char num;
final String label;
final String type;
final boolean input;
public Edge(int from, int to) {
this(from, to, (char) 0, null, null, false);
}
public Edge(int from, int to, char num, String label, String type, boolean input) {
this.from = from;
this.to = to;
this.label = label != null ? label.intern() : label;
this.type = type != null ? type.intern() : type;
this.num = num;
this.input = input;
}
}
}