blob: 8a44890fe66903400ebcc53664fec56c79a97edb [file]
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.classfile.impl;
import java.lang.classfile.*;
import java.lang.classfile.AnnotationValue.*;
import java.lang.classfile.attribute.*;
import java.lang.classfile.attribute.StackMapFrameInfo.ObjectVerificationTypeInfo;
import java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo;
import java.lang.classfile.attribute.StackMapFrameInfo.UninitializedVerificationTypeInfo;
import java.lang.classfile.attribute.StackMapFrameInfo.VerificationTypeInfo;
import jdk.internal.classfile.components.ClassPrinter.LeafNode;
import jdk.internal.classfile.components.ClassPrinter.ListNode;
import jdk.internal.classfile.components.ClassPrinter.MapNode;
import jdk.internal.classfile.components.ClassPrinter.Node;
import jdk.internal.classfile.components.ClassPrinter.Verbosity;
import java.lang.classfile.constantpool.*;
import java.lang.classfile.instruction.*;
import java.lang.constant.ConstantDesc;
import java.lang.constant.DirectMethodHandleDesc;
import java.lang.reflect.AccessFlag;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.lang.classfile.constantpool.PoolEntry.TAG_CLASS;
import static java.lang.classfile.constantpool.PoolEntry.TAG_DOUBLE;
import static java.lang.classfile.constantpool.PoolEntry.TAG_FLOAT;
import static java.lang.classfile.constantpool.PoolEntry.TAG_LONG;
import static java.lang.classfile.constantpool.PoolEntry.TAG_STRING;
import static java.lang.classfile.constantpool.PoolEntry.*;
import static java.lang.constant.ConstantDescs.BSM_NULL_CONSTANT;
import static java.util.Objects.requireNonNull;
import static jdk.internal.classfile.impl.ClassPrinterImpl.Style.BLOCK;
import static jdk.internal.classfile.impl.ClassPrinterImpl.Style.FLOW;
public final class ClassPrinterImpl {
public enum Style { BLOCK, FLOW }
public record LeafNodeImpl(ConstantDesc name, ConstantDesc value) implements LeafNode {
@Override
public Stream<Node> walk() {
return Stream.of(this);
}
}
public static sealed class ListNodeImpl extends AbstractList<Node> implements ListNode {
private final Style style;
private final ConstantDesc name;
protected final List<Node> nodes;
public ListNodeImpl(Style style, ConstantDesc name, Stream<Node> nodes) {
this.style = style;
this.name = name;
this.nodes = nodes.toList();
}
protected ListNodeImpl(Style style, ConstantDesc name, List<Node> nodes) {
this.style = style;
this.name = name;
this.nodes = nodes;
}
@Override
public ConstantDesc name() {
return name;
}
@Override
public Stream<Node> walk() {
return Stream.concat(Stream.of(this), stream().flatMap(Node::walk));
}
public Style style() {
return style;
}
@Override
public Node get(int index) {
return nodes.get(index);
}
@Override
public int size() {
return nodes.size();
}
}
public static final class MapNodeImpl implements MapNode {
private static final class PrivateListNodeImpl extends ListNodeImpl {
PrivateListNodeImpl(Style style, ConstantDesc name, Node... n) {
super(style, name, new ArrayList<>(List.of(n)));
}
}
private final Style style;
private final ConstantDesc name;
private final Map<ConstantDesc, Node> map;
public MapNodeImpl(Style style, ConstantDesc name) {
this.style = style;
this.name = name;
this.map = new LinkedHashMap<>();
}
@Override
public ConstantDesc name() {
return name;
}
@Override
public Stream<Node> walk() {
return Stream.concat(Stream.of(this), values().stream().flatMap(Node::walk));
}
public Style style() {
return style;
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
@Override
public Node get(Object key) {
return map.get(key);
}
@Override
public Node put(ConstantDesc key, Node value) {
throw new UnsupportedOperationException();
}
@Override
public Node remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends ConstantDesc, ? extends Node> m) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public Set<ConstantDesc> keySet() {
return Collections.unmodifiableSet(map.keySet());
}
@Override
public Collection<Node> values() {
return Collections.unmodifiableCollection(map.values());
}
@Override
public Set<Entry<ConstantDesc, Node>> entrySet() {
return Collections.unmodifiableSet(map.entrySet());
}
MapNodeImpl with(Node... nodes) {
for (var n : nodes) {
if (n != null) {
var prev = map.putIfAbsent(n.name(), n);
if (prev != null) {
//nodes with duplicite keys are joined into a list
if (prev instanceof PrivateListNodeImpl list) {
list.nodes.add(n);
} else {
map.put(n.name(), new PrivateListNodeImpl(style, n.name(), prev, n));
}
}
}
}
return this;
}
}
private static Node leaf(ConstantDesc name, ConstantDesc value) {
return new LeafNodeImpl(name, value);
}
private static Node[] leafs(ConstantDesc... namesAndValues) {
if ((namesAndValues.length & 1) > 0)
throw new AssertionError("Odd number of arguments: " + Arrays.toString(namesAndValues));
var nodes = new Node[namesAndValues.length >> 1];
for (int i = 0, j = 0; i < nodes.length; i ++) {
nodes[i] = leaf(namesAndValues[j++], namesAndValues[j++]);
}
return nodes;
}
private static Node list(ConstantDesc listName, ConstantDesc itemsName, Stream<ConstantDesc> values) {
return new ListNodeImpl(FLOW, listName, values.map(v -> leaf(itemsName, v)));
}
private static Node map(ConstantDesc mapName, ConstantDesc... keysAndValues) {
return new MapNodeImpl(FLOW, mapName).with(leafs(keysAndValues));
}
private static final String NL = System.lineSeparator();
private static final char[] DIGITS = "0123456789ABCDEF".toCharArray();
private static void escape(int c, StringBuilder sb) {
switch (c) {
case '\\' -> sb.append('\\').append('\\');
case '"' -> sb.append('\\').append('"');
case '\b' -> sb.append('\\').append('b');
case '\n' -> sb.append('\\').append('n');
case '\t' -> sb.append('\\').append('t');
case '\f' -> sb.append('\\').append('f');
case '\r' -> sb.append('\\').append('r');
default -> {
if (c >= 0x20 && c < 0x7f) {
sb.append((char)c);
} else {
sb.append('\\').append('u').append(DIGITS[(c >> 12) & 0xf])
.append(DIGITS[(c >> 8) & 0xf]).append(DIGITS[(c >> 4) & 0xf]).append(DIGITS[(c) & 0xf]);
}
}
}
}
public static void toYaml(Node node, Consumer<String> out) {
toYaml(0, false, new ListNodeImpl(BLOCK, null, Stream.of(node)), out);
out.accept(NL);
}
private static void toYaml(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) {
switch (node) {
case LeafNode leaf -> {
var v = leaf.value();
if (BSM_NULL_CONSTANT.equals(v)) {
out.accept("null");
} else {
out.accept(quoteAndEscapeYaml(v));
}
}
case ListNodeImpl list -> {
switch (list.style()) {
case FLOW -> {
out.accept("[");
boolean first = true;
for (var n : list) {
if (first) first = false;
else out.accept(", ");
toYaml(0, false, n, out);
}
out.accept("]");
}
case BLOCK -> {
for (var n : list) {
out.accept(NL + " ".repeat(indent) + " - ");
toYaml(indent + 1, true, n, out);
}
}
}
}
case MapNodeImpl map -> {
switch (map.style()) {
case FLOW -> {
out.accept("{");
boolean first = true;
for (var n : map.values()) {
if (first) first = false;
else out.accept(", ");
out.accept(quoteAndEscapeYaml(n.name()) + ": ");
toYaml(0, false, n, out);
}
out.accept("}");
}
case BLOCK -> {
for (var n : map.values()) {
if (skipFirstIndent) {
skipFirstIndent = false;
} else {
out.accept(NL + " ".repeat(indent));
}
out.accept(quoteAndEscapeYaml(n.name()) + ": ");
toYaml(n instanceof ListNodeImpl pl && pl.style() == BLOCK ? indent : indent + 1, false, n, out);
}
}
}
}
}
}
private static String quoteAndEscapeYaml(ConstantDesc value) {
String s = String.valueOf(value);
if (value instanceof Number) return s;
if (s.length() == 0) return "''";
if (s.equalsIgnoreCase("null")) return "'" + s + "'";
var sb = new StringBuilder(s.length() << 1);
s.chars().forEach(c -> {
switch (c) {
case '\'' -> sb.append("''");
default -> escape(c, sb);
}});
String esc = sb.toString();
if (esc.length() != s.length()) return "'" + esc + "'";
switch (esc.charAt(0)) {
case '-', '?', ':', ',', '[', ']', '{', '}', '#', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`':
return "'" + esc + "'";
}
for (int i = 1; i < esc.length(); i++) {
switch (esc.charAt(i)) {
case ',', '[', ']', '{', '}':
return "'" + esc + "'";
}
}
return esc;
}
public static void toJson(Node node, Consumer<String> out) {
toJson(1, true, node, out);
out.accept(NL);
}
private static void toJson(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) {
switch (node) {
case LeafNode leaf -> {
var v = leaf.value();
if (BSM_NULL_CONSTANT.equals(v)) {
out.accept("null");
} else {
out.accept(quoteAndEscapeJson(v));
}
}
case ListNodeImpl list -> {
out.accept("[");
boolean first = true;
switch (list.style()) {
case FLOW -> {
for (var n : list) {
if (first) first = false;
else out.accept(", ");
toJson(0, false, n, out);
}
}
case BLOCK -> {
for (var n : list) {
if (first) first = false;
else out.accept(",");
out.accept(NL + " ".repeat(indent));
toJson(indent + 1, true, n, out);
}
}
}
out.accept("]");
}
case MapNodeImpl map -> {
switch (map.style()) {
case FLOW -> {
out.accept("{");
boolean first = true;
for (var n : map.values()) {
if (first) first = false;
else out.accept(", ");
out.accept(quoteAndEscapeJson(n.name().toString()) + ": ");
toJson(0, false, n, out);
}
}
case BLOCK -> {
if (skipFirstIndent) out.accept(" { ");
else out.accept("{");
boolean first = true;
for (var n : map.values()) {
if (first) first = false;
else out.accept(",");
if (skipFirstIndent) skipFirstIndent = false;
else out.accept(NL + " ".repeat(indent));
out.accept(quoteAndEscapeJson(n.name().toString()) + ": ");
toJson(indent + 1, false, n, out);
}
}
}
out.accept("}");
}
}
}
private static String quoteAndEscapeJson(ConstantDesc value) {
String s = String.valueOf(value);
if (value instanceof Number) return s;
var sb = new StringBuilder(s.length() << 1);
sb.append('"');
s.chars().forEach(c -> escape(c, sb));
sb.append('"');
return sb.toString();
}
public static void toXml(Node node, Consumer<String> out) {
out.accept("<?xml version = '1.0'?>");
toXml(0, false, node, out);
out.accept(NL);
}
private static void toXml(int indent, boolean skipFirstIndent, Node node, Consumer<String> out) {
var name = toXmlName(node.name().toString());
switch (node) {
case LeafNode leaf -> {
out.accept("<" + name + ">");
var v = leaf.value();
if (BSM_NULL_CONSTANT.equals(v)) {
out.accept("<null/>");
} else {
out.accept(xmlEscape(v));
}
}
case ListNodeImpl list -> {
switch (list.style()) {
case FLOW -> {
out.accept("<" + name + ">");
for (var n : list) {
toXml(0, false, n, out);
}
}
case BLOCK -> {
if (!skipFirstIndent)
out.accept(NL + " ".repeat(indent));
out.accept("<" + name + ">");
for (var n : list) {
out.accept(NL + " ".repeat(indent + 1));
toXml(indent + 1, true, n, out);
}
}
}
}
case MapNodeImpl map -> {
switch (map.style()) {
case FLOW -> {
out.accept("<" + name + ">");
for (var n : map.values()) {
toXml(0, false, n, out);
}
}
case BLOCK -> {
if (!skipFirstIndent)
out.accept(NL + " ".repeat(indent));
out.accept("<" + name + ">");
for (var n : map.values()) {
out.accept(NL + " ".repeat(indent + 1));
toXml(indent + 1, true, n, out);
}
}
}
}
}
out.accept("</" + name + ">");
}
private static String xmlEscape(ConstantDesc value) {
var s = String.valueOf(value);
var sb = new StringBuilder(s.length() << 1);
s.chars().forEach(c -> {
switch (c) {
case '<' -> sb.append("&lt;");
case '>' -> sb.append("&gt;");
case '"' -> sb.append("&quot;");
case '&' -> sb.append("&amp;");
case '\'' -> sb.append("&apos;");
default -> escape(c, sb);
}});
return sb.toString();
}
private static String toXmlName(String name) {
if (Character.isDigit(name.charAt(0)))
name = "_" + name;
return name.replaceAll("[^A-Za-z_0-9]", "_");
}
private static Node[] elementValueToTree(AnnotationValue v) {
return switch (v) {
case OfString cv -> leafs("string", String.valueOf(cv.stringValue()));
case OfDouble cv -> leafs("double", String.valueOf(cv.doubleValue()));
case OfFloat cv -> leafs("float", String.valueOf(cv.floatValue()));
case OfLong cv -> leafs("long", String.valueOf(cv.longValue()));
case OfInt cv -> leafs("int", String.valueOf(cv.intValue()));
case OfShort cv -> leafs("short", String.valueOf(cv.shortValue()));
case OfChar cv -> leafs("char", String.valueOf(cv.charValue()));
case OfByte cv -> leafs("byte", String.valueOf(cv.byteValue()));
case OfBoolean cv -> leafs("boolean", String.valueOf(cv.booleanValue()));
case OfClass clv -> leafs("class", clv.className().stringValue());
case OfEnum ev -> leafs("enum class", ev.className().stringValue(),
"constant name", ev.constantName().stringValue());
case OfAnnotation av -> leafs("annotation class", av.annotation().className().stringValue());
case OfArray av -> new Node[]{new ListNodeImpl(FLOW, "array", av.values().stream().map(
ev -> new MapNodeImpl(FLOW, "value").with(elementValueToTree(ev))))};
};
}
private static Node elementValuePairsToTree(List<AnnotationElement> evps) {
return new ListNodeImpl(FLOW, "values", evps.stream().map(evp -> new MapNodeImpl(FLOW, "pair").with(
leaf("name", evp.name().stringValue()),
new MapNodeImpl(FLOW, "value").with(elementValueToTree(evp.value())))));
}
private static Stream<ConstantDesc> convertVTIs(CodeAttribute lr, List<VerificationTypeInfo> vtis) {
return vtis.stream().mapMulti((vti, ret) -> {
switch (vti) {
case SimpleVerificationTypeInfo s -> {
switch (s) {
case DOUBLE -> {
ret.accept("double");
ret.accept("double2");
}
case FLOAT ->
ret.accept("float");
case INTEGER ->
ret.accept("int");
case LONG -> {
ret.accept("long");
ret.accept("long2");
}
case NULL -> ret.accept(BSM_NULL_CONSTANT);
case TOP -> ret.accept("?");
case UNINITIALIZED_THIS -> ret.accept("THIS");
}
}
case ObjectVerificationTypeInfo o ->
ret.accept(o.className().name().stringValue());
case UninitializedVerificationTypeInfo u ->
ret.accept("UNINITIALIZED @" + lr.labelToBci(u.newTarget()));
}
});
}
private record ExceptionHandler(int start, int end, int handler, String catchType) {}
public static MapNode modelToTree(CompoundElement<?> model, Verbosity verbosity) {
requireNonNull(verbosity); // we are using == checks in implementations
return switch(model) {
case ClassModel cm -> classToTree(cm, verbosity);
case FieldModel fm -> fieldToTree(fm, verbosity);
case MethodModel mm -> methodToTree(mm, verbosity);
case CodeModel com -> codeToTree((CodeAttribute)com, verbosity);
};
}
private static MapNode classToTree(ClassModel clm, Verbosity verbosity) {
return new MapNodeImpl(BLOCK, "class")
.with(leaf("class name", clm.thisClass().asInternalName()),
leaf("version", clm.majorVersion() + "." + clm.minorVersion()),
list("flags", "flag", clm.flags().flags().stream().map(AccessFlag::name)),
leaf("superclass", clm.superclass().map(ClassEntry::asInternalName).orElse("")),
list("interfaces", "interface", clm.interfaces().stream().map(ClassEntry::asInternalName)),
list("attributes", "attribute", clm.attributes().stream().map(Attribute::attributeName).map(Utf8Entry::stringValue)))
.with(constantPoolToTree(clm.constantPool(), verbosity))
.with(attributesToTree(clm.attributes(), verbosity))
.with(new ListNodeImpl(BLOCK, "fields", clm.fields().stream().map(f ->
fieldToTree(f, verbosity))))
.with(new ListNodeImpl(BLOCK, "methods", clm.methods().stream().map(mm ->
methodToTree(mm, verbosity))));
}
private static Node[] constantPoolToTree(ConstantPool cp, Verbosity verbosity) {
if (verbosity == Verbosity.TRACE_ALL) {
var cpNode = new MapNodeImpl(BLOCK, "constant pool");
for (PoolEntry e : cp) {
cpNode.with(new MapNodeImpl(FLOW, e.index())
.with(leaf("tag", switch (e.tag()) {
case TAG_UTF8 -> "Utf8";
case TAG_INTEGER -> "Integer";
case TAG_FLOAT -> "Float";
case TAG_LONG -> "Long";
case TAG_DOUBLE -> "Double";
case TAG_CLASS -> "Class";
case TAG_STRING -> "String";
case TAG_FIELDREF -> "Fieldref";
case TAG_METHODREF -> "Methodref";
case TAG_INTERFACE_METHODREF -> "InterfaceMethodref";
case TAG_NAME_AND_TYPE -> "NameAndType";
case TAG_METHOD_HANDLE -> "MethodHandle";
case TAG_METHOD_TYPE -> "MethodType";
case TAG_DYNAMIC -> "Dynamic";
case TAG_INVOKE_DYNAMIC -> "InvokeDynamic";
case TAG_MODULE -> "Module";
case TAG_PACKAGE -> "Package";
default -> throw new AssertionError("Unknown CP tag: " + e.tag());
}))
.with(switch (e) {
case ClassEntry ce -> leafs(
"class name index", ce.name().index(),
"class internal name", ce.asInternalName());
case ModuleEntry me -> leafs(
"module name index", me.name().index(),
"module name", me.name().stringValue());
case PackageEntry pe -> leafs(
"package name index", pe.name().index(),
"package name", pe.name().stringValue());
case StringEntry se -> leafs(
"value index", se.utf8().index(),
"value", se.stringValue());
case MemberRefEntry mre -> leafs(
"owner index", mre.owner().index(),
"name and type index", mre.nameAndType().index(),
"owner", mre.owner().name().stringValue(),
"name", mre.name().stringValue(),
"type", mre.type().stringValue());
case NameAndTypeEntry nte -> leafs(
"name index", nte.name().index(),
"type index", nte.type().index(),
"name", nte.name().stringValue(),
"type", nte.type().stringValue());
case MethodHandleEntry mhe -> leafs(
"reference kind", DirectMethodHandleDesc.Kind.valueOf(mhe.kind()).name(),
"reference index", mhe.reference().index(),
"owner", mhe.reference().owner().asInternalName(),
"name", mhe.reference().name().stringValue(),
"type", mhe.reference().type().stringValue());
case MethodTypeEntry mte -> leafs(
"descriptor index", mte.descriptor().index(),
"descriptor", mte.descriptor().stringValue());
case DynamicConstantPoolEntry dcpe -> new Node[] {
leaf("bootstrap method handle index", dcpe.bootstrap().bootstrapMethod().index()),
list("bootstrap method arguments indexes",
"index", dcpe.bootstrap().arguments().stream().map(en -> en.index())),
leaf("name and type index", dcpe.nameAndType().index()),
leaf("name", dcpe.name().stringValue()),
leaf("type", dcpe.type().stringValue())};
case AnnotationConstantValueEntry ve -> leafs(
"value", String.valueOf(ve.constantValue())
);
}));
}
return new Node[]{cpNode};
} else {
return new Node[0];
}
}
private static Node frameToTree(ConstantDesc name, CodeAttribute lr, StackMapFrameInfo f) {
return new MapNodeImpl(FLOW, name).with(
list("locals", "item", convertVTIs(lr, f.locals())),
list("stack", "item", convertVTIs(lr, f.stack())));
}
private static MapNode fieldToTree(FieldModel f, Verbosity verbosity) {
return new MapNodeImpl(BLOCK, "field")
.with(leaf("field name", f.fieldName().stringValue()),
list("flags",
"flag", f.flags().flags().stream().map(AccessFlag::name)),
leaf("field type", f.fieldType().stringValue()),
list("attributes",
"attribute", f.attributes().stream().map(Attribute::attributeName).map(Utf8Entry::stringValue)))
.with(attributesToTree(f.attributes(), verbosity));
}
public static MapNode methodToTree(MethodModel m, Verbosity verbosity) {
return new MapNodeImpl(BLOCK, "method")
.with(leaf("method name", m.methodName().stringValue()),
list("flags",
"flag", m.flags().flags().stream().map(AccessFlag::name)),
leaf("method type", m.methodType().stringValue()),
list("attributes",
"attribute", m.attributes().stream().map(Attribute::attributeName).map(Utf8Entry::stringValue)))
.with(attributesToTree(m.attributes(), verbosity))
.with(codeToTree((CodeAttribute)m.code().orElse(null), verbosity));
}
private static MapNode codeToTree(CodeAttribute com, Verbosity verbosity) {
if (verbosity != Verbosity.MEMBERS_ONLY && com != null) {
var codeNode = new MapNodeImpl(BLOCK, "code");
codeNode.with(leaf("max stack", com.maxStack()));
codeNode.with(leaf("max locals", com.maxLocals()));
codeNode.with(list("attributes",
"attribute", com.attributes().stream().map(Attribute::attributeName).map(Utf8Entry::stringValue)));
var stackMap = new MapNodeImpl(BLOCK, "stack map frames");
var visibleTypeAnnos = new LinkedHashMap<Integer, List<TypeAnnotation>>();
var invisibleTypeAnnos = new LinkedHashMap<Integer, List<TypeAnnotation>>();
List<LocalVariableInfo> locals = List.of();
for (var attr : com.attributes()) {
if (attr instanceof StackMapTableAttribute smta) {
codeNode.with(stackMap);
for (var smf : smta.entries()) {
stackMap.with(frameToTree(com.labelToBci(smf.target()), com, smf));
}
} else if (verbosity == Verbosity.TRACE_ALL && attr != null) switch (attr) {
case LocalVariableTableAttribute lvta -> {
locals = lvta.localVariables();
codeNode.with(new ListNodeImpl(BLOCK, "local variables",
IntStream.range(0, locals.size()).mapToObj(i -> {
var lv = lvta.localVariables().get(i);
return map(i + 1,
"start", lv.startPc(),
"end", lv.startPc() + lv.length(),
"slot", lv.slot(),
"name", lv.name().stringValue(),
"type", lv.type().stringValue());
})));
}
case LocalVariableTypeTableAttribute lvtta -> {
codeNode.with(new ListNodeImpl(BLOCK, "local variable types",
IntStream.range(0, lvtta.localVariableTypes().size()).mapToObj(i -> {
var lvt = lvtta.localVariableTypes().get(i);
return map(i + 1,
"start", lvt.startPc(),
"end", lvt.startPc() + lvt.length(),
"slot", lvt.slot(),
"name", lvt.name().stringValue(),
"signature", lvt.signature().stringValue());
})));
}
case LineNumberTableAttribute lnta -> {
codeNode.with(new ListNodeImpl(BLOCK, "line numbers",
IntStream.range(0, lnta.lineNumbers().size()).mapToObj(i -> {
var ln = lnta.lineNumbers().get(i);
return map(i + 1,
"start", ln.startPc(),
"line number", ln.lineNumber());
})));
}
case CharacterRangeTableAttribute crta -> {
codeNode.with(new ListNodeImpl(BLOCK, "character ranges",
IntStream.range(0, crta.characterRangeTable().size()).mapToObj(i -> {
var cr = crta.characterRangeTable().get(i);
return map(i + 1,
"start", cr.startPc(),
"end", cr.endPc(),
"range start", cr.characterRangeStart(),
"range end", cr.characterRangeEnd(),
"flags", cr.flags());
})));
}
case RuntimeVisibleTypeAnnotationsAttribute rvtaa ->
rvtaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) ->
visibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an)));
case RuntimeInvisibleTypeAnnotationsAttribute ritaa ->
ritaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) ->
invisibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an)));
case Object o -> {}
}
}
codeNode.with(attributesToTree(com.attributes(), verbosity));
if (!stackMap.containsKey(0)) {
codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @0").with(
list("locals", "item", convertVTIs(com, StackMapDecoder.initFrameLocals(com.parent().get()))),
list("stack", "item", Stream.of())));
}
var excHandlers = com.exceptionHandlers().stream().map(exc -> new ExceptionHandler(
com.labelToBci(exc.tryStart()),
com.labelToBci(exc.tryEnd()),
com.labelToBci(exc.handler()),
exc.catchType().map(ct -> ct.name().stringValue()).orElse(null))).toList();
int bci = 0;
for (var coe : com) {
if (coe instanceof Instruction ins) {
var frame = stackMap.get(bci);
if (frame != null) {
codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @" + bci)
.with(((MapNodeImpl)frame).values().toArray(new Node[2])));
}
var annos = invisibleTypeAnnos.get(bci);
if (annos != null) {
codeNode.with(typeAnnotationsToTree(FLOW, "//invisible type annotations @" + bci, annos));
}
annos = visibleTypeAnnos.get(bci);
if (annos != null) {
codeNode.with(typeAnnotationsToTree(FLOW, "//visible type annotations @" + bci, annos));
}
for (int i = 0; i < excHandlers.size(); i++) {
var exc = excHandlers.get(i);
if (exc.start() == bci) {
codeNode.with(map("//try block " + (i + 1) + " start",
"start", exc.start(),
"end", exc.end(),
"handler", exc.handler(),
"catch type", exc.catchType()));
}
if (exc.end() == bci) {
codeNode.with(map("//try block " + (i + 1) + " end",
"start", exc.start(),
"end", exc.end(),
"handler", exc.handler(),
"catch type", exc.catchType()));
}
if (exc.handler() == bci) {
codeNode.with(map("//exception handler " + (i + 1) + " start",
"start", exc.start(),
"end", exc.end(),
"handler", exc.handler(),
"catch type", exc.catchType()));
}
}
var in = new MapNodeImpl(FLOW, bci).with(leaf("opcode", ins.opcode().name()));
codeNode.with(in);
switch (coe) {
case IncrementInstruction inc -> in.with(leafs(
"slot", inc.slot(),
"const", inc.constant()))
.with(localInfoToTree(locals, inc.slot(), bci));
case LoadInstruction lv -> in.with(leaf(
"slot", lv.slot()))
.with(localInfoToTree(locals, lv.slot(), bci));
case StoreInstruction lv -> in.with(leaf(
"slot", lv.slot()))
.with(localInfoToTree(locals, lv.slot(), bci));
case FieldInstruction fa -> in.with(leafs(
"owner", fa.owner().name().stringValue(),
"field name", fa.name().stringValue(),
"field type", fa.type().stringValue()));
case InvokeInstruction inv -> in.with(leafs(
"owner", inv.owner().name().stringValue(),
"method name", inv.name().stringValue(),
"method type", inv.type().stringValue()));
case InvokeDynamicInstruction invd -> {
in.with(leafs(
"name", invd.name().stringValue(),
"descriptor", invd.type().stringValue(),
"bootstrap method", invd.bootstrapMethod().kind().name()
+ " " + Util.toInternalName(invd.bootstrapMethod().owner())
+ "::" + invd.bootstrapMethod().methodName()));
in.with(list("arguments", "arg", invd.bootstrapArgs().stream()));
}
case NewObjectInstruction newo -> in.with(leaf(
"type", newo.className().name().stringValue()));
case NewPrimitiveArrayInstruction newa -> in.with(leafs(
"dimensions", 1,
"descriptor", newa.typeKind().upperBound().displayName()));
case NewReferenceArrayInstruction newa -> in.with(leafs(
"dimensions", 1,
"descriptor", newa.componentType().name().stringValue()));
case NewMultiArrayInstruction newa -> in.with(leafs(
"dimensions", newa.dimensions(),
"descriptor", newa.arrayType().name().stringValue()));
case TypeCheckInstruction tch -> in.with(leaf(
"type", tch.type().name().stringValue()));
case ConstantInstruction cons -> in.with(leaf(
"constant value", cons.constantValue()));
case BranchInstruction br -> in.with(leaf(
"target", com.labelToBci(br.target())));
case LookupSwitchInstruction si -> in.with(list(
"targets", "target", Stream.concat(Stream.of(si.defaultTarget())
.map(com::labelToBci), si.cases().stream()
.map(sc -> com.labelToBci(sc.target())))));
case TableSwitchInstruction si -> in.with(list(
"targets", "target", Stream.concat(Stream.of(si.defaultTarget())
.map(com::labelToBci), si.cases().stream()
.map(sc -> com.labelToBci(sc.target())))));
case DiscontinuedInstruction.JsrInstruction jsr -> in.with(leaf(
"target", com.labelToBci(jsr.target())));
case DiscontinuedInstruction.RetInstruction ret -> in.with(leaf(
"slot", ret.slot()));
default -> {}
}
bci += ins.sizeInBytes();
}
}
if (!excHandlers.isEmpty()) {
var handlersNode = new MapNodeImpl(BLOCK, "exception handlers");
codeNode.with(handlersNode);
for (int i = 0; i < excHandlers.size(); i++) {
var exc = excHandlers.get(i);
handlersNode.with(map("handler " + (i + 1),
"start", exc.start(),
"end", exc.end(),
"handler", exc.handler(),
"type", exc.catchType()));
}
}
return codeNode;
}
return null;
}
private static Node[] attributesToTree(List<Attribute<?>> attributes, Verbosity verbosity) {
var nodes = new LinkedList<Node>();
if (verbosity != Verbosity.MEMBERS_ONLY) for (var attr : attributes) {
switch (attr) {
case BootstrapMethodsAttribute bma ->
nodes.add(new ListNodeImpl(BLOCK, "bootstrap methods", bma.bootstrapMethods().stream().map(
bm -> {
var mh = bm.bootstrapMethod();
var mref = mh.reference();
var bmNode = new MapNodeImpl(FLOW, "bm");
bmNode.with(leafs(
"index", bm.bsmIndex(),
"kind", DirectMethodHandleDesc.Kind.valueOf(mh.kind(),
mref instanceof InterfaceMethodRefEntry).name(),
"owner", mref.owner().asInternalName(),
"name", mref.nameAndType().name().stringValue()));
bmNode.with(list("args", "arg", bm.arguments().stream().map(LoadableConstantEntry::constantValue)));
return bmNode;
})));
case ConstantValueAttribute cva ->
nodes.add(leaf("constant value", cva.constant().constantValue()));
case NestHostAttribute nha ->
nodes.add(leaf("nest host", nha.nestHost().name().stringValue()));
case NestMembersAttribute nma ->
nodes.add(list("nest members", "member", nma.nestMembers().stream()
.map(mp -> mp.name().stringValue())));
case PermittedSubclassesAttribute psa ->
nodes.add(list("permitted subclasses", "subclass", psa.permittedSubclasses().stream()
.map(e -> e.name().stringValue())));
default -> {}
}
if (verbosity == Verbosity.TRACE_ALL) switch (attr) {
case EnclosingMethodAttribute ema ->
nodes.add(map("enclosing method",
"class", ema.enclosingClass().name().stringValue(),
"method name", ema.enclosingMethodName()
.<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT),
"method type", ema.enclosingMethodType()
.<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)));
case ExceptionsAttribute exa ->
nodes.add(list("exceptions", "exc", exa.exceptions().stream()
.map(e -> e.name().stringValue())));
case InnerClassesAttribute ica ->
nodes.add(new ListNodeImpl(BLOCK, "inner classes", ica.classes().stream()
.map(ic -> new MapNodeImpl(FLOW, "cls").with(
leaf("inner class", ic.innerClass().name().stringValue()),
leaf("outer class", ic.outerClass()
.map(cle -> (ConstantDesc)cle.name().stringValue()).orElse(BSM_NULL_CONSTANT)),
leaf("inner name", ic.innerName().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
list("flags", "flag", ic.flags().stream().map(AccessFlag::name))))));
case MethodParametersAttribute mpa -> {
var n = new MapNodeImpl(BLOCK, "method parameters");
for (int i = 0; i < mpa.parameters().size(); i++) {
var p = mpa.parameters().get(i);
n.with(new MapNodeImpl(FLOW, i + 1).with(
leaf("name", p.name().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
list("flags", "flag", p.flags().stream().map(AccessFlag::name))));
}
}
case ModuleAttribute ma ->
nodes.add(new MapNodeImpl(BLOCK, "module")
.with(leaf("name", ma.moduleName().name().stringValue()),
list("flags","flag", ma.moduleFlags().stream().map(AccessFlag::name)),
leaf("version", ma.moduleVersion().<ConstantDesc>map(Utf8Entry::stringValue).orElse(BSM_NULL_CONSTANT)),
list("uses", "class", ma.uses().stream().map(ce -> ce.name().stringValue())),
new ListNodeImpl(BLOCK, "requires", ma.requires().stream().map(req ->
new MapNodeImpl(FLOW, "req").with(
leaf("name", req.requires().name().stringValue()),
list("flags", "flag", req.requiresFlags().stream()
.map(AccessFlag::name)),
leaf("version", req.requiresVersion()
.map(Utf8Entry::stringValue).orElse(null))))),
new ListNodeImpl(BLOCK, "exports", ma.exports().stream().map(exp ->
new MapNodeImpl(FLOW, "exp").with(
leaf("package", exp.exportedPackage().asSymbol().name()),
list("flags", "flag", exp.exportsFlags().stream()
.map(AccessFlag::name)),
list("to", "module", exp.exportsTo().stream()
.map(me -> me.name().stringValue()))))),
new ListNodeImpl(BLOCK, "opens", ma.opens().stream().map(opn ->
new MapNodeImpl(FLOW, "opn").with(
leaf("package", opn.openedPackage().asSymbol().name()),
list("flags", "flag", opn.opensFlags().stream()
.map(AccessFlag::name)),
list("to", "module", opn.opensTo().stream()
.map(me -> me.name().stringValue()))))),
new ListNodeImpl(BLOCK, "provides", ma.provides().stream()
.map(prov -> new MapNodeImpl(FLOW, "prov").with(
leaf("class", prov.provides().name().stringValue()),
list("with", "cls", prov.providesWith().stream()
.map(ce -> ce.name().stringValue())))))));
case ModulePackagesAttribute mopa ->
nodes.add(list("module packages", "subclass", mopa.packages().stream()
.map(mp -> mp.asSymbol().name())));
case ModuleMainClassAttribute mmca ->
nodes.add(leaf("module main class", mmca.mainClass().name().stringValue()));
case RecordAttribute ra ->
nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream()
.map(rc -> new MapNodeImpl(BLOCK, "component")
.with(leafs(
"name", rc.name().stringValue(),
"type", rc.descriptor().stringValue()))
.with(list("attributes", "attribute", rc.attributes().stream()
.map(Attribute::attributeName).map(Utf8Entry::stringValue)))
.with(attributesToTree(rc.attributes(), verbosity)))));
case AnnotationDefaultAttribute ada ->
nodes.add(new MapNodeImpl(FLOW, "annotation default").with(elementValueToTree(ada.defaultValue())));
case RuntimeInvisibleAnnotationsAttribute aa ->
nodes.add(annotationsToTree("invisible annotations", aa.annotations()));
case RuntimeVisibleAnnotationsAttribute aa ->
nodes.add(annotationsToTree("visible annotations", aa.annotations()));
case RuntimeInvisibleParameterAnnotationsAttribute aa ->
nodes.add(parameterAnnotationsToTree("invisible parameter annotations", aa.parameterAnnotations()));
case RuntimeVisibleParameterAnnotationsAttribute aa ->
nodes.add(parameterAnnotationsToTree("visible parameter annotations", aa.parameterAnnotations()));
case RuntimeInvisibleTypeAnnotationsAttribute aa ->
nodes.add(typeAnnotationsToTree(BLOCK, "invisible type annotations", aa.annotations()));
case RuntimeVisibleTypeAnnotationsAttribute aa ->
nodes.add(typeAnnotationsToTree(BLOCK, "visible type annotations", aa.annotations()));
case SignatureAttribute sa ->
nodes.add(leaf("signature", sa.signature().stringValue()));
case SourceFileAttribute sfa ->
nodes.add(leaf("source file", sfa.sourceFile().stringValue()));
default -> {}
}
}
return nodes.toArray(Node[]::new);
}
private static Node annotationsToTree(String name, List<Annotation> annos) {
return new ListNodeImpl(BLOCK, name, annos.stream().map(a ->
new MapNodeImpl(FLOW, "anno")
.with(leaf("annotation class", a.className().stringValue()))
.with(elementValuePairsToTree(a.elements()))));
}
private static Node typeAnnotationsToTree(Style style, String name, List<TypeAnnotation> annos) {
return new ListNodeImpl(style, name, annos.stream().map(a ->
new MapNodeImpl(FLOW, "anno")
.with(leaf("annotation class", a.annotation().className().stringValue()),
leaf("target info", a.targetInfo().targetType().name()))
.with(elementValuePairsToTree(a.annotation().elements()))));
}
private static MapNodeImpl parameterAnnotationsToTree(String name, List<List<Annotation>> paramAnnotations) {
var node = new MapNodeImpl(BLOCK, name);
for (int i = 0; i < paramAnnotations.size(); i++) {
var annos = paramAnnotations.get(i);
if (!annos.isEmpty()) {
node.with(new ListNodeImpl(FLOW, "parameter " + (i + 1), annos.stream().map(a ->
new MapNodeImpl(FLOW, "anno")
.with(leaf("annotation class", a.className().stringValue()))
.with(elementValuePairsToTree(a.elements())))));
}
}
return node;
}
private static Node[] localInfoToTree(List<LocalVariableInfo> locals, int slot, int bci) {
if (locals != null) {
for (var l : locals) {
if (l.slot() == slot && l.startPc() <= bci && l.length() + l.startPc() >= bci) {
return leafs("type", l.type().stringValue(),
"variable name", l.name().stringValue());
}
}
}
return new Node[0];
}
private static void forEachOffset(TypeAnnotation ta, CodeAttribute lr, BiConsumer<Integer, TypeAnnotation> consumer) {
switch (ta.targetInfo()) {
case TypeAnnotation.OffsetTarget ot ->
consumer.accept(lr.labelToBci(ot.target()), ta);
case TypeAnnotation.TypeArgumentTarget tat ->
consumer.accept(lr.labelToBci(tat.target()), ta);
case TypeAnnotation.LocalVarTarget lvt ->
lvt.table().forEach(lvti -> consumer.accept(lr.labelToBci(lvti.startLabel()), ta));
default -> {}
}
}
}