InvokeDynamicPrinter.java
/*
* Copyright (c) 2001-2024, Jean Tessier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Jean Tessier nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jeantessier.classreader;
import org.apache.logging.log4j.*;
import java.io.PrintWriter;
import java.util.stream.IntStream;
import static java.util.stream.Collectors.*;
public class InvokeDynamicPrinter extends Printer {
private Classfile currentClassfile;
private Method_info currentMethod;
private int currentConstantPoolIndex;
private int currentBootstrapMethodIndex;
public InvokeDynamicPrinter(PrintWriter out) {
super(out);
}
public void visitClassfile(Classfile classfile) {
LogManager.getLogger(getClass()).debug("visitClassfile({})", classfile.getClassName());
currentClassfile = classfile;
classfile.getAllMethods().forEach(method -> method.accept(this));
}
// ConstantPool
public void visitClass_info(Class_info entry) {
LogManager.getLogger(getClass()).debug("visitClass_info({})", entry.getName());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getName()).eol();
raiseIndent();
currentConstantPoolIndex = entry.getNameIndex();
entry.getRawName().accept(this);
lowerIndent();
}
public void visitFieldRef_info(FieldRef_info entry) {
LogManager.getLogger(getClass()).debug("visitFieldRef_info({})", entry.getFullSignature());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getFullSignature()).eol();
raiseIndent();
currentConstantPoolIndex = entry.getClassIndex();
entry.getRawClass().accept(this);
currentConstantPoolIndex = entry.getNameAndTypeIndex();
entry.getRawNameAndType().accept(this);
lowerIndent();
}
public void visitMethodRef_info(MethodRef_info entry) {
LogManager.getLogger(getClass()).debug("visitMethodRef_info({})", entry.getFullSignature());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getFullSignature()).eol();
raiseIndent();
currentConstantPoolIndex = entry.getClassIndex();
entry.getRawClass().accept(this);
currentConstantPoolIndex = entry.getNameAndTypeIndex();
entry.getRawNameAndType().accept(this);
lowerIndent();
}
public void visitInterfaceMethodRef_info(InterfaceMethodRef_info entry) {
LogManager.getLogger(getClass()).debug("visitInterfaceMethodRef_info({})", entry.getFullSignature());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getFullSignature()).eol();
raiseIndent();
currentConstantPoolIndex = entry.getClassIndex();
entry.getRawClass().accept(this);
currentConstantPoolIndex = entry.getNameAndTypeIndex();
entry.getRawNameAndType().accept(this);
lowerIndent();
}
public void visitString_info(String_info entry) {
LogManager.getLogger(getClass()).debug("visitString_info({})", entry.getValue());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" \"").append(entry.getValue()).append("\"").eol();
raiseIndent();
currentConstantPoolIndex = entry.getValueIndex();
entry.getRawValue().accept(this);
lowerIndent();
}
public void visitInteger_info(Integer_info entry) {
LogManager.getLogger(getClass()).debug("visitInteger_info({})", entry.getValue());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getValue()).eol();
}
public void visitFloat_info(Float_info entry) {
LogManager.getLogger(getClass()).debug("visitFloat_info({})", entry.getValue());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getValue()).eol();
}
public void visitLong_info(Long_info entry) {
LogManager.getLogger(getClass()).debug("visitLong_info({})", entry.getValue());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getValue()).eol();
}
public void visitDouble_info(Double_info entry) {
LogManager.getLogger(getClass()).debug("visitDouble_info({})", entry.getValue());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getValue()).eol();
}
public void visitNameAndType_info(NameAndType_info entry) {
LogManager.getLogger(getClass()).debug("visitNameAndType_info({} + {})", entry.getName(), entry.getType());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).eol();
raiseIndent();
currentConstantPoolIndex = entry.getNameIndex();
entry.getRawName().accept(this);
currentConstantPoolIndex = entry.getTypeIndex();
entry.getRawType().accept(this);
lowerIndent();
}
public void visitUTF8_info(UTF8_info entry) {
LogManager.getLogger(getClass()).debug("visitUTF8_info({})", entry.getValue());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" \"").append(entry.getValue()).append("\"").eol();
}
public void visitMethodHandle_info(MethodHandle_info entry) {
LogManager.getLogger(getClass()).debug("visitMethodHandle_info({} {})", entry.getReferenceKind().getDescription(), entry.getReference().getFullSignature());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry.getReferenceKind().getDescription()).append("(").append(entry.getRawReferenceKind()).append(") ").append(entry.getReference().getFullSignature()).eol();
raiseIndent();
currentConstantPoolIndex = entry.getReferenceIndex();
entry.getReference().accept(this);
lowerIndent();
}
public void visitMethodType_info(MethodType_info entry) {
LogManager.getLogger(getClass()).debug("visitMethodType_info({})", entry.getDescriptor());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry).eol();
raiseIndent();
currentConstantPoolIndex = entry.getDescriptorIndex();
entry.getRawDescriptor().accept(this);
lowerIndent();
}
public void visitDynamic_info(Dynamic_info entry) {
LogManager.getLogger(getClass()).debug("visitDynamic_info(bootstrap method #{} + {})", entry.getBootstrapMethodAttrIndex(), entry.getSignature());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry).eol();
raiseIndent();
currentConstantPoolIndex = entry.getNameAndTypeIndex();
entry.getRawNameAndType().accept(this);
currentBootstrapMethodIndex = entry.getBootstrapMethodAttrIndex();
var finder = new BootstrapMethodFinder(entry.getBootstrapMethodAttrIndex());
currentClassfile.accept(finder);
finder.getBootstrapMethod().accept(this);
lowerIndent();
}
public void visitInvokeDynamic_info(InvokeDynamic_info entry) {
LogManager.getLogger(getClass()).debug("visitInvokeDynamic_info(bootstrap method #{} + {})", entry.getBootstrapMethodAttrIndex(), entry.getSignature());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).append(" ").append(entry).eol();
raiseIndent();
currentConstantPoolIndex = entry.getNameAndTypeIndex();
entry.getRawNameAndType().accept(this);
currentBootstrapMethodIndex = entry.getBootstrapMethodAttrIndex();
var finder = new BootstrapMethodFinder(entry.getBootstrapMethodAttrIndex());
currentClassfile.accept(finder);
finder.getBootstrapMethod().accept(this);
lowerIndent();
}
public void visitModule_info(Module_info entry) {
LogManager.getLogger(getClass()).debug("visitModule_info({})", entry.getName());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).eol();
raiseIndent();
currentConstantPoolIndex = entry.getNameIndex();
entry.getRawName().accept(this);
lowerIndent();
}
public void visitPackage_info(Package_info entry) {
LogManager.getLogger(getClass()).debug("visitPackage_info({})", entry.getName());
indent().append(currentConstantPoolIndex).append(" : ").append(entry.getClass().getSimpleName()).eol();
raiseIndent();
currentConstantPoolIndex = entry.getNameIndex();
entry.getRawName().accept(this);
lowerIndent();
}
// Features
public void visitMethod_info(Method_info entry) {
LogManager.getLogger(getClass()).debug("visitMethod_info({})", entry.getFullSignature());
currentMethod = entry;
super.visitMethod_info(entry);
}
// Attributes
public void visitCode_attribute(Code_attribute attribute) {
attribute.forEach(instruction -> instruction.accept(this));
}
public void visitBootstrapMethods_attribute(BootstrapMethods_attribute attribute) {
LogManager.getLogger(getClass()).debug("visitBootstrapMethods_attribute(w/ {} method(s))", attribute.getBootstrapMethods().size());
// Do not traverse the BootstrapMethods from the attribute.
// Only from the invokedynamic instructions.
}
// Attribute helpers
public void visitInstruction(Instruction helper) {
LogManager.getLogger(getClass()).debug("visitInstruction({} : {})", helper.getStart(), helper.getMnemonic());
if (helper.getOpcode() == 0xba /* invokedynamic */) {
indent().append(currentMethod.getFullSignature()).eol();
raiseIndent();
indent().append("pc=").append(helper.getStart()).append(" : ").append(helper.getMnemonic());
var indexedEntry = helper.getIndexedConstantPoolEntry();
if (indexedEntry instanceof Dynamic_info dynamic_info) {
append(" ").append(dynamic_info.getName());
} else if (indexedEntry instanceof InvokeDynamic_info invokeDynamic_info) {
append(" ").append(invokeDynamic_info.getName());
}
// TODO: Replace with type pattern matching in switch expression in Java 21
// switch (helper.getIndexedConstantPoolEntry()) {
// case Dynamic_info entry -> append(" ").append(entry.getName());
// case InvokeDynamic_info entry -> append(" ").append(entry.getName());
// default -> append("");
// }
eol();
raiseIndent();
currentConstantPoolIndex = helper.getIndex();
helper.getIndexedConstantPoolEntry().accept(this);
lowerIndent();
lowerIndent();
eol();
}
}
public void visitBootstrapMethod(BootstrapMethod helper) {
LogManager.getLogger(getClass()).debug("visitBootstrapMethod({} w/ [{}])", () -> helper.getBootstrapMethod().getReference().getFullSignature(), () -> helper.getArgumentIndices().stream().map(String::valueOf).collect(joining(" ,")));
indent().append(currentBootstrapMethodIndex).append(" : BootstrapMethod").eol();
raiseIndent();
currentConstantPoolIndex = helper.getBootstrapMethodRef();
helper.getBootstrapMethod().accept(this);
var argumentIndices = helper.getArgumentIndices().toArray();
IntStream.range(0, argumentIndices.length).forEach(i -> {
indent().append("argument " + i).eol();
raiseIndent();
var argumentIndex = (int) argumentIndices[i];
currentConstantPoolIndex = argumentIndex;
currentClassfile.getConstantPool().get(argumentIndex).accept(this);
lowerIndent();
});
lowerIndent();
}
}