1 /* 2 * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.graalvm.graphio; 24 25 import java.io.Closeable; 26 import java.io.IOException; 27 import java.nio.ByteBuffer; 28 import java.nio.channels.WritableByteChannel; 29 import java.nio.charset.Charset; 30 import java.util.Collection; 31 import java.util.HashMap; 32 import java.util.LinkedHashMap; 33 import java.util.LinkedList; 34 import java.util.Map; 35 36 abstract class GraphProtocol<Graph, Node, NodeClass, Edges, Block, ResolvedJavaMethod, ResolvedJavaField, Signature, NodeSourcePosition> implements Closeable { 37 private static final Charset UTF8 = Charset.forName("UTF-8"); 38 39 private static final int CONSTANT_POOL_MAX_SIZE = 8000; 40 41 private static final int BEGIN_GROUP = 0x00; 42 private static final int BEGIN_GRAPH = 0x01; 43 private static final int CLOSE_GROUP = 0x02; 44 45 private static final int POOL_NEW = 0x00; 46 private static final int POOL_STRING = 0x01; 47 private static final int POOL_ENUM = 0x02; 48 private static final int POOL_CLASS = 0x03; 49 private static final int POOL_METHOD = 0x04; 50 private static final int POOL_NULL = 0x05; 51 private static final int POOL_NODE_CLASS = 0x06; 52 private static final int POOL_FIELD = 0x07; 53 private static final int POOL_SIGNATURE = 0x08; 54 private static final int POOL_NODE_SOURCE_POSITION = 0x09; 55 56 private static final int PROPERTY_POOL = 0x00; 57 private static final int PROPERTY_INT = 0x01; 58 private static final int PROPERTY_LONG = 0x02; 59 private static final int PROPERTY_DOUBLE = 0x03; 60 private static final int PROPERTY_FLOAT = 0x04; 61 private static final int PROPERTY_TRUE = 0x05; 62 private static final int PROPERTY_FALSE = 0x06; 63 private static final int PROPERTY_ARRAY = 0x07; 64 private static final int PROPERTY_SUBGRAPH = 0x08; 65 66 private static final int KLASS = 0x00; 67 private static final int ENUM_KLASS = 0x01; 68 69 private static final byte[] MAGIC_BYTES = {'B', 'I', 'G', 'V'}; 70 71 private final ConstantPool constantPool; 72 private final ByteBuffer buffer; 73 private final WritableByteChannel channel; 74 private final int versionMajor; 75 private final int versionMinor; 76 77 protected GraphProtocol(WritableByteChannel channel) throws IOException { 78 this(channel, 4, 0); 79 } 80 81 private GraphProtocol(WritableByteChannel channel, int major, int minor) throws IOException { 82 if (major > 4) { 83 throw new IllegalArgumentException(); 84 } 85 if (major == 4 && minor > 0) { 86 throw new IllegalArgumentException(); 87 } 88 this.versionMajor = major; 89 this.versionMinor = minor; 90 this.constantPool = new ConstantPool(); 91 this.buffer = ByteBuffer.allocateDirect(256 * 1024); 92 this.channel = channel; 93 writeVersion(); 94 } 95 96 @SuppressWarnings("all") 97 public final void print(Graph graph, Map<? extends Object, ? extends Object> properties, int id, String format, Object... args) throws IOException { 98 writeByte(BEGIN_GRAPH); 99 if (versionMajor >= 3) { 100 writeInt(id); 101 writeString(format); 102 writeInt(args.length); 103 for (Object a : args) { 104 writePropertyObject(graph, a); 105 } 106 } else { 107 writePoolObject(formatTitle(graph, id, format, args)); 108 } 109 writeGraph(graph, properties); 110 flush(); 111 } 112 113 public final void beginGroup(Graph noGraph, String name, String shortName, ResolvedJavaMethod method, int bci, Map<? extends Object, ? extends Object> properties) throws IOException { 114 writeByte(BEGIN_GROUP); 115 writePoolObject(name); 116 writePoolObject(shortName); 117 writePoolObject(method); 118 writeInt(bci); 119 writeProperties(noGraph, properties); 120 } 121 122 public final void endGroup() throws IOException { 123 writeByte(CLOSE_GROUP); 124 } 125 126 @Override 127 public final void close() { 128 try { 129 flush(); 130 channel.close(); 131 } catch (IOException ex) { 132 throw new Error(ex); 133 } 134 } 135 136 protected abstract Graph findGraph(Graph current, Object obj); 137 138 protected abstract ResolvedJavaMethod findMethod(Object obj); 139 140 protected abstract NodeClass findNodeClass(Object obj); 141 142 /** 143 * Find a Java class. The returned object must be acceptable by 144 * {@link #findJavaTypeName(java.lang.Object)} and return valid name for the class. 145 * 146 * @param clazz node class object 147 * @return object representing the class, for example {@link Class} 148 */ 149 protected abstract Object findJavaClass(NodeClass clazz); 150 151 protected abstract Object findEnumClass(Object enumValue); 152 153 protected abstract String findNameTemplate(NodeClass clazz); 154 155 protected abstract Edges findClassEdges(NodeClass nodeClass, boolean dumpInputs); 156 157 protected abstract int findNodeId(Node n); 158 159 protected abstract void findExtraNodes(Node node, Collection<? super Node> extraNodes); 160 161 protected abstract boolean hasPredecessor(Node node); 162 163 protected abstract int findNodesCount(Graph info); 164 165 protected abstract Iterable<? extends Node> findNodes(Graph info); 166 167 protected abstract void findNodeProperties(Node node, Map<String, Object> props, Graph info); 168 169 protected abstract Collection<? extends Node> findBlockNodes(Graph info, Block block); 170 171 protected abstract int findBlockId(Block sux); 172 173 protected abstract Collection<? extends Block> findBlocks(Graph graph); 174 175 protected abstract Collection<? extends Block> findBlockSuccessors(Block block); 176 177 protected abstract String formatTitle(Graph graph, int id, String format, Object... args); 178 179 protected abstract int findSize(Edges edges); 180 181 protected abstract boolean isDirect(Edges edges, int i); 182 183 protected abstract String findName(Edges edges, int i); 184 185 protected abstract Object findType(Edges edges, int i); 186 187 protected abstract Collection<? extends Node> findNodes(Graph graph, Node node, Edges edges, int i); 188 189 protected abstract int findEnumOrdinal(Object obj); 190 191 protected abstract String[] findEnumTypeValues(Object clazz); 192 193 protected abstract String findJavaTypeName(Object obj); 194 195 protected abstract byte[] findMethodCode(ResolvedJavaMethod method); 196 197 protected abstract int findMethodModifiers(ResolvedJavaMethod method); 198 199 protected abstract Signature findMethodSignature(ResolvedJavaMethod method); 200 201 protected abstract String findMethodName(ResolvedJavaMethod method); 202 203 protected abstract Object findMethodDeclaringClass(ResolvedJavaMethod method); 204 205 protected abstract int findFieldModifiers(ResolvedJavaField field); 206 207 protected abstract String findFieldTypeName(ResolvedJavaField field); 208 209 protected abstract String findFieldName(ResolvedJavaField field); 210 211 protected abstract Object findFieldDeclaringClass(ResolvedJavaField field); 212 213 protected abstract ResolvedJavaField findJavaField(Object object); 214 215 protected abstract Signature findSignature(Object object); 216 217 protected abstract int findSignatureParameterCount(Signature signature); 218 219 protected abstract String findSignatureParameterTypeName(Signature signature, int index); 220 221 protected abstract String findSignatureReturnTypeName(Signature signature); 222 223 protected abstract NodeSourcePosition findNodeSourcePosition(Object object); 224 225 protected abstract ResolvedJavaMethod findNodeSourcePositionMethod(NodeSourcePosition pos); 226 227 protected abstract NodeSourcePosition findNodeSourcePositionCaller(NodeSourcePosition pos); 228 229 protected abstract int findNodeSourcePositionBCI(NodeSourcePosition pos); 230 231 protected abstract StackTraceElement findMethodStackTraceElement(ResolvedJavaMethod method, int bci, NodeSourcePosition pos); 232 233 private void writeVersion() throws IOException { 234 writeBytesRaw(MAGIC_BYTES); 235 writeByte(versionMajor); 236 writeByte(versionMinor); 237 } 238 239 private void flush() throws IOException { 240 buffer.flip(); 241 /* 242 * Try not to let interrupted threads aborting the write. There's still a race here but an 243 * interrupt that's been pending for a long time shouldn't stop this writing. 244 */ 245 boolean interrupted = Thread.interrupted(); 246 try { 247 channel.write(buffer); 248 } finally { 249 if (interrupted) { 250 Thread.currentThread().interrupt(); 251 } 252 } 253 buffer.compact(); 254 } 255 256 private void ensureAvailable(int i) throws IOException { 257 assert buffer.capacity() >= i : "Can not make " + i + " bytes available, buffer is too small"; 258 while (buffer.remaining() < i) { 259 flush(); 260 } 261 } 262 263 private void writeByte(int b) throws IOException { 264 ensureAvailable(1); 265 buffer.put((byte) b); 266 } 267 268 private void writeInt(int b) throws IOException { 269 ensureAvailable(4); 270 buffer.putInt(b); 271 } 272 273 private void writeLong(long b) throws IOException { 274 ensureAvailable(8); 275 buffer.putLong(b); 276 } 277 278 private void writeDouble(double b) throws IOException { 279 ensureAvailable(8); 280 buffer.putDouble(b); 281 } 282 283 private void writeFloat(float b) throws IOException { 284 ensureAvailable(4); 285 buffer.putFloat(b); 286 } 287 288 private void writeShort(char b) throws IOException { 289 ensureAvailable(2); 290 buffer.putChar(b); 291 } 292 293 private void writeString(String str) throws IOException { 294 byte[] bytes = str.getBytes(UTF8); 295 writeBytes(bytes); 296 } 297 298 private void writeBytes(byte[] b) throws IOException { 299 if (b == null) { 300 writeInt(-1); 301 } else { 302 writeInt(b.length); 303 writeBytesRaw(b); 304 } 305 } 306 307 private void writeBytesRaw(byte[] b) throws IOException { 308 int bytesWritten = 0; 309 while (bytesWritten < b.length) { 310 int toWrite = Math.min(b.length - bytesWritten, buffer.capacity()); 311 ensureAvailable(toWrite); 312 buffer.put(b, bytesWritten, toWrite); 313 bytesWritten += toWrite; 314 } 315 } 316 317 private void writeInts(int[] b) throws IOException { 318 if (b == null) { 319 writeInt(-1); 320 } else { 321 writeInt(b.length); 322 int sizeInBytes = b.length * 4; 323 ensureAvailable(sizeInBytes); 324 buffer.asIntBuffer().put(b); 325 buffer.position(buffer.position() + sizeInBytes); 326 } 327 } 328 329 private void writeDoubles(double[] b) throws IOException { 330 if (b == null) { 331 writeInt(-1); 332 } else { 333 writeInt(b.length); 334 int sizeInBytes = b.length * 8; 335 ensureAvailable(sizeInBytes); 336 buffer.asDoubleBuffer().put(b); 337 buffer.position(buffer.position() + sizeInBytes); 338 } 339 } 340 341 private void writePoolObject(Object object) throws IOException { 342 if (object == null) { 343 writeByte(POOL_NULL); 344 return; 345 } 346 Character id = constantPool.get(object); 347 if (id == null) { 348 addPoolEntry(object); 349 } else { 350 if (object instanceof Enum<?> || findEnumOrdinal(object) >= 0) { 351 writeByte(POOL_ENUM); 352 } else if (object instanceof Class<?> || findJavaTypeName(object) != null) { 353 writeByte(POOL_CLASS); 354 } else if (findJavaField(object) != null) { 355 writeByte(POOL_FIELD); 356 } else if (findSignature(object) != null) { 357 writeByte(POOL_SIGNATURE); 358 } else if (versionMajor >= 4 && findNodeSourcePosition(object) != null) { 359 writeByte(POOL_NODE_SOURCE_POSITION); 360 } else { 361 if (findNodeClass(object) != null) { 362 writeByte(POOL_NODE_CLASS); 363 } else if (findMethod(object) != null) { 364 writeByte(POOL_METHOD); 365 } else { 366 writeByte(POOL_STRING); 367 } 368 } 369 writeShort(id.charValue()); 370 } 371 } 372 373 private void writeGraph(Graph graph, Map<? extends Object, ? extends Object> properties) throws IOException { 374 writeProperties(graph, properties); 375 writeNodes(graph); 376 writeBlocks(findBlocks(graph), graph); 377 } 378 379 private void writeNodes(Graph info) throws IOException { 380 Map<String, Object> props = new HashMap<>(); 381 382 final int size = findNodesCount(info); 383 writeInt(size); 384 int cnt = 0; 385 for (Node node : findNodes(info)) { 386 NodeClass nodeClass = findNodeClass(node); 387 if (nodeClass == null) { 388 throw new IOException("No class for " + node); 389 } 390 findNodeProperties(node, props, info); 391 392 writeInt(findNodeId(node)); 393 writePoolObject(nodeClass); 394 writeByte(hasPredecessor(node) ? 1 : 0); 395 writeProperties(info, props); 396 writeEdges(info, node, true); 397 writeEdges(info, node, false); 398 399 props.clear(); 400 cnt++; 401 } 402 if (size != cnt) { 403 throw new IOException("Expecting " + size + " nodes, but found " + cnt); 404 } 405 } 406 407 private void writeEdges(Graph graph, Node node, boolean dumpInputs) throws IOException { 408 NodeClass clazz = findNodeClass(node); 409 Edges edges = findClassEdges(clazz, dumpInputs); 410 int size = findSize(edges); 411 for (int i = 0; i < size; i++) { 412 Collection<? extends Node> list = findNodes(graph, node, edges, i); 413 if (isDirect(edges, i)) { 414 if (list != null && list.size() != 1) { 415 throw new IOException("Edge " + i + " in " + edges + " is direct, but list isn't singleton: " + list); 416 } 417 Node n = null; 418 if (list != null && !list.isEmpty()) { 419 n = list.iterator().next(); 420 } 421 writeNodeRef(n); 422 } else { 423 if (list == null) { 424 writeShort((char) 0); 425 } else { 426 int listSize = list.size(); 427 assert listSize == ((char) listSize); 428 writeShort((char) listSize); 429 for (Node edge : list) { 430 writeNodeRef(edge); 431 } 432 } 433 } 434 } 435 } 436 437 private void writeNodeRef(Node node) throws IOException { 438 writeInt(findNodeId(node)); 439 } 440 441 private void writeBlocks(Collection<? extends Block> blocks, Graph info) throws IOException { 442 if (blocks != null) { 443 for (Block block : blocks) { 444 Collection<? extends Node> nodes = findBlockNodes(info, block); 445 if (nodes == null) { 446 writeInt(0); 447 return; 448 } 449 } 450 writeInt(blocks.size()); 451 for (Block block : blocks) { 452 Collection<? extends Node> nodes = findBlockNodes(info, block); 453 writeInt(findBlockId(block)); 454 writeInt(nodes.size()); 455 for (Node node : nodes) { 456 writeInt(findNodeId(node)); 457 } 458 final Collection<? extends Block> successors = findBlockSuccessors(block); 459 writeInt(successors.size()); 460 for (Block sux : successors) { 461 writeInt(findBlockId(sux)); 462 } 463 } 464 } else { 465 writeInt(0); 466 } 467 } 468 469 private void writeEdgesInfo(NodeClass nodeClass, boolean dumpInputs) throws IOException { 470 Edges edges = findClassEdges(nodeClass, dumpInputs); 471 int size = findSize(edges); 472 writeShort((char) size); 473 for (int i = 0; i < size; i++) { 474 writeByte(isDirect(edges, i) ? 0 : 1); 475 writePoolObject(findName(edges, i)); 476 if (dumpInputs) { 477 writePoolObject(findType(edges, i)); 478 } 479 } 480 } 481 482 @SuppressWarnings("all") 483 private void addPoolEntry(Object object) throws IOException { 484 ResolvedJavaField field; 485 String typeName; 486 Signature signature; 487 NodeSourcePosition pos; 488 int enumOrdinal; 489 char index = constantPool.add(object); 490 writeByte(POOL_NEW); 491 writeShort(index); 492 if ((typeName = findJavaTypeName(object)) != null) { 493 writeByte(POOL_CLASS); 494 writeString(typeName); 495 String[] enumValueNames = findEnumTypeValues(object); 496 if (enumValueNames != null) { 497 writeByte(ENUM_KLASS); 498 writeInt(enumValueNames.length); 499 for (String o : enumValueNames) { 500 writePoolObject(o); 501 } 502 } else { 503 writeByte(KLASS); 504 } 505 } else if ((enumOrdinal = findEnumOrdinal(object)) >= 0) { 506 writeByte(POOL_ENUM); 507 writePoolObject(findEnumClass(object)); 508 writeInt(enumOrdinal); 509 } else if ((field = findJavaField(object)) != null) { 510 writeByte(POOL_FIELD); 511 writePoolObject(findFieldDeclaringClass(field)); 512 writePoolObject(findFieldName(field)); 513 writePoolObject(findFieldTypeName(field)); 514 writeInt(findFieldModifiers(field)); 515 } else if ((signature = findSignature(object)) != null) { 516 writeByte(POOL_SIGNATURE); 517 int args = findSignatureParameterCount(signature); 518 writeShort((char) args); 519 for (int i = 0; i < args; i++) { 520 writePoolObject(findSignatureParameterTypeName(signature, i)); 521 } 522 writePoolObject(findSignatureReturnTypeName(signature)); 523 } else if (versionMajor >= 4 && (pos = findNodeSourcePosition(object)) != null) { 524 writeByte(POOL_NODE_SOURCE_POSITION); 525 ResolvedJavaMethod method = findNodeSourcePositionMethod(pos); 526 writePoolObject(method); 527 final int bci = findNodeSourcePositionBCI(pos); 528 writeInt(bci); 529 StackTraceElement ste = findMethodStackTraceElement(method, bci, pos); 530 if (ste != null) { 531 writePoolObject(ste.getFileName()); 532 writeInt(ste.getLineNumber()); 533 } else { 534 writePoolObject(null); 535 } 536 writePoolObject(findNodeSourcePositionCaller(pos)); 537 } else { 538 NodeClass nodeClass = findNodeClass(object); 539 if (nodeClass != null) { 540 writeByte(POOL_NODE_CLASS); 541 final Object clazz = findJavaClass(nodeClass); 542 if (versionMajor >= 3) { 543 writePoolObject(clazz); 544 writeString(findNameTemplate(nodeClass)); 545 } else { 546 writeString(((Class<?>) clazz).getSimpleName()); 547 String nameTemplate = findNameTemplate(nodeClass); 548 writeString(nameTemplate); 549 } 550 writeEdgesInfo(nodeClass, true); 551 writeEdgesInfo(nodeClass, false); 552 return; 553 } 554 ResolvedJavaMethod method = findMethod(object); 555 if (method == null) { 556 writeByte(POOL_STRING); 557 writeString(object.toString()); 558 return; 559 } 560 writeByte(POOL_METHOD); 561 writePoolObject(findMethodDeclaringClass(method)); 562 writePoolObject(findMethodName(method)); 563 writePoolObject(findMethodSignature(method)); 564 writeInt(findMethodModifiers(method)); 565 writeBytes(findMethodCode(method)); 566 } 567 } 568 569 private void writePropertyObject(Graph graph, Object obj) throws IOException { 570 if (obj instanceof Integer) { 571 writeByte(PROPERTY_INT); 572 writeInt(((Integer) obj).intValue()); 573 } else if (obj instanceof Long) { 574 writeByte(PROPERTY_LONG); 575 writeLong(((Long) obj).longValue()); 576 } else if (obj instanceof Double) { 577 writeByte(PROPERTY_DOUBLE); 578 writeDouble(((Double) obj).doubleValue()); 579 } else if (obj instanceof Float) { 580 writeByte(PROPERTY_FLOAT); 581 writeFloat(((Float) obj).floatValue()); 582 } else if (obj instanceof Boolean) { 583 if (((Boolean) obj).booleanValue()) { 584 writeByte(PROPERTY_TRUE); 585 } else { 586 writeByte(PROPERTY_FALSE); 587 } 588 } else if (obj != null && obj.getClass().isArray()) { 589 Class<?> componentType = obj.getClass().getComponentType(); 590 if (componentType.isPrimitive()) { 591 if (componentType == Double.TYPE) { 592 writeByte(PROPERTY_ARRAY); 593 writeByte(PROPERTY_DOUBLE); 594 writeDoubles((double[]) obj); 595 } else if (componentType == Integer.TYPE) { 596 writeByte(PROPERTY_ARRAY); 597 writeByte(PROPERTY_INT); 598 writeInts((int[]) obj); 599 } else { 600 writeByte(PROPERTY_POOL); 601 writePoolObject(obj); 602 } 603 } else { 604 writeByte(PROPERTY_ARRAY); 605 writeByte(PROPERTY_POOL); 606 Object[] array = (Object[]) obj; 607 writeInt(array.length); 608 for (Object o : array) { 609 writePoolObject(o); 610 } 611 } 612 } else { 613 Graph g = findGraph(graph, obj); 614 if (g == null) { 615 writeByte(PROPERTY_POOL); 616 writePoolObject(obj); 617 } else { 618 writeByte(PROPERTY_SUBGRAPH); 619 writeGraph(g, null); 620 } 621 } 622 } 623 624 private void writeProperties(Graph graph, Map<? extends Object, ? extends Object> props) throws IOException { 625 if (props == null) { 626 writeShort((char) 0); 627 return; 628 } 629 final int size = props.size(); 630 // properties 631 writeShort((char) size); 632 int cnt = 0; 633 for (Map.Entry<? extends Object, ? extends Object> entry : props.entrySet()) { 634 String key = entry.getKey().toString(); 635 writePoolObject(key); 636 writePropertyObject(graph, entry.getValue()); 637 cnt++; 638 } 639 if (size != cnt) { 640 throw new IOException("Expecting " + size + " properties, but found only " + cnt); 641 } 642 } 643 644 private static final class ConstantPool extends LinkedHashMap<Object, Character> { 645 646 private final LinkedList<Character> availableIds; 647 private char nextId; 648 private static final long serialVersionUID = -2676889957907285681L; 649 650 ConstantPool() { 651 super(50, 0.65f); 652 availableIds = new LinkedList<>(); 653 } 654 655 @Override 656 protected boolean removeEldestEntry(java.util.Map.Entry<Object, Character> eldest) { 657 if (size() > CONSTANT_POOL_MAX_SIZE) { 658 availableIds.addFirst(eldest.getValue()); 659 return true; 660 } 661 return false; 662 } 663 664 private Character nextAvailableId() { 665 if (!availableIds.isEmpty()) { 666 return availableIds.removeFirst(); 667 } 668 return nextId++; 669 } 670 671 public char add(Object obj) { 672 Character id = nextAvailableId(); 673 put(obj, id); 674 return id; 675 } 676 } 677 678 }