/* * Copyright (c) 2011, 2017, 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. * * 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 org.graalvm.graphio; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.nio.charset.Charset; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; abstract class GraphProtocol implements Closeable { private static final Charset UTF8 = Charset.forName("UTF-8"); private static final int CONSTANT_POOL_MAX_SIZE = 8000; 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 POOL_NODE_SOURCE_POSITION = 0x09; private static final int POOL_NODE = 0x0a; 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 int KLASS = 0x00; private static final int ENUM_KLASS = 0x01; private static final byte[] MAGIC_BYTES = {'B', 'I', 'G', 'V'}; private final ConstantPool constantPool; private final ByteBuffer buffer; private final WritableByteChannel channel; final int versionMajor; final int versionMinor; GraphProtocol(WritableByteChannel channel, int major, int minor) throws IOException { if (major > 5 || (major == 5 && minor > 0)) { throw new IllegalArgumentException("Unrecognized version " + major + "." + minor); } this.versionMajor = major; this.versionMinor = minor; this.constantPool = new ConstantPool(); this.buffer = ByteBuffer.allocateDirect(256 * 1024); this.channel = channel; writeVersion(); } GraphProtocol(GraphProtocol parent) { this.versionMajor = parent.versionMajor; this.versionMinor = parent.versionMinor; this.constantPool = parent.constantPool; this.buffer = parent.buffer; this.channel = parent.channel; } @SuppressWarnings("all") public final void print(Graph graph, Map properties, int id, String format, Object... args) throws IOException { writeByte(BEGIN_GRAPH); if (versionMajor >= 3) { writeInt(id); writeString(format); writeInt(args.length); for (Object a : args) { writePropertyObject(graph, a); } } else { writePoolObject(formatTitle(graph, id, format, args)); } writeGraph(graph, properties); flush(); } public final void beginGroup(Graph noGraph, String name, String shortName, ResolvedJavaMethod method, int bci, Map properties) throws IOException { writeByte(BEGIN_GROUP); writePoolObject(name); writePoolObject(shortName); writePoolObject(method); writeInt(bci); writeProperties(noGraph, properties); } public final void endGroup() throws IOException { writeByte(CLOSE_GROUP); } @Override public final void close() { try { flush(); channel.close(); } catch (IOException ex) { throw new Error(ex); } } protected abstract Graph findGraph(Graph current, Object obj); protected abstract ResolvedJavaMethod findMethod(Object obj); /** * Attempts to recognize the provided object as a node. Used to encode it with * {@link #POOL_NODE} pool type. * * @param obj any object * @return null if it is not a node object, non-null otherwise */ protected abstract Node findNode(Object obj); /** * Determines whether the provided object is node class or not. * * @param obj object to check * @return {@code null} if {@code obj} does not represent a NodeClass otherwise the NodeClass * represented by {@code obj} */ protected abstract NodeClass findNodeClass(Object obj); /** * Returns the NodeClass for a given Node {@code obj}. * * @param obj instance of node * @return non-{@code null} instance of the node's class object */ protected abstract NodeClass findClassForNode(Node obj); /** * Find a Java class. The returned object must be acceptable by * {@link #findJavaTypeName(java.lang.Object)} and return valid name for the class. * * @param clazz node class object * @return object representing the class, for example {@link Class} */ protected abstract Object findJavaClass(NodeClass clazz); protected abstract Object findEnumClass(Object enumValue); protected abstract String findNameTemplate(NodeClass clazz); protected abstract Edges findClassEdges(NodeClass nodeClass, boolean dumpInputs); protected abstract int findNodeId(Node n); protected abstract void findExtraNodes(Node node, Collection extraNodes); protected abstract boolean hasPredecessor(Node node); protected abstract int findNodesCount(Graph info); protected abstract Iterable findNodes(Graph info); protected abstract void findNodeProperties(Node node, Map props, Graph info); protected abstract Collection findBlockNodes(Graph info, Block block); protected abstract int findBlockId(Block sux); protected abstract Collection findBlocks(Graph graph); protected abstract Collection findBlockSuccessors(Block block); protected abstract String formatTitle(Graph graph, int id, String format, Object... args); protected abstract int findSize(Edges edges); protected abstract boolean isDirect(Edges edges, int i); protected abstract String findName(Edges edges, int i); protected abstract Object findType(Edges edges, int i); protected abstract Collection findNodes(Graph graph, Node node, Edges edges, int i); protected abstract int findEnumOrdinal(Object obj); protected abstract String[] findEnumTypeValues(Object clazz); protected abstract String findJavaTypeName(Object obj); protected abstract byte[] findMethodCode(ResolvedJavaMethod method); protected abstract int findMethodModifiers(ResolvedJavaMethod method); protected abstract Signature findMethodSignature(ResolvedJavaMethod method); protected abstract String findMethodName(ResolvedJavaMethod method); protected abstract Object findMethodDeclaringClass(ResolvedJavaMethod method); protected abstract int findFieldModifiers(ResolvedJavaField field); protected abstract String findFieldTypeName(ResolvedJavaField field); protected abstract String findFieldName(ResolvedJavaField field); protected abstract Object findFieldDeclaringClass(ResolvedJavaField field); protected abstract ResolvedJavaField findJavaField(Object object); protected abstract Signature findSignature(Object object); protected abstract int findSignatureParameterCount(Signature signature); protected abstract String findSignatureParameterTypeName(Signature signature, int index); protected abstract String findSignatureReturnTypeName(Signature signature); protected abstract NodeSourcePosition findNodeSourcePosition(Object object); protected abstract ResolvedJavaMethod findNodeSourcePositionMethod(NodeSourcePosition pos); protected abstract NodeSourcePosition findNodeSourcePositionCaller(NodeSourcePosition pos); protected abstract int findNodeSourcePositionBCI(NodeSourcePosition pos); protected abstract StackTraceElement findMethodStackTraceElement(ResolvedJavaMethod method, int bci, NodeSourcePosition pos); private void writeVersion() throws IOException { writeBytesRaw(MAGIC_BYTES); writeByte(versionMajor); writeByte(versionMinor); } private void flush() throws IOException { buffer.flip(); /* * Try not to let interrupted threads abort the write. There's still a race here but an * interrupt that's been pending for a long time shouldn't stop this writing. */ boolean interrupted = Thread.interrupted(); try { channel.write(buffer); } finally { if (interrupted) { Thread.currentThread().interrupt(); } } buffer.compact(); } private void ensureAvailable(int i) throws IOException { assert buffer.capacity() >= i : "Can not make " + i + " bytes available, buffer is too small"; while (buffer.remaining() < i) { flush(); } } private void writeByte(int b) throws IOException { ensureAvailable(1); buffer.put((byte) b); } private void writeInt(int b) throws IOException { ensureAvailable(4); buffer.putInt(b); } private void writeLong(long b) throws IOException { ensureAvailable(8); buffer.putLong(b); } private void writeDouble(double b) throws IOException { ensureAvailable(8); buffer.putDouble(b); } private void writeFloat(float b) throws IOException { ensureAvailable(4); buffer.putFloat(b); } private void writeShort(char b) throws IOException { ensureAvailable(2); buffer.putChar(b); } private void writeString(String str) throws IOException { byte[] bytes = str.getBytes(UTF8); writeBytes(bytes); } private void writeBytes(byte[] b) throws IOException { if (b == null) { writeInt(-1); } else { writeInt(b.length); writeBytesRaw(b); } } private void writeBytesRaw(byte[] b) throws IOException { int bytesWritten = 0; while (bytesWritten < b.length) { int toWrite = Math.min(b.length - bytesWritten, buffer.capacity()); ensureAvailable(toWrite); buffer.put(b, bytesWritten, toWrite); bytesWritten += toWrite; } } private void writeInts(int[] b) throws IOException { if (b == null) { writeInt(-1); } else { writeInt(b.length); int sizeInBytes = b.length * 4; ensureAvailable(sizeInBytes); buffer.asIntBuffer().put(b); buffer.position(buffer.position() + sizeInBytes); } } private void writeDoubles(double[] b) throws IOException { if (b == null) { writeInt(-1); } else { writeInt(b.length); int sizeInBytes = b.length * 8; ensureAvailable(sizeInBytes); buffer.asDoubleBuffer().put(b); buffer.position(buffer.position() + sizeInBytes); } } private void writePoolObject(Object obj) throws IOException { Object object = obj; if (object == null) { writeByte(POOL_NULL); return; } Character id = constantPool.get(object); if (id == null) { addPoolEntry(object); } else { if (findJavaField(object) != null) { writeByte(POOL_FIELD); } else if (findSignature(object) != null) { writeByte(POOL_SIGNATURE); } else if (versionMajor >= 4 && findNodeSourcePosition(object) != null) { writeByte(POOL_NODE_SOURCE_POSITION); } else { final Node node = findNode(object); if (versionMajor == 4 && node != null) { object = classForNode(node); } if (findNodeClass(object) != null) { writeByte(POOL_NODE_CLASS); } else if (versionMajor >= 5 && node != null) { writeByte(POOL_NODE); } else if (findMethod(object) != null) { writeByte(POOL_METHOD); } else { if (object instanceof Enum || findEnumOrdinal(object) >= 0) { writeByte(POOL_ENUM); } else if (object instanceof Class || findJavaTypeName(object) != null) { writeByte(POOL_CLASS); } else { writeByte(POOL_STRING); } } } writeShort(id.charValue()); } } private void writeGraph(Graph graph, Map properties) throws IOException { writeProperties(graph, properties); writeNodes(graph); writeBlocks(findBlocks(graph), graph); } private void writeNodes(Graph info) throws IOException { Map props = new HashMap<>(); final int size = findNodesCount(info); writeInt(size); int cnt = 0; for (Node node : findNodes(info)) { NodeClass nodeClass = classForNode(node); findNodeProperties(node, props, info); writeInt(findNodeId(node)); writePoolObject(nodeClass); writeByte(hasPredecessor(node) ? 1 : 0); writeProperties(info, props); writeEdges(info, node, true); writeEdges(info, node, false); props.clear(); cnt++; } if (size != cnt) { throw new IOException("Expecting " + size + " nodes, but found " + cnt); } } private void writeEdges(Graph graph, Node node, boolean dumpInputs) throws IOException { NodeClass clazz = classForNode(node); Edges edges = findClassEdges(clazz, dumpInputs); int size = findSize(edges); for (int i = 0; i < size; i++) { Collection list = findNodes(graph, node, edges, i); if (isDirect(edges, i)) { if (list != null && list.size() != 1) { throw new IOException("Edge " + i + " in " + edges + " is direct, but list isn't singleton: " + list); } Node n = null; if (list != null && !list.isEmpty()) { n = list.iterator().next(); } writeNodeRef(n); } else { if (list == null) { writeShort((char) 0); } else { int listSize = list.size(); assert listSize == ((char) listSize); writeShort((char) listSize); for (Node edge : list) { writeNodeRef(edge); } } } } } private NodeClass classForNode(Node node) throws IOException { NodeClass clazz = findClassForNode(node); if (clazz == null) { throw new IOException("No class for " + node); } return clazz; } private void writeNodeRef(Node node) throws IOException { writeInt(findNodeId(node)); } private void writeBlocks(Collection blocks, Graph info) throws IOException { if (blocks != null) { for (Block block : blocks) { Collection nodes = findBlockNodes(info, block); if (nodes == null) { writeInt(0); return; } } writeInt(blocks.size()); for (Block block : blocks) { Collection nodes = findBlockNodes(info, block); writeInt(findBlockId(block)); writeInt(nodes.size()); for (Node node : nodes) { writeInt(findNodeId(node)); } final Collection successors = findBlockSuccessors(block); writeInt(successors.size()); for (Block sux : successors) { writeInt(findBlockId(sux)); } } } else { writeInt(0); } } private void writeEdgesInfo(NodeClass nodeClass, boolean dumpInputs) throws IOException { Edges edges = findClassEdges(nodeClass, dumpInputs); int size = findSize(edges); writeShort((char) size); for (int i = 0; i < size; i++) { writeByte(isDirect(edges, i) ? 0 : 1); writePoolObject(findName(edges, i)); if (dumpInputs) { writePoolObject(findType(edges, i)); } } } @SuppressWarnings("all") private void addPoolEntry(Object obj) throws IOException { Object object = obj; ResolvedJavaField field; String typeName; Signature signature; NodeSourcePosition pos; int enumOrdinal; char index = constantPool.add(object); writeByte(POOL_NEW); writeShort(index); if ((field = findJavaField(object)) != null) { writeByte(POOL_FIELD); writePoolObject(findFieldDeclaringClass(field)); writePoolObject(findFieldName(field)); writePoolObject(findFieldTypeName(field)); writeInt(findFieldModifiers(field)); } else if ((signature = findSignature(object)) != null) { writeByte(POOL_SIGNATURE); int args = findSignatureParameterCount(signature); writeShort((char) args); for (int i = 0; i < args; i++) { writePoolObject(findSignatureParameterTypeName(signature, i)); } writePoolObject(findSignatureReturnTypeName(signature)); } else if (versionMajor >= 4 && (pos = findNodeSourcePosition(object)) != null) { writeByte(POOL_NODE_SOURCE_POSITION); ResolvedJavaMethod method = findNodeSourcePositionMethod(pos); writePoolObject(method); final int bci = findNodeSourcePositionBCI(pos); writeInt(bci); StackTraceElement ste = findMethodStackTraceElement(method, bci, pos); if (ste != null && ste.getFileName() != null) { writePoolObject(ste.getFileName()); writeInt(ste.getLineNumber()); } else { writePoolObject(null); } writePoolObject(findNodeSourcePositionCaller(pos)); } else { Node node = findNode(object); if (node != null) { if (versionMajor >= 5) { writeByte(POOL_NODE); writeInt(findNodeId(node)); writePoolObject(classForNode(node)); return; } if (versionMajor == 4) { object = classForNode(node); } } NodeClass nodeClass = findNodeClass(object); if (nodeClass != null) { writeByte(POOL_NODE_CLASS); final Object clazz = findJavaClass(nodeClass); if (versionMajor >= 3) { writePoolObject(clazz); writeString(findNameTemplate(nodeClass)); } else { writeString(((Class) clazz).getSimpleName()); String nameTemplate = findNameTemplate(nodeClass); writeString(nameTemplate); } writeEdgesInfo(nodeClass, true); writeEdgesInfo(nodeClass, false); return; } ResolvedJavaMethod method = findMethod(object); if (method == null) { if ((typeName = findJavaTypeName(object)) != null) { writeByte(POOL_CLASS); writeString(typeName); String[] enumValueNames = findEnumTypeValues(object); if (enumValueNames != null) { writeByte(ENUM_KLASS); writeInt(enumValueNames.length); for (String o : enumValueNames) { writePoolObject(o); } } else { writeByte(KLASS); } } else if ((enumOrdinal = findEnumOrdinal(object)) >= 0) { writeByte(POOL_ENUM); writePoolObject(findEnumClass(object)); writeInt(enumOrdinal); } else { writeByte(POOL_STRING); writeString(object.toString()); } return; } writeByte(POOL_METHOD); writePoolObject(findMethodDeclaringClass(method)); writePoolObject(findMethodName(method)); writePoolObject(findMethodSignature(method)); writeInt(findMethodModifiers(method)); writeBytes(findMethodCode(method)); } } private void writePropertyObject(Graph graph, Object obj) throws IOException { if (obj instanceof Integer) { writeByte(PROPERTY_INT); writeInt(((Integer) obj).intValue()); } else if (obj instanceof Long) { writeByte(PROPERTY_LONG); writeLong(((Long) obj).longValue()); } else if (obj instanceof Double) { writeByte(PROPERTY_DOUBLE); writeDouble(((Double) obj).doubleValue()); } else if (obj instanceof Float) { writeByte(PROPERTY_FLOAT); writeFloat(((Float) obj).floatValue()); } else if (obj instanceof Boolean) { if (((Boolean) obj).booleanValue()) { writeByte(PROPERTY_TRUE); } else { writeByte(PROPERTY_FALSE); } } else if (obj != null && obj.getClass().isArray()) { Class componentType = obj.getClass().getComponentType(); if (componentType.isPrimitive()) { if (componentType == Double.TYPE) { writeByte(PROPERTY_ARRAY); writeByte(PROPERTY_DOUBLE); writeDoubles((double[]) obj); } else if (componentType == Integer.TYPE) { writeByte(PROPERTY_ARRAY); writeByte(PROPERTY_INT); writeInts((int[]) obj); } else { writeByte(PROPERTY_POOL); writePoolObject(obj); } } else { writeByte(PROPERTY_ARRAY); writeByte(PROPERTY_POOL); Object[] array = (Object[]) obj; writeInt(array.length); for (Object o : array) { writePoolObject(o); } } } else { Graph g = findGraph(graph, obj); if (g == null) { writeByte(PROPERTY_POOL); writePoolObject(obj); } else { writeByte(PROPERTY_SUBGRAPH); writeGraph(g, null); } } } private void writeProperties(Graph graph, Map props) throws IOException { if (props == null) { writeShort((char) 0); return; } final int size = props.size(); // properties writeShort((char) size); int cnt = 0; for (Map.Entry entry : props.entrySet()) { String key = entry.getKey().toString(); writePoolObject(key); writePropertyObject(graph, entry.getValue()); cnt++; } if (size != cnt) { throw new IOException("Expecting " + size + " properties, but found only " + cnt); } } private static final class ConstantPool extends LinkedHashMap { private final LinkedList availableIds; private char nextId; private static final long serialVersionUID = -2676889957907285681L; ConstantPool() { super(50, 0.65f); availableIds = new LinkedList<>(); } @Override protected boolean removeEldestEntry(java.util.Map.Entry eldest) { if (size() > CONSTANT_POOL_MAX_SIZE) { availableIds.addFirst(eldest.getValue()); return true; } return false; } private Character nextAvailableId() { if (!availableIds.isEmpty()) { return availableIds.removeFirst(); } return nextId++; } public char add(Object obj) { Character id = nextAvailableId(); put(obj, id); return id; } } }