Decompilation of synchronized blocks generated by the Kotlin compiler
This commit is contained in:
committed by
Roman Shevchenko
parent
2431c0fe94
commit
7e98f686c0
@@ -17,6 +17,7 @@ public interface IFernflowerPreferences {
|
|||||||
String HIDE_DEFAULT_CONSTRUCTOR = "hdc";
|
String HIDE_DEFAULT_CONSTRUCTOR = "hdc";
|
||||||
String DECOMPILE_GENERIC_SIGNATURES = "dgs";
|
String DECOMPILE_GENERIC_SIGNATURES = "dgs";
|
||||||
String NO_EXCEPTIONS_RETURN = "ner";
|
String NO_EXCEPTIONS_RETURN = "ner";
|
||||||
|
String KT_SYNCHRONIZED_MONITOR = "ksm";
|
||||||
String DECOMPILE_ENUM = "den";
|
String DECOMPILE_ENUM = "den";
|
||||||
String REMOVE_GET_CLASS_NEW = "rgn";
|
String REMOVE_GET_CLASS_NEW = "rgn";
|
||||||
String LITERALS_AS_IS = "lit";
|
String LITERALS_AS_IS = "lit";
|
||||||
@@ -61,6 +62,7 @@ public interface IFernflowerPreferences {
|
|||||||
defaults.put(HIDE_DEFAULT_CONSTRUCTOR, "1");
|
defaults.put(HIDE_DEFAULT_CONSTRUCTOR, "1");
|
||||||
defaults.put(DECOMPILE_GENERIC_SIGNATURES, "0");
|
defaults.put(DECOMPILE_GENERIC_SIGNATURES, "0");
|
||||||
defaults.put(NO_EXCEPTIONS_RETURN, "1");
|
defaults.put(NO_EXCEPTIONS_RETURN, "1");
|
||||||
|
defaults.put(KT_SYNCHRONIZED_MONITOR, "1");
|
||||||
defaults.put(DECOMPILE_ENUM, "1");
|
defaults.put(DECOMPILE_ENUM, "1");
|
||||||
defaults.put(REMOVE_GET_CLASS_NEW, "1");
|
defaults.put(REMOVE_GET_CLASS_NEW, "1");
|
||||||
defaults.put(LITERALS_AS_IS, "0");
|
defaults.put(LITERALS_AS_IS, "0");
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ public class MethodProcessorRunnable implements Runnable {
|
|||||||
ExceptionDeobfuscator.removeEmptyRanges(graph);
|
ExceptionDeobfuscator.removeEmptyRanges(graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DecompilerContext.getOption(IFernflowerPreferences.KT_SYNCHRONIZED_MONITOR)) {
|
||||||
|
// special case: search for 'synchronized' ranges w/o monitorexit instruction (generated by the Kotlin compiler)
|
||||||
|
DeadCodeHelper.extendSynchronizedRangeToMonitorexit(graph);
|
||||||
|
}
|
||||||
|
|
||||||
if (DecompilerContext.getOption(IFernflowerPreferences.NO_EXCEPTIONS_RETURN)) {
|
if (DecompilerContext.getOption(IFernflowerPreferences.NO_EXCEPTIONS_RETURN)) {
|
||||||
// special case: single return instruction outside of a protected range
|
// special case: single return instruction outside of a protected range
|
||||||
DeadCodeHelper.incorporateValueReturns(graph);
|
DeadCodeHelper.incorporateValueReturns(graph);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package org.jetbrains.java.decompiler.modules.code;
|
|||||||
import org.jetbrains.java.decompiler.code.CodeConstants;
|
import org.jetbrains.java.decompiler.code.CodeConstants;
|
||||||
import org.jetbrains.java.decompiler.code.Instruction;
|
import org.jetbrains.java.decompiler.code.Instruction;
|
||||||
import org.jetbrains.java.decompiler.code.InstructionSequence;
|
import org.jetbrains.java.decompiler.code.InstructionSequence;
|
||||||
|
import org.jetbrains.java.decompiler.code.SimpleInstructionSequence;
|
||||||
import org.jetbrains.java.decompiler.code.cfg.BasicBlock;
|
import org.jetbrains.java.decompiler.code.cfg.BasicBlock;
|
||||||
import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph;
|
import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph;
|
||||||
import org.jetbrains.java.decompiler.code.cfg.ExceptionRangeCFG;
|
import org.jetbrains.java.decompiler.code.cfg.ExceptionRangeCFG;
|
||||||
@@ -254,6 +255,152 @@ public class DeadCodeHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void extendSynchronizedRangeToMonitorexit(ControlFlowGraph graph) {
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
|
||||||
|
boolean range_extended = false;
|
||||||
|
|
||||||
|
for (ExceptionRangeCFG range : graph.getExceptions()) {
|
||||||
|
|
||||||
|
Set<BasicBlock> setPreds = new HashSet<>();
|
||||||
|
for (BasicBlock block : range.getProtectedRange()) {
|
||||||
|
setPreds.addAll(block.getPreds());
|
||||||
|
}
|
||||||
|
setPreds.removeAll(range.getProtectedRange());
|
||||||
|
|
||||||
|
if(setPreds.size() != 1) {
|
||||||
|
continue; // multiple predecessors, obfuscated range
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicBlock predBlock = setPreds.iterator().next();
|
||||||
|
InstructionSequence predSeq = predBlock.getSeq();
|
||||||
|
if(predSeq.isEmpty() || predSeq.getLastInstr().opcode != CodeConstants.opc_monitorenter) {
|
||||||
|
continue; // not a synchronized range
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean monitorexit_in_range = false;
|
||||||
|
Set<BasicBlock> setProtectedBlocks = new HashSet<>();
|
||||||
|
setProtectedBlocks.addAll(range.getProtectedRange());
|
||||||
|
setProtectedBlocks.add(range.getHandler());
|
||||||
|
|
||||||
|
for (BasicBlock block : setProtectedBlocks) {
|
||||||
|
InstructionSequence blockSeq = block.getSeq();
|
||||||
|
for (int i = 0; i < blockSeq.length(); i++) {
|
||||||
|
if (blockSeq.getInstr(i).opcode == CodeConstants.opc_monitorexit) {
|
||||||
|
monitorexit_in_range = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(monitorexit_in_range) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(monitorexit_in_range) {
|
||||||
|
continue; // protected range already contains monitorexit
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<BasicBlock> setSuccs = new HashSet<>();
|
||||||
|
for (BasicBlock block : range.getProtectedRange()) {
|
||||||
|
setSuccs.addAll(block.getSuccs());
|
||||||
|
}
|
||||||
|
setSuccs.removeAll(range.getProtectedRange());
|
||||||
|
|
||||||
|
if(setSuccs.size() != 1) {
|
||||||
|
continue; // non-unique successor
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicBlock succBlock = setSuccs.iterator().next();
|
||||||
|
InstructionSequence succSeq = succBlock.getSeq();
|
||||||
|
|
||||||
|
int succ_monitorexit_index = -1;
|
||||||
|
for (int i = 0; i < succSeq.length(); i++) {
|
||||||
|
if (succSeq.getInstr(i).opcode == CodeConstants.opc_monitorexit) {
|
||||||
|
succ_monitorexit_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(succ_monitorexit_index < 0) {
|
||||||
|
continue; // monitorexit not found in the single successor block
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicBlock handlerBlock = range.getHandler();
|
||||||
|
if(handlerBlock.getSuccs().size() != 1) {
|
||||||
|
continue; // non-unique handler successor
|
||||||
|
}
|
||||||
|
BasicBlock succHandler = handlerBlock.getSuccs().get(0);
|
||||||
|
InstructionSequence succHandlerSeq = succHandler.getSeq();
|
||||||
|
if(succHandlerSeq.isEmpty() || succHandlerSeq.getLastInstr().opcode != CodeConstants.opc_athrow) {
|
||||||
|
continue; // not a standard synchronized range
|
||||||
|
}
|
||||||
|
|
||||||
|
int handler_monitorexit_index = -1;
|
||||||
|
for (int i = 0; i < succHandlerSeq.length(); i++) {
|
||||||
|
if (succHandlerSeq.getInstr(i).opcode == CodeConstants.opc_monitorexit) {
|
||||||
|
handler_monitorexit_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(handler_monitorexit_index < 0) {
|
||||||
|
continue; // monitorexit not found in the handler successor block
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks successful, prerequisites satisfied, now extend the range
|
||||||
|
if(succ_monitorexit_index < succSeq.length() - 1) { // split block
|
||||||
|
|
||||||
|
SimpleInstructionSequence seq = new SimpleInstructionSequence();
|
||||||
|
for(int counter = 0; counter < succ_monitorexit_index; counter++) {
|
||||||
|
seq.addInstruction(succSeq.getInstr(0), -1);
|
||||||
|
succSeq.removeInstruction(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build a separate block
|
||||||
|
BasicBlock newblock = new BasicBlock(++graph.last_id);
|
||||||
|
newblock.setSeq(seq);
|
||||||
|
|
||||||
|
// insert new block
|
||||||
|
for (BasicBlock block : succBlock.getPreds()) {
|
||||||
|
block.replaceSuccessor(succBlock, newblock);
|
||||||
|
}
|
||||||
|
|
||||||
|
newblock.addSuccessor(succBlock);
|
||||||
|
graph.getBlocks().addWithKey(newblock, newblock.id);
|
||||||
|
|
||||||
|
succBlock = newblock;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy exception edges and extend protected ranges (successor block)
|
||||||
|
BasicBlock rangeExitBlock = succBlock.getPreds().get(0);
|
||||||
|
for (int j = 0; j < rangeExitBlock.getSuccExceptions().size(); j++) {
|
||||||
|
BasicBlock hd = rangeExitBlock.getSuccExceptions().get(j);
|
||||||
|
succBlock.addSuccessorException(hd);
|
||||||
|
|
||||||
|
ExceptionRangeCFG rng = graph.getExceptionRange(hd, rangeExitBlock);
|
||||||
|
rng.getProtectedRange().add(succBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy instructions (handler successor block)
|
||||||
|
InstructionSequence handlerSeq = handlerBlock.getSeq();
|
||||||
|
for(int counter = 0; counter < handler_monitorexit_index; counter++) {
|
||||||
|
handlerSeq.addInstruction(succHandlerSeq.getInstr(0), -1);
|
||||||
|
succHandlerSeq.removeInstruction(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
range_extended = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!range_extended) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void incorporateValueReturns(ControlFlowGraph graph) {
|
public static void incorporateValueReturns(ControlFlowGraph graph) {
|
||||||
|
|
||||||
for (BasicBlock block : graph.getBlocks()) {
|
for (BasicBlock block : graph.getBlocks()) {
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ public class SingleClassesTest {
|
|||||||
@Test public void testMissingConstructorCallBad() { doTest("pkg/TestMissingConstructorCallBad"); }
|
@Test public void testMissingConstructorCallBad() { doTest("pkg/TestMissingConstructorCallBad"); }
|
||||||
@Test public void testEmptyBlocks() { doTest("pkg/TestEmptyBlocks"); }
|
@Test public void testEmptyBlocks() { doTest("pkg/TestEmptyBlocks"); }
|
||||||
@Test public void testPrivateEmptyConstructor() { doTest("pkg/TestPrivateEmptyConstructor"); }
|
@Test public void testPrivateEmptyConstructor() { doTest("pkg/TestPrivateEmptyConstructor"); }
|
||||||
|
@Test public void testSynchronizedUnprotected() { doTest("pkg/TestSynchronizedUnprotected"); }
|
||||||
|
|
||||||
|
|
||||||
// TODO: fix all below
|
// TODO: fix all below
|
||||||
|
|||||||
BIN
testData/classes/pkg/TestSynchronizedUnprotected.class
Normal file
BIN
testData/classes/pkg/TestSynchronizedUnprotected.class
Normal file
Binary file not shown.
25
testData/results/TestSynchronizedUnprotected.dec
Normal file
25
testData/results/TestSynchronizedUnprotected.dec
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
public final class TestSynchronizedUnprotected {
|
||||||
|
public final void test() {
|
||||||
|
synchronized(this) {// 5
|
||||||
|
System.out.println("test");// 6
|
||||||
|
}
|
||||||
|
}// 7
|
||||||
|
}
|
||||||
|
|
||||||
|
class 'TestSynchronizedUnprotected' {
|
||||||
|
method 'test ()V' {
|
||||||
|
3 2
|
||||||
|
4 3
|
||||||
|
7 3
|
||||||
|
9 3
|
||||||
|
e 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lines mapping:
|
||||||
|
5 <-> 3
|
||||||
|
6 <-> 4
|
||||||
|
7 <-> 6
|
||||||
|
Not mapped:
|
||||||
|
8
|
||||||
|
|
||||||
Reference in New Issue
Block a user