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