blob: 8e5b70f1ba135bef758fdd9560fff960a8c1a8ab [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Hashtable;
import java.util.StringTokenizer;
import org.apache.bcel.Const;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.ConstantValue;
import org.apache.bcel.classfile.Deprecated;
import org.apache.bcel.classfile.ExceptionTable;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.Synthetic;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.generic.BranchHandle;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LineNumberGen;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Select;
import org.apache.bcel.generic.TABLESWITCH;
/**
* Disassemble Java class object into the <a href="https://jasmin.sourceforge.net"> Jasmin</a> format.
*/
public class JasminVisitor extends org.apache.bcel.classfile.EmptyVisitor {
public static void main(final String[] argv) throws Exception {
JavaClass javaClass;
if (argv.length == 0) {
System.err.println("disassemble: No input files specified");
return;
}
for (final String arg : argv) {
if ((javaClass = Repository.lookupClass(arg)) == null) {
javaClass = new ClassParser(arg).parse();
}
String className = javaClass.getClassName();
final int index = className.lastIndexOf('.');
final String path = className.substring(0, index + 1).replace('.', File.separatorChar);
className = className.substring(index + 1);
if (!path.isEmpty()) {
final File file = new File(path);
if (!file.mkdirs()) {
System.err.println("Couldn't create directories for " + file);
}
}
final String name = path + className + ".j";
try (FileOutputStream out = new FileOutputStream(name)) {
new JasminVisitor(javaClass, out).disassemble();
System.out.println("File dumped to: " + name);
}
}
}
private final JavaClass javaClass;
private final PrintWriter printWriter;
private final String className;
private final ConstantPoolGen cp;
private Method method;
private Hashtable<InstructionHandle, String> map;
public JasminVisitor(final JavaClass clazz, final OutputStream out) {
this.javaClass = clazz;
this.printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)), false);
this.className = clazz.getClassName();
this.cp = new ConstantPoolGen(clazz.getConstantPool());
}
/**
* Start traversal using DefaultVisitor pattern.
*/
public void disassemble() {
new org.apache.bcel.classfile.DescendingVisitor(javaClass, this).visit();
printWriter.close();
}
private String get(final InstructionHandle ih) {
final String str = new StringTokenizer(map.get(ih), "\n").nextToken();
return str.substring(0, str.length() - 1);
}
/**
* Unfortunately Jasmin expects ".end method" after each method. Thus we've to check for every of the method's
* attributes if it's the last one and print ".end method" then.
*/
private void printEndMethod(final Attribute attr) {
final Attribute[] attributes = method.getAttributes();
if (attr == attributes[attributes.length - 1]) {
printWriter.println(".end method");
}
}
private void put(final InstructionHandle ih, final String line) {
final String str = map.get(ih);
if (str == null) {
map.put(ih, line);
} else {
if (line.startsWith("Label") || str.endsWith(line)) {
return;
}
map.put(ih, str + "\n" + line); // append
}
}
@Override
public void visitCode(final Code code) {
int labelCounter = 0;
printWriter.println(".limit stack " + code.getMaxStack());
printWriter.println(".limit locals " + code.getMaxLocals());
final MethodGen mg = new MethodGen(method, className, cp);
final InstructionList il = mg.getInstructionList();
final InstructionHandle[] ihs = il.getInstructionHandles();
/*
* Pass 1: Give all referenced instruction handles a symbolic name, for example a label.
*/
map = new Hashtable<>();
for (final InstructionHandle ih1 : ihs) {
if (ih1 instanceof BranchHandle) {
final BranchInstruction bi = (BranchInstruction) ih1.getInstruction();
if (bi instanceof Select) { // Special cases LOOKUPSWITCH and TABLESWITCH
for (final InstructionHandle target : ((Select) bi).getTargets()) {
put(target, "Label" + labelCounter++ + ":");
}
}
final InstructionHandle ih = bi.getTarget();
put(ih, "Label" + labelCounter++ + ":");
}
}
final LocalVariableGen[] lvs = mg.getLocalVariables();
for (final LocalVariableGen lv : lvs) {
InstructionHandle ih = lv.getStart();
put(ih, "Label" + labelCounter++ + ":");
ih = lv.getEnd();
put(ih, "Label" + labelCounter++ + ":");
}
final CodeExceptionGen[] ehs = mg.getExceptionHandlers();
for (final CodeExceptionGen c : ehs) {
InstructionHandle ih = c.getStartPC();
put(ih, "Label" + labelCounter++ + ":");
ih = c.getEndPC();
put(ih, "Label" + labelCounter++ + ":");
ih = c.getHandlerPC();
put(ih, "Label" + labelCounter++ + ":");
}
final LineNumberGen[] lns = mg.getLineNumbers();
for (final LineNumberGen ln : lns) {
final InstructionHandle ih = ln.getInstruction();
put(ih, ".line " + ln.getSourceLine());
}
// Pass 2: Output code.
for (final LocalVariableGen l : lvs) {
printWriter.println(
".var " + l.getIndex() + " is " + l.getName() + " " + l.getType().getSignature() + " from " + get(l.getStart()) + " to " + get(l.getEnd()));
}
printWriter.print("\n");
for (InstructionHandle ih : ihs) {
final Instruction inst = ih.getInstruction();
String str = map.get(ih);
if (str != null) {
printWriter.println(str);
}
if (inst instanceof BranchInstruction) {
if (inst instanceof Select) { // Special cases LOOKUPSWITCH and TABLESWITCH
final Select s = (Select) inst;
final int[] matchs = s.getMatchs();
final InstructionHandle[] targets = s.getTargets();
if (s instanceof TABLESWITCH) {
printWriter.println("\ttableswitch " + matchs[0] + " " + matchs[matchs.length - 1]);
for (final InstructionHandle target : targets) {
printWriter.println("\t\t" + get(target));
}
} else { // LOOKUPSWITCH
printWriter.println("\tlookupswitch ");
for (int j = 0; j < targets.length; j++) {
printWriter.println("\t\t" + matchs[j] + " : " + get(targets[j]));
}
}
printWriter.println("\t\tdefault: " + get(s.getTarget())); // Applies for both
} else {
final BranchInstruction bi = (BranchInstruction) inst;
ih = bi.getTarget();
str = get(ih);
printWriter.println("\t" + Const.getOpcodeName(bi.getOpcode()) + " " + str);
}
} else {
printWriter.println("\t" + inst.toString(cp.getConstantPool()));
}
}
printWriter.print("\n");
for (final CodeExceptionGen c : ehs) {
final ObjectType caught = c.getCatchType();
final String className = caught == null ? // catch any exception, used when compiling finally
"all" : Utility.packageToPath(caught.getClassName());
printWriter.println(".catch " + className + " from " + get(c.getStartPC()) + " to " + get(c.getEndPC()) + " using " + get(c.getHandlerPC()));
}
printEndMethod(code);
}
@Override
public void visitConstantValue(final ConstantValue cv) {
printWriter.println(" = " + cv);
}
@Override
public void visitDeprecated(final Deprecated attribute) {
printEndMethod(attribute);
}
@Override
public void visitExceptionTable(final ExceptionTable e) {
for (final String name : e.getExceptionNames()) {
printWriter.println(".throws " + Utility.packageToPath(name));
}
printEndMethod(e);
}
@Override
public void visitField(final Field field) {
printWriter.print(".field " + Utility.accessToString(field.getAccessFlags()) + " \"" + field.getName() + "\"" + field.getSignature());
if (field.getAttributes().length == 0) {
printWriter.print("\n");
}
}
@Override
public void visitJavaClass(final JavaClass clazz) {
printWriter.println(";; Produced by JasminVisitor (BCEL)");
printWriter.println(";; https://commons.apache.org/bcel/");
printWriter.println(";; " + new Date() + "\n");
printWriter.println(".source " + clazz.getSourceFileName());
printWriter.println("." + Utility.classOrInterface(clazz.getAccessFlags()) + " " + Utility.accessToString(clazz.getAccessFlags(), true) + " "
+ Utility.packageToPath(clazz.getClassName()));
printWriter.println(".super " + Utility.packageToPath(clazz.getSuperclassName()));
for (final String iface : clazz.getInterfaceNames()) {
printWriter.println(".implements " + Utility.packageToPath(iface));
}
printWriter.print("\n");
}
@Override
public void visitMethod(final Method visitMethod) {
this.method = visitMethod; // Remember for use in subsequent visitXXX calls
printWriter.println("\n.method " + Utility.accessToString(method.getAccessFlags()) + " " + method.getName() + method.getSignature());
final Attribute[] attributes = method.getAttributes();
if (attributes == null || attributes.length == 0) {
printWriter.println(".end method");
}
}
@Override
public void visitSynthetic(final Synthetic attribute) {
if (method != null) {
printEndMethod(attribute);
}
}
}