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