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