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