Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decompiler updates regarding Kotlin - 1 #846

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,6 +1,8 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.java.decompiler.code;

import org.jetbrains.java.decompiler.util.TextUtil;

public class Instruction implements CodeConstants {
public static Instruction create(int opcode, boolean wide, int group, int bytecodeVersion, int[] operands) {
if (opcode >= opc_ifeq && opcode <= opc_if_acmpne ||
Expand Down Expand Up @@ -57,6 +59,25 @@ public boolean canFallThrough() {
opcode != opc_jsr && opcode != opc_tableswitch && opcode != opc_lookupswitch;
}

public String toString() {

String res = wide ? "@wide " : "";
res += "@" + TextUtil.getInstructionName(opcode);

int len = operandsCount();
for (int i = 0; i < len; i++) {
int op = operands[i];
if (op < 0) {
res += " -" + Integer.toHexString(-op);
}
else {
res += " " + Integer.toHexString(op);
}
}

return res;
}

@Override
@SuppressWarnings("MethodDoesntCallSuperMethod")
public Instruction clone() {
Expand Down
Expand Up @@ -5,6 +5,8 @@
import java.util.List;
import java.util.stream.Collectors;

import org.jetbrains.java.decompiler.main.DecompilerContext;

public class ExceptionRangeCFG {
private final List<BasicBlock> protectedRange; // FIXME: replace with set
private BasicBlock handler;
Expand All @@ -23,6 +25,28 @@ public boolean isCircular() {
return protectedRange.contains(handler);
}

public String toString() {

String new_line_separator = DecompilerContext.getNewLineSeparator();

StringBuilder buf = new StringBuilder();

buf.append("exceptionType:");
for (String exception_type : exceptionTypes) {
buf.append(" ").append(exception_type);
}
buf.append(new_line_separator);

buf.append("handler: ").append(handler.id).append(new_line_separator);
buf.append("range: ");
for (int i = 0; i < protectedRange.size(); i++) {
buf.append(protectedRange.get(i).id).append(" ");
}
buf.append(new_line_separator);

return buf.toString();
}

public BasicBlock getHandler() {
return handler;
}
Expand Down
Expand Up @@ -820,7 +820,7 @@ else if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarT
BytecodeMappingTracer codeTracer = new BytecodeMappingTracer(tracer.getCurrentSourceLine());
TextBuffer code = root.toJava(indent + 1, codeTracer);

hideMethod = (clinit || dinit || hideConstructor(wrapper, init, throwsExceptions, paramCount)) && code.length() == 0;
hideMethod = (code.length() == 0) && (clinit || dinit || hideConstructor(node, init, throwsExceptions, paramCount, flags));

buffer.append(code);

Expand Down Expand Up @@ -859,13 +859,25 @@ else if (root != null) {
return !hideMethod;
}

private static boolean hideConstructor(ClassWrapper wrapper, boolean init, boolean throwsExceptions, int paramCount) {
private static boolean hideConstructor(ClassNode node, boolean init, boolean throwsExceptions, int paramCount, int methodAccessFlags) {

if (!init || throwsExceptions || paramCount > 0 || !DecompilerContext.getOption(IFernflowerPreferences.HIDE_DEFAULT_CONSTRUCTOR)) {
return false;
}


ClassWrapper wrapper = node.getWrapper();
StructClass cl = wrapper.getClassStruct();

int classAccesFlags = node.type == ClassNode.CLASS_ROOT ? cl.getAccessFlags() : node.access;
boolean isEnum = cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);

// default constructor requires same accessibility flags. Exception: enum constructor which is always private
if(!isEnum && ((classAccesFlags & ACCESSIBILITY_FLAGS) != (methodAccessFlags & ACCESSIBILITY_FLAGS))) {
return false;
}

int count = 0;
for (StructMethod mt : wrapper.getClassStruct().getMethods()) {
for (StructMethod mt : cl.getMethods()) {
if (CodeConstants.INIT_NAME.equals(mt.getName())) {
if (++count > 1) {
return false;
Expand Down Expand Up @@ -1031,6 +1043,8 @@ private static void appendTypeAnnotations(TextBuffer buffer, int indent, StructM
private static final int FIELD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL;
private static final int METHOD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_ABSTRACT;

private static final int ACCESSIBILITY_FLAGS = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE;

private static void appendModifiers(TextBuffer buffer, int flags, int allowed, boolean isInterface, int excluded) {
flags &= allowed;
if (!isInterface) excluded = 0;
Expand Down
Expand Up @@ -4,6 +4,8 @@
package org.jetbrains.java.decompiler.main;

import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.code.Instruction;
import org.jetbrains.java.decompiler.code.InstructionSequence;
import org.jetbrains.java.decompiler.main.collectors.BytecodeSourceMapper;
import org.jetbrains.java.decompiler.main.collectors.ImportCollector;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
Expand All @@ -19,6 +21,7 @@
import org.jetbrains.java.decompiler.struct.StructContext;
import org.jetbrains.java.decompiler.struct.StructMethod;
import org.jetbrains.java.decompiler.struct.attr.StructInnerClassesAttribute;
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
import org.jetbrains.java.decompiler.struct.gen.VarType;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import org.jetbrains.java.decompiler.util.TextBuffer;
Expand All @@ -27,7 +30,7 @@
import java.util.*;
import java.util.Map.Entry;

public class ClassesProcessor {
public class ClassesProcessor implements CodeConstants {
public static final int AVERAGE_CLASS_SIZE = 16 * 1024;

private final StructContext context;
Expand Down Expand Up @@ -172,20 +175,24 @@ else if (!Inner.equal(existingRec, rec)) {
if (nestedNode.type == ClassNode.CLASS_ANONYMOUS) {
StructClass cl = nestedNode.classStruct;

// remove static if anonymous class (a common compiler bug)
nestedNode.access &= ~CodeConstants.ACC_STATIC;

int[] interfaces = cl.getInterfaces();

if (interfaces.length > 0) {
if (interfaces.length > 1) {
String message = "Inconsistent anonymous class definition: " + cl.qualifiedName;
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN);
// sanity checks of the class supposed to be anonymous
boolean isAnonymousChecked = checkClassAnonymous(cl, scl);

if(isAnonymousChecked) {
// remove static if anonymous class (a common compiler bug)
nestedNode.access &= ~CodeConstants.ACC_STATIC;

if (interfaces.length > 0) {
nestedNode.anonymousClassType = new VarType(cl.getInterface(0), true);
}
nestedNode.anonymousClassType = new VarType(cl.getInterface(0), true);
}
else {
nestedNode.anonymousClassType = new VarType(cl.superClass.getString(), true);
else {
nestedNode.anonymousClassType = new VarType(cl.superClass.getString(), true);
}
} else { // change it to a local class
nestedNode.type = ClassNode.CLASS_LOCAL;
nestedNode.access &= (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_FINAL);
}
}
else if (nestedNode.type == ClassNode.CLASS_LOCAL) {
Expand All @@ -206,6 +213,87 @@ else if (nestedNode.type == ClassNode.CLASS_LOCAL) {
}
}
}

private boolean checkClassAnonymous(StructClass cl, StructClass enclosing_cl) {

int[] interfaces = cl.getInterfaces();
boolean hasNonTrivialSuperClass = cl.superClass != null && !VarType.VARTYPE_OBJECT.equals(new VarType(cl.superClass.getString(), true));

// checking super class and interfaces
if(interfaces.length > 0) {
if(hasNonTrivialSuperClass || interfaces.length > 1) { // can't have multiple 'sources'
String message = "Inconsistent anonymous class definition: '" + cl.qualifiedName+"'. Multiple interfaces and/or super class defined.";
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN);
return false;
}
} else if(cl.superClass == null) { // neither interface nor super class defined
String message = "Inconsistent anonymous class definition: '" + cl.qualifiedName+"'. Neither interface nor super class defined.";
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN);
return false;
}

// FIXME: check constructors
// FIXME: check enclosing class/method

ConstantPool pool = enclosing_cl.getPool();

int ref_counter = 0;
boolean ref_not_new = false;

// checking references in the enclosing class (TODO: limit to the enclosing method?)
for (StructMethod mt : enclosing_cl.getMethods()) {
try {
mt.expandData();
InstructionSequence seq = mt.getInstructionSequence();

if(seq != null) {

int len = seq.length();
for (int i = 0; i < len; i++) {
Instruction instr = seq.getInstr(i);

switch(instr.opcode) {
case opc_checkcast:
case opc_instanceof:
if(cl.qualifiedName.equals(pool.getPrimitiveConstant(instr.operand(0)).getString())) {
ref_counter++;
ref_not_new = true;
}
break;
case opc_new:
case opc_anewarray:
case opc_multianewarray:
if(cl.qualifiedName.equals(pool.getPrimitiveConstant(instr.operand(0)).getString())) {
ref_counter++;
}
break;
case opc_getstatic:
case opc_putstatic:
if(cl.qualifiedName.equals(pool.getLinkConstant(instr.operand(0)).classname)) {
ref_counter++;
ref_not_new = true;
}
}
}
}

mt.releaseResources();
} catch(IOException ex) {
String message = "Could not read method while checking anonymous class definition: '"+enclosing_cl.qualifiedName+"', '"+
InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())+"'";
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN);
return false;
}

if(ref_counter > 1 || ref_not_new) {
String message = "Inconsistent references to the class '"+cl.qualifiedName+"' which is supposed to be anonymous";
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN);
return false;
}
}

return true;
}

public void writeClass(StructClass cl, TextBuffer buffer) throws IOException {
ClassNode root = mapRootClasses.get(cl.qualifiedName);
Expand Down
Expand Up @@ -17,6 +17,7 @@ public interface IFernflowerPreferences {
String HIDE_DEFAULT_CONSTRUCTOR = "hdc";
String DECOMPILE_GENERIC_SIGNATURES = "dgs";
String NO_EXCEPTIONS_RETURN = "ner";
String ENSURE_SYNCHRONIZED_MONITOR = "esm";
String DECOMPILE_ENUM = "den";
String REMOVE_GET_CLASS_NEW = "rgn";
String LITERALS_AS_IS = "lit";
Expand Down Expand Up @@ -61,6 +62,7 @@ static Map<String, Object> getDefaults() {
defaults.put(HIDE_DEFAULT_CONSTRUCTOR, "1");
defaults.put(DECOMPILE_GENERIC_SIGNATURES, "0");
defaults.put(NO_EXCEPTIONS_RETURN, "1");
defaults.put(ENSURE_SYNCHRONIZED_MONITOR, "1");
defaults.put(DECOMPILE_ENUM, "1");
defaults.put(REMOVE_GET_CLASS_NEW, "1");
defaults.put(LITERALS_AS_IS, "0");
Expand Down
Expand Up @@ -88,6 +88,11 @@ public static RootStatement codeToJava(StructMethod mt, MethodDescriptor md, Var
ExceptionDeobfuscator.removeEmptyRanges(graph);
}

if (DecompilerContext.getOption(IFernflowerPreferences.ENSURE_SYNCHRONIZED_MONITOR)) {
// special case: search for 'synchronized' ranges w/o monitorexit instruction (as generated by Kotlin and Scala)
DeadCodeHelper.extendSynchronizedRangeToMonitorexit(graph);
}

if (DecompilerContext.getOption(IFernflowerPreferences.NO_EXCEPTIONS_RETURN)) {
// special case: single return instruction outside of a protected range
DeadCodeHelper.incorporateValueReturns(graph);
Expand Down
Expand Up @@ -71,8 +71,11 @@ public void processClass(ClassNode root, ClassNode node) {
else if (child.type != ClassNode.CLASS_MEMBER || (child.access & CodeConstants.ACC_STATIC) == 0) {
insertLocalVars(node, child);

if (child.type == ClassNode.CLASS_LOCAL) {
setLocalClassDefinition(node.getWrapper().getMethods().getWithKey(child.enclosingMethod), child);
if (child.type == ClassNode.CLASS_LOCAL && child.enclosingMethod != null) {
MethodWrapper enclosingMethodWrapper = node.getWrapper().getMethods().getWithKey(child.enclosingMethod);
if(enclosingMethodWrapper != null) { // e.g. in case of switch-on-enum. FIXME: some proper handling of multiple enclosing classes
setLocalClassDefinition(enclosingMethodWrapper, child);
}
}
}
}
Expand Down