/*
* BytecodeLanguage.java
*
* Copyright (c) 2013 Mike Strobel
*
* This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
* and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0.
* A copy of the license can be found in the License.html file at the root of this distribution.
* By using this source code in any fashion, you are agreeing to be bound by the terms of the
* Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*/
package com.strobel.decompiler.languages;
import com.strobel.annotations.NotNull;
import com.strobel.assembler.ir.*;
import com.strobel.assembler.ir.attributes.*;
import com.strobel.assembler.metadata.*;
import com.strobel.core.StringUtilities;
import com.strobel.core.VerifyArgument;
import com.strobel.decompiler.DecompilationOptions;
import com.strobel.decompiler.DecompilerHelpers;
import com.strobel.decompiler.DecompilerSettings;
import com.strobel.decompiler.ITextOutput;
import com.strobel.decompiler.NameSyntax;
import com.strobel.decompiler.PlainTextOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import static java.lang.String.format;
public class BytecodeLanguage extends Language {
@Override
public String getName() {
return "Bytecode";
}
@Override
public String getFileExtension() {
return ".class";
}
@Override
public TypeDecompilationResults decompileType(final TypeDefinition type, final ITextOutput output, final DecompilationOptions options) {
VerifyArgument.notNull(type, "type");
VerifyArgument.notNull(output, "output");
VerifyArgument.notNull(options, "options");
if (type.isInterface()) {
if (type.isAnnotation()) {
output.writeKeyword("@interface");
}
else {
output.writeKeyword("interface");
}
}
else if (type.isEnum()) {
output.writeKeyword("enum");
}
else {
output.writeKeyword("class");
}
output.write(' ');
DecompilerHelpers.writeType(output, type, NameSyntax.TYPE_NAME, true);
output.writeLine();
output.indent();
try {
writeTypeHeader(output, type);
for (final SourceAttribute attribute : type.getSourceAttributes()) {
writeTypeAttribute(output, type, attribute);
}
final ConstantPool constantPool = type.getConstantPool();
if (constantPool != null) {
constantPool.accept(new ConstantPoolPrinter(output, options.getSettings()));
}
for (final FieldDefinition field : type.getDeclaredFields()) {
output.writeLine();
decompileField(field, output, options);
}
for (final MethodDefinition method : type.getDeclaredMethods()) {
output.writeLine();
decompileMethod(method, output, options);
}
}
finally {
output.unindent();
}
if (!options.getSettings().getExcludeNestedTypes()) {
for (final TypeDefinition innerType : type.getDeclaredTypes()) {
output.writeLine();
decompileType(innerType, output, options);
}
}
return new TypeDecompilationResults( null /*no line number mapping*/);
}
private void writeTypeAttribute(final ITextOutput output, final TypeDefinition type, final SourceAttribute attribute) {
if (attribute instanceof BlobAttribute) {
return;
}
switch (attribute.getName()) {
case AttributeNames.SourceFile: {
output.writeAttribute("SourceFile");
output.write(": ");
output.writeTextLiteral(((SourceFileAttribute) attribute).getSourceFile());
output.writeLine();
break;
}
case AttributeNames.Deprecated: {
output.writeAttribute("Deprecated");
output.writeLine();
break;
}
case AttributeNames.EnclosingMethod: {
final TypeReference enclosingType = ((EnclosingMethodAttribute) attribute).getEnclosingType();
final MethodReference enclosingMethod = ((EnclosingMethodAttribute) attribute).getEnclosingMethod();
if (enclosingType != null) {
output.writeAttribute("EnclosingType");
output.write(": ");
output.writeReference(enclosingType.getInternalName(), enclosingType);
output.writeLine();
}
if (enclosingMethod != null) {
final TypeReference declaringType = enclosingMethod.getDeclaringType();
output.writeAttribute("EnclosingMethod");
output.write(": ");
output.writeReference(declaringType.getInternalName(), declaringType);
output.writeDelimiter(".");
output.writeReference(enclosingMethod.getName(), enclosingMethod);
output.writeDelimiter(":");
DecompilerHelpers.writeMethodSignature(output, enclosingMethod);
output.writeLine();
}
break;
}
case AttributeNames.InnerClasses: {
final InnerClassesAttribute innerClasses = (InnerClassesAttribute) attribute;
final List<InnerClassEntry> entries = innerClasses.getEntries();
output.writeAttribute("InnerClasses");
output.writeLine(": ");
output.indent();
try {
for (final InnerClassEntry entry : entries) {
writeInnerClassEntry(output, type, entry);
}
}
finally {
output.unindent();
}
}
case AttributeNames.Signature: {
output.writeAttribute("Signature");
output.write(": ");
DecompilerHelpers.writeGenericSignature(output, type);
output.writeLine();
}
}
}
private void writeInnerClassEntry(final ITextOutput output, final TypeDefinition type, final InnerClassEntry entry) {
final String shortName = entry.getShortName();
final String innerClassName = entry.getInnerClassName();
final String outerClassName = entry.getOuterClassName();
final EnumSet<Flags.Flag> flagsSet = Flags.asFlagSet(entry.getAccessFlags(), Flags.Kind.InnerClass);
for (final Flags.Flag flag : flagsSet) {
output.writeKeyword(flag.toString());
output.write(' ');
}
final MetadataParser parser = new MetadataParser(type);
if (tryWriteType(output, parser, shortName, innerClassName)) {
output.writeDelimiter(" = ");
}
if (!tryWriteType(output, parser, innerClassName, innerClassName)) {
output.writeError("?");
}
if (!StringUtilities.isNullOrEmpty(outerClassName)) {
output.writeDelimiter(" of ");
if (!tryWriteType(output, parser, outerClassName, outerClassName)) {
output.writeError("?");
}
}
output.writeLine();
}
private boolean tryWriteType(
@NotNull final ITextOutput output,
@NotNull final MetadataParser parser,
final String text,
final String descriptor) {
if (StringUtilities.isNullOrEmpty(text)) {
return false;
}
if (StringUtilities.isNullOrEmpty(descriptor)) {
output.writeError(text);
return true;
}
try {
final TypeReference type = parser.parseTypeDescriptor(descriptor);
output.writeReference(text, type);
return true;
}
catch (Throwable ignored) {
}
try {
output.writeReference(text, new DummyTypeReference(descriptor));
return true;
}
catch (Throwable ignored) {
}
output.writeError(text);
return true;
}
private void writeTypeHeader(final ITextOutput output, final TypeDefinition type) {
output.writeAttribute("Minor version");
output.write(": ");
output.writeLiteral(type.getCompilerMinorVersion());
output.writeLine();
output.writeAttribute("Major version");
output.write(": ");
output.writeLiteral(type.getCompilerMajorVersion());
output.writeLine();
final long flags = type.getFlags();
final List<String> flagStrings = new ArrayList<>();
final EnumSet<Flags.Flag> flagsSet = Flags.asFlagSet(
flags,
type.isInnerClass() ? Flags.Kind.InnerClass : Flags.Kind.Class
);
for (final Flags.Flag flag : flagsSet) {
flagStrings.add(flag.name());
}
if (!flagStrings.isEmpty()) {
output.writeAttribute("Flags");
output.write(": ");
for (int i = 0; i < flagStrings.size(); i++) {
if (i != 0) {
output.write(", ");
}
output.writeLiteral(flagStrings.get(i));
}
output.writeLine();
}
}
@Override
public void decompileField(final FieldDefinition field, final ITextOutput output, final DecompilationOptions options) {
final long flags = field.getFlags();
final EnumSet<Flags.Flag> flagSet = Flags.asFlagSet(flags & Flags.VarFlags & ~Flags.ENUM, Flags.Kind.Field);
final List<String> flagStrings = new ArrayList<>();
for (final Flags.Flag flag : flagSet) {
flagStrings.add(flag.toString());
}
if (flagSet.size() > 0) {
for (int i = 0; i < flagStrings.size(); i++) {
output.writeKeyword(flagStrings.get(i));
output.write(' ');
}
}
DecompilerHelpers.writeType(output, field.getFieldType(), NameSyntax.TYPE_NAME);
output.write(' ');
output.writeDefinition(field.getName(), field);
output.writeDelimiter(";");
output.writeLine();
flagStrings.clear();
for (final Flags.Flag flag : Flags.asFlagSet(flags & (Flags.VarFlags | ~Flags.StandardFlags), Flags.Kind.Field)) {
flagStrings.add(flag.name());
}
if (flagStrings.isEmpty()) {
return;
}
output.indent();
try {
output.writeAttribute("Flags");
output.write(": ");
for (int i = 0; i < flagStrings.size(); i++) {
if (i != 0) {
output.write(", ");
}
output.writeLiteral(flagStrings.get(i));
}
output.writeLine();
for (final SourceAttribute attribute : field.getSourceAttributes()) {
writeFieldAttribute(output, field, attribute);
}
}
finally {
output.unindent();
}
}
private void writeFieldAttribute(final ITextOutput output, final FieldDefinition field, final SourceAttribute attribute) {
switch (attribute.getName()) {
case AttributeNames.ConstantValue: {
final Object constantValue = ((ConstantValueAttribute) attribute).getValue();
output.writeAttribute("ConstantValue");
output.write(": ");
if (constantValue != null) {
final String typeDescriptor = constantValue.getClass().getName().replace('.', '/');
final TypeReference valueType = field.getDeclaringType().getResolver().lookupType(typeDescriptor);
if (valueType != null) {
DecompilerHelpers.writeType(
output,
MetadataHelper.getUnderlyingPrimitiveTypeOrSelf(valueType),
NameSyntax.TYPE_NAME
);
output.write(' ');
}
}
DecompilerHelpers.writeOperand(output, constantValue);
output.writeLine();
break;
}
case AttributeNames.Signature: {
output.writeAttribute("Signature");
output.write(": ");
DecompilerHelpers.writeType(output, field.getFieldType(), NameSyntax.SIGNATURE, false);
output.writeLine();
break;
}
}
}
@Override
public void decompileMethod(final MethodDefinition method, final ITextOutput output, final DecompilationOptions options) {
writeMethodHeader(output, method);
writeMethodBody(output, method, options);
for (final SourceAttribute attribute : method.getSourceAttributes()) {
writeMethodAttribute(output, method, attribute);
}
writeMethodEnd(output, method, options);
}
private void writeMethodHeader(final ITextOutput output, final MethodDefinition method) {
final String name = method.getName();
final long flags = Flags.fromStandardFlags(method.getFlags(), Flags.Kind.Method);
final List<String> flagStrings = new ArrayList<>();
if ("<clinit>".equals(name)) {
output.writeKeyword("static");
output.write(" {}");
}
else {
final EnumSet<Flags.Flag> flagSet = Flags.asFlagSet(flags & Flags.MethodFlags, Flags.Kind.Method);
for (final Flags.Flag flag : flagSet) {
flagStrings.add(flag.toString());
}
if (flagSet.size() > 0) {
for (int i = 0; i < flagStrings.size(); i++) {
output.writeKeyword(flagStrings.get(i));
output.write(' ');
}
}
final List<GenericParameter> genericParameters = method.getGenericParameters();
if (!genericParameters.isEmpty()) {
output.writeDelimiter("<");
for (int i = 0; i < genericParameters.size(); i++) {
if (i != 0) {
output.writeDelimiter(", ");
}
DecompilerHelpers.writeType(output, genericParameters.get(i), NameSyntax.TYPE_NAME, true);
}
output.writeDelimiter(">");
output.write(' ');
}
DecompilerHelpers.writeType(output, method.getReturnType(), NameSyntax.TYPE_NAME, false);
output.write(' ');
output.writeDefinition(name, method);
output.writeDelimiter("(");
final List<ParameterDefinition> parameters = method.getParameters();
for (int i = 0; i < parameters.size(); i++) {
if (i != 0) {
output.writeDelimiter(", ");
}
final ParameterDefinition parameter = parameters.get(i);
if (Flags.testAny(flags, Flags.ACC_VARARGS | Flags.VARARGS) && i == parameters.size() - 1) {
DecompilerHelpers.writeType(output, parameter.getParameterType().getElementType(), NameSyntax.TYPE_NAME, false);
output.writeDelimiter("...");
}
else {
DecompilerHelpers.writeType(output, parameter.getParameterType(), NameSyntax.TYPE_NAME, false);
}
output.write(' ');
final String parameterName = parameter.getName();
if (StringUtilities.isNullOrEmpty(parameterName)) {
output.write("p%d", i);
}
else {
output.write(parameterName);
}
}
output.writeDelimiter(")");
final List<TypeReference> thrownTypes = method.getThrownTypes();
if (!thrownTypes.isEmpty()) {
output.writeKeyword(" throws ");
for (int i = 0; i < thrownTypes.size(); i++) {
if (i != 0) {
output.writeDelimiter(", ");
}
DecompilerHelpers.writeType(output, thrownTypes.get(i), NameSyntax.TYPE_NAME, false);
}
}
}
output.writeDelimiter(";");
output.writeLine();
flagStrings.clear();
for (final Flags.Flag flag : Flags.asFlagSet(flags & (Flags.MethodFlags | ~Flags.StandardFlags), Flags.Kind.Method)) {
flagStrings.add(flag.name());
}
if (flagStrings.isEmpty()) {
return;
}
output.indent();
try {
output.writeAttribute("Flags");
output.write(": ");
for (int i = 0; i < flagStrings.size(); i++) {
if (i != 0) {
output.write(", ");
}
output.writeLiteral(flagStrings.get(i));
}
output.writeLine();
}
finally {
output.unindent();
}
}
@SuppressWarnings("ConstantConditions")
private void writeMethodAttribute(final ITextOutput output, final MethodDefinition method, final SourceAttribute attribute) {
switch (attribute.getName()) {
case AttributeNames.Exceptions: {
final ExceptionsAttribute exceptionsAttribute = (ExceptionsAttribute) attribute;
final List<TypeReference> exceptionTypes = exceptionsAttribute.getExceptionTypes();
if (!exceptionTypes.isEmpty()) {
output.indent();
try {
output.writeAttribute("Exceptions");
output.writeLine(":");
output.indent();
try {
for (final TypeReference exceptionType : exceptionTypes) {
output.writeKeyword("throws");
output.write(' ');
DecompilerHelpers.writeType(output, exceptionType, NameSyntax.TYPE_NAME);
output.writeLine();
}
}
finally {
output.unindent();
}
}
finally {
output.unindent();
}
}
break;
}
case AttributeNames.LocalVariableTable:
case AttributeNames.LocalVariableTypeTable: {
final LocalVariableTableAttribute localVariables = (LocalVariableTableAttribute) attribute;
final List<LocalVariableTableEntry> entries = localVariables.getEntries();
int longestName = "Name".length();
int longestSignature = "Signature".length();
for (final LocalVariableTableEntry entry : entries) {
final String name = entry.getName();
final String signature;
final TypeReference type = entry.getType();
if (type != null) {
if (attribute.getName().equals(AttributeNames.LocalVariableTypeTable)) {
signature = type.getSignature();
}
else {
signature = type.getErasedSignature();
}
if (signature.length() > longestSignature) {
longestSignature = signature.length();
}
}
if (name != null && name.length() > longestName) {
longestName = name.length();
}
}
output.indent();
try {
output.writeAttribute(attribute.getName());
output.writeLine(":");
output.indent();
try {
output.write("Start Length Slot %1$-" + longestName + "s Signature", "Name");
output.writeLine();
output.write(
"----- ------ ---- %1$-" + longestName + "s %2$-" + longestSignature + "s",
StringUtilities.repeat('-', longestName),
StringUtilities.repeat('-', longestSignature)
);
output.writeLine();
final MethodBody body = method.getBody();
for (final LocalVariableTableEntry entry : entries) {
final NameSyntax nameSyntax;
final VariableDefinitionCollection variables = body != null ? body.getVariables() : null;
if (attribute.getName().equals(AttributeNames.LocalVariableTypeTable)) {
nameSyntax = NameSyntax.SIGNATURE;
}
else {
nameSyntax = NameSyntax.ERASED_SIGNATURE;
}
output.writeLiteral(format("%1$-5d", entry.getScopeOffset()));
output.write(" ");
output.writeLiteral(format("%1$-6d", entry.getScopeLength()));
output.write(" ");
output.writeLiteral(format("%1$-4d", entry.getIndex()));
output.writeReference(
String.format(" %1$-" + longestName + "s ", entry.getName()),
variables != null ? variables.tryFind(entry.getIndex(), entry.getScopeOffset()) : null
);
DecompilerHelpers.writeType(output, entry.getType(), nameSyntax);
output.writeLine();
}
}
finally {
output.unindent();
}
}
finally {
output.unindent();
}
break;
}
case AttributeNames.MethodParameters: {
final MethodParametersAttribute parameters = (MethodParametersAttribute) attribute;
final List<MethodParameterEntry> entries = parameters.getEntries();
int longestName = "Name".length();
int longestFlags = "Flags".length();
for (final MethodParameterEntry entry : entries) {
final String name = entry.getName();
final String flags = Flags.toString(entry.getFlags());
if (name != null && name.length() > longestName) {
longestName = name.length();
}
if (flags != null && flags.length() > longestFlags) {
longestFlags = flags.length();
}
}
output.indent();
try {
output.writeAttribute(attribute.getName());
output.writeLine(":");
output.indent();
try {
output.write("%1$-" + longestName + "s %2$-" + longestFlags + "s ", "Name", "Flags");
output.writeLine();
output.write(
"%1$-" + longestName + "s %2$-" + longestFlags + "s",
StringUtilities.repeat('-', longestName),
StringUtilities.repeat('-', longestFlags)
);
output.writeLine();
for (int i = 0; i < entries.size(); i++) {
final MethodParameterEntry entry = entries.get(i);
final List<ParameterDefinition> parameterDefinitions = method.getParameters();
output.writeReference(
String.format("%1$-" + longestName + "s ", entry.getName()),
i < parameterDefinitions.size() ? parameterDefinitions.get(i) : null
);
final EnumSet<Flags.Flag> flags = Flags.asFlagSet(entry.getFlags());
boolean firstFlag = true;
for (final Flags.Flag flag : flags) {
if (!firstFlag) {
output.writeDelimiter(", ");
}
output.writeLiteral(flag.name());
firstFlag = false;
}
output.writeLine();
}
}
finally {
output.unindent();
}
}
finally {
output.unindent();
}
break;
}
case AttributeNames.Signature: {
output.indent();
try {
final String signature = ((SignatureAttribute) attribute).getSignature();
output.writeAttribute(attribute.getName());
output.writeLine(":");
output.indent();
final PlainTextOutput temp = new PlainTextOutput();
DecompilerHelpers.writeMethodSignature(temp, method);
DecompilerHelpers.writeMethodSignature(output, method);
if (!StringUtilities.equals(temp.toString(), signature)) {
output.write(' ');
output.writeDelimiter("[");
output.write("from metadata: ");
output.writeError(signature);
output.writeDelimiter("]");
output.writeLine();
}
output.writeLine();
output.unindent();
}
finally {
output.unindent();
}
break;
}
}
}
private void writeMethodBody(final ITextOutput output, final MethodDefinition method, final DecompilationOptions options) {
final MethodBody body = method.getBody();
if (body == null) {
return;
}
output.indent();
try {
output.writeAttribute("Code");
output.writeLine(":");
output.indent();
try {
output.write("stack=");
output.writeLiteral(body.getMaxStackSize());
output.write(", locals=");
output.writeLiteral(body.getMaxLocals());
output.write(", arguments=");
output.writeLiteral(method.getParameters().size());
output.writeLine();
}
finally {
output.unindent();
}
final InstructionCollection instructions = body.getInstructions();
if (!instructions.isEmpty()) {
int[] lineNumbers;
if (options.getSettings().getIncludeLineNumbersInBytecode()) {
final LineNumberTableAttribute lineNumbersAttribute = SourceAttribute.find(
AttributeNames.LineNumberTable,
method.getSourceAttributes()
);
if (lineNumbersAttribute != null) {
lineNumbers = new int[body.getCodeSize()];
Arrays.fill(lineNumbers, -1);
for (final LineNumberTableEntry entry : lineNumbersAttribute.getEntries()) {
if (entry.getOffset() >= lineNumbers.length) {
//
// Fix for ArrayIndexOutOfBoundsException when decompiling dex2jar output with bad
// line number mappings.
//
lineNumbers = Arrays.copyOf(lineNumbers, entry.getOffset() + 1);
}
lineNumbers[entry.getOffset()] = entry.getLineNumber();
}
}
else {
lineNumbers = null;
}
}
else {
lineNumbers = null;
}
final InstructionPrinter printer = new InstructionPrinter(output, method, options.getSettings(), lineNumbers);
for (final Instruction instruction : instructions) {
printer.visit(instruction);
}
}
}
finally {
output.unindent();
}
}
@SuppressWarnings({ "ConstantConditions", "UnusedParameters" })
private void writeMethodEnd(final ITextOutput output, final MethodDefinition method, final DecompilationOptions options) {
final MethodBody body = method.getBody();
if (body == null) {
return;
}
final List<ExceptionHandler> handlers = body.getExceptionHandlers();
final List<StackMapFrame> stackMapFrames = body.getStackMapFrames();
if (!handlers.isEmpty()) {
output.indent();
try {
int longestType = "Type".length();
for (final ExceptionHandler handler : handlers) {
final TypeReference catchType = handler.getCatchType();
if (catchType != null) {
final String signature = catchType.getSignature();
if (signature.length() > longestType) {
longestType = signature.length();
}
}
}
output.writeAttribute("Exceptions");
output.writeLine(":");
output.indent();
try {
output.write("Try Handler");
output.writeLine();
output.write("Start End Start End %1$-" + longestType + "s", "Type");
output.writeLine();
output.write(
"----- ----- ----- ----- %1$-" + longestType + "s",
StringUtilities.repeat('-', longestType)
);
output.writeLine();
for (final ExceptionHandler handler : handlers) {
final boolean isFinally;
TypeReference catchType = handler.getCatchType();
if (catchType != null) {
isFinally = false;
}
else {
catchType = getResolver(body).lookupType("java/lang/Throwable");
isFinally = true;
}
output.writeLiteral(format("%1$-5d", handler.getTryBlock().getFirstInstruction().getOffset()));
output.write(" ");
output.writeLiteral(format("%1$-5d", handler.getTryBlock().getLastInstruction().getEndOffset()));
output.write(" ");
output.writeLiteral(format("%1$-5d", handler.getHandlerBlock().getFirstInstruction().getOffset()));
output.write(" ");
output.writeLiteral(format("%1$-5d", handler.getHandlerBlock().getLastInstruction().getEndOffset()));
output.write(" ");
if (isFinally) {
output.writeReference("Any", catchType);
}
else {
DecompilerHelpers.writeType(output, catchType, NameSyntax.SIGNATURE);
}
output.writeLine();
}
}
finally {
output.unindent();
}
}
finally {
output.unindent();
}
}
if (!stackMapFrames.isEmpty()) {
output.indent();
try {
output.writeAttribute("Stack Map Frames");
output.writeLine(":");
output.indent();
try {
for (final StackMapFrame frame : stackMapFrames) {
DecompilerHelpers.writeOffsetReference(output, frame.getStartInstruction());
output.write(' ');
DecompilerHelpers.writeFrame(output, frame.getFrame());
output.writeLine();
}
}
finally {
output.unindent();
}
}
finally {
output.unindent();
}
}
}
private static IMetadataResolver getResolver(final MethodBody body) {
final MethodReference method = body.getMethod();
if (method != null) {
final MethodDefinition resolvedMethod = method.resolve();
if (resolvedMethod != null) {
final TypeDefinition declaringType = resolvedMethod.getDeclaringType();
if (declaringType != null) {
return declaringType.getResolver();
}
}
}
return MetadataSystem.instance();
}
private final static class InstructionPrinter implements InstructionVisitor {
private static final int MAX_OPCODE_LENGTH;
private static final String[] OPCODE_NAMES;
private static final String LINE_NUMBER_CODE = "linenumber";
static {
int maxLength = LINE_NUMBER_CODE.length();
final OpCode[] values = OpCode.values();
final String[] names = new String[values.length];
for (int i = 0; i < values.length; i++) {
final OpCode op = values[i];
final int length = op.name().length();
if (length > maxLength) {
maxLength = length;
}
names[i] = op.name().toLowerCase();
}
MAX_OPCODE_LENGTH = maxLength;
OPCODE_NAMES = names;
}
private final DecompilerSettings _settings;
private final ITextOutput _output;
private final MethodBody _body;
private final int[] _lineNumbers;
private int _currentOffset = -1;
private InstructionPrinter(final ITextOutput output, final MethodDefinition method, final DecompilerSettings settings, final int[] lineNumbers) {
_settings = settings;
_output = VerifyArgument.notNull(output, "output");
_body = VerifyArgument.notNull(method, "method").getBody();
_lineNumbers = lineNumbers;
}
private void printOpCode(final OpCode opCode) {
switch (opCode) {
case TABLESWITCH:
case LOOKUPSWITCH:
_output.writeReference(OPCODE_NAMES[opCode.ordinal()], opCode);
break;
default:
_output.writeReference(String.format("%1$-" + MAX_OPCODE_LENGTH + "s", OPCODE_NAMES[opCode.ordinal()]), opCode);
break;
}
}
@Override
public void visit(final Instruction instruction) {
VerifyArgument.notNull(instruction, "instruction");
if (_lineNumbers != null) {
final int lineNumber = _lineNumbers[instruction.getOffset()];
if (lineNumber >= 0) {
_output.write(" ");
_output.write("%1$-" + MAX_OPCODE_LENGTH + "s", LINE_NUMBER_CODE);
_output.write(' ');
_output.writeLiteral(lineNumber);
_output.writeLine();
}
}
_currentOffset = instruction.getOffset();
try {
_output.writeLabel(String.format("%1$8d", instruction.getOffset()));
_output.write(": ");
instruction.accept(this);
}
catch (Throwable t) {
printOpCode(instruction.getOpCode());
boolean foundError = false;
for (int i = 0; i < instruction.getOperandCount(); i++) {
final Object operand = instruction.getOperand(i);
if (operand instanceof ErrorOperand) {
_output.write(String.valueOf(operand));
foundError = true;
break;
}
}
if (!foundError) {
_output.write("!!! ERROR");
}
_output.writeLine();
}
finally {
_currentOffset = -1;
}
}
@Override
public void visit(final OpCode op) {
printOpCode(op);
final int slot = OpCodeHelpers.getLoadStoreMacroArgumentIndex(op);
if (slot >= 0) {
final VariableDefinitionCollection variables = _body.getVariables();
if (slot < variables.size()) {
final VariableDefinition variable = findVariable(op, slot, _currentOffset);
if (variable != null &&
variable.hasName() &&
variable.isFromMetadata()) {
_output.writeComment(
" /* %s */",
StringUtilities.escape(variable.getName(), false, _settings.isUnicodeOutputEnabled())
);
}
}
}
_output.writeLine();
}
private VariableDefinition findVariable(final OpCode op, final int slot, final int offset) {
VariableDefinition variable = _body.getVariables().tryFind(slot, offset);
if (variable == null && op.isStore()) {
variable = _body.getVariables().tryFind(slot, offset + op.getSize() + op.getOperandType().getBaseSize());
}
return variable;
}
@Override
public void visitConstant(final OpCode op, final TypeReference value) {
printOpCode(op);
_output.write(' ');
DecompilerHelpers.writeType(_output, value, NameSyntax.ERASED_SIGNATURE);
_output.write(".class");
_output.writeLine();
}
@Override
public void visitConstant(final OpCode op, final int value) {
printOpCode(op);
_output.write(' ');
_output.writeLiteral(value);
_output.writeLine();
}
@Override
public void visitConstant(final OpCode op, final long value) {
printOpCode(op);
_output.write(' ');
_output.writeLiteral(value);
_output.writeLine();
}
@Override
public void visitConstant(final OpCode op, final float value) {
printOpCode(op);
_output.write(' ');
_output.writeLiteral(value);
_output.writeLine();
}
@Override
public void visitConstant(final OpCode op, final double value) {
printOpCode(op);
_output.write(' ');
_output.writeLiteral(value);
_output.writeLine();
}
@Override
public void visitConstant(final OpCode op, final String value) {
printOpCode(op);
_output.write(' ');
_output.writeTextLiteral(StringUtilities.escape(value, true, _settings.isUnicodeOutputEnabled()));
_output.writeLine();
}
@Override
public void visitBranch(final OpCode op, final Instruction target) {
printOpCode(op);
_output.write(' ');
_output.writeLabel(String.valueOf(target.getOffset()));
_output.writeLine();
}
@Override
public void visitVariable(final OpCode op, final VariableReference variable) {
printOpCode(op);
_output.write(' ');
final VariableDefinition definition = findVariable(op, variable.getSlot(), _currentOffset);
if (definition != null && definition.hasName() && definition.isFromMetadata()) {
_output.writeReference(variable.getName(), variable);
}
else {
_output.writeLiteral(variable.getSlot());
}
_output.writeLine();
}
@Override
public void visitVariable(final OpCode op, final VariableReference variable, final int operand) {
printOpCode(op);
_output.write(' ');
final VariableDefinition definition;
if (variable instanceof VariableDefinition) {
definition = (VariableDefinition) variable;
}
else {
definition = findVariable(op, variable.getSlot(), _currentOffset);
}
if (definition != null && definition.hasName() && definition.isFromMetadata()) {
_output.writeReference(variable.getName(), variable);
}
else {
_output.writeLiteral(variable.getSlot());
}
_output.write(", ");
_output.writeLiteral(String.valueOf(operand));
_output.writeLine();
}
@Override
public void visitType(final OpCode op, final TypeReference type) {
printOpCode(op);
_output.write(' ');
DecompilerHelpers.writeType(_output, type, NameSyntax.SIGNATURE);
_output.writeLine();
}
@Override
public void visitMethod(final OpCode op, final MethodReference method) {
printOpCode(op);
_output.write(' ');
DecompilerHelpers.writeMethod(_output, method);
_output.writeLine();
}
@Override
public void visitDynamicCallSite(final OpCode op, final DynamicCallSite callSite) {
printOpCode(op);
_output.write(' ');
_output.writeReference(callSite.getMethodName(), callSite.getMethodType());
_output.writeDelimiter(":");
DecompilerHelpers.writeMethodSignature(_output, callSite.getMethodType());
_output.writeLine();
}
@Override
public void visitField(final OpCode op, final FieldReference field) {
printOpCode(op);
_output.write(' ');
DecompilerHelpers.writeField(_output, field);
_output.writeLine();
}
@Override
public void visitLabel(final Label label) {
}
@Override
public void visitSwitch(final OpCode op, final SwitchInfo switchInfo) {
printOpCode(op);
_output.write(" {");
_output.writeLine();
switch (op) {
case TABLESWITCH: {
final Instruction[] targets = switchInfo.getTargets();
int caseValue = switchInfo.getLowValue();
for (final Instruction target : targets) {
_output.write(" ");
_output.writeLiteral(format("%1$7d", switchInfo.getLowValue() + caseValue++));
_output.write(": ");
_output.writeLabel(String.valueOf(target.getOffset()));
_output.writeLine();
}
_output.write(" ");
_output.writeKeyword("default");
_output.write(": ");
_output.writeLabel(String.valueOf(switchInfo.getDefaultTarget().getOffset()));
_output.writeLine();
break;
}
case LOOKUPSWITCH: {
final int[] keys = switchInfo.getKeys();
final Instruction[] targets = switchInfo.getTargets();
for (int i = 0; i < keys.length; i++) {
final int key = keys[i];
final Instruction target = targets[i];
_output.write(" ");
_output.writeLiteral(format("%1$7d", key));
_output.write(": ");
_output.writeLabel(String.valueOf(target.getOffset()));
_output.writeLine();
}
_output.write(" ");
_output.writeKeyword("default");
_output.write(": ");
_output.writeLabel(String.valueOf(switchInfo.getDefaultTarget().getOffset()));
_output.writeLine();
break;
}
}
_output.write(" }");
_output.writeLine();
}
@Override
public void visitEnd() {
}
}
private static final class DummyTypeReference extends TypeReference {
private final String _descriptor;
private final String _fullName;
private final String _simpleName;
public DummyTypeReference(final String descriptor) {
_descriptor = VerifyArgument.notNull(descriptor, "descriptor");
_fullName = descriptor.replace('/', '.');
final int delimiterIndex = _fullName.lastIndexOf('.');
if (delimiterIndex < 0 || delimiterIndex == _fullName.length() - 1) {
_simpleName = _fullName;
}
else {
_simpleName = _fullName.substring(delimiterIndex + 1);
}
}
@Override
public final String getSimpleName() {
return _simpleName;
}
@Override
public final String getFullName() {
return _fullName;
}
@Override
public final String getInternalName() {
return _descriptor;
}
@Override
public final <R, P> R accept(final TypeMetadataVisitor<P, R> visitor, final P parameter) {
return visitor.visitClassType(this, parameter);
}
}
}