1 /* 2 * Copyright (c) 2022, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.internal.classfile.impl; 26 27 import java.lang.constant.ConstantDesc; 28 import java.lang.constant.DirectMethodHandleDesc; 29 import java.lang.reflect.AccessFlag; 30 import java.util.AbstractList; 31 import java.util.Arrays; 32 import java.util.Collection; 33 import java.util.Collections; 34 import java.util.LinkedHashMap; 35 import java.util.LinkedList; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Objects; 39 import java.util.function.BiConsumer; 40 import java.util.Set; 41 import java.util.function.Consumer; 42 import java.util.stream.IntStream; 43 import java.util.stream.Stream; 44 import java.lang.classfile.Annotation; 45 46 import java.lang.classfile.AnnotationElement; 47 import java.lang.classfile.AnnotationValue; 48 import java.lang.classfile.AnnotationValue.*; 49 import java.lang.classfile.Attribute; 50 import java.lang.classfile.ClassModel; 51 import java.lang.classfile.components.ClassPrinter.*; 52 import java.lang.classfile.CodeModel; 53 import java.lang.classfile.Instruction; 54 import java.lang.classfile.MethodModel; 55 import java.lang.classfile.TypeAnnotation; 56 import java.lang.classfile.attribute.*; 57 import java.lang.classfile.attribute.StackMapFrameInfo.*; 58 import java.lang.classfile.constantpool.*; 59 import java.lang.classfile.instruction.*; 60 61 import static java.lang.classfile.ClassFile.*; 62 import java.lang.classfile.CompoundElement; 63 import java.lang.classfile.FieldModel; 64 import static jdk.internal.classfile.impl.ClassPrinterImpl.Style.*; 65 66 public final class ClassPrinterImpl { 67 68 public enum Style { BLOCK, FLOW } 69 70 public record LeafNodeImpl(ConstantDesc name, ConstantDesc value) implements LeafNode { 71 72 @Override 73 public Stream<Node> walk() { 74 return Stream.of(this); 75 } 76 } 77 78 public static final class ListNodeImpl extends AbstractList<Node> implements ListNode { 79 80 private final Style style; 81 private final ConstantDesc name; 82 private final Node[] nodes; 83 84 public ListNodeImpl(Style style, ConstantDesc name, Stream<Node> nodes) { 85 this.style = style; 86 this.name = name; 87 this.nodes = nodes.toArray(Node[]::new); 88 } 89 90 @Override 91 public ConstantDesc name() { 92 return name; 93 } 94 95 @Override 96 public Stream<Node> walk() { 97 return Stream.concat(Stream.of(this), stream().flatMap(Node::walk)); 98 } 99 100 public Style style() { 101 return style; 102 } 103 104 @Override 105 public Node get(int index) { 106 Objects.checkIndex(index, nodes.length); 107 return nodes[index]; 108 } 109 110 @Override 111 public int size() { 112 return nodes.length; 113 } 114 } 115 116 public static final class MapNodeImpl implements MapNode { 117 118 private final Style style; 119 private final ConstantDesc name; 120 private final Map<ConstantDesc, Node> map; 121 122 public MapNodeImpl(Style style, ConstantDesc name) { 123 this.style = style; 124 this.name = name; 125 this.map = new LinkedHashMap<>(); 126 } 127 128 @Override 129 public ConstantDesc name() { 130 return name; 131 } 132 133 @Override 134 public Stream<Node> walk() { 135 return Stream.concat(Stream.of(this), values().stream().flatMap(Node::walk)); 136 } 137 138 public Style style() { 139 return style; 140 } 141 142 @Override 143 public int size() { 144 return map.size(); 145 } 146 @Override 147 public boolean isEmpty() { 148 return map.isEmpty(); 149 } 150 @Override 151 public boolean containsKey(Object key) { 152 return map.containsKey(key); 153 } 154 @Override 155 public boolean containsValue(Object value) { 156 return map.containsValue(value); 157 } 158 159 @Override 160 public Node get(Object key) { 161 return map.get(key); 162 } 163 164 @Override 165 public Node put(ConstantDesc key, Node value) { 166 throw new UnsupportedOperationException(); 167 } 168 169 @Override 170 public Node remove(Object key) { 171 throw new UnsupportedOperationException(); 172 } 173 174 @Override 175 public void putAll(Map<? extends ConstantDesc, ? extends Node> m) { 176 throw new UnsupportedOperationException(); 177 } 178 179 @Override 180 public void clear() { 181 throw new UnsupportedOperationException(); 182 } 183 184 @Override 185 public Set<ConstantDesc> keySet() { 186 return Collections.unmodifiableSet(map.keySet()); 187 } 188 189 @Override 190 public Collection<Node> values() { 191 return Collections.unmodifiableCollection(map.values()); 192 } 193 194 @Override 195 public Set<Entry<ConstantDesc, Node>> entrySet() { 196 return Collections.unmodifiableSet(map.entrySet()); 197 } 198 199 200 MapNodeImpl with(Node... nodes) { 201 for (var n : nodes) 202 if (n != null && map.put(n.name(), n) != null) 203 throw new AssertionError("Double entry of " + n.name() + " into " + name); 204 return this; 205 } 206 } 207 208 private static Node leaf(ConstantDesc name, ConstantDesc value) { 209 return new LeafNodeImpl(name, value); 210 } 211 212 private static Node[] leafs(ConstantDesc... namesAndValues) { 213 if ((namesAndValues.length & 1) > 0) 214 throw new AssertionError("Odd number of arguments: " + Arrays.toString(namesAndValues)); 215 var nodes = new Node[namesAndValues.length >> 1]; 216 for (int i = 0, j = 0; i < nodes.length; i ++) { 217 nodes[i] = leaf(namesAndValues[j++], namesAndValues[j++]); 218 } 219 return nodes; 220 } 221 222 private static Node list(ConstantDesc listName, ConstantDesc itemsName, Stream<ConstantDesc> values) { 223 return new ListNodeImpl(FLOW, listName, values.map(v -> leaf(itemsName, v))); 224 } 225 226 private static Node map(ConstantDesc mapName, ConstantDesc... keysAndValues) { 227 return new MapNodeImpl(FLOW, mapName).with(leafs(keysAndValues)); 228 } 229 230 private static final String NL = System.lineSeparator(); 231 232 private static final char[] DIGITS = "0123456789ABCDEF".toCharArray(); 233 234 private static void escape(int c, StringBuilder sb) { 235 switch (c) { 236 case '\\' -> sb.append('\\').append('\\'); 237 case '"' -> sb.append('\\').append('"'); 238 case '\b' -> sb.append('\\').append('b'); 239 case '\n' -> sb.append('\\').append('n'); 240 case '\t' -> sb.append('\\').append('t'); 241 case '\f' -> sb.append('\\').append('f'); 242 case '\r' -> sb.append('\\').append('r'); 243 default -> { 244 if (c >= 0x20 && c < 0x7f) { 245 sb.append((char)c); 246 } else { 247 sb.append('\\').append('u').append(DIGITS[(c >> 12) & 0xf]) 248 .append(DIGITS[(c >> 8) & 0xf]).append(DIGITS[(c >> 4) & 0xf]).append(DIGITS[(c) & 0xf]); 249 } 250 } 251 } 252 } 253 254 public static void toYaml(Node node, Consumer<String> out) { 255 toYaml(0, false, new ListNodeImpl(BLOCK, null, Stream.of(node)), out); 256 out.accept(NL); 257 } 258 259 private static void toYaml(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) { 260 switch (node) { 261 case LeafNode leaf -> { 262 out.accept(quoteAndEscapeYaml(leaf.value())); 263 } 264 case ListNodeImpl list -> { 265 switch (list.style()) { 266 case FLOW -> { 267 out.accept("["); 268 boolean first = true; 269 for (var n : list) { 270 if (first) first = false; 271 else out.accept(", "); 272 toYaml(0, false, n, out); 273 } 274 out.accept("]"); 275 } 276 case BLOCK -> { 277 for (var n : list) { 278 out.accept(NL + " ".repeat(indent) + " - "); 279 toYaml(indent + 1, true, n, out); 280 } 281 } 282 } 283 } 284 case MapNodeImpl map -> { 285 switch (map.style()) { 286 case FLOW -> { 287 out.accept("{"); 288 boolean first = true; 289 for (var n : map.values()) { 290 if (first) first = false; 291 else out.accept(", "); 292 out.accept(quoteAndEscapeYaml(n.name()) + ": "); 293 toYaml(0, false, n, out); 294 } 295 out.accept("}"); 296 } 297 case BLOCK -> { 298 for (var n : map.values()) { 299 if (skipFirstIndent) { 300 skipFirstIndent = false; 301 } else { 302 out.accept(NL + " ".repeat(indent)); 303 } 304 out.accept(quoteAndEscapeYaml(n.name()) + ": "); 305 toYaml(n instanceof ListNodeImpl pl && pl.style() == BLOCK ? indent : indent + 1, false, n, out); 306 } 307 } 308 } 309 } 310 } 311 } 312 313 private static String quoteAndEscapeYaml(ConstantDesc value) { 314 String s = String.valueOf(value); 315 if (value instanceof Number) return s; 316 if (s.length() == 0) return "''"; 317 var sb = new StringBuilder(s.length() << 1); 318 s.chars().forEach(c -> { 319 switch (c) { 320 case '\'' -> sb.append("''"); 321 default -> escape(c, sb); 322 }}); 323 String esc = sb.toString(); 324 if (esc.length() != s.length()) return "'" + esc + "'"; 325 switch (esc.charAt(0)) { 326 case '-', '?', ':', ',', '[', ']', '{', '}', '#', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': 327 return "'" + esc + "'"; 328 } 329 for (int i = 1; i < esc.length(); i++) { 330 switch (esc.charAt(i)) { 331 case ',', '[', ']', '{', '}': 332 return "'" + esc + "'"; 333 } 334 } 335 return esc; 336 } 337 338 public static void toJson(Node node, Consumer<String> out) { 339 toJson(1, true, node, out); 340 out.accept(NL); 341 } 342 343 private static void toJson(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) { 344 switch (node) { 345 case LeafNode leaf -> { 346 out.accept(quoteAndEscapeJson(leaf.value())); 347 } 348 case ListNodeImpl list -> { 349 out.accept("["); 350 boolean first = true; 351 switch (list.style()) { 352 case FLOW -> { 353 for (var n : list) { 354 if (first) first = false; 355 else out.accept(", "); 356 toJson(0, false, n, out); 357 } 358 } 359 case BLOCK -> { 360 for (var n : list) { 361 if (first) first = false; 362 else out.accept(","); 363 out.accept(NL + " ".repeat(indent)); 364 toJson(indent + 1, true, n, out); 365 } 366 } 367 } 368 out.accept("]"); 369 } 370 case MapNodeImpl map -> { 371 switch (map.style()) { 372 case FLOW -> { 373 out.accept("{"); 374 boolean first = true; 375 for (var n : map.values()) { 376 if (first) first = false; 377 else out.accept(", "); 378 out.accept(quoteAndEscapeJson(n.name().toString()) + ": "); 379 toJson(0, false, n, out); 380 } 381 } 382 case BLOCK -> { 383 if (skipFirstIndent) out.accept(" { "); 384 else out.accept("{"); 385 boolean first = true; 386 for (var n : map.values()) { 387 if (first) first = false; 388 else out.accept(","); 389 if (skipFirstIndent) skipFirstIndent = false; 390 else out.accept(NL + " ".repeat(indent)); 391 out.accept(quoteAndEscapeJson(n.name().toString()) + ": "); 392 toJson(indent + 1, false, n, out); 393 } 394 } 395 } 396 out.accept("}"); 397 } 398 } 399 } 400 401 private static String quoteAndEscapeJson(ConstantDesc value) { 402 String s = String.valueOf(value); 403 if (value instanceof Number) return s; 404 var sb = new StringBuilder(s.length() << 1); 405 sb.append('"'); 406 s.chars().forEach(c -> escape(c, sb)); 407 sb.append('"'); 408 return sb.toString(); 409 } 410 411 public static void toXml(Node node, Consumer<String> out) { 412 out.accept("<?xml version = '1.0'?>"); 413 toXml(0, false, node, out); 414 out.accept(NL); 415 } 416 417 private static void toXml(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) { 418 var name = toXmlName(node.name().toString()); 419 switch (node) { 420 case LeafNode leaf -> { 421 out.accept("<" + name + ">"); 422 out.accept(xmlEscape(leaf.value())); 423 } 424 case ListNodeImpl list -> { 425 switch (list.style()) { 426 case FLOW -> { 427 out.accept("<" + name + ">"); 428 for (var n : list) { 429 toXml(0, false, n, out); 430 } 431 } 432 case BLOCK -> { 433 if (!skipFirstIndent) 434 out.accept(NL + " ".repeat(indent)); 435 out.accept("<" + name + ">"); 436 for (var n : list) { 437 out.accept(NL + " ".repeat(indent + 1)); 438 toXml(indent + 1, true, n, out); 439 } 440 } 441 } 442 } 443 case MapNodeImpl map -> { 444 switch (map.style()) { 445 case FLOW -> { 446 out.accept("<" + name + ">"); 447 for (var n : map.values()) { 448 toXml(0, false, n, out); 449 } 450 } 451 case BLOCK -> { 452 if (!skipFirstIndent) 453 out.accept(NL + " ".repeat(indent)); 454 out.accept("<" + name + ">"); 455 for (var n : map.values()) { 456 out.accept(NL + " ".repeat(indent + 1)); 457 toXml(indent + 1, true, n, out); 458 } 459 } 460 } 461 } 462 } 463 out.accept("</" + name + ">"); 464 } 465 466 private static String xmlEscape(ConstantDesc value) { 467 var s = String.valueOf(value); 468 var sb = new StringBuilder(s.length() << 1); 469 s.chars().forEach(c -> { 470 switch (c) { 471 case '<' -> sb.append("<"); 472 case '>' -> sb.append(">"); 473 case '"' -> sb.append("""); 474 case '&' -> sb.append("&"); 475 case '\'' -> sb.append("'"); 476 default -> escape(c, sb); 477 }}); 478 return sb.toString(); 479 } 480 481 private static String toXmlName(String name) { 482 if (Character.isDigit(name.charAt(0))) 483 name = "_" + name; 484 return name.replaceAll("[^A-Za-z_0-9]", "_"); 485 } 486 487 private static Node[] elementValueToTree(AnnotationValue v) { 488 return switch (v) { 489 case OfString cv -> leafs("string", String.valueOf(cv.constantValue())); 490 case OfDouble cv -> leafs("double", String.valueOf(cv.constantValue())); 491 case OfFloat cv -> leafs("float", String.valueOf(cv.constantValue())); 492 case OfLong cv -> leafs("long", String.valueOf(cv.constantValue())); 493 case OfInteger cv -> leafs("int", String.valueOf(cv.constantValue())); 494 case OfShort cv -> leafs("short", String.valueOf(cv.constantValue())); 495 case OfCharacter cv -> leafs("char", String.valueOf(cv.constantValue())); 496 case OfByte cv -> leafs("byte", String.valueOf(cv.constantValue())); 497 case OfBoolean cv -> leafs("boolean", String.valueOf((int)cv.constantValue() != 0)); 498 case OfClass clv -> leafs("class", clv.className().stringValue()); 499 case OfEnum ev -> leafs("enum class", ev.className().stringValue(), 500 "constant name", ev.constantName().stringValue()); 501 case OfAnnotation av -> leafs("annotation class", av.annotation().className().stringValue()); 502 case OfArray av -> new Node[]{new ListNodeImpl(FLOW, "array", av.values().stream().map( 503 ev -> new MapNodeImpl(FLOW, "value").with(elementValueToTree(ev))))}; 504 }; 505 } 506 507 private static Node elementValuePairsToTree(List<AnnotationElement> evps) { 508 return new ListNodeImpl(FLOW, "values", evps.stream().map(evp -> new MapNodeImpl(FLOW, "pair").with( 509 leaf("name", evp.name().stringValue()), 510 new MapNodeImpl(FLOW, "value").with(elementValueToTree(evp.value()))))); 511 } 512 513 private static Stream<ConstantDesc> convertVTIs(CodeAttribute lr, List<VerificationTypeInfo> vtis) { 514 return vtis.stream().mapMulti((vti, ret) -> { 515 switch (vti) { 516 case SimpleVerificationTypeInfo s -> { 517 switch (s) { 518 case ITEM_DOUBLE -> { 519 ret.accept("double"); 520 ret.accept("double2"); 521 } 522 case ITEM_FLOAT -> 523 ret.accept("float"); 524 case ITEM_INTEGER -> 525 ret.accept("int"); 526 case ITEM_LONG -> { 527 ret.accept("long"); 528 ret.accept("long2"); 529 } 530 case ITEM_NULL -> ret.accept("null"); 531 case ITEM_TOP -> ret.accept("?"); 532 case ITEM_UNINITIALIZED_THIS -> ret.accept("THIS"); 533 } 534 } 535 case ObjectVerificationTypeInfo o -> 536 ret.accept(o.className().name().stringValue()); 537 case UninitializedVerificationTypeInfo u -> 538 ret.accept("UNINITIALIZED @" + lr.labelToBci(u.newTarget())); 539 } 540 }); 541 } 542 543 private record ExceptionHandler(int start, int end, int handler, String catchType) {} 544 545 public static MapNode modelToTree(CompoundElement<?> model, Verbosity verbosity) { 546 return switch(model) { 547 case ClassModel cm -> classToTree(cm, verbosity); 548 case FieldModel fm -> fieldToTree(fm, verbosity); 549 case MethodModel mm -> methodToTree(mm, verbosity); 550 case CodeModel com -> codeToTree((CodeAttribute)com, verbosity); 551 }; 552 } 553 554 private static MapNode classToTree(ClassModel clm, Verbosity verbosity) { 555 return new MapNodeImpl(BLOCK, "class") 556 .with(leaf("class name", clm.thisClass().asInternalName()), 557 leaf("version", clm.majorVersion() + "." + clm.minorVersion()), 558 list("flags", "flag", clm.flags().flags().stream().map(AccessFlag::name)), 559 leaf("superclass", clm.superclass().map(ClassEntry::asInternalName).orElse("")), 560 list("interfaces", "interface", clm.interfaces().stream().map(ClassEntry::asInternalName)), 561 list("attributes", "attribute", clm.attributes().stream().map(Attribute::attributeName))) 562 .with(constantPoolToTree(clm.constantPool(), verbosity)) 563 .with(attributesToTree(clm.attributes(), verbosity)) 564 .with(new ListNodeImpl(BLOCK, "fields", clm.fields().stream().map(f -> 565 fieldToTree(f, verbosity)))) 566 .with(new ListNodeImpl(BLOCK, "methods", clm.methods().stream().map(mm -> 567 methodToTree(mm, verbosity)))); 568 } 569 570 private static Node[] constantPoolToTree(ConstantPool cp, Verbosity verbosity) { 571 if (verbosity == Verbosity.TRACE_ALL) { 572 var cpNode = new MapNodeImpl(BLOCK, "constant pool"); 573 for (PoolEntry e : cp) { 574 cpNode.with(new MapNodeImpl(FLOW, e.index()) 575 .with(leaf("tag", switch (e.tag()) { 576 case TAG_UTF8 -> "Utf8"; 577 case TAG_INTEGER -> "Integer"; 578 case TAG_FLOAT -> "Float"; 579 case TAG_LONG -> "Long"; 580 case TAG_DOUBLE -> "Double"; 581 case TAG_CLASS -> "Class"; 582 case TAG_STRING -> "String"; 583 case TAG_FIELDREF -> "Fieldref"; 584 case TAG_METHODREF -> "Methodref"; 585 case TAG_INTERFACEMETHODREF -> "InterfaceMethodref"; 586 case TAG_NAMEANDTYPE -> "NameAndType"; 587 case TAG_METHODHANDLE -> "MethodHandle"; 588 case TAG_METHODTYPE -> "MethodType"; 589 case TAG_CONSTANTDYNAMIC -> "Dynamic"; 590 case TAG_INVOKEDYNAMIC -> "InvokeDynamic"; 591 case TAG_MODULE -> "Module"; 592 case TAG_PACKAGE -> "Package"; 593 default -> throw new AssertionError("Unknown CP tag: " + e.tag()); 594 })) 595 .with(switch (e) { 596 case ClassEntry ce -> leafs( 597 "class name index", ce.name().index(), 598 "class internal name", ce.asInternalName()); 599 case ModuleEntry me -> leafs( 600 "module name index", me.name().index(), 601 "module name", me.name().stringValue()); 602 case PackageEntry pe -> leafs( 603 "package name index", pe.name().index(), 604 "package name", pe.name().stringValue()); 605 case StringEntry se -> leafs( 606 "value index", se.utf8().index(), 607 "value", se.stringValue()); 608 case MemberRefEntry mre -> leafs( 609 "owner index", mre.owner().index(), 610 "name and type index", mre.nameAndType().index(), 611 "owner", mre.owner().name().stringValue(), 612 "name", mre.name().stringValue(), 613 "type", mre.type().stringValue()); 614 case NameAndTypeEntry nte -> leafs( 615 "name index", nte.name().index(), 616 "type index", nte.type().index(), 617 "name", nte.name().stringValue(), 618 "type", nte.type().stringValue()); 619 case MethodHandleEntry mhe -> leafs( 620 "reference kind", DirectMethodHandleDesc.Kind.valueOf(mhe.kind()).name(), 621 "reference index", mhe.reference().index(), 622 "owner", mhe.reference().owner().asInternalName(), 623 "name", mhe.reference().name().stringValue(), 624 "type", mhe.reference().type().stringValue()); 625 case MethodTypeEntry mte -> leafs( 626 "descriptor index", mte.descriptor().index(), 627 "descriptor", mte.descriptor().stringValue()); 628 case DynamicConstantPoolEntry dcpe -> new Node[] { 629 leaf("bootstrap method handle index", dcpe.bootstrap().bootstrapMethod().index()), 630 list("bootstrap method arguments indexes", 631 "index", dcpe.bootstrap().arguments().stream().map(en -> en.index())), 632 leaf("name and type index", dcpe.nameAndType().index()), 633 leaf("name", dcpe.name().stringValue()), 634 leaf("type", dcpe.type().stringValue())}; 635 case AnnotationConstantValueEntry ve -> leafs( 636 "value", String.valueOf(ve.constantValue()) 637 ); 638 })); 639 } 640 return new Node[]{cpNode}; 641 } else { 642 return new Node[0]; 643 } 644 } 645 646 private static Node frameToTree(ConstantDesc name, CodeAttribute lr, StackMapFrameInfo f) { 647 return new MapNodeImpl(FLOW, name).with( 648 list("locals", "item", convertVTIs(lr, f.locals())), 649 list("stack", "item", convertVTIs(lr, f.stack()))); 650 } 651 652 private static MapNode fieldToTree(FieldModel f, Verbosity verbosity) { 653 return new MapNodeImpl(BLOCK, "field") 654 .with(leaf("field name", f.fieldName().stringValue()), 655 list("flags", 656 "flag", f.flags().flags().stream().map(AccessFlag::name)), 657 leaf("field type", f.fieldType().stringValue()), 658 list("attributes", 659 "attribute", f.attributes().stream().map(Attribute::attributeName))) 660 .with(attributesToTree(f.attributes(), verbosity)); 661 } 662 663 public static MapNode methodToTree(MethodModel m, Verbosity verbosity) { 664 return new MapNodeImpl(BLOCK, "method") 665 .with(leaf("method name", m.methodName().stringValue()), 666 list("flags", 667 "flag", m.flags().flags().stream().map(AccessFlag::name)), 668 leaf("method type", m.methodType().stringValue()), 669 list("attributes", 670 "attribute", m.attributes().stream().map(Attribute::attributeName))) 671 .with(attributesToTree(m.attributes(), verbosity)) 672 .with(codeToTree((CodeAttribute)m.code().orElse(null), verbosity)); 673 } 674 675 private static MapNode codeToTree(CodeAttribute com, Verbosity verbosity) { 676 if (verbosity != Verbosity.MEMBERS_ONLY && com != null) { 677 var codeNode = new MapNodeImpl(BLOCK, "code"); 678 codeNode.with(leaf("max stack", com.maxStack())); 679 codeNode.with(leaf("max locals", com.maxLocals())); 680 codeNode.with(list("attributes", 681 "attribute", com.attributes().stream().map(Attribute::attributeName))); 682 var stackMap = new MapNodeImpl(BLOCK, "stack map frames"); 683 var visibleTypeAnnos = new LinkedHashMap<Integer, List<TypeAnnotation>>(); 684 var invisibleTypeAnnos = new LinkedHashMap<Integer, List<TypeAnnotation>>(); 685 List<LocalVariableInfo> locals = List.of(); 686 for (var attr : com.attributes()) { 687 if (attr instanceof StackMapTableAttribute smta) { 688 codeNode.with(stackMap); 689 for (var smf : smta.entries()) { 690 stackMap.with(frameToTree(com.labelToBci(smf.target()), com, smf)); 691 } 692 } else if (verbosity == Verbosity.TRACE_ALL && attr != null) switch (attr) { 693 case LocalVariableTableAttribute lvta -> { 694 locals = lvta.localVariables(); 695 codeNode.with(new ListNodeImpl(BLOCK, "local variables", 696 IntStream.range(0, locals.size()).mapToObj(i -> { 697 var lv = lvta.localVariables().get(i); 698 return map(i + 1, 699 "start", lv.startPc(), 700 "end", lv.startPc() + lv.length(), 701 "slot", lv.slot(), 702 "name", lv.name().stringValue(), 703 "type", lv.type().stringValue()); 704 }))); 705 } 706 case LocalVariableTypeTableAttribute lvtta -> { 707 codeNode.with(new ListNodeImpl(BLOCK, "local variable types", 708 IntStream.range(0, lvtta.localVariableTypes().size()).mapToObj(i -> { 709 var lvt = lvtta.localVariableTypes().get(i); 710 return map(i + 1, 711 "start", lvt.startPc(), 712 "end", lvt.startPc() + lvt.length(), 713 "slot", lvt.slot(), 714 "name", lvt.name().stringValue(), 715 "signature", lvt.signature().stringValue()); 716 }))); 717 } 718 case LineNumberTableAttribute lnta -> { 719 codeNode.with(new ListNodeImpl(BLOCK, "line numbers", 720 IntStream.range(0, lnta.lineNumbers().size()).mapToObj(i -> { 721 var ln = lnta.lineNumbers().get(i); 722 return map(i + 1, 723 "start", ln.startPc(), 724 "line number", ln.lineNumber()); 725 }))); 726 } 727 case CharacterRangeTableAttribute crta -> { 728 codeNode.with(new ListNodeImpl(BLOCK, "character ranges", 729 IntStream.range(0, crta.characterRangeTable().size()).mapToObj(i -> { 730 var cr = crta.characterRangeTable().get(i); 731 return map(i + 1, 732 "start", cr.startPc(), 733 "end", cr.endPc(), 734 "range start", cr.characterRangeStart(), 735 "range end", cr.characterRangeEnd(), 736 "flags", cr.flags()); 737 }))); 738 } 739 case RuntimeVisibleTypeAnnotationsAttribute rvtaa -> 740 rvtaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) -> 741 visibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an))); 742 case RuntimeInvisibleTypeAnnotationsAttribute ritaa -> 743 ritaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) -> 744 invisibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an))); 745 case Object o -> {} 746 } 747 } 748 codeNode.with(attributesToTree(com.attributes(), verbosity)); 749 if (!stackMap.containsKey(0)) { 750 codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @0").with( 751 list("locals", "item", convertVTIs(com, StackMapDecoder.initFrameLocals(com.parent().get()))), 752 list("stack", "item", Stream.of()))); 753 } 754 var excHandlers = com.exceptionHandlers().stream().map(exc -> new ExceptionHandler( 755 com.labelToBci(exc.tryStart()), 756 com.labelToBci(exc.tryEnd()), 757 com.labelToBci(exc.handler()), 758 exc.catchType().map(ct -> ct.name().stringValue()).orElse(null))).toList(); 759 int bci = 0; 760 for (var coe : com) { 761 if (coe instanceof Instruction ins) { 762 var frame = stackMap.get(bci); 763 if (frame != null) { 764 codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @" + bci) 765 .with(((MapNodeImpl)frame).values().toArray(new Node[2]))); 766 } 767 var annos = invisibleTypeAnnos.get(bci); 768 if (annos != null) { 769 codeNode.with(typeAnnotationsToTree(FLOW, "//invisible type annotations @" + bci, annos)); 770 } 771 annos = visibleTypeAnnos.get(bci); 772 if (annos != null) { 773 codeNode.with(typeAnnotationsToTree(FLOW, "//visible type annotations @" + bci, annos)); 774 } 775 for (int i = 0; i < excHandlers.size(); i++) { 776 var exc = excHandlers.get(i); 777 if (exc.start() == bci) { 778 codeNode.with(map("//try block " + (i + 1) + " start", 779 "start", exc.start(), 780 "end", exc.end(), 781 "handler", exc.handler(), 782 "catch type", exc.catchType())); 783 } 784 if (exc.end() == bci) { 785 codeNode.with(map("//try block " + (i + 1) + " end", 786 "start", exc.start(), 787 "end", exc.end(), 788 "handler", exc.handler(), 789 "catch type", exc.catchType())); 790 } 791 if (exc.handler() == bci) { 792 codeNode.with(map("//exception handler " + (i + 1) + " start", 793 "start", exc.start(), 794 "end", exc.end(), 795 "handler", exc.handler(), 796 "catch type", exc.catchType())); 797 } 798 } 799 var in = new MapNodeImpl(FLOW, bci).with(leaf("opcode", ins.opcode().name())); 800 codeNode.with(in); 801 switch (coe) { 802 case IncrementInstruction inc -> in.with(leafs( 803 "slot", inc.slot(), 804 "const", inc.constant())) 805 .with(localInfoToTree(locals, inc.slot(), bci)); 806 case LoadInstruction lv -> in.with(leaf( 807 "slot", lv.slot())) 808 .with(localInfoToTree(locals, lv.slot(), bci)); 809 case StoreInstruction lv -> in.with(leaf( 810 "slot", lv.slot())) 811 .with(localInfoToTree(locals, lv.slot(), bci)); 812 case FieldInstruction fa -> in.with(leafs( 813 "owner", fa.owner().name().stringValue(), 814 "field name", fa.name().stringValue(), 815 "field type", fa.type().stringValue())); 816 case InvokeInstruction inv -> in.with(leafs( 817 "owner", inv.owner().name().stringValue(), 818 "method name", inv.name().stringValue(), 819 "method type", inv.type().stringValue())); 820 case InvokeDynamicInstruction invd -> in.with(leafs( 821 "name", invd.name().stringValue(), 822 "descriptor", invd.type().stringValue(), 823 "kind", invd.bootstrapMethod().kind().name(), 824 "owner", invd.bootstrapMethod().owner().descriptorString(), 825 "method name", invd.bootstrapMethod().methodName(), 826 "invocation type", invd.bootstrapMethod().invocationType().descriptorString())); 827 case NewObjectInstruction newo -> in.with(leaf( 828 "type", newo.className().name().stringValue())); 829 case NewPrimitiveArrayInstruction newa -> in.with(leafs( 830 "dimensions", 1, 831 "descriptor", newa.typeKind().typeName())); 832 case NewReferenceArrayInstruction newa -> in.with(leafs( 833 "dimensions", 1, 834 "descriptor", newa.componentType().name().stringValue())); 835 case NewMultiArrayInstruction newa -> in.with(leafs( 836 "dimensions", newa.dimensions(), 837 "descriptor", newa.arrayType().name().stringValue())); 838 case TypeCheckInstruction tch -> in.with(leaf( 839 "type", tch.type().name().stringValue())); 840 case ConstantInstruction cons -> in.with(leaf( 841 "constant value", cons.constantValue())); 842 case BranchInstruction br -> in.with(leaf( 843 "target", com.labelToBci(br.target()))); 844 case LookupSwitchInstruction si -> in.with(list( 845 "targets", "target", Stream.concat(Stream.of(si.defaultTarget()) 846 .map(com::labelToBci), si.cases().stream() 847 .map(sc -> com.labelToBci(sc.target()))))); 848 case TableSwitchInstruction si -> in.with(list( 849 "targets", "target", Stream.concat(Stream.of(si.defaultTarget()) 850 .map(com::labelToBci), si.cases().stream() 851 .map(sc -> com.labelToBci(sc.target()))))); 852 case DiscontinuedInstruction.JsrInstruction jsr -> in.with(leaf( 853 "target", com.labelToBci(jsr.target()))); 854 case DiscontinuedInstruction.RetInstruction ret -> in.with(leaf( 855 "slot", ret.slot())); 856 default -> {} 857 } 858 bci += ins.sizeInBytes(); 859 } 860 } 861 if (!excHandlers.isEmpty()) { 862 var handlersNode = new MapNodeImpl(BLOCK, "exception handlers"); 863 codeNode.with(handlersNode); 864 for (int i = 0; i < excHandlers.size(); i++) { 865 var exc = excHandlers.get(i); 866 handlersNode.with(map("handler " + (i + 1), 867 "start", exc.start(), 868 "end", exc.end(), 869 "handler", exc.handler(), 870 "type", exc.catchType())); 871 } 872 } 873 return codeNode; 874 } 875 return null; 876 } 877 878 private static Node[] attributesToTree(List<Attribute<?>> attributes, Verbosity verbosity) { 879 var nodes = new LinkedList<Node>(); 880 if (verbosity != Verbosity.MEMBERS_ONLY) for (var attr : attributes) { 881 switch (attr) { 882 case BootstrapMethodsAttribute bma -> 883 nodes.add(new ListNodeImpl(BLOCK, "bootstrap methods", bma.bootstrapMethods().stream().map( 884 bm -> { 885 var mh = bm.bootstrapMethod(); 886 var mref = mh.reference(); 887 return map("bm", 888 "kind", DirectMethodHandleDesc.Kind.valueOf(mh.kind(), 889 mref instanceof InterfaceMethodRefEntry).name(), 890 "owner", mref.owner().name().stringValue(), 891 "name", mref.nameAndType().name().stringValue(), 892 "type", mref.nameAndType().type().stringValue()); 893 }))); 894 case ConstantValueAttribute cva -> 895 nodes.add(leaf("constant value", cva.constant().constantValue())); 896 case NestHostAttribute nha -> 897 nodes.add(leaf("nest host", nha.nestHost().name().stringValue())); 898 case NestMembersAttribute nma -> 899 nodes.add(list("nest members", "member", nma.nestMembers().stream() 900 .map(mp -> mp.name().stringValue()))); 901 case PermittedSubclassesAttribute psa -> 902 nodes.add(list("permitted subclasses", "subclass", psa.permittedSubclasses().stream() 903 .map(e -> e.name().stringValue()))); 904 default -> {} 905 } 906 if (verbosity == Verbosity.TRACE_ALL) switch (attr) { 907 case EnclosingMethodAttribute ema -> 908 nodes.add(map("enclosing method", 909 "class", ema.enclosingClass().name().stringValue(), 910 "method name", ema.enclosingMethodName() 911 .map(Utf8Entry::stringValue).orElse("null"), 912 "method type", ema.enclosingMethodType() 913 .map(Utf8Entry::stringValue).orElse("null"))); 914 case ExceptionsAttribute exa -> 915 nodes.add(list("exceptions", "exc", exa.exceptions().stream() 916 .map(e -> e.name().stringValue()))); 917 case InnerClassesAttribute ica -> 918 nodes.add(new ListNodeImpl(BLOCK, "inner classes", ica.classes().stream() 919 .map(ic -> new MapNodeImpl(FLOW, "cls").with( 920 leaf("inner class", ic.innerClass().name().stringValue()), 921 leaf("outer class", ic.outerClass() 922 .map(cle -> cle.name().stringValue()).orElse("null")), 923 leaf("inner name", ic.innerName().map(Utf8Entry::stringValue).orElse("null")), 924 list("flags", "flag", ic.flags().stream().map(AccessFlag::name)))))); 925 case MethodParametersAttribute mpa -> { 926 var n = new MapNodeImpl(BLOCK, "method parameters"); 927 for (int i = 0; i < mpa.parameters().size(); i++) { 928 var p = mpa.parameters().get(i); 929 n.with(new MapNodeImpl(FLOW, i + 1).with( 930 leaf("name", p.name().map(Utf8Entry::stringValue).orElse("null")), 931 list("flags", "flag", p.flags().stream().map(AccessFlag::name)))); 932 } 933 } 934 case ModuleAttribute ma -> 935 nodes.add(new MapNodeImpl(BLOCK, "module") 936 .with(leaf("name", ma.moduleName().name().stringValue()), 937 list("flags","flag", ma.moduleFlags().stream().map(AccessFlag::name)), 938 leaf("version", ma.moduleVersion().map(Utf8Entry::stringValue).orElse("null")), 939 list("uses", "class", ma.uses().stream().map(ce -> ce.name().stringValue())), 940 new ListNodeImpl(BLOCK, "requires", ma.requires().stream().map(req -> 941 new MapNodeImpl(FLOW, "req").with( 942 leaf("name", req.requires().name().stringValue()), 943 list("flags", "flag", req.requiresFlags().stream() 944 .map(AccessFlag::name)), 945 leaf("version", req.requiresVersion() 946 .map(Utf8Entry::stringValue).orElse(null))))), 947 new ListNodeImpl(BLOCK, "exports", ma.exports().stream().map(exp -> 948 new MapNodeImpl(FLOW, "exp").with( 949 leaf("package", exp.exportedPackage().asSymbol().name()), 950 list("flags", "flag", exp.exportsFlags().stream() 951 .map(AccessFlag::name)), 952 list("to", "module", exp.exportsTo().stream() 953 .map(me -> me.name().stringValue()))))), 954 new ListNodeImpl(BLOCK, "opens", ma.opens().stream().map(opn -> 955 new MapNodeImpl(FLOW, "opn").with( 956 leaf("package", opn.openedPackage().asSymbol().name()), 957 list("flags", "flag", opn.opensFlags().stream() 958 .map(AccessFlag::name)), 959 list("to", "module", opn.opensTo().stream() 960 .map(me -> me.name().stringValue()))))), 961 new ListNodeImpl(BLOCK, "provides", ma.provides().stream() 962 .map(prov -> new MapNodeImpl(FLOW, "prov").with( 963 leaf("class", prov.provides().name().stringValue()), 964 list("with", "cls", prov.providesWith().stream() 965 .map(ce -> ce.name().stringValue()))))))); 966 case ModulePackagesAttribute mopa -> 967 nodes.add(list("module packages", "subclass", mopa.packages().stream() 968 .map(mp -> mp.asSymbol().name()))); 969 case ModuleMainClassAttribute mmca -> 970 nodes.add(leaf("module main class", mmca.mainClass().name().stringValue())); 971 case RecordAttribute ra -> 972 nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream() 973 .map(rc -> new MapNodeImpl(BLOCK, "record") 974 .with(leafs( 975 "name", rc.name().stringValue(), 976 "type", rc.descriptor().stringValue())) 977 .with(list("attributes", "attribute", rc.attributes().stream() 978 .map(Attribute::attributeName))) 979 .with(attributesToTree(rc.attributes(), verbosity))))); 980 case AnnotationDefaultAttribute ada -> 981 nodes.add(new MapNodeImpl(FLOW, "annotation default").with(elementValueToTree(ada.defaultValue()))); 982 case RuntimeInvisibleAnnotationsAttribute aa -> 983 nodes.add(annotationsToTree("invisible annotations", aa.annotations())); 984 case RuntimeVisibleAnnotationsAttribute aa -> 985 nodes.add(annotationsToTree("visible annotations", aa.annotations())); 986 case RuntimeInvisibleParameterAnnotationsAttribute aa -> 987 nodes.add(parameterAnnotationsToTree("invisible parameter annotations", aa.parameterAnnotations())); 988 case RuntimeVisibleParameterAnnotationsAttribute aa -> 989 nodes.add(parameterAnnotationsToTree("visible parameter annotations", aa.parameterAnnotations())); 990 case RuntimeInvisibleTypeAnnotationsAttribute aa -> 991 nodes.add(typeAnnotationsToTree(BLOCK, "invisible type annotations", aa.annotations())); 992 case RuntimeVisibleTypeAnnotationsAttribute aa -> 993 nodes.add(typeAnnotationsToTree(BLOCK, "visible type annotations", aa.annotations())); 994 case SignatureAttribute sa -> 995 nodes.add(leaf("signature", sa.signature().stringValue())); 996 case SourceFileAttribute sfa -> 997 nodes.add(leaf("source file", sfa.sourceFile().stringValue())); 998 default -> {} 999 } 1000 } 1001 return nodes.toArray(Node[]::new); 1002 } 1003 1004 private static Node annotationsToTree(String name, List<Annotation> annos) { 1005 return new ListNodeImpl(BLOCK, name, annos.stream().map(a -> 1006 new MapNodeImpl(FLOW, "anno") 1007 .with(leaf("annotation class", a.className().stringValue())) 1008 .with(elementValuePairsToTree(a.elements())))); 1009 1010 } 1011 1012 private static Node typeAnnotationsToTree(Style style, String name, List<TypeAnnotation> annos) { 1013 return new ListNodeImpl(style, name, annos.stream().map(a -> 1014 new MapNodeImpl(FLOW, "anno") 1015 .with(leaf("annotation class", a.className().stringValue()), 1016 leaf("target info", a.targetInfo().targetType().name())) 1017 .with(elementValuePairsToTree(a.elements())))); 1018 1019 } 1020 1021 private static MapNodeImpl parameterAnnotationsToTree(String name, List<List<Annotation>> paramAnnotations) { 1022 var node = new MapNodeImpl(BLOCK, name); 1023 for (int i = 0; i < paramAnnotations.size(); i++) { 1024 var annos = paramAnnotations.get(i); 1025 if (!annos.isEmpty()) { 1026 node.with(new ListNodeImpl(FLOW, "parameter " + (i + 1), annos.stream().map(a -> 1027 new MapNodeImpl(FLOW, "anno") 1028 .with(leaf("annotation class", a.className().stringValue())) 1029 .with(elementValuePairsToTree(a.elements()))))); 1030 } 1031 } 1032 return node; 1033 } 1034 1035 private static Node[] localInfoToTree(List<LocalVariableInfo> locals, int slot, int bci) { 1036 if (locals != null) { 1037 for (var l : locals) { 1038 if (l.slot() == slot && l.startPc() <= bci && l.length() + l.startPc() >= bci) { 1039 return leafs("type", l.type().stringValue(), 1040 "variable name", l.name().stringValue()); 1041 } 1042 } 1043 } 1044 return new Node[0]; 1045 } 1046 1047 private static void forEachOffset(TypeAnnotation ta, CodeAttribute lr, BiConsumer<Integer, TypeAnnotation> consumer) { 1048 switch (ta.targetInfo()) { 1049 case TypeAnnotation.OffsetTarget ot -> 1050 consumer.accept(lr.labelToBci(ot.target()), ta); 1051 case TypeAnnotation.TypeArgumentTarget tat -> 1052 consumer.accept(lr.labelToBci(tat.target()), ta); 1053 case TypeAnnotation.LocalVarTarget lvt -> 1054 lvt.table().forEach(lvti -> consumer.accept(lr.labelToBci(lvti.startLabel()), ta)); 1055 default -> {} 1056 } 1057 } 1058 }