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 case LoadableDescriptorsAttribute pa ->
943 nodes.add(list("loadable descriptors", "descriptor", pa.loadableDescriptors().stream()
944 .map(e -> e.stringValue())));
945 default -> {}
946 }
947 if (verbosity == Verbosity.TRACE_ALL) switch (attr) {
948 case EnclosingMethodAttribute ema ->
949 nodes.add(map("enclosing method",
950 "class", ema.enclosingClass().name().stringValue(),
951 "method name", ema.enclosingMethodName()
952 .<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT),
953 "method type", ema.enclosingMethodType()
954 .<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)));
955 case ExceptionsAttribute exa ->
956 nodes.add(list("exceptions", "exc", exa.exceptions().stream()
957 .map(e -> e.name().stringValue())));
958 case InnerClassesAttribute ica ->
959 nodes.add(new ListNodeImpl(BLOCK, "inner classes", ica.classes().stream()
960 .map(ic -> new MapNodeImpl(FLOW, "cls").with(
961 leaf("inner class", ic.innerClass().name().stringValue()),
962 leaf("outer class", ic.outerClass()
963 .map(cle -> (ConstantDesc)cle.name().stringValue()).orElse(BSM_NULL_CONSTANT)),
964 leaf("inner name", ic.innerName().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
965 list("flags", "flag", ic.flags().stream().map(AccessFlag::name))))));
966 case MethodParametersAttribute mpa -> {
967 var n = new MapNodeImpl(BLOCK, "method parameters");
968 for (int i = 0; i < mpa.parameters().size(); i++) {
969 var p = mpa.parameters().get(i);
970 n.with(new MapNodeImpl(FLOW, i + 1).with(
971 leaf("name", p.name().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
972 list("flags", "flag", p.flags().stream().map(AccessFlag::name))));
973 }
974 }
975 case ModuleAttribute ma ->
976 nodes.add(new MapNodeImpl(BLOCK, "module")
977 .with(leaf("name", ma.moduleName().name().stringValue()),
978 list("flags","flag", ma.moduleFlags().stream().map(AccessFlag::name)),
979 leaf("version", ma.moduleVersion().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
980 list("uses", "class", ma.uses().stream().map(ce -> ce.name().stringValue())),
981 new ListNodeImpl(BLOCK, "requires", ma.requires().stream().map(req ->
982 new MapNodeImpl(FLOW, "req").with(
983 leaf("name", req.requires().name().stringValue()),
984 list("flags", "flag", req.requiresFlags().stream()
985 .map(AccessFlag::name)),
986 leaf("version", req.requiresVersion()
987 .map(Utf8Entry::stringValue).orElse(null))))),
988 new ListNodeImpl(BLOCK, "exports", ma.exports().stream().map(exp ->
989 new MapNodeImpl(FLOW, "exp").with(
990 leaf("package", exp.exportedPackage().asSymbol().name()),
991 list("flags", "flag", exp.exportsFlags().stream()
992 .map(AccessFlag::name)),
993 list("to", "module", exp.exportsTo().stream()
994 .map(me -> me.name().stringValue()))))),
995 new ListNodeImpl(BLOCK, "opens", ma.opens().stream().map(opn ->
996 new MapNodeImpl(FLOW, "opn").with(
997 leaf("package", opn.openedPackage().asSymbol().name()),
998 list("flags", "flag", opn.opensFlags().stream()
999 .map(AccessFlag::name)),
1000 list("to", "module", opn.opensTo().stream()
1001 .map(me -> me.name().stringValue()))))),
1002 new ListNodeImpl(BLOCK, "provides", ma.provides().stream()
1003 .map(prov -> new MapNodeImpl(FLOW, "prov").with(
1004 leaf("class", prov.provides().name().stringValue()),
1005 list("with", "cls", prov.providesWith().stream()
1006 .map(ce -> ce.name().stringValue())))))));
1007 case ModulePackagesAttribute mopa ->
1008 nodes.add(list("module packages", "subclass", mopa.packages().stream()
1009 .map(mp -> mp.asSymbol().name())));
1010 case ModuleMainClassAttribute mmca ->
1011 nodes.add(leaf("module main class", mmca.mainClass().name().stringValue()));
1012 case RecordAttribute ra ->
1013 nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream()
1014 .map(rc -> new MapNodeImpl(BLOCK, "component")
1015 .with(leafs(
1016 "name", rc.name().stringValue(),
1017 "type", rc.descriptor().stringValue()))
1018 .with(list("attributes", "attribute", rc.attributes().stream()
1019 .map(Attribute::attributeName).map(Utf8Entry::stringValue)))
1020 .with(attributesToTree(rc.attributes(), verbosity)))));
1021 case AnnotationDefaultAttribute ada ->
1022 nodes.add(new MapNodeImpl(FLOW, "annotation default").with(elementValueToTree(ada.defaultValue())));
1023 case RuntimeInvisibleAnnotationsAttribute aa ->
1024 nodes.add(annotationsToTree("invisible annotations", aa.annotations()));
1025 case RuntimeVisibleAnnotationsAttribute aa ->
1026 nodes.add(annotationsToTree("visible annotations", aa.annotations()));
1027 case RuntimeInvisibleParameterAnnotationsAttribute aa ->
1028 nodes.add(parameterAnnotationsToTree("invisible parameter annotations", aa.parameterAnnotations()));
1029 case RuntimeVisibleParameterAnnotationsAttribute aa ->
1030 nodes.add(parameterAnnotationsToTree("visible parameter annotations", aa.parameterAnnotations()));
1031 case RuntimeInvisibleTypeAnnotationsAttribute aa ->
1032 nodes.add(typeAnnotationsToTree(BLOCK, "invisible type annotations", aa.annotations()));
1033 case RuntimeVisibleTypeAnnotationsAttribute aa ->
1034 nodes.add(typeAnnotationsToTree(BLOCK, "visible type annotations", aa.annotations()));
1035 case SignatureAttribute sa ->
1036 nodes.add(leaf("signature", sa.signature().stringValue()));
1037 case SourceFileAttribute sfa ->
1038 nodes.add(leaf("source file", sfa.sourceFile().stringValue()));
1039 default -> {}
1040 }
1041 }
1042 return nodes.toArray(Node[]::new);
1043 }
1044
1045 private static Node annotationsToTree(String name, List<Annotation> annos) {
1046 return new ListNodeImpl(BLOCK, name, annos.stream().map(a ->
1047 new MapNodeImpl(FLOW, "anno")
1048 .with(leaf("annotation class", a.className().stringValue()))
1049 .with(elementValuePairsToTree(a.elements()))));
1050
1051 }
1052
1053 private static Node typeAnnotationsToTree(Style style, String name, List<TypeAnnotation> annos) {
1054 return new ListNodeImpl(style, name, annos.stream().map(a ->
1055 new MapNodeImpl(FLOW, "anno")
1056 .with(leaf("annotation class", a.annotation().className().stringValue()),
1057 leaf("target info", a.targetInfo().targetType().name()))
1058 .with(elementValuePairsToTree(a.annotation().elements()))));
1059
1060 }
1061
1062 private static MapNodeImpl parameterAnnotationsToTree(String name, List<List<Annotation>> paramAnnotations) {
1063 var node = new MapNodeImpl(BLOCK, name);
1064 for (int i = 0; i < paramAnnotations.size(); i++) {
1065 var annos = paramAnnotations.get(i);
1066 if (!annos.isEmpty()) {
1067 node.with(new ListNodeImpl(FLOW, "parameter " + (i + 1), annos.stream().map(a ->
1068 new MapNodeImpl(FLOW, "anno")
1069 .with(leaf("annotation class", a.className().stringValue()))
1070 .with(elementValuePairsToTree(a.elements())))));
1071 }
1072 }
1073 return node;
1074 }
1075
1076 private static Node[] localInfoToTree(List<LocalVariableInfo> locals, int slot, int bci) {
1077 if (locals != null) {
1078 for (var l : locals) {
1079 if (l.slot() == slot && l.startPc() <= bci && l.length() + l.startPc() >= bci) {
1080 return leafs("type", l.type().stringValue(),
1081 "variable name", l.name().stringValue());
1082 }
1083 }
1084 }
1085 return new Node[0];
1086 }
1087
1088 private static void forEachOffset(TypeAnnotation ta, CodeAttribute lr, BiConsumer<Integer, TypeAnnotation> consumer) {
1089 switch (ta.targetInfo()) {
1090 case TypeAnnotation.OffsetTarget ot ->
1091 consumer.accept(lr.labelToBci(ot.target()), ta);
1092 case TypeAnnotation.TypeArgumentTarget tat ->
1093 consumer.accept(lr.labelToBci(tat.target()), ta);
1094 case TypeAnnotation.LocalVarTarget lvt ->
1095 lvt.table().forEach(lvti -> consumer.accept(lr.labelToBci(lvti.startLabel()), ta));
1096 default -> {}
1097 }
1098 }
1099 }